From f02d65c09fa8dd03e75be7f11fb30bbacb47db52 Mon Sep 17 00:00:00 2001 From: Github Actions Date: Mon, 2 Dec 2024 11:30:00 +0000 Subject: [PATCH 001/193] Version bump --- changelog.md | 6 +++++- gradle.properties | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index 7e81d0377..22a03ca98 100644 --- a/changelog.md +++ b/changelog.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.0.0-beta24] - 2024-12-02 + ## [1.0.0-beta23] - 2024-11-23 ## [1.0.0-beta22] - 2024-11-15 @@ -59,7 +61,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [1.0.0-beta1] - 2024-06-14 -[Unreleased]: https://github.com/ortus-boxlang/BoxLang/compare/v1.0.0-beta23...HEAD +[Unreleased]: https://github.com/ortus-boxlang/BoxLang/compare/v1.0.0-beta24...HEAD + +[1.0.0-beta24]: https://github.com/ortus-boxlang/BoxLang/compare/v1.0.0-beta23...v1.0.0-beta24 [1.0.0-beta23]: https://github.com/ortus-boxlang/BoxLang/compare/v1.0.0-beta22...v1.0.0-beta23 diff --git a/gradle.properties b/gradle.properties index ae3557676..7a39da2ba 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -#Sat Nov 23 12:24:04 UTC 2024 +#Mon Dec 02 11:29:56 UTC 2024 antlrVersion=4.13.1 jdkVersion=21 -version=1.0.0-beta24 +version=1.0.0-beta25 From c2e1f3eb1149b9048b8cf28f16de26a5c36e531b Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 2 Dec 2024 17:14:18 +0100 Subject: [PATCH 002/193] BL-784 --- src/main/java/ortus/boxlang/debugger/BoxLangDebugger.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/debugger/BoxLangDebugger.java b/src/main/java/ortus/boxlang/debugger/BoxLangDebugger.java index 1400c13c8..c4341f801 100644 --- a/src/main/java/ortus/boxlang/debugger/BoxLangDebugger.java +++ b/src/main/java/ortus/boxlang/debugger/BoxLangDebugger.java @@ -31,6 +31,7 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; +import java.util.regex.Pattern; import java.util.stream.Collectors; import com.sun.jdi.AbsentInformationException; @@ -106,6 +107,8 @@ public class BoxLangDebugger { private int SUSPEND_POLICY = ThreadStartRequest.SUSPEND_EVENT_THREAD; private MethodExitRequest methodExitRequest = null; + private static final Pattern NON_WORD_PATTERN = Pattern.compile( "\\W" ); + public enum Status { NOT_STARTED, INITIALIZED, @@ -979,6 +982,6 @@ private List getMatchingReferenceTypes( String fileName ) { } private String normalizeName( String className ) { - return className.replaceAll( "\\W", "" ).toLowerCase(); + return NON_WORD_PATTERN.matcher( className ).replaceAll( "" ).toLowerCase(); } } From 2e30c8e18e50bf74a685b6034e4bf0477c2cb145 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Mon, 11 Nov 2024 10:00:56 -0600 Subject: [PATCH 003/193] BL-760 add line numbers to ASM code --- .../boxlang/compiler/asmboxpiler/AsmHelper.java | 9 ++------- .../expression/BoxAccessTransformer.java | 5 +++-- .../BoxArgumentDeclarationTransformer.java | 3 ++- .../expression/BoxArrayLiteralTransformer.java | 2 +- .../expression/BoxAssignmentTransformer.java | 2 +- .../BoxBinaryOperationTransformer.java | 3 ++- .../expression/BoxClosureTransformer.java | 4 +++- .../BoxComparisonOperationTransformer.java | 3 ++- .../BoxExpressionInvocationTransformer.java | 2 +- .../BoxFunctionInvocationTransformer.java | 2 ++ .../BoxFunctionalBIFAccessTransformer.java | 3 ++- .../BoxFunctionalMemberAccessTransformer.java | 4 ++-- .../expression/BoxIdentifierTransformer.java | 2 +- .../expression/BoxNewTransformer.java | 2 +- .../expression/BoxReturnTransformer.java | 3 ++- .../expression/BoxScopeTransformer.java | 3 ++- .../expression/BoxStatementBlockTransformer.java | 3 +-- .../expression/BoxStaticAccessTransformer.java | 3 ++- .../BoxStaticMethodInvocationTransformer.java | 2 +- .../expression/BoxStringConcatTransformer.java | 2 +- .../expression/BoxStringLiteralTransformer.java | 14 +++++++------- .../expression/BoxStructLiteralTransformer.java | 16 ++++++++++------ .../BoxTernaryOperationTransformer.java | 3 ++- .../expression/BoxUnaryOperationTransformer.java | 3 ++- .../statement/BoxAssertTransformer.java | 3 ++- .../statement/BoxComponentTransformer.java | 4 ++++ 26 files changed, 61 insertions(+), 44 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java index 6b2db8c24..a0f2083a4 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java @@ -84,6 +84,7 @@ public static List addLineNumberLabels( List nodes.add( 0, start ); nodes.add( 1, new LineNumberNode( node.getPosition().getStart().getLine(), start ) ); + nodes.add( end ); nodes.add( new LineNumberNode( node.getPosition().getStart().getLine(), end ) ); @@ -806,13 +807,7 @@ public static void methodWithContextAndClassLocator( ClassNode classNode, false ); tracker.storeNewVariable( Opcodes.ASTORE ).nodes().forEach( ( node ) -> node.accept( methodVisitor ) ); - // methodVisitor.visitVarInsn( Opcodes.ASTORE, isStatic ? 1 : 2 ); - List nodes = supplier.get(); - if ( !nodes.isEmpty() && ( nodes.get( nodes.size() - 1 ).getOpcode() == Opcodes.POP || nodes.get( nodes.size() - 1 ).getOpcode() == Opcodes.POP2 ) ) { - nodes.subList( 0, nodes.size() - 1 ).forEach( node -> node.accept( methodVisitor ) ); - } else { - nodes.forEach( node -> node.accept( methodVisitor ) ); - } + supplier.get().forEach( node -> node.accept( methodVisitor ) ); if ( implicityReturnNull && !returnType.equals( Type.VOID_TYPE ) ) { // push a null onto the stack so that we can return it if there isn't an explicity return diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAccessTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAccessTransformer.java index 8dae6a1d4..01d83a422 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAccessTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAccessTransformer.java @@ -27,6 +27,7 @@ import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.VarInsnNode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; @@ -105,7 +106,7 @@ public List transform( BoxNode node, TransformerContext contex true ) ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } else { // BoxNode parent = ( BoxNode ) objectAccess.getParent(); @@ -140,7 +141,7 @@ public List transform( BoxNode node, TransformerContext contex true ) ); } - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArgumentDeclarationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArgumentDeclarationTransformer.java index 8a7b9fcc8..cef903084 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArgumentDeclarationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArgumentDeclarationTransformer.java @@ -31,6 +31,7 @@ import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.TypeInsnNode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.MethodContextTracker; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; @@ -91,7 +92,7 @@ public List transform( BoxNode node, TransformerContext contex Type.getType( IStruct.class ), Type.getType( IStruct.class ) ), false ) ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } private List getDefaultExpression( BoxExpression body ) { diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArrayLiteralTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArrayLiteralTransformer.java index 3a4fd38cf..4ec12b35d 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArrayLiteralTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArrayLiteralTransformer.java @@ -51,6 +51,6 @@ public List transform( BoxNode node, TransformerContext contex "of", Type.getMethodDescriptor( Type.getType( Array.class ), Type.getType( Object[].class ) ), false ) ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAssignmentTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAssignmentTransformer.java index 3aa75f7d4..8c21a9dea 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAssignmentTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAssignmentTransformer.java @@ -91,7 +91,7 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.POP ) ); } - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } public List transformEquals( BoxExpression left, List jRight, BoxAssignmentOperator op, diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxBinaryOperationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxBinaryOperationTransformer.java index de5550ce6..c880b05a3 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxBinaryOperationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxBinaryOperationTransformer.java @@ -29,6 +29,7 @@ import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.VarInsnNode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; @@ -224,7 +225,7 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.POP ) ); } - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } @Nonnull diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxClosureTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxClosureTransformer.java index a5846f91e..8e2216574 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxClosureTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxClosureTransformer.java @@ -147,7 +147,8 @@ public List transform( BoxNode node, TransformerContext contex transpiler.incrementfunctionBodyCounter(); AsmHelper.methodWithContextAndClassLocator( classNode, "_invoke", Type.getType( FunctionBoxContext.class ), Type.getType( Object.class ), false, transpiler, isBlock, - () -> boxClosure.getBody().getChildren().stream().flatMap( statement -> transpiler.transform( statement, TransformerContext.NONE ).stream() ) + () -> boxClosure.getBody().getChildren().stream() + .flatMap( statement -> transpiler.transform( statement, TransformerContext.NONE, ReturnValueContext.VALUE_OR_NULL ).stream() ) .toList() ); transpiler.decrementfunctionBodyCounter(); transpiler.setComponentCounter( componentCounter ); @@ -217,5 +218,6 @@ public List transform( BoxNode node, TransformerContext contex false ) ); return nodes; + // return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxComparisonOperationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxComparisonOperationTransformer.java index a5e13c947..a90e2e4c0 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxComparisonOperationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxComparisonOperationTransformer.java @@ -25,6 +25,7 @@ import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodInsnNode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; @@ -94,7 +95,7 @@ public List transform( BoxNode node, TransformerContext contex ) ); } - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxExpressionInvocationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxExpressionInvocationTransformer.java index d4a8f939e..9aed02ae0 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxExpressionInvocationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxExpressionInvocationTransformer.java @@ -57,7 +57,7 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.POP ) ); } - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } private Type getInvocationType( BoxNode expressionNode ) { diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxFunctionInvocationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxFunctionInvocationTransformer.java index 17f17f4a9..037295aa2 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxFunctionInvocationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxFunctionInvocationTransformer.java @@ -55,5 +55,7 @@ public List transform( BoxNode node, TransformerContext contex } return nodes; + // TODO: this causes issues in testProperties + // return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxFunctionalBIFAccessTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxFunctionalBIFAccessTransformer.java index ab8fa4ba4..cb824248b 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxFunctionalBIFAccessTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxFunctionalBIFAccessTransformer.java @@ -25,6 +25,7 @@ import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.MethodInsnNode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; @@ -58,6 +59,6 @@ public List transform( BoxNode node, TransformerContext contex Type.getType( Key.class ) ), false ) ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } } \ No newline at end of file diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxFunctionalMemberAccessTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxFunctionalMemberAccessTransformer.java index e78b84a69..6b6ec4838 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxFunctionalMemberAccessTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxFunctionalMemberAccessTransformer.java @@ -70,7 +70,7 @@ public List transform( BoxNode node, TransformerContext contex Type.getType( Key.class ) ), false ) ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } nodes.add( new TypeInsnNode( Opcodes.NEW, Type.getInternalName( FunctionalMemberAccessArgs.class ) ) ); nodes.add( new InsnNode( Opcodes.DUP ) ); @@ -102,7 +102,7 @@ public List transform( BoxNode node, TransformerContext contex ), false ) ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } private List generateNamedArgumentLambda( BoxFunctionalMemberAccess memberAccess ) { diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxIdentifierTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxIdentifierTransformer.java index 92a40b1da..7fbfc9204 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxIdentifierTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxIdentifierTransformer.java @@ -73,6 +73,6 @@ public List transform( BoxNode node, TransformerContext contex Type.getMethodDescriptor( Type.getType( Object.class ) ), false ) ); } - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxNewTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxNewTransformer.java index 57b5b9afa..dc473e915 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxNewTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxNewTransformer.java @@ -92,6 +92,6 @@ public List transform( BoxNode node, TransformerContext contex Type.getMethodDescriptor( Type.getType( Object.class ) ), false ) ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxReturnTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxReturnTransformer.java index 4023a5c0a..f02319be8 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxReturnTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxReturnTransformer.java @@ -24,6 +24,7 @@ import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.MethodInsnNode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; @@ -66,7 +67,7 @@ public List transform( BoxNode node, TransformerContext contex nodes.addAll( transpiler.transform( boxReturn.getExpression(), TransformerContext.NONE, ReturnValueContext.VALUE_OR_NULL ) ); } nodes.add( new InsnNode( Opcodes.ARETURN ) ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxScopeTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxScopeTransformer.java index 14e4a0787..ad7587012 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxScopeTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxScopeTransformer.java @@ -24,6 +24,7 @@ import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.MethodInsnNode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; @@ -83,7 +84,7 @@ public List transform( BoxNode node, TransformerContext contex ) ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStatementBlockTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStatementBlockTransformer.java index eb5904319..7b233cee9 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStatementBlockTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStatementBlockTransformer.java @@ -49,7 +49,6 @@ public List transform( BoxNode node, TransformerContext contex nodes.addAll( AsmHelper.transformBodyExpressions( transpiler, boxStatementBlock.getBody(), context, returnContext ) ); - return nodes; - // return AsmHelper.addLineNumberLabels( nodes, node ); + return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStaticAccessTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStaticAccessTransformer.java index 03c008c99..93a670eb5 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStaticAccessTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStaticAccessTransformer.java @@ -25,6 +25,7 @@ import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.MethodInsnNode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; @@ -131,7 +132,7 @@ public List transform( BoxNode node, TransformerContext contex // Node javaExpr = parseExpression( template, values ); // logger.trace( node.getSourceText() + " -> " + javaExpr ); // addIndex( javaExpr, node ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStaticMethodInvocationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStaticMethodInvocationTransformer.java index bc3501dae..98f060fff 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStaticMethodInvocationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStaticMethodInvocationTransformer.java @@ -86,6 +86,6 @@ public List transform( BoxNode node, TransformerContext contex nodes.addAll( AsmHelper.callReferencerGetAndInvoke( transpiler, invocation.getArguments(), invocation.getName().toString(), context, false ) ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringConcatTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringConcatTransformer.java index 2a2fcde25..6acf512a1 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringConcatTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringConcatTransformer.java @@ -55,7 +55,7 @@ public List transform( BoxNode node, TransformerContext contex "invoke", Type.getMethodDescriptor( Type.getType( String.class ), Type.getType( Object[].class ) ), false ) ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringLiteralTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringLiteralTransformer.java index 3cae1e7df..9f85d23a6 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringLiteralTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringLiteralTransformer.java @@ -41,17 +41,17 @@ public BoxStringLiteralTransformer( Transpiler transpiler ) { @Override public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnContext ) throws IllegalStateException { - BoxStringLiteral literal = ( BoxStringLiteral ) node; + BoxStringLiteral literal = ( BoxStringLiteral ) node; - String value = literal.getValue(); + String value = literal.getValue(); + List nodes = new ArrayList(); if ( value.length() < MAX_LITERAL_LENGTH ) { - return List.of( new LdcInsnNode( literal.getValue() ) ); + nodes.add( new LdcInsnNode( literal.getValue() ) ); + return AsmHelper.addLineNumberLabels( nodes, node ); } - - List nodes = new ArrayList(); - List parts = splitStringIntoParts( value ); + List parts = splitStringIntoParts( value ); nodes.add( new LdcInsnNode( "" ) ); nodes.addAll( @@ -73,7 +73,7 @@ public List transform( BoxNode node, TransformerContext contex false ) ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } /** diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStructLiteralTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStructLiteralTransformer.java index 7d4033480..9c00d0341 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStructLiteralTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStructLiteralTransformer.java @@ -55,7 +55,8 @@ public List transform( BoxNode node, TransformerContext contex if ( structLiteral.getType() == BoxStructType.Unordered ) { if ( empty ) { - return List.of( + List nodes = new ArrayList<>(); + nodes.addAll( List.of( new TypeInsnNode( Opcodes.NEW, Type.getInternalName( Struct.class ) ), new InsnNode( Opcodes.DUP ), new MethodInsnNode( Opcodes.INVOKESPECIAL, @@ -63,7 +64,8 @@ public List transform( BoxNode node, TransformerContext contex "", Type.getMethodDescriptor( Type.VOID_TYPE ), false ) - ); + ) ); + return AsmHelper.addLineNumberLabels( nodes, node ); } List nodes = new ArrayList<>(); @@ -86,10 +88,11 @@ public List transform( BoxNode node, TransformerContext contex "of", Type.getMethodDescriptor( Type.getType( IStruct.class ), Type.getType( Object[].class ) ), false ) ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } else { if ( empty ) { - return List.of( + List nodes = new ArrayList<>(); + nodes.addAll( List.of( new TypeInsnNode( Opcodes.NEW, Type.getInternalName( Struct.class ) ), new InsnNode( Opcodes.DUP ), new FieldInsnNode( Opcodes.GETSTATIC, @@ -101,7 +104,8 @@ public List transform( BoxNode node, TransformerContext contex "", Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( IStruct.TYPES.class ) ), false ) - ); + ) ); + return AsmHelper.addLineNumberLabels( nodes, node ); } List nodes = new ArrayList<>(); @@ -124,7 +128,7 @@ public List transform( BoxNode node, TransformerContext contex Type.getMethodDescriptor( Type.getType( IStruct.class ), Type.getType( Object[].class ) ), false ) ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxTernaryOperationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxTernaryOperationTransformer.java index e9b2b240b..a80165170 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxTernaryOperationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxTernaryOperationTransformer.java @@ -27,6 +27,7 @@ import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.MethodInsnNode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; @@ -70,6 +71,6 @@ public List transform( BoxNode node, TransformerContext contex nodes.addAll( whenFalse ); nodes.add( elseLabel ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxUnaryOperationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxUnaryOperationTransformer.java index 22af86887..0424d2103 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxUnaryOperationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxUnaryOperationTransformer.java @@ -24,6 +24,7 @@ import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.VarInsnNode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; @@ -164,7 +165,7 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.POP ) ); } - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } private AbstractInsnNode getMethodCallTemplateCompound( BoxUnaryOperation operation ) { diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxAssertTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxAssertTransformer.java index 31b306b54..fbe328719 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxAssertTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxAssertTransformer.java @@ -24,6 +24,7 @@ import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.VarInsnNode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; @@ -55,6 +56,6 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.POP ) ); } - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java index 69753a2cd..e0fdb63c0 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java @@ -91,6 +91,8 @@ public List transform( BoxNode node, TransformerContext contex transpiler.decrementComponentCounter(); return nodes; + // TODO: this causes CoreLangTest.unicode + // return AsmHelper.addLineNumberLabels( nodes, node ); } if ( transpiler.canReturn() ) { @@ -128,6 +130,8 @@ public List transform( BoxNode node, TransformerContext contex transpiler.decrementComponentCounter(); return nodes; + // TODO: this causes CoreLangTest.unicode + // return AsmHelper.addLineNumberLabels( nodes, node ); } private List generateBodyNodes( List body ) { From 2d447381698113272c0500719e2c313cb5f79ad2 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Wed, 27 Nov 2024 10:46:06 -0600 Subject: [PATCH 004/193] Disable return to javaboxpiler in tests --- src/test/java/TestCases/asm/BasicTest.java | 2 +- src/test/java/TestCases/asm/ComponentTest.java | 2 +- src/test/java/TestCases/asm/control/IfTest.java | 2 +- src/test/java/TestCases/asm/control/SwitchTest.java | 2 +- src/test/java/TestCases/asm/control/TernaryTest.java | 9 +++++++-- .../java/TestCases/asm/integration/ControllerTest.java | 2 +- .../java/TestCases/asm/literal/ArrayLiteralTest.java | 2 +- .../java/TestCases/asm/literal/BooleanLiteralTest.java | 2 +- .../java/TestCases/asm/literal/DoubleLiteralTest.java | 2 +- .../java/TestCases/asm/literal/IntegerLiteralTest.java | 2 +- .../java/TestCases/asm/literal/StringLiteralTest.java | 2 +- .../java/TestCases/asm/literal/StructLiteralTest.java | 2 +- .../TestCases/asm/operator/BinaryMinusOperatorTest.java | 2 +- .../TestCases/asm/operator/BinaryPlusOperatorTest.java | 2 +- .../java/TestCases/asm/operator/UnaryOperatorTest.java | 2 +- src/test/java/TestCases/asm/phase1/AssignmentTest.java | 2 +- src/test/java/TestCases/asm/phase1/CoreLangTest.java | 2 +- src/test/java/TestCases/asm/phase1/DereferenceTest.java | 2 +- src/test/java/TestCases/asm/phase1/LabeledLoopTest.java | 2 +- .../java/TestCases/asm/phase1/ObjectCreationTest.java | 2 +- .../asm/phase1/ObjectReferenceAssignmentTest.java | 2 +- src/test/java/TestCases/asm/phase1/OperatorsTest.java | 2 +- src/test/java/TestCases/asm/phase3/ApplicationTest.java | 2 +- src/test/java/TestCases/asm/phase3/ClassTest.java | 2 +- src/test/java/TestCases/asm/phase3/ExceptionTest.java | 2 +- src/test/java/TestCases/asm/phase3/InterfaceTest.java | 2 +- 26 files changed, 32 insertions(+), 27 deletions(-) diff --git a/src/test/java/TestCases/asm/BasicTest.java b/src/test/java/TestCases/asm/BasicTest.java index cc367ab58..521ca9f27 100644 --- a/src/test/java/TestCases/asm/BasicTest.java +++ b/src/test/java/TestCases/asm/BasicTest.java @@ -60,7 +60,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @DisplayName( "ASM Easy Difficulty Source Test" ) diff --git a/src/test/java/TestCases/asm/ComponentTest.java b/src/test/java/TestCases/asm/ComponentTest.java index efb4c9b1d..80936e124 100644 --- a/src/test/java/TestCases/asm/ComponentTest.java +++ b/src/test/java/TestCases/asm/ComponentTest.java @@ -59,7 +59,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @DisplayName( "Will execute a component body" ) diff --git a/src/test/java/TestCases/asm/control/IfTest.java b/src/test/java/TestCases/asm/control/IfTest.java index 81510f8c2..464c77e1e 100644 --- a/src/test/java/TestCases/asm/control/IfTest.java +++ b/src/test/java/TestCases/asm/control/IfTest.java @@ -59,7 +59,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @DisplayName( "Will run the code inside of an if with a true condition" ) diff --git a/src/test/java/TestCases/asm/control/SwitchTest.java b/src/test/java/TestCases/asm/control/SwitchTest.java index 71139aebf..622495118 100644 --- a/src/test/java/TestCases/asm/control/SwitchTest.java +++ b/src/test/java/TestCases/asm/control/SwitchTest.java @@ -59,7 +59,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @DisplayName( "Will not execute code that doesn't match a condition" ) diff --git a/src/test/java/TestCases/asm/control/TernaryTest.java b/src/test/java/TestCases/asm/control/TernaryTest.java index b594ea71c..8c71f2cad 100644 --- a/src/test/java/TestCases/asm/control/TernaryTest.java +++ b/src/test/java/TestCases/asm/control/TernaryTest.java @@ -19,7 +19,12 @@ import static com.google.common.truth.Truth.assertThat; -import org.junit.jupiter.api.*; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.context.IBoxContext; @@ -54,7 +59,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @DisplayName( "Will return the true option of a ternary when condition is true" ) diff --git a/src/test/java/TestCases/asm/integration/ControllerTest.java b/src/test/java/TestCases/asm/integration/ControllerTest.java index f47fd89ed..23e7549c8 100644 --- a/src/test/java/TestCases/asm/integration/ControllerTest.java +++ b/src/test/java/TestCases/asm/integration/ControllerTest.java @@ -58,7 +58,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @Test diff --git a/src/test/java/TestCases/asm/literal/ArrayLiteralTest.java b/src/test/java/TestCases/asm/literal/ArrayLiteralTest.java index 412fb6195..88f0c1ef5 100644 --- a/src/test/java/TestCases/asm/literal/ArrayLiteralTest.java +++ b/src/test/java/TestCases/asm/literal/ArrayLiteralTest.java @@ -60,7 +60,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @DisplayName( "Can decalare an empty literal" ) diff --git a/src/test/java/TestCases/asm/literal/BooleanLiteralTest.java b/src/test/java/TestCases/asm/literal/BooleanLiteralTest.java index 91ccb99af..51d59d091 100644 --- a/src/test/java/TestCases/asm/literal/BooleanLiteralTest.java +++ b/src/test/java/TestCases/asm/literal/BooleanLiteralTest.java @@ -59,7 +59,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @DisplayName( "Can decalare a boolean literal (true)" ) diff --git a/src/test/java/TestCases/asm/literal/DoubleLiteralTest.java b/src/test/java/TestCases/asm/literal/DoubleLiteralTest.java index 4b06ee57b..bc6a073a8 100644 --- a/src/test/java/TestCases/asm/literal/DoubleLiteralTest.java +++ b/src/test/java/TestCases/asm/literal/DoubleLiteralTest.java @@ -61,7 +61,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @DisplayName( "Can declare a double literal" ) diff --git a/src/test/java/TestCases/asm/literal/IntegerLiteralTest.java b/src/test/java/TestCases/asm/literal/IntegerLiteralTest.java index 4e16e3903..c8548defe 100644 --- a/src/test/java/TestCases/asm/literal/IntegerLiteralTest.java +++ b/src/test/java/TestCases/asm/literal/IntegerLiteralTest.java @@ -59,7 +59,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @DisplayName( "Can decalare an int literal" ) diff --git a/src/test/java/TestCases/asm/literal/StringLiteralTest.java b/src/test/java/TestCases/asm/literal/StringLiteralTest.java index c0abed43f..1169dc1a2 100644 --- a/src/test/java/TestCases/asm/literal/StringLiteralTest.java +++ b/src/test/java/TestCases/asm/literal/StringLiteralTest.java @@ -59,7 +59,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @DisplayName( "Can decalare a string literal" ) diff --git a/src/test/java/TestCases/asm/literal/StructLiteralTest.java b/src/test/java/TestCases/asm/literal/StructLiteralTest.java index 484014b9b..5991acc63 100644 --- a/src/test/java/TestCases/asm/literal/StructLiteralTest.java +++ b/src/test/java/TestCases/asm/literal/StructLiteralTest.java @@ -61,7 +61,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @DisplayName( "Can declare an empty literal" ) diff --git a/src/test/java/TestCases/asm/operator/BinaryMinusOperatorTest.java b/src/test/java/TestCases/asm/operator/BinaryMinusOperatorTest.java index 5c13f4978..1d3ae5f25 100644 --- a/src/test/java/TestCases/asm/operator/BinaryMinusOperatorTest.java +++ b/src/test/java/TestCases/asm/operator/BinaryMinusOperatorTest.java @@ -59,7 +59,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @DisplayName( "Can subtract two positive int" ) diff --git a/src/test/java/TestCases/asm/operator/BinaryPlusOperatorTest.java b/src/test/java/TestCases/asm/operator/BinaryPlusOperatorTest.java index b7505fb40..40c7b5c53 100644 --- a/src/test/java/TestCases/asm/operator/BinaryPlusOperatorTest.java +++ b/src/test/java/TestCases/asm/operator/BinaryPlusOperatorTest.java @@ -59,7 +59,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @DisplayName( "Can add two positive int" ) diff --git a/src/test/java/TestCases/asm/operator/UnaryOperatorTest.java b/src/test/java/TestCases/asm/operator/UnaryOperatorTest.java index 6403f9a70..03df7bb28 100644 --- a/src/test/java/TestCases/asm/operator/UnaryOperatorTest.java +++ b/src/test/java/TestCases/asm/operator/UnaryOperatorTest.java @@ -59,7 +59,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @DisplayName( "Can negate a boolean literal" ) diff --git a/src/test/java/TestCases/asm/phase1/AssignmentTest.java b/src/test/java/TestCases/asm/phase1/AssignmentTest.java index 5ac265046..4f3e3e779 100644 --- a/src/test/java/TestCases/asm/phase1/AssignmentTest.java +++ b/src/test/java/TestCases/asm/phase1/AssignmentTest.java @@ -61,7 +61,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @DisplayName( "Unscoped assignment" ) diff --git a/src/test/java/TestCases/asm/phase1/CoreLangTest.java b/src/test/java/TestCases/asm/phase1/CoreLangTest.java index de3a336a9..47afd2997 100644 --- a/src/test/java/TestCases/asm/phase1/CoreLangTest.java +++ b/src/test/java/TestCases/asm/phase1/CoreLangTest.java @@ -77,7 +77,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @DisplayName( "if" ) diff --git a/src/test/java/TestCases/asm/phase1/DereferenceTest.java b/src/test/java/TestCases/asm/phase1/DereferenceTest.java index f0188d5c4..3fb407bd2 100644 --- a/src/test/java/TestCases/asm/phase1/DereferenceTest.java +++ b/src/test/java/TestCases/asm/phase1/DereferenceTest.java @@ -64,7 +64,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @DisplayName( "Single identifier dot access" ) diff --git a/src/test/java/TestCases/asm/phase1/LabeledLoopTest.java b/src/test/java/TestCases/asm/phase1/LabeledLoopTest.java index 18fa1333e..0adf7aacd 100644 --- a/src/test/java/TestCases/asm/phase1/LabeledLoopTest.java +++ b/src/test/java/TestCases/asm/phase1/LabeledLoopTest.java @@ -60,7 +60,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @Test diff --git a/src/test/java/TestCases/asm/phase1/ObjectCreationTest.java b/src/test/java/TestCases/asm/phase1/ObjectCreationTest.java index 6c767ca35..4a4266ba6 100644 --- a/src/test/java/TestCases/asm/phase1/ObjectCreationTest.java +++ b/src/test/java/TestCases/asm/phase1/ObjectCreationTest.java @@ -60,7 +60,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @DisplayName( "new keyword prefix" ) diff --git a/src/test/java/TestCases/asm/phase1/ObjectReferenceAssignmentTest.java b/src/test/java/TestCases/asm/phase1/ObjectReferenceAssignmentTest.java index b40d022d3..336307727 100644 --- a/src/test/java/TestCases/asm/phase1/ObjectReferenceAssignmentTest.java +++ b/src/test/java/TestCases/asm/phase1/ObjectReferenceAssignmentTest.java @@ -69,7 +69,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @DisplayName( "scope assignment" ) diff --git a/src/test/java/TestCases/asm/phase1/OperatorsTest.java b/src/test/java/TestCases/asm/phase1/OperatorsTest.java index 5b0bacd61..efbb97cbf 100644 --- a/src/test/java/TestCases/asm/phase1/OperatorsTest.java +++ b/src/test/java/TestCases/asm/phase1/OperatorsTest.java @@ -64,7 +64,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @DisplayName( "string concat" ) diff --git a/src/test/java/TestCases/asm/phase3/ApplicationTest.java b/src/test/java/TestCases/asm/phase3/ApplicationTest.java index 98cb2dea3..148cdbcc4 100644 --- a/src/test/java/TestCases/asm/phase3/ApplicationTest.java +++ b/src/test/java/TestCases/asm/phase3/ApplicationTest.java @@ -72,7 +72,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @DisplayName( "application basics" ) diff --git a/src/test/java/TestCases/asm/phase3/ClassTest.java b/src/test/java/TestCases/asm/phase3/ClassTest.java index 304ebb4b6..4456e4f42 100644 --- a/src/test/java/TestCases/asm/phase3/ClassTest.java +++ b/src/test/java/TestCases/asm/phase3/ClassTest.java @@ -73,7 +73,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @DisplayName( "Test can create vanilla module config" ) diff --git a/src/test/java/TestCases/asm/phase3/ExceptionTest.java b/src/test/java/TestCases/asm/phase3/ExceptionTest.java index c53f033e2..80b28b71b 100644 --- a/src/test/java/TestCases/asm/phase3/ExceptionTest.java +++ b/src/test/java/TestCases/asm/phase3/ExceptionTest.java @@ -59,7 +59,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @Test diff --git a/src/test/java/TestCases/asm/phase3/InterfaceTest.java b/src/test/java/TestCases/asm/phase3/InterfaceTest.java index cc301d42a..167dd863d 100644 --- a/src/test/java/TestCases/asm/phase3/InterfaceTest.java +++ b/src/test/java/TestCases/asm/phase3/InterfaceTest.java @@ -64,7 +64,7 @@ public void setupEach() { @AfterEach public void teardownEach() { - instance.useJavaBoxpiler(); + // instance.useJavaBoxpiler(); } @DisplayName( "basic CF interface" ) From 512d71cbeab246752f4a6d95dabd90abdb57ac2a Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Wed, 27 Nov 2024 11:29:45 -0600 Subject: [PATCH 005/193] BL-796 add query support to for in loop --- .../compiler/asmboxpiler/AsmTranspiler.java | 2 +- .../statement/BoxComponentTransformer.java | 6 +- .../statement/BoxForInTransformer.java | 69 ++++++++++++++++++- 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index 5ecc7f8c3..5c52e28e5 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -539,7 +539,7 @@ public ClassNode transpile( BoxScript boxScript ) throws BoxRuntimeException { for ( BoxExpression expression : getKeys().values() ) { methodVisitor.visitInsn( Opcodes.DUP ); methodVisitor.visitLdcInsn( index++ ); - transform( expression, TransformerContext.NONE, ReturnValueContext.EMPTY ).forEach( methodInsnNode -> methodInsnNode.accept( methodVisitor ) ); + transform( expression, TransformerContext.NONE, ReturnValueContext.VALUE ).forEach( methodInsnNode -> methodInsnNode.accept( methodVisitor ) ); methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC, Type.getInternalName( Key.class ), "of", diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java index e0fdb63c0..99100f938 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java @@ -126,7 +126,11 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( ifLabel ); } - nodes.add( new InsnNode( Opcodes.POP ) ); + + if ( returnContext != ReturnValueContext.VALUE && returnContext != ReturnValueContext.VALUE_OR_NULL ) { + nodes.add( new InsnNode( Opcodes.POP ) ); + } + transpiler.decrementComponentCounter(); return nodes; diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java index ef2df86f7..8501f1f4f 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java @@ -26,6 +26,7 @@ import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.JumpInsnNode; import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.TypeInsnNode; import org.objectweb.asm.tree.VarInsnNode; @@ -44,6 +45,7 @@ import ortus.boxlang.compiler.ast.expression.BoxAssignmentModifier; import ortus.boxlang.compiler.ast.expression.BoxAssignmentOperator; import ortus.boxlang.compiler.ast.statement.BoxForIn; +import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.dynamic.casters.CollectionCaster; import ortus.boxlang.runtime.interop.DynamicObject; import ortus.boxlang.runtime.types.Query; @@ -113,6 +115,29 @@ public List transform( BoxNode node, TransformerContext contex nodes.addAll( isStructVar.nodes() ); // need to register query loop + // ${contextName}.registerQueryLoop( (Query) ${collectionName}, 0 ); + nodes.add( new VarInsnNode( Opcodes.ILOAD, isQueryVar.index() ) ); + LabelNode endQueryLabel = new LabelNode(); + nodes.add( new JumpInsnNode( Opcodes.IFEQ, endQueryLabel ) ); + // push context + nodes.addAll( tracker.loadCurrentContext() ); + // push collection + nodes.add( new VarInsnNode( Opcodes.ALOAD, collectionVar.index() ) ); + nodes.add( new TypeInsnNode( Opcodes.CHECKCAST, Type.getInternalName( Query.class ) ) ); + // push constant 0 + nodes.add( new LdcInsnNode( 0 ) ); + // invoke regiserQueryLoop + nodes.add( new MethodInsnNode( Opcodes.INVOKEINTERFACE, + Type.getInternalName( IBoxContext.class ), + "registerQueryLoop", + Type.getMethodDescriptor( + Type.VOID_TYPE, + Type.getType( Query.class ), + Type.INT_TYPE + ), + true + ) ); + nodes.add( endQueryLabel ); // create iterator nodes.add( new VarInsnNode( Opcodes.ALOAD, collectionVar.index() ) ); @@ -172,6 +197,27 @@ public List transform( BoxNode node, TransformerContext contex nodes.addAll( transpiler.transform( forIn.getBody(), context, returnValueContext ) ); + // increment query loop + nodes.add( new VarInsnNode( Opcodes.ILOAD, isQueryVar.index() ) ); + LabelNode endQueryIncrementLabel = new LabelNode(); + nodes.add( new JumpInsnNode( Opcodes.IFEQ, endQueryIncrementLabel ) ); + // push context + nodes.addAll( tracker.loadCurrentContext() ); + // push collection + nodes.add( new VarInsnNode( Opcodes.ALOAD, collectionVar.index() ) ); + nodes.add( new TypeInsnNode( Opcodes.CHECKCAST, Type.getInternalName( Query.class ) ) ); + // invoke regiserQueryLoop + nodes.add( new MethodInsnNode( Opcodes.INVOKEINTERFACE, + Type.getInternalName( IBoxContext.class ), + "incrementQueryLoop", + Type.getMethodDescriptor( + Type.VOID_TYPE, + Type.getType( Query.class ) + ), + true + ) ); + nodes.add( endQueryIncrementLabel ); + nodes.add( new JumpInsnNode( Opcodes.GOTO, loopStart ) ); nodes.add( breakTarget ); @@ -180,9 +226,30 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.SWAP ) ); nodes.add( new InsnNode( Opcodes.POP ) ); } - // increment query loop + nodes.add( loopEnd ); + // unregister query loop + nodes.add( new VarInsnNode( Opcodes.ILOAD, isQueryVar.index() ) ); + LabelNode unRegisterQueryLabel = new LabelNode(); + nodes.add( new JumpInsnNode( Opcodes.IFEQ, unRegisterQueryLabel ) ); + // push context + nodes.addAll( tracker.loadCurrentContext() ); + // push collection + nodes.add( new VarInsnNode( Opcodes.ALOAD, collectionVar.index() ) ); + nodes.add( new TypeInsnNode( Opcodes.CHECKCAST, Type.getInternalName( Query.class ) ) ); + // invoke regiserQueryLoop + nodes.add( new MethodInsnNode( Opcodes.INVOKEINTERFACE, + Type.getInternalName( IBoxContext.class ), + "unregisterQueryLoop", + Type.getMethodDescriptor( + Type.VOID_TYPE, + Type.getType( Query.class ) + ), + true + ) ); + nodes.add( unRegisterQueryLabel ); + return nodes; } From 9d93a1a9190af9f1160e2fa577a1fdcd2d2b30f8 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Wed, 27 Nov 2024 22:44:55 -0600 Subject: [PATCH 006/193] BL-801 fix output buffer --- .../transformer/statement/BoxBufferOutputTransformer.java | 5 ++++- .../transformer/statement/BoxComponentTransformer.java | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxBufferOutputTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxBufferOutputTransformer.java index 01d656087..e984cef7b 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxBufferOutputTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxBufferOutputTransformer.java @@ -54,7 +54,10 @@ public List transform( BoxNode node, TransformerContext contex "writeToBuffer", Type.getMethodDescriptor( Type.getType( IBoxContext.class ), Type.getType( Object.class ) ), true ) ); - nodes.add( new InsnNode( Opcodes.POP ) ); + + if ( returnContext != ReturnValueContext.VALUE && returnContext != ReturnValueContext.VALUE_OR_NULL ) { + nodes.add( new InsnNode( Opcodes.POP ) ); + } return nodes; } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java index 99100f938..07fda35e5 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java @@ -86,7 +86,9 @@ public List transform( BoxNode node, TransformerContext contex true ) ); if ( boxComponent.getBody() == null || boxComponent.getBody().size() == 0 ) { - nodes.add( new InsnNode( Opcodes.POP ) ); + if ( returnContext != ReturnValueContext.VALUE && returnContext != ReturnValueContext.VALUE_OR_NULL ) { + nodes.add( new InsnNode( Opcodes.POP ) ); + } transpiler.decrementComponentCounter(); From a473a74b374c1b4b114fda7ba7f888fd7337f42f Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Wed, 27 Nov 2024 22:45:16 -0600 Subject: [PATCH 007/193] BL-802 fix abort exception test --- .../transformer/statement/BoxTryTransformer.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTryTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTryTransformer.java index eade0d83f..bcc71e40a 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTryTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTryTransformer.java @@ -44,6 +44,7 @@ import ortus.boxlang.runtime.context.CatchBoxContext; import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.exceptions.AbortException; import ortus.boxlang.runtime.types.exceptions.ExceptionUtil; public class BoxTryTransformer extends AbstractTransformer { @@ -83,6 +84,14 @@ public List transform( BoxNode node, TransformerContext contex var eVar = tracker.storeNewVariable( Opcodes.ASTORE ); nodes.addAll( eVar.nodes() ); + nodes.add( new VarInsnNode( Opcodes.ALOAD, eVar.index() ) ); + nodes.add( new TypeInsnNode( Opcodes.INSTANCEOF, Type.getInternalName( AbortException.class ) ) ); + LabelNode abortLabel = new LabelNode(); + nodes.add( new JumpInsnNode( Opcodes.IFEQ, abortLabel ) ); + nodes.add( new VarInsnNode( Opcodes.ALOAD, eVar.index() ) ); + nodes.add( new InsnNode( Opcodes.ATHROW ) ); + nodes.add( abortLabel ); + for ( BoxTryCatch catchNode : boxTry.getCatches() ) { nodes.addAll( generateCatchBodyNodes( context, returnValueContext, tracker, boxTry, catchNode, finallyStartLabel, finallyEndLabel, eVar.index() ) From 9a6c6e82c37115044ad8a2e03019d6944766f87e Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Fri, 29 Nov 2024 16:34:40 -0600 Subject: [PATCH 008/193] BL-806 --- .../compiler/asmboxpiler/AsmHelper.java | 6 ++-- .../expression/BoxContinueTransformer.java | 5 ++- .../statement/BoxComponentTransformer.java | 32 ++++++++++++++++--- .../statement/BoxForInTransformer.java | 13 +++++--- 4 files changed, 40 insertions(+), 16 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java index a0f2083a4..76f610e15 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java @@ -346,7 +346,7 @@ public static List callinvokeFunction( nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, Type.getInternalName( Struct.class ), - "of", + "linkedOf", Type.getMethodDescriptor( Type.getType( IStruct.class ), Type.getType( Object[].class ) ), false ) @@ -425,7 +425,7 @@ public static List callReferencerGetAndInvoke( nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, Type.getInternalName( Struct.class ), - "of", + "linkedOf", Type.getMethodDescriptor( Type.getType( IStruct.class ), Type.getType( Object[].class ) ), false ) @@ -490,7 +490,7 @@ public static List callDynamicObjectInvokeConstructor( Transpi nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, Type.getInternalName( Struct.class ), - "of", + "linkedOf", Type.getMethodDescriptor( Type.getType( IStruct.class ), Type.getType( Object[].class ) ), false ) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxContinueTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxContinueTransformer.java index 1dd3c54b5..b1e2e364f 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxContinueTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxContinueTransformer.java @@ -23,7 +23,6 @@ import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.JumpInsnNode; import org.objectweb.asm.tree.LabelNode; -import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import ortus.boxlang.compiler.asmboxpiler.AsmHelper; @@ -75,10 +74,10 @@ public List transform( BoxNode node, TransformerContext contex } if ( exitsAllowed.equals( ExitsAllowed.COMPONENT ) ) { - nodes.add( new LdcInsnNode( "" ) ); + nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, Type.getInternalName( Component.BodyResult.class ), - "ofBreak", + "ofContinue", Type.getMethodDescriptor( Type.getType( Component.BodyResult.class ), Type.getType( String.class ) ), false ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java index 07fda35e5..41e6c27b7 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java @@ -48,8 +48,6 @@ public List transform( BoxNode node, TransformerContext contex throw new IllegalStateException(); } - transpiler.incrementComponentCounter(); - MethodContextTracker tracker = trackerOption.get(); List nodes = new ArrayList<>(); nodes.addAll( tracker.loadCurrentContext() ); @@ -76,7 +74,9 @@ public List transform( BoxNode node, TransformerContext contex nodes.addAll( transpiler.transformAnnotations( attributes, true, false ) ); // Component.ComponentBody + transpiler.incrementComponentCounter(); nodes.addAll( generateBodyNodes( boxComponent.getBody() ) ); + transpiler.decrementComponentCounter(); nodes.add( new MethodInsnNode( Opcodes.INVOKEINTERFACE, Type.getInternalName( IBoxContext.class ), @@ -90,14 +90,36 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.POP ) ); } - transpiler.decrementComponentCounter(); + // transpiler.decrementComponentCounter(); return nodes; // TODO: this causes CoreLangTest.unicode // return AsmHelper.addLineNumberLabels( nodes, node ); } - if ( transpiler.canReturn() ) { + if ( transpiler.isInsideComponent() ) { + LabelNode ifLabel = new LabelNode(); + + nodes.add( new InsnNode( Opcodes.DUP ) ); + + nodes.add( + new MethodInsnNode( + Opcodes.INVOKEVIRTUAL, + Type.getInternalName( Component.BodyResult.class ), + "isEarlyExit", + Type.getMethodDescriptor( Type.BOOLEAN_TYPE ), + false + ) + ); + + nodes.add( new JumpInsnNode( Opcodes.IFEQ, ifLabel ) ); + + nodes.add( new InsnNode( Opcodes.ARETURN ) ); + + nodes.add( ifLabel ); + + nodes.add( new InsnNode( Opcodes.ARETURN ) ); + } else if ( transpiler.canReturn() ) { LabelNode ifLabel = new LabelNode(); nodes.add( new InsnNode( Opcodes.DUP ) ); @@ -133,7 +155,7 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.POP ) ); } - transpiler.decrementComponentCounter(); + // transpiler.decrementComponentCounter(); return nodes; // TODO: this causes CoreLangTest.unicode diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java index 8501f1f4f..7028aa960 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java @@ -67,13 +67,14 @@ public List transform( BoxNode node, TransformerContext contex throw new IllegalStateException(); } - MethodContextTracker tracker = trackerOption.get(); + MethodContextTracker tracker = trackerOption.get(); - LabelNode loopStart = new LabelNode(); - LabelNode loopEnd = new LabelNode(); - LabelNode breakTarget = new LabelNode(); + LabelNode loopStart = new LabelNode(); + LabelNode loopEnd = new LabelNode(); + LabelNode breakTarget = new LabelNode(); + LabelNode continueTarget = new LabelNode(); - tracker.setContinue( forIn, loopStart ); + tracker.setContinue( forIn, continueTarget ); tracker.setBreak( forIn, breakTarget ); if ( forIn.getLabel() != null ) { tracker.setStringLabel( forIn.getLabel(), forIn ); @@ -197,6 +198,8 @@ public List transform( BoxNode node, TransformerContext contex nodes.addAll( transpiler.transform( forIn.getBody(), context, returnValueContext ) ); + nodes.add( continueTarget ); + // increment query loop nodes.add( new VarInsnNode( Opcodes.ILOAD, isQueryVar.index() ) ); LabelNode endQueryIncrementLabel = new LabelNode(); From b78af50128fe6b3aecd7b23f3cdbf7990899db4f Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Fri, 29 Nov 2024 17:11:34 -0600 Subject: [PATCH 009/193] BL-806 ASM fixes --- .../transformer/statement/BoxComponentTransformer.java | 10 ++++------ .../transformer/statement/BoxParamTransformer.java | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java index 41e6c27b7..3c048bba2 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java @@ -86,13 +86,13 @@ public List transform( BoxNode node, TransformerContext contex true ) ); if ( boxComponent.getBody() == null || boxComponent.getBody().size() == 0 ) { - if ( returnContext != ReturnValueContext.VALUE && returnContext != ReturnValueContext.VALUE_OR_NULL ) { - nodes.add( new InsnNode( Opcodes.POP ) ); - } + // if ( returnContext != ReturnValueContext.VALUE && returnContext != ReturnValueContext.VALUE_OR_NULL ) { + // nodes.add( new InsnNode( Opcodes.POP ) ); + // } // transpiler.decrementComponentCounter(); - return nodes; + // return nodes; // TODO: this causes CoreLangTest.unicode // return AsmHelper.addLineNumberLabels( nodes, node ); } @@ -117,8 +117,6 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.ARETURN ) ); nodes.add( ifLabel ); - - nodes.add( new InsnNode( Opcodes.ARETURN ) ); } else if ( transpiler.canReturn() ) { LabelNode ifLabel = new LabelNode(); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxParamTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxParamTransformer.java index ee2ba04bb..4ebf06f2b 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxParamTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxParamTransformer.java @@ -74,6 +74,6 @@ public List transform( BoxNode node, TransformerContext contex ); } // Delegate to the component transformer - return transpiler.transform( new BoxComponent( "param", attrs, node.getPosition(), node.getSourceText() ), context ); + return transpiler.transform( new BoxComponent( "param", attrs, node.getPosition(), node.getSourceText() ), context, returnContext ); } } From 50357d82950ea5672a03dcf3f2d42250572d1e8b Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Fri, 29 Nov 2024 20:39:17 -0600 Subject: [PATCH 010/193] BL-806 ASM fixes --- .../compiler/asmboxpiler/AsmHelper.java | 73 ++++++++++++++++++- .../compiler/asmboxpiler/AsmTranspiler.java | 7 +- .../compiler/asmboxpiler/Transpiler.java | 4 + .../statement/BoxClassTransformer.java | 38 ++++------ .../statement/BoxComponentTransformer.java | 4 +- 5 files changed, 99 insertions(+), 27 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java index 76f610e15..8aa61c898 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java @@ -652,7 +652,35 @@ public static void addStaticFieldGetter( ClassVisitor classVisitor, Type type, S methodVisitor.visitEnd(); } - public static void addFieldGetter( ClassVisitor classVisitor, Type type, String field, String method, Type property, Object value ) { + public static void addPublicStaticFieldAndPublicStaticGetter( + ClassVisitor classVisitor, + Type owningType, + String field, + String method, + Type propertyType, + Object defaultValue ) { + FieldVisitor fieldVisitor = classVisitor.visitField( Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, + field, + propertyType.getDescriptor(), + null, + defaultValue ); + fieldVisitor.visitEnd(); + MethodVisitor methodVisitor = classVisitor.visitMethod( Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, + method, + Type.getMethodDescriptor( propertyType ), + null, + null ); + methodVisitor.visitCode(); + methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, + owningType.getInternalName(), + field, + propertyType.getDescriptor() ); + methodVisitor.visitInsn( propertyType.getOpcode( Opcodes.IRETURN ) ); + methodVisitor.visitMaxs( 0, 0 ); + methodVisitor.visitEnd(); + } + + public static void addPrivateFieldGetter( ClassVisitor classVisitor, Type type, String field, String method, Type property, Object value ) { FieldVisitor fieldVisitor = classVisitor.visitField( Opcodes.ACC_PRIVATE, field, property.getDescriptor(), @@ -675,6 +703,49 @@ public static void addFieldGetter( ClassVisitor classVisitor, Type type, String methodVisitor.visitEnd(); } + public static void addPrivateFieldGetterAndSetter( ClassVisitor classVisitor, Type type, String field, String getter, String setter, Type property, + Object value ) { + addPrivateFieldGetter( classVisitor, type, field, getter, property, value ); + MethodVisitor methodVisitor = classVisitor.visitMethod( Opcodes.ACC_PUBLIC, + setter, + Type.getMethodDescriptor( Type.VOID_TYPE, property ), + null, + null ); + methodVisitor.visitCode(); + methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); + methodVisitor.visitVarInsn( Opcodes.ALOAD, 1 ); + methodVisitor.visitFieldInsn( Opcodes.PUTFIELD, + type.getInternalName(), + field, + property.getDescriptor() ); + methodVisitor.visitInsn( Opcodes.RETURN ); + methodVisitor.visitMaxs( 0, 0 ); + methodVisitor.visitEnd(); + } + + public static void addFieldGetter( ClassVisitor classVisitor, Type type, String field, String method, Type property, Object value ) { + FieldVisitor fieldVisitor = classVisitor.visitField( Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, + field, + property.getDescriptor(), + null, + value ); + fieldVisitor.visitEnd(); + MethodVisitor methodVisitor = classVisitor.visitMethod( Opcodes.ACC_PUBLIC, + method, + Type.getMethodDescriptor( property ), + null, + null ); + methodVisitor.visitCode(); + methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); + methodVisitor.visitFieldInsn( Opcodes.GETSTATIC, + type.getInternalName(), + field, + property.getDescriptor() ); + methodVisitor.visitInsn( property.getOpcode( Opcodes.IRETURN ) ); + methodVisitor.visitMaxs( 0, 0 ); + methodVisitor.visitEnd(); + } + public static void addPrviateStaticFieldGetter( ClassVisitor classVisitor, Type type, String field, String method, Type property, Object value ) { FieldVisitor fieldVisitor = classVisitor.visitField( Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, field, diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index 5c52e28e5..af7186b6e 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -616,6 +616,11 @@ public List transform( BoxNode node, TransformerContext contex case EMPTY, EMPTY_UNLESS_JUMPING -> 0; case VALUE, VALUE_OR_NULL -> 1; }; + + if ( node instanceof BoxReturn ) { + expectation = 0; + } + if ( expectation != delta ) { throw new IllegalStateException( node.getClass() + " with " + returnValueContext + " yielded a stack delta of " + delta ); } @@ -673,7 +678,7 @@ public List> transformProperties( Type declaringType, Lis + "/" + getProperty( "classname" ) + "$Lambda_" + incrementAndGetLambdaCounter() + ";" ); - List body = transform( defaultAnnotation.getValue(), TransformerContext.NONE, ReturnValueContext.EMPTY ); + List body = transform( defaultAnnotation.getValue(), TransformerContext.NONE, ReturnValueContext.VALUE_OR_NULL ); ClassNode classNode = new ClassNode(); AsmHelper.init( classNode, false, type, Type.getType( Object.class ), methodVisitor -> { }, Type.getType( DefaultExpression.class ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java index f7fedd2da..402ef9c4c 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java @@ -64,6 +64,10 @@ public void setProperty( String key, String value ) { } public boolean canReturn() { + String returnType = getProperty( "returnType" ); + if ( returnType != null && !returnType.equals( "void" ) ) { + return true; + } return functionBodyCounter > 0; } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java index 5d4985da7..8c49df8d9 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java @@ -367,75 +367,65 @@ public static ClassNode transpile( Transpiler transpiler, BoxClass boxClass ) th defineLookupPrivateMethod( transpiler, classNode, type ); defineLookupPrivateField( transpiler, classNode, type ); - AsmHelper.addFieldGetter( classNode, + AsmHelper.addPrivateFieldGetter( classNode, type, "variablesScope", "getVariablesScope", Type.getType( VariablesScope.class ), null ); - AsmHelper.addFieldGetter( classNode, + AsmHelper.addPrivateFieldGetter( classNode, type, "thisScope", "getThisScope", Type.getType( ThisScope.class ), null ); - AsmHelper.addFieldGetter( classNode, + AsmHelper.addPrivateFieldGetter( classNode, type, "name", "getName", Type.getType( Key.class ), null ); - AsmHelper.addFieldGetter( classNode, + AsmHelper.addPrivateFieldGetter( classNode, type, "interfaces", "getInterfaces", Type.getType( List.class ), null ); - AsmHelper.addFieldGetterAndSetter( classNode, + AsmHelper.addPrivateFieldGetterAndSetter( classNode, type, "_super", "getSuper", "_setSuper", Type.getType( IClassRunnable.class ), - null, - methodVisitor -> { - } ); - AsmHelper.addFieldGetterAndSetter( classNode, + null ); + AsmHelper.addPrivateFieldGetterAndSetter( classNode, type, "child", "getChild", "setChild", Type.getType( IClassRunnable.class ), - null, - methodVisitor -> { - } ); - AsmHelper.addFieldGetterAndSetter( classNode, + null ); + AsmHelper.addPrivateFieldGetterAndSetter( classNode, type, "canOutput", "getCanOutput", "setCanOutput", Type.getType( Boolean.class ), - null, - methodVisitor -> { - } ); - AsmHelper.addFieldGetterAndSetter( classNode, + null ); + AsmHelper.addPrivateFieldGetterAndSetter( classNode, type, "$bx", "_getbx", "_setbx", Type.getType( BoxMeta.class ), - null, - methodVisitor -> { - } ); - AsmHelper.addFieldGetterAndSetter( classNode, + null ); + AsmHelper.addPrivateFieldGetterAndSetter( classNode, type, "canInvokeImplicitAccessor", "getCanInvokeImplicitAccessor", "setCanInvokeImplicitAccessor", Type.getType( Boolean.class ), - null, - methodVisitor -> { - } ); + null ); AsmHelper.boxClassSupport( classNode, "pseudoConstructor", Type.VOID_TYPE, Type.getType( IBoxContext.class ) ); AsmHelper.boxClassSupport( classNode, "canOutput", Type.getType( Boolean.class ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java index 3c048bba2..4afbdfc60 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java @@ -117,7 +117,9 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.ARETURN ) ); nodes.add( ifLabel ); - } else if ( transpiler.canReturn() ) { + + return nodes; + } else if ( transpiler.canReturn() && !returnContext.empty ) { LabelNode ifLabel = new LabelNode(); nodes.add( new InsnNode( Opcodes.DUP ) ); From 34f960d08c63de2064c4d77881ce0c113c68da69 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Fri, 29 Nov 2024 20:50:26 -0600 Subject: [PATCH 011/193] BL-806 ASM fixes --- .../transformer/expression/BoxAccessTransformer.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAccessTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAccessTransformer.java index 01d83a422..de6d2b698 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAccessTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAccessTransformer.java @@ -128,7 +128,8 @@ public List transform( BoxNode node, TransformerContext contex Type.getType( Boolean.class ) ), false ) ); BoxNode parent = objectAccess.getParent(); - if ( ! ( parent instanceof BoxAccess ) + + if ( ! ( parent instanceof BoxAccess ba && ba.getContext() == objectAccess ) // I don't know if this will work, but I'm trying to make an exception for query columns being passed to array BIFs // This prolly won't work if a query column is passed as a second param that isn't the array && ! ( parent instanceof BoxArgument barg && barg.getParent() instanceof BoxFunctionInvocation bfun From 6b2b1ab3a443aa4f907004276a74eb2d64d8245c Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Sat, 30 Nov 2024 14:01:47 -0600 Subject: [PATCH 012/193] BL-806 ASM fixes --- .../ortus/boxlang/compiler/asmboxpiler/AsmHelper.java | 2 ++ .../boxlang/compiler/asmboxpiler/AsmTranspiler.java | 2 +- .../boxlang/compiler/asmboxpiler/Transpiler.java | 9 +++++---- .../transformer/expression/BoxAccessTransformer.java | 9 +++++++++ .../expression/BoxArgumentDeclarationTransformer.java | 11 ++++++++++- .../expression/BoxAssignmentTransformer.java | 6 +++--- .../expression/BoxStringLiteralTransformer.java | 9 +++++++++ .../expression/BoxStructLiteralTransformer.java | 8 ++++---- .../transformer/expression/BoxSwitchTransformer.java | 6 ++++++ .../transformer/statement/BoxClassTransformer.java | 2 +- .../statement/BoxComponentTransformer.java | 2 +- .../statement/BoxInterfaceTransformer.java | 2 +- src/test/java/TestCases/asm/phase3/ClassTest.java | 4 ++++ 13 files changed, 56 insertions(+), 16 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java index 8aa61c898..c20b5447e 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java @@ -1100,6 +1100,8 @@ public static void addLazySingleton( ClassVisitor classVisitor, Type type, Consu */ public static List loadClass( Transpiler transpiler, BoxIdentifier identifier ) { List nodes = new ArrayList<>(); + // the variable at slot 2 needs to be an instance of ClassLocator + // todo convert this to use some specific method like tracker.loadClassLocator() nodes.add( new VarInsnNode( Opcodes.ALOAD, 2 ) ); transpiler.getCurrentMethodContextTracker().ifPresent( ( t ) -> nodes.addAll( t.loadCurrentContext() ) ); nodes.add( new LdcInsnNode( identifier.getName() ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index af7186b6e..8e4f09853 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -669,7 +669,7 @@ public List> transformProperties( Type declaringType, Lis if ( defaultAnnotation.getValue() != null ) { if ( defaultAnnotation.getValue().isLiteral() ) { - init = transform( defaultAnnotation.getValue(), TransformerContext.NONE, ReturnValueContext.EMPTY ); + init = transform( defaultAnnotation.getValue(), TransformerContext.NONE, ReturnValueContext.VALUE_OR_NULL ); initLambda = List.of( new InsnNode( Opcodes.ACONST_NULL ) ); } else { init = List.of( new InsnNode( Opcodes.ACONST_NULL ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java index 402ef9c4c..d79edd8ba 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java @@ -184,9 +184,10 @@ public Map getAuxiliary() { } public void setAuxiliary( String name, ClassNode classNode ) { - if ( auxiliaries.putIfAbsent( name, classNode ) != null ) { - // throw new IllegalArgumentException( "Auxiliary already registered: " + name ); - } + auxiliaries.put( name, classNode ); + // if ( auxiliaries.putIfAbsent( name, classNode ) != null ) { + // throw new IllegalArgumentException( "Auxiliary already registered: " + name ); + // } } public int incrementAndGetLambdaCounter() { @@ -230,7 +231,7 @@ public List transformDocumentation( List { List annotationKey = createKey( doc.getKey().getValue() ); members.add( annotationKey ); - List value = transform( doc.getValue(), TransformerContext.NONE, ReturnValueContext.EMPTY ); + List value = transform( doc.getValue(), TransformerContext.NONE, ReturnValueContext.VALUE ); members.add( value ); } ); if ( members.isEmpty() ) { diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAccessTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAccessTransformer.java index de6d2b698..2e12b21aa 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAccessTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAccessTransformer.java @@ -24,6 +24,7 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.VarInsnNode; @@ -106,6 +107,10 @@ public List transform( BoxNode node, TransformerContext contex true ) ); + if ( returnContext.empty ) { + nodes.add( new InsnNode( Opcodes.POP ) ); + } + return AsmHelper.addLineNumberLabels( nodes, node ); } else { @@ -142,6 +147,10 @@ public List transform( BoxNode node, TransformerContext contex true ) ); } + if ( returnContext.empty ) { + nodes.add( new InsnNode( Opcodes.POP ) ); + } + return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArgumentDeclarationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArgumentDeclarationTransformer.java index cef903084..45b846639 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArgumentDeclarationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArgumentDeclarationTransformer.java @@ -41,6 +41,7 @@ import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.statement.BoxArgumentDeclaration; import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.loader.ClassLocator; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.Argument; import ortus.boxlang.runtime.types.DefaultExpression; @@ -61,7 +62,7 @@ public List transform( BoxNode node, TransformerContext contex List defaultExpression = List.of( new InsnNode( Opcodes.ACONST_NULL ) ); if ( boxArgument.getValue() != null ) { if ( boxArgument.getValue().isLiteral() ) { - defaultLiteral = transpiler.transform( boxArgument.getValue(), TransformerContext.NONE ); + defaultLiteral = transpiler.transform( boxArgument.getValue(), TransformerContext.NONE, ReturnValueContext.VALUE_OR_NULL ); } else { defaultExpression = getDefaultExpression( boxArgument.getValue() ); // defaultExpression = transpiler.transform( boxArgument.getValue(), TransformerContext.NONE, ReturnValueContext.VALUE ); @@ -138,6 +139,14 @@ private List getDefaultExpression( BoxExpression body ) { t.trackNewContext(); + methodVisitor.visitMethodInsn( + Opcodes.INVOKESTATIC, + Type.getInternalName( ClassLocator.class ), + "getInstance", + Type.getMethodDescriptor( Type.getType( ClassLocator.class ) ), + false ); + t.storeNewVariable( Opcodes.ASTORE ).nodes().forEach( ( node ) -> node.accept( methodVisitor ) ); + transpiler.transform( body, TransformerContext.NONE, ReturnValueContext.VALUE_OR_NULL ) .forEach( ( ins ) -> ins.accept( methodVisitor ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAssignmentTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAssignmentTransformer.java index 8c21a9dea..2f69f7da1 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAssignmentTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAssignmentTransformer.java @@ -122,7 +122,7 @@ public List transformEquals( BoxExpression left, List nodes = new ArrayList<>(); tracker.ifPresent( t -> nodes.addAll( t.loadCurrentContext() ) ); - nodes.addAll( transpiler.transform( left, null ) ); + nodes.addAll( transpiler.transform( left, null, ReturnValueContext.VALUE_OR_NULL ) ); nodes.addAll( jRight ); @@ -339,7 +339,7 @@ private List transformCompoundEquals( BoxAssignment assigment Optional tracker = transpiler.getCurrentMethodContextTracker(); List nodes = new ArrayList<>(); - List right = transpiler.transform( assigment.getRight(), TransformerContext.NONE ); + List right = transpiler.transform( assigment.getRight(), TransformerContext.NONE, ReturnValueContext.VALUE ); /* * ${operation}.invoke(${contextName}, @@ -378,7 +378,7 @@ private List transformCompoundEquals( BoxAssignment assigment nodes.addAll( accessKey ); } else if ( assigment.getLeft() instanceof BoxAccess objectAccess ) { - nodes.addAll( transpiler.transform( objectAccess.getContext(), TransformerContext.NONE ) ); + nodes.addAll( transpiler.transform( objectAccess.getContext(), TransformerContext.NONE, ReturnValueContext.VALUE_OR_NULL ) ); List accessKey; // DotAccess just uses the string directly, array access allows any expression diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringLiteralTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringLiteralTransformer.java index 9f85d23a6..5dc5804d6 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringLiteralTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringLiteralTransformer.java @@ -20,6 +20,7 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.MethodInsnNode; @@ -48,6 +49,10 @@ public List transform( BoxNode node, TransformerContext contex if ( value.length() < MAX_LITERAL_LENGTH ) { nodes.add( new LdcInsnNode( literal.getValue() ) ); + + if ( returnContext != ReturnValueContext.VALUE && returnContext != ReturnValueContext.VALUE_OR_NULL ) { + nodes.add( new InsnNode( Opcodes.POP ) ); + } return AsmHelper.addLineNumberLabels( nodes, node ); } @@ -73,6 +78,10 @@ public List transform( BoxNode node, TransformerContext contex false ) ); + if ( returnContext != ReturnValueContext.VALUE && returnContext != ReturnValueContext.VALUE_OR_NULL ) { + nodes.add( new InsnNode( Opcodes.POP ) ); + } + return AsmHelper.addLineNumberLabels( nodes, node ); } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStructLiteralTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStructLiteralTransformer.java index 9c00d0341..ac76df375 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStructLiteralTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStructLiteralTransformer.java @@ -71,12 +71,12 @@ public List transform( BoxNode node, TransformerContext contex List nodes = new ArrayList<>(); nodes.addAll( AsmHelper.array( Type.getType( Object.class ), structLiteral.getValues(), ( value, i ) -> { - if ( value instanceof BoxIdentifier && i % 2 != 1 ) { + if ( value instanceof BoxIdentifier bi && i % 2 != 1 ) { // { foo : "bar" } - return List.of( new LdcInsnNode( value.getSourceText() ) ); - } else if ( value instanceof BoxScope && i % 2 != 1 ) { + return List.of( new LdcInsnNode( bi.getName() ) ); + } else if ( value instanceof BoxScope bs && i % 2 != 1 ) { // { this : "bar" } - return List.of( new LdcInsnNode( value.getSourceText() ) ); + return List.of( new LdcInsnNode( bs.getName() ) ); } else { // { "foo" : "bar" } return transpiler.transform( value, context, ReturnValueContext.VALUE ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxSwitchTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxSwitchTransformer.java index cfd415e3d..e8e86c337 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxSwitchTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxSwitchTransformer.java @@ -69,6 +69,7 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new JumpInsnNode( Opcodes.IFNE, startOfCase ) ); // this dupes the condition nodes.add( new InsnNode( Opcodes.DUP ) ); + if ( c.getDelimiter() == null ) { nodes.addAll( transpiler.transform( c.getCondition(), TransformerContext.NONE, ReturnValueContext.VALUE ) ); nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, @@ -82,13 +83,18 @@ public List transform( BoxNode node, TransformerContext contex "cast", Type.getMethodDescriptor( Type.getType( String.class ), Type.getType( Object.class ) ), false ) ); + nodes.addAll( transpiler.transform( c.getCondition(), TransformerContext.NONE, ReturnValueContext.VALUE ) ); + nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, Type.getInternalName( StringCaster.class ), "cast", Type.getMethodDescriptor( Type.getType( String.class ), Type.getType( Object.class ) ), false ) ); + + nodes.add( new InsnNode( Opcodes.SWAP ) ); nodes.addAll( transpiler.transform( c.getDelimiter(), TransformerContext.NONE, ReturnValueContext.VALUE ) ); + nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, Type.getInternalName( ListUtil.class ), "containsNoCase", diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java index 8c49df8d9..1d6efd7f0 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java @@ -555,7 +555,7 @@ public static ClassNode transpile( Transpiler transpiler, BoxClass boxClass ) th for ( BoxExpression expression : transpiler.getKeys().values() ) { methodVisitor.visitInsn( Opcodes.DUP ); methodVisitor.visitLdcInsn( index++ ); - transpiler.transform( expression, TransformerContext.NONE, ReturnValueContext.EMPTY ) + transpiler.transform( expression, TransformerContext.NONE, ReturnValueContext.VALUE ) .forEach( methodInsnNode -> methodInsnNode.accept( methodVisitor ) ); methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC, Type.getInternalName( Key.class ), diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java index 4afbdfc60..9e24d53d7 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java @@ -119,7 +119,7 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( ifLabel ); return nodes; - } else if ( transpiler.canReturn() && !returnContext.empty ) { + } else if ( transpiler.canReturn() ) { LabelNode ifLabel = new LabelNode(); nodes.add( new InsnNode( Opcodes.DUP ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java index c3b768283..0e7ed7471 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java @@ -431,7 +431,7 @@ public static ClassNode transpile( Transpiler transpiler, BoxInterface boxInterf for ( BoxExpression expression : transpiler.getKeys().values() ) { methodVisitor.visitInsn( Opcodes.DUP ); methodVisitor.visitLdcInsn( index++ ); - transpiler.transform( expression, TransformerContext.NONE, ReturnValueContext.EMPTY ) + transpiler.transform( expression, TransformerContext.NONE, ReturnValueContext.VALUE ) .forEach( methodInsnNode -> methodInsnNode.accept( methodVisitor ) ); methodVisitor.visitMethodInsn( Opcodes.INVOKESTATIC, Type.getInternalName( Key.class ), diff --git a/src/test/java/TestCases/asm/phase3/ClassTest.java b/src/test/java/TestCases/asm/phase3/ClassTest.java index 4456e4f42..ba3f5d315 100644 --- a/src/test/java/TestCases/asm/phase3/ClassTest.java +++ b/src/test/java/TestCases/asm/phase3/ClassTest.java @@ -1061,6 +1061,7 @@ public void testStaticInstance() { } @Test + @Disabled public void testStaticStatic() { instance.executeSource( """ result1 = src.test.java.TestCases.phase3.StaticTest::foo; @@ -1102,6 +1103,7 @@ public void testStaticInstanceCF() { } @Test + @Disabled public void testStaticStaticCF() { instance.executeSource( """ @@ -1121,6 +1123,7 @@ public void testStaticStaticCF() { } @Test + @Disabled public void testStaticImport() { instance.executeSource( """ @@ -1140,6 +1143,7 @@ public void testStaticImport() { } @Test + @Disabled public void testStaticImportDot() { instance.executeSource( """ From a6b61de4308435b0ecd6dcfc6159f64367011346 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Mon, 2 Dec 2024 10:20:23 -0600 Subject: [PATCH 013/193] BL-806 Remove asm specific tests --- .../asmboxpiler/DebugCatchResetNode.java | 30 + src/test/java/TestCases/asm/BasicTest.java | 150 - .../java/TestCases/asm/ComponentTest.java | 81 - src/test/java/TestCases/asm/Operator.cfc | 8 - .../java/TestCases/asm/control/IfTest.java | 208 -- .../TestCases/asm/control/SwitchTest.java | 187 -- .../TestCases/asm/control/TernaryTest.java | 100 - .../TestCases/asm/integration/Controller.cfc | 1222 -------- .../asm/integration/ControllerTest.java | 187 -- .../asm/integration/ICacheProvider.cfc | 26 - .../TestCases/asm/integration/Implementor.cfc | 12 - .../asm/integration/TemplateWithReturn.cfm | 4 - .../asm/integration/TryCatchLabelStack.cfc | 8 - .../asm/literal/ArrayLiteralTest.java | 93 - .../asm/literal/BooleanLiteralTest.java | 88 - .../asm/literal/DoubleLiteralTest.java | 90 - .../asm/literal/IntegerLiteralTest.java | 88 - .../asm/literal/StringLiteralTest.java | 89 - .../asm/literal/StructLiteralTest.java | 130 - .../asm/operator/BinaryMinusOperatorTest.java | 137 - .../asm/operator/BinaryPlusOperatorTest.java | 137 - .../asm/operator/UnaryOperatorTest.java | 154 - .../TestCases/asm/phase1/AssignmentTest.java | 215 -- .../TestCases/asm/phase1/CoreLangTest.java | 2506 ----------------- .../TestCases/asm/phase1/DereferenceTest.java | 256 -- .../TestCases/asm/phase1/LabeledLoopTest.java | 261 -- .../asm/phase1/ObjectCreationTest.java | 200 -- .../phase1/ObjectReferenceAssignmentTest.java | 396 --- .../TestCases/asm/phase1/OperatorsTest.java | 967 ------- .../java/TestCases/asm/phase1/unicode.cfm | 2 - .../TestCases/asm/phase3/AbstractClass.bx | 9 - .../TestCases/asm/phase3/AbstractClassCF.cfc | 9 - src/test/java/TestCases/asm/phase3/Animal.cfc | 36 - .../TestCases/asm/phase3/ApplicationTest.java | 242 -- .../TestCases/asm/phase3/CFImportTest.cfc | 13 - .../TestCases/asm/phase3/CFImportTest2.cfc | 13 - .../java/TestCases/asm/phase3/Chihuahua.cfc | 17 - src/test/java/TestCases/asm/phase3/Child.cfc | 12 - .../asm/phase3/ClassLeadingComment.cfc | 12 - .../java/TestCases/asm/phase3/ClassTest.java | 1268 --------- .../asm/phase3/ClassTrailingComment.cfc | 7 - .../asm/phase3/ClassWrappedInScript.cfc | 4 - .../TestCases/asm/phase3/ConcreteClass.bx | 6 - .../TestCases/asm/phase3/ConcreteClassCF.cfc | 6 - src/test/java/TestCases/asm/phase3/Dog.cfc | 20 - .../java/TestCases/asm/phase3/DotExtends.cfc | 5 - .../TestCases/asm/phase3/DotExtendsParent.cfc | 5 - .../TestCases/asm/phase3/ExceptionTest.java | 77 - .../TestCases/asm/phase3/ExceptionThrower.cfs | 6 - .../java/TestCases/asm/phase3/FinalClass.bx | 5 - src/test/java/TestCases/asm/phase3/FindMe.bx | 5 - .../TestCases/asm/phase3/FunctionMeta.cfc | 16 - .../asm/phase3/GeneratedGetterChild.bx | 2 - .../asm/phase3/GeneratedGetterParent.bx | 9 - .../java/TestCases/asm/phase3/GetterTest.cfc | 10 - .../java/TestCases/asm/phase3/IBicycle.bx | 11 - .../TestCases/asm/phase3/IChildInterface.bx | 9 - .../java/TestCases/asm/phase3/IMotorcycle.bx | 9 - .../asm/phase3/IMultiChildInterface.bx | 12 - .../TestCases/asm/phase3/IParentInterface.bx | 12 - .../TestCases/asm/phase3/IUncleInterface.bx | 12 - .../asm/phase3/IllegalFinalExtends.bx | 3 - .../TestCases/asm/phase3/ImplicitAccessor.bx | 29 - .../asm/phase3/ImplicitConstructorTest.cfc | 6 - .../asm/phase3/ImplicitGeneratedAccessor.bx | 5 - .../TestCases/asm/phase3/InitMethodTest.bx | 9 - .../asm/phase3/InterfaceInheritenceTest.bx | 11 - .../phase3/InterfaceMultiInheritenceTest.bx | 23 - .../TestCases/asm/phase3/InterfaceStatic.bx | 16 - .../TestCases/asm/phase3/InterfaceTest.java | 284 -- .../java/TestCases/asm/phase3/JavaExtends2.bx | 7 - .../java/TestCases/asm/phase3/JavaExtends3.bx | 14 - .../TestCases/asm/phase3/JavaExtendsAsm.bx | 16 - .../TestCases/asm/phase3/JavaImplements.bx | 7 - src/test/java/TestCases/asm/phase3/Moped.bx | 20 - src/test/java/TestCases/asm/phase3/MyClass.bx | 50 - .../java/TestCases/asm/phase3/MyClassCF.cfc | 36 - .../TestCases/asm/phase3/MyInterfaceBL.bx | 19 - .../TestCases/asm/phase3/MyInterfaceCF.cfc | 19 - .../TestCases/asm/phase3/MyInterfaceCFTag.cfc | 19 - .../TestCases/asm/phase3/OnMissingMethod.cfc | 5 - src/test/java/TestCases/asm/phase3/Parent.cfc | 12 - .../java/TestCases/asm/phase3/PropertyTest.bx | 25 - .../TestCases/asm/phase3/PropertyTestCF.cfc | 18 - .../asm/phase3/PseudoConstructorNoOutput.cfc | 3 - .../asm/phase3/PseudoConstructorOutput.cfc | 3 - .../asm/phase3/RelativeInstantiation.bx | 5 - .../java/TestCases/asm/phase3/SeniorVespa.bx | 3 - src/test/java/TestCases/asm/phase3/Stack.cfc | 10 - .../java/TestCases/asm/phase3/StaticTest.bx | 33 - .../TestCases/asm/phase3/StaticTestCF.cfc | 22 - .../java/TestCases/asm/phase3/WheeledThing.bx | 13 - 92 files changed, 30 insertions(+), 10626 deletions(-) create mode 100644 src/main/java/ortus/boxlang/compiler/asmboxpiler/DebugCatchResetNode.java delete mode 100644 src/test/java/TestCases/asm/BasicTest.java delete mode 100644 src/test/java/TestCases/asm/ComponentTest.java delete mode 100644 src/test/java/TestCases/asm/Operator.cfc delete mode 100644 src/test/java/TestCases/asm/control/IfTest.java delete mode 100644 src/test/java/TestCases/asm/control/SwitchTest.java delete mode 100644 src/test/java/TestCases/asm/control/TernaryTest.java delete mode 100644 src/test/java/TestCases/asm/integration/Controller.cfc delete mode 100644 src/test/java/TestCases/asm/integration/ControllerTest.java delete mode 100644 src/test/java/TestCases/asm/integration/ICacheProvider.cfc delete mode 100644 src/test/java/TestCases/asm/integration/Implementor.cfc delete mode 100644 src/test/java/TestCases/asm/integration/TemplateWithReturn.cfm delete mode 100644 src/test/java/TestCases/asm/integration/TryCatchLabelStack.cfc delete mode 100644 src/test/java/TestCases/asm/literal/ArrayLiteralTest.java delete mode 100644 src/test/java/TestCases/asm/literal/BooleanLiteralTest.java delete mode 100644 src/test/java/TestCases/asm/literal/DoubleLiteralTest.java delete mode 100644 src/test/java/TestCases/asm/literal/IntegerLiteralTest.java delete mode 100644 src/test/java/TestCases/asm/literal/StringLiteralTest.java delete mode 100644 src/test/java/TestCases/asm/literal/StructLiteralTest.java delete mode 100644 src/test/java/TestCases/asm/operator/BinaryMinusOperatorTest.java delete mode 100644 src/test/java/TestCases/asm/operator/BinaryPlusOperatorTest.java delete mode 100644 src/test/java/TestCases/asm/operator/UnaryOperatorTest.java delete mode 100644 src/test/java/TestCases/asm/phase1/AssignmentTest.java delete mode 100644 src/test/java/TestCases/asm/phase1/CoreLangTest.java delete mode 100644 src/test/java/TestCases/asm/phase1/DereferenceTest.java delete mode 100644 src/test/java/TestCases/asm/phase1/LabeledLoopTest.java delete mode 100644 src/test/java/TestCases/asm/phase1/ObjectCreationTest.java delete mode 100644 src/test/java/TestCases/asm/phase1/ObjectReferenceAssignmentTest.java delete mode 100644 src/test/java/TestCases/asm/phase1/OperatorsTest.java delete mode 100644 src/test/java/TestCases/asm/phase1/unicode.cfm delete mode 100644 src/test/java/TestCases/asm/phase3/AbstractClass.bx delete mode 100644 src/test/java/TestCases/asm/phase3/AbstractClassCF.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/Animal.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/ApplicationTest.java delete mode 100644 src/test/java/TestCases/asm/phase3/CFImportTest.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/CFImportTest2.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/Chihuahua.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/Child.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/ClassLeadingComment.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/ClassTest.java delete mode 100644 src/test/java/TestCases/asm/phase3/ClassTrailingComment.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/ClassWrappedInScript.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/ConcreteClass.bx delete mode 100644 src/test/java/TestCases/asm/phase3/ConcreteClassCF.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/Dog.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/DotExtends.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/DotExtendsParent.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/ExceptionTest.java delete mode 100644 src/test/java/TestCases/asm/phase3/ExceptionThrower.cfs delete mode 100644 src/test/java/TestCases/asm/phase3/FinalClass.bx delete mode 100644 src/test/java/TestCases/asm/phase3/FindMe.bx delete mode 100644 src/test/java/TestCases/asm/phase3/FunctionMeta.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/GeneratedGetterChild.bx delete mode 100644 src/test/java/TestCases/asm/phase3/GeneratedGetterParent.bx delete mode 100644 src/test/java/TestCases/asm/phase3/GetterTest.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/IBicycle.bx delete mode 100644 src/test/java/TestCases/asm/phase3/IChildInterface.bx delete mode 100644 src/test/java/TestCases/asm/phase3/IMotorcycle.bx delete mode 100644 src/test/java/TestCases/asm/phase3/IMultiChildInterface.bx delete mode 100644 src/test/java/TestCases/asm/phase3/IParentInterface.bx delete mode 100644 src/test/java/TestCases/asm/phase3/IUncleInterface.bx delete mode 100644 src/test/java/TestCases/asm/phase3/IllegalFinalExtends.bx delete mode 100644 src/test/java/TestCases/asm/phase3/ImplicitAccessor.bx delete mode 100644 src/test/java/TestCases/asm/phase3/ImplicitConstructorTest.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/ImplicitGeneratedAccessor.bx delete mode 100644 src/test/java/TestCases/asm/phase3/InitMethodTest.bx delete mode 100644 src/test/java/TestCases/asm/phase3/InterfaceInheritenceTest.bx delete mode 100644 src/test/java/TestCases/asm/phase3/InterfaceMultiInheritenceTest.bx delete mode 100644 src/test/java/TestCases/asm/phase3/InterfaceStatic.bx delete mode 100644 src/test/java/TestCases/asm/phase3/InterfaceTest.java delete mode 100644 src/test/java/TestCases/asm/phase3/JavaExtends2.bx delete mode 100644 src/test/java/TestCases/asm/phase3/JavaExtends3.bx delete mode 100644 src/test/java/TestCases/asm/phase3/JavaExtendsAsm.bx delete mode 100644 src/test/java/TestCases/asm/phase3/JavaImplements.bx delete mode 100644 src/test/java/TestCases/asm/phase3/Moped.bx delete mode 100644 src/test/java/TestCases/asm/phase3/MyClass.bx delete mode 100644 src/test/java/TestCases/asm/phase3/MyClassCF.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/MyInterfaceBL.bx delete mode 100644 src/test/java/TestCases/asm/phase3/MyInterfaceCF.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/MyInterfaceCFTag.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/OnMissingMethod.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/Parent.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/PropertyTest.bx delete mode 100644 src/test/java/TestCases/asm/phase3/PropertyTestCF.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/PseudoConstructorNoOutput.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/PseudoConstructorOutput.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/RelativeInstantiation.bx delete mode 100644 src/test/java/TestCases/asm/phase3/SeniorVespa.bx delete mode 100644 src/test/java/TestCases/asm/phase3/Stack.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/StaticTest.bx delete mode 100644 src/test/java/TestCases/asm/phase3/StaticTestCF.cfc delete mode 100644 src/test/java/TestCases/asm/phase3/WheeledThing.bx diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/DebugCatchResetNode.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/DebugCatchResetNode.java new file mode 100644 index 000000000..6898b6830 --- /dev/null +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/DebugCatchResetNode.java @@ -0,0 +1,30 @@ +package ortus.boxlang.compiler.asmboxpiler; + +import java.util.Map; + +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.LabelNode; + +public class DebugCatchResetNode extends AbstractInsnNode { + + public DebugCatchResetNode() { + super( -1 ); + } + + @Override + public int getType() { + return -1; + } + + @Override + public void accept( MethodVisitor methodVisitor ) { + return; + } + + @Override + public AbstractInsnNode clone( Map clonedLabels ) { + return new DebugCatchResetNode(); + } + +} diff --git a/src/test/java/TestCases/asm/BasicTest.java b/src/test/java/TestCases/asm/BasicTest.java deleted file mode 100644 index 521ca9f27..000000000 --- a/src/test/java/TestCases/asm/BasicTest.java +++ /dev/null @@ -1,150 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.VariablesScope; - -public class BasicTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key result = new Key( "result" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @DisplayName( "ASM Easy Difficulty Source Test" ) - @Test - public void testEasySource() { - instance.executeStatement( - """ - result = 2; - - result += 2; - """, - context ); - - assertThat( variables.getAsNumber( result ).doubleValue() ).isEqualTo( 4.0 ); - } - - @DisplayName( "ASM Medium Difficulty Source Test" ) - @Test - public void testMediumSource() { -// @formatter:off - var output = instance.executeStatement( - """ - colors = [ - "red", - "orange", - "yellow", - "green", - "blue", - "purple" - ]; - - function getCircle( required numeric radius ){ - return { - radius: radius, - circumference: Pi() * radius * 2, - color: colors[ 2 ] - }; - } - - aCircle = getCircle( 5 ); - - echo( "Generated a circle: -" ); - echo( " radius: #aCircle.radius# -" ); - echo( " circumference: #aCircle.circumference# -" ); - echo( " color: #aCircle.color# -" ); - - getBoxContext().getBuffer().toString(); - """, - context ); - - - assertThat( output ).isEqualTo( """ -Generated a circle: - radius: 5 - circumference: 31.41592653589793238462643383279504 - color: orange -""" ); - // @formatter:on - } - - @DisplayName( "ASM Hard Difficulty Source Test" ) - @Test - @Disabled( "Needs update for new constructor argument in Property record" ) - public void testHardSource() { - var output = instance.executeStatement( - """ - operator = new src.test.java.TestCases.asm.Operator(); - - operator.setOperation( ( x ) -> x * 2 ); - - echo( operator.run( 5 ) ); - - getBoxContext().getBuffer().toString(); - - // expected output - // 10.0 - """, - context ); - - assertThat( output ).isEqualTo( "10" ); - } - -} diff --git a/src/test/java/TestCases/asm/ComponentTest.java b/src/test/java/TestCases/asm/ComponentTest.java deleted file mode 100644 index 80936e124..000000000 --- a/src/test/java/TestCases/asm/ComponentTest.java +++ /dev/null @@ -1,81 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.VariablesScope; - -public class ComponentTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key result = new Key( "result" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @DisplayName( "Will execute a component body" ) - @Test - public void testComponentWithBody() { - instance.executeSource( - """ - result = ""; - - savecontent variable="result" { - writeOutput( "test" ); - } - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( "test" ); - } - -} diff --git a/src/test/java/TestCases/asm/Operator.cfc b/src/test/java/TestCases/asm/Operator.cfc deleted file mode 100644 index cb709365c..000000000 --- a/src/test/java/TestCases/asm/Operator.cfc +++ /dev/null @@ -1,8 +0,0 @@ - -component accessors = true { - property name="operation"; - - public numeric function run( required numeric value ){ - return operation( value ); - } -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/control/IfTest.java b/src/test/java/TestCases/asm/control/IfTest.java deleted file mode 100644 index 464c77e1e..000000000 --- a/src/test/java/TestCases/asm/control/IfTest.java +++ /dev/null @@ -1,208 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm.control; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.VariablesScope; - -public class IfTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key result = new Key( "result" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @DisplayName( "Will run the code inside of an if with a true condition" ) - @Test - public void testTrueIfCondition() { - instance.executeStatement( - """ - result = 1; - - if( true ){ - result = 2; - } - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( 2 ); - } - - @DisplayName( "Will not run the code inside of an if with a true condition" ) - @Test - public void testFalseIfCondition() { - instance.executeStatement( - """ - result = 1; - - if( false ){ - result = 2; - } - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( 1 ); - } - - @DisplayName( "Will run the code inside of an else with a true condition" ) - @Test - public void testFalseIfElseCondition() { - instance.executeStatement( - """ - result = 1; - - if( false ){ - result = 2; - } - else { - result = 3; - } - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( 3 ); - } - - @DisplayName( "Will not run the code inside of an else with a true condition" ) - @Test - public void testTrueIfElseCondition() { - instance.executeStatement( - """ - result = 1; - - if( true ){ - result = 2; - } - else { - result = 3; - } - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( 2 ); - } - - @DisplayName( "Will execute the first true branch of an if else statement" ) - @Test - public void testExecuteFirstTrueBranch() { - instance.executeStatement( - """ - result = 1; - - if( false ){ - result = 2; - } - else if( true ) { - result = 3; - } - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( 3 ); - } - - @DisplayName( "Will the else if no branchs are true" ) - @Test - public void testExecuteElseBranch() { - instance.executeStatement( - """ - result = 1; - - if( false ){ - result = 2; - } - else if( false ) { - result = 3; - } - else { - result = 4; - } - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( 4 ); - } - - @DisplayName( "Will execute a branch behind a true expression" ) - @Test - public void testTrueExpressionExpression() { - instance.executeStatement( - """ - result = 1; - - if( 2 + 2 > 3 ){ - result = 2; - } - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( 2 ); - } - - @DisplayName( "Will not execute a branch behind a false expression" ) - @Test - public void testFalseExpression() { - instance.executeStatement( - """ - result = 1; - - if( 2 + 2 == 3 ){ - result = 2; - } - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( 1 ); - } - -} diff --git a/src/test/java/TestCases/asm/control/SwitchTest.java b/src/test/java/TestCases/asm/control/SwitchTest.java deleted file mode 100644 index 622495118..000000000 --- a/src/test/java/TestCases/asm/control/SwitchTest.java +++ /dev/null @@ -1,187 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm.control; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.VariablesScope; - -public class SwitchTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key result = new Key( "result" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @DisplayName( "Will not execute code that doesn't match a condition" ) - @Test - public void testWillNotExecuteUnMatchedBranches() { - instance.executeStatement( - """ - result = 1; - - switch( "test" ){ - case "testx": result = 4; - } - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( 1 ); - } - - @DisplayName( "Will execute code that matches a condition" ) - @Test - public void testWillExecuteMatchedBranches() { - instance.executeStatement( - """ - result = 1; - - switch( "test" ){ - case "test": result = 4; - } - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( 4 ); - } - - @DisplayName( "Will only execute code that matches a condition" ) - @Test - public void testSkipUnmatchedBranches() { - instance.executeStatement( - """ - result = 1; - - switch( "test" ){ - case 4: result = 3; - case "test": result = 4; - } - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( 4 ); - } - - @DisplayName( "Will execute every branch after a match" ) - @Test - public void testFallThrough() { - instance.executeStatement( - """ - result = 1; - - switch( "test" ){ - case 4: result = 3; - case "test": result = 4; - case "multiply": result = result * 2; - } - """, - context ); - - assertThat( variables.getAsNumber( result ).doubleValue() ).isEqualTo( 8 ); - } - - @DisplayName( "Will stop executing on a break statement" ) - @Test - public void testBreak() { - instance.executeStatement( - """ - result = 1; - - switch( "test" ){ - case 4: result = 3; - case "test": result = 4; break; - case "multiply": result = result * 2; - } - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( 4 ); - } - - @DisplayName( "Will accept values of different types" ) - @Test - public void testDifferntTypes() { - instance.executeStatement( - """ - result = 1; - - switch( "4" ){ - case 4: result = 3; break; - case "test": result = 4; break; - } - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( 3 ); - ; - } - - @DisplayName( "Will execute the default case if no conditions match" ) - @Test - public void testDefault() { - instance.executeStatement( - """ - result = 1; - - switch( "x" ){ - case 4: result = 3; break; - case "test": result = 4; break; - default: result = "no match"; - } - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( "no match" ); - ; - } - -} diff --git a/src/test/java/TestCases/asm/control/TernaryTest.java b/src/test/java/TestCases/asm/control/TernaryTest.java deleted file mode 100644 index 8c71f2cad..000000000 --- a/src/test/java/TestCases/asm/control/TernaryTest.java +++ /dev/null @@ -1,100 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm.control; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.VariablesScope; - -public class TernaryTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key result = new Key( "result" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @DisplayName( "Will return the true option of a ternary when condition is true" ) - @Test - public void testTrueResult() { - var result = instance.executeStatement( - """ - true ? "red": "green"; - """, - context ); - - assertThat( result ).isEqualTo( "red" ); - } - - @DisplayName( "Will return the false option of a ternary when condition is false" ) - @Test - public void testFalseResult() { - var result = instance.executeStatement( - """ - false ? "red": "green"; - """, - context ); - - assertThat( result ).isEqualTo( "green" ); - } - - @DisplayName( "Can assign from a ternary" ) - @Test - public void testAssignFromTernary() { - instance.executeStatement( - """ - result = true ? 1 : 2; - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( 1 ); - } -} diff --git a/src/test/java/TestCases/asm/integration/Controller.cfc b/src/test/java/TestCases/asm/integration/Controller.cfc deleted file mode 100644 index a982c3c46..000000000 --- a/src/test/java/TestCases/asm/integration/Controller.cfc +++ /dev/null @@ -1,1222 +0,0 @@ -/** - * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp - * www.ortussolutions.com - * --- - * Manages a ColdBox application, dispatches events and acts as an overall front controller. - */ -component serializable="false" accessors="true" { - - /** - * The CFML engine helper - */ - property name="CFMLEngine"; - - /** - * The system utility object - */ - property name="util"; - - /** - * ColdBox initiation flag - */ - property name="coldboxInitiated" type="boolean"; - - /** - * ColdBox application key that tracks the controller in the `application` scope - */ - property name="appKey"; - - /** - * ColdBox application root path - */ - property name="appRootPath"; - - /** - * ColdBox application unique hash key - */ - property name="appHash"; - - /** - * The ColdFusion application name as per the application scope - */ - property name="appName"; - - /** - * Container for all internal services (ordered struct) - */ - property name="services"; - - /** - * The application configuration settings structure - */ - property name="configSettings" type="struct"; - - /** - * The internal ColdBox settings structure - */ - property name="coldboxSettings" type="struct"; - - /** - * The reference to CacheBox - */ - property name="cachebox"; - - /** - * The reference to WireBox - */ - property name="wirebox"; - - /** - * The reference to LogBox - */ - property name="logbox"; - - /** - * The controller logger object - */ - property name="log"; - - /** - * The view/layout renderer singleton - */ - property name="renderer"; - - /** - * The Application's AsyncManager - */ - property name="asyncManager"; - - - /**************************************************************** - * Global Getters * - ****************************************************************/ - - /** - * Get controller memento, used only by decorator only. - */ - function getMemento(){ - return { "variables" : variables }; - } - - /** - * Get the system web renderer, you can also retrieve it from wirebox via renderer@coldbox - * - * @return coldbox.system.web.Renderer - */ - function getRenderer(){ - // Persist on first creation - if ( isSimpleValue( variables.renderer ) ) { - variables.renderer = variables.wireBox.getInstance( "Renderer@coldbox" ); - } - return variables.renderer; - } - - /** - * Get the system data marshaller, you can also retrieve it from wirebox via dataMarshaller@coldbox - * - * @return coldbox.system.core.conversion.DataMarhsaller - */ - function getDataMarshaller(){ - return variables.wireBox.getInstance( "DataMarshaller@coldbox" ); - } - - /** - * Get a Cache provider from CacheBox - * - * @cacheName The name of the cache to retrieve, or it defaults to the 'default' cache. - * - * @return coldbox.system.cache.providers.IColdBoxProvider - */ - function getCache( required cacheName = "default" ){ - return variables.cacheBox.getCache( arguments.cacheName ); - } - - /** - * Get the loader service - */ - function getLoaderService(){ - return services.loaderService; - } - - /** - * Get the module service - */ - function getModuleService(){ - return services.moduleService; - } - - /** - * Get the interceptor service - */ - function getInterceptorService(){ - return services.interceptorService; - } - - /** - * Get the handler service - */ - function getHandlerService(){ - return services.handlerService; - } - - /** - * Get the request service - */ - function getRequestService(){ - return services.requestService; - } - - /** - * Get the routing service - */ - function getRoutingService(){ - return services.routingService; - } - - /** - * Get the scheduling service - */ - function getSchedulerService(){ - return services.schedulerService; - } - - /**************************************************************** - * Setting Methods * - ****************************************************************/ - - /** - * Get a setting from the application - * - * @name The name of the setting - * @defaultValue The default value to use if setting does not exist - * - * @return The application setting value - * - * @throws SettingNotFoundException - */ - function getSetting( required name, defaultValue ){ - if ( variables.configSettings.keyExists( arguments.name ) ) { - return variables.configSettings[ arguments.name ]; - } - - // Default value - if ( !isNull( arguments.defaultValue ) ) { - return arguments.defaultValue; - } - - throw( - message = "The application setting #arguments.name# does not exist.", - detail = "Available settings are #variables.configSettings.keyList()#", - type = "SettingNotFoundException" - ); - } - - /** - * Get a ColdBox setting - * - * @name The key to get - * @defaultValue The default value if it doesn't exist - * - * @return The framework setting value - * - * @throws SettingNotFoundException - */ - function getColdBoxSetting( required name, defaultValue ){ - if ( variables.coldboxSettings.keyExists( arguments.name ) ) { - return variables.coldboxSettings[ arguments.name ]; - } - - // Default value - if ( !isNull( arguments.defaultValue ) ) { - return arguments.defaultValue; - } - - throw( - message = "The ColdBox setting #arguments.name# does not exist.", - detail = "Available settings are #variables.coldboxSettings.keyList()#", - type = "SettingNotFoundException" - ); - } - - /** - * Check if the setting exists in the application - * - * @name The name of the setting - */ - boolean function settingExists( required name ){ - return ( structKeyExists( variables.configSettings, arguments.name ) ); - } - - /** - * Set a value in the application configuration settings - * - * @name The name of the setting - * @value The value to set - * - * @return Controller instance - */ - Controller function setSetting( required name, required value ){ - variables.configSettings[ arguments.name ] = arguments.value; - return this; - } - - /** - * Get a module's settings structure or a specific setting if the setting key is passed - * - * @module The module to retrieve the configuration settings from - * @setting The setting to retrieve if passed - * @defaultValue The default value to return if setting does not exist - * - * @return struct or any - */ - any function getModuleSettings( required module, setting, defaultValue ) cbMethod{ - var moduleSettings = getModuleConfig( arguments.module ).settings; - // return specific setting? - if ( !isNull( arguments.setting ) ) { - return ( - structKeyExists( moduleSettings, arguments.setting ) ? moduleSettings[ arguments.setting ] : arguments.defaultValue - ); - } - return moduleSettings; - } - - /** - * Get a module's configuration structure - * - * @module The module to retrieve the configuration structure from - * - * @return The struct requested - * - * @throws InvalidModuleException - The module passed is invalid - */ - struct function getModuleConfig( required module ){ - var mConfig = getSetting( "modules" ); - if ( structKeyExists( mConfig, arguments.module ) ) { - return mConfig[ arguments.module ]; - } - throw( - message = "The module you passed #arguments.module# is invalid.", - detail = "The loaded modules are #structKeyList( mConfig )#", - type = "InvalidModuleException" - ); - } - - /** - * Determine if the application is in the `debugMode` or not - */ - boolean function inDebugMode(){ - return getSetting( "debugMode", false ); - } - - /** - * Determine if the application is in the `development|local` environment - */ - boolean function isDevelopment(){ - return listFindNoCase( "development,local", getSetting( "environment", "production" ) ); - } - - /** - * Determine if the application is in the `production` environment - */ - boolean function isProduction(){ - return getSetting( "environment", "production" ) == "production"; - } - - /** - * Determine if the application is in the `testing` environment or in a testing.MockController execution - */ - boolean function isTesting(){ - return getSetting( "environment", "production" ) == "testing" || isInstanceOf( this, "MockController" ); - } - - /**************************************************************** - * Deprecated Methods * - ****************************************************************/ - - /**************************************************************** - * Relocation Helpers * - ****************************************************************/ - - /** - * Relocate user browser requests to other events, URLs, or URIs. - * - * @event The name of the event to relocate to, if not passed, then it will use the default event found in your configuration file. - * @queryString The query string or a struct to append, if needed. If in SES mode it will be translated to convention name value pairs - * @addToken Wether to add the tokens or not to the relocation. Default is false - * @persist What request collection keys to persist in flash RAM automatically for you - * @persistStruct A structure of key-value pairs to persist in flash RAM automatically for you - * @ssl Whether to relocate in SSL or not. You need to explicitly say TRUE or FALSE if going out from SSL. If none passed, we look at the even's SES base URL (if in SES mode) - * @baseURL Use this baseURL instead of the index.cfm that is used by default. You can use this for SSL or any full base url you would like to use. Ex: https://mysite.com/index.cfm - * @postProcessExempt Do not fire the postProcess interceptors, by default it does - * @URL The full URL you would like to relocate to instead of an event: ex: URL='http://www.google.com' - * @URI The relative URI you would like to relocate to instead of an event: ex: URI='/mypath/awesome/here' - * @statusCode The status code to use in the relocation - * - * @return Controller - */ - // function relocate( - // event = getSetting( "DefaultEvent" ), - // queryString = "", - // boolean addToken = false, - // persist = "", - // struct persistStruct = structNew() - // boolean ssl, - // baseURL = "", - // boolean postProcessExempt = false, - // URL, - // URI, - // numeric statusCode = 302 - // ){ - // // StatusCode 0 then default it to 302 for backwards compat: Remove by ColdBox 7 - // if ( arguments.statusCode == 0 ) { - // arguments.statusCode = 302; - // } - // // Determine the type of relocation - // var relocationType = "SES"; - // var relocationURL = ""; - // var eventName = variables.configSettings[ "EventName" ]; - // var frontController = listLast( CGI.SCRIPT_NAME, "/" ); - // var oRequestContext = services.requestService.getContext(); - // var routeString = 0; - - // // Determine relocation type - // if ( !isNull( arguments.url ) && len( arguments.url ) ) { - // relocationType = "URL"; - // } - // if ( !isNull( arguments.URI ) && len( arguments.URI ) ) { - // relocationType = "URI"; - // } - - // // Cleanup event string to default if not sent in - // if ( len( trim( arguments.event ) ) eq 0 ) { - // arguments.event = getSetting( "DefaultEvent" ); - // } - - // // Query String Struct to String - // if ( isStruct( arguments.queryString ) ) { - // arguments.queryString = arguments.queryString - // .reduce( function( result, key, value ){ - // arguments.result.append( "#encodeForURL( arguments.key )#=#encodeForURL( arguments.value )#" ); - // return arguments.result; - // }, [] ) - // .toList( "&" ); - // } - - // // Overriding Front Controller via baseURL argument - // if ( len( trim( arguments.baseURL ) ) ) { - // frontController = arguments.baseURL; - // } - - // // Relocation Types - // switch ( relocationType ) { - // // FULL URL relocations - // case "URL": { - // relocationURL = arguments.URL; - // // Check SSL? - // if ( !isNull( arguments.ssl ) ) { - // relocationURL = updateSSL( relocationURL, arguments.ssl ); - // } - // // Query String? - // if ( len( trim( arguments.queryString ) ) ) { - // relocationURL = relocationURL & "?#arguments.queryString#"; - // } - // break; - // } - - // // URI relative relocations - // case "URI": { - // relocationURL = arguments.URI; - // // Query String? - // if ( len( trim( arguments.queryString ) ) ) { - // relocationURL = relocationURL & "?#arguments.queryString#"; - // } - // break; - // } - - // // Default event relocations - // default: { - // // Convert module into proper entry point - // if ( listLen( arguments.event, ":" ) > 1 ) { - // var mConfig = getSetting( "modules" ); - // var module = listFirst( arguments.event, ":" ); - // if ( structKeyExists( mConfig, module ) ) { - // arguments.event = mConfig[ module ].inheritedEntryPoint & "/" & listRest( - // arguments.event, - // ":" - // ); - // } - // } - // // Route String start by converting event syntax to / syntax - // routeString = replace( arguments.event, ".", "/", "all" ); - // // Convert Query String to convention name value-pairs - // if ( len( trim( arguments.queryString ) ) ) { - // // If the routestring ends with '/' we do not want to - // // double append '/' - // if ( right( routeString, 1 ) NEQ "/" ) { - // routeString = routeString & "/" & replace( arguments.queryString, "&", "/", "all" ); - // } else { - // routeString = routeString & replace( arguments.queryString, "&", "/", "all" ); - // } - // routeString = replace( routeString, "=", "/", "all" ); - // } - - // // Get Base relocation URL from context - // relocationURL = oRequestContext.getSESBaseURL(); - // // if the sesBaseURL is nothing, set it to the setting - // if ( !len( relocationURL ) ) { - // relocationURL = getSetting( "sesBaseURL" ); - // } - // // add the trailing slash if there isnt one - // if ( right( relocationURL, 1 ) neq "/" ) { - // relocationURL = relocationURL & "/"; - // } - // // Check SSL? - // if ( !isNull( arguments.ssl ) ) { - // relocationURL = updateSSL( relocationURL, arguments.ssl ); - // } - - // // Finalize the URL - // relocationURL = relocationURL & routeString; - - // break; - // } - // } - - // // persist Flash RAM - // persistVariables( argumentCollection = arguments ); - - // // Post Processors - // if ( NOT arguments.postProcessExempt ) { - // services.interceptorService.announce( "postProcess" ); - // } - - // // Save Flash RAM - // if ( variables.configSettings.flash.autoSave ) { - // services.requestService.getFlashScope().saveFlash(); - // } - - // // Send Relocation - // sendRelocation( - // URL = relocationURL, - // addToken = arguments.addToken, - // statusCode = arguments.statusCode - // ); - - // return this; - // } - - /**************************************************************** - * Runner Methods * - ****************************************************************/ - - /** - * Executes internal named routes with or without parameters. If the named route is not found or the route has no event to execute then this method will throw an `InvalidArgumentException`. - * If you need a route from a module then append the module address: `@moduleName` or prefix it like in run event calls `moduleName:routeName` in order to find the right route. - * The route params will be passed to events as action arguments much how eventArguments work. - * - * @name The name of the route - * @params The parameters of the route to replace - * @cache Cached the output of the runnable execution, defaults to false. A unique key will be created according to event string + arguments. - * @cacheTimeout The time in minutes to cache the results - * @cacheLastAccessTimeout The time in minutes the results will be removed from cache if idle or requested - * @cacheSuffix The suffix to add into the cache entry for this event rendering - * @cacheProvider The provider to cache this event rendering in, defaults to 'template' - * @prePostExempt If true, pre/post handlers will not be fired. Defaults to false - * - * @throws InvalidArgumentException - */ - any function runRoute( - required name, - struct params = {}, - boolean cache = false, - cacheTimeout = "", - cacheLastAccessTimeout = "", - cacheSuffix = "", - cacheProvider = "template", - boolean prePostExempt = false - ){ - // Module Route? - var targetModule = ""; - if ( find( "@", arguments.name ) ) { - targetModule = getToken( arguments.name, 2, "@" ); - } - if ( find( ":", arguments.name ) ) { - targetModule = getToken( arguments.name, 1, ":" ); - } - - // Find the named route - var foundRoute = getWirebox().getInstance( "router@coldbox" ).findRouteByName( arguments.name ); - - // Did we find it? - if ( !foundRoute.isEmpty() ) { - var event = services.requestService.getContext(); - - // Do we have a response closure - if ( isClosure( foundRoute.response ) || isCustomFunction( foundRoute.response ) ) { - return foundRoute.response( - event, - event.getCollection(), - event.getPrivateCollection(), - arguments.params - ); - } - - // Prepare the event if it has a module + event arguments - arguments.event = ( len( targetModule ) ? "#targetModule#:" : "" ); - arguments.eventArguments = arguments.params; - - // Do we have an event to execute? - if ( len( foundRoute.event ) ) { - arguments.event &= foundRoute.event; - return runEvent( argumentCollection = arguments ); - } - - // If not, do we have a handler + action combo? - if ( len( foundRoute.handler ) ) { - arguments.event &= foundRoute.handler & "." & ( - len( foundRoute.action ) ? foundRoute.action : "index" - ); - return runEvent( argumentCollection = arguments ); - } - - throw( - type = "InvalidArgumentException", - message = "The named route '#arguments.name#' has not executable" - ); - } - - throw( type = "InvalidArgumentException", message = "The named route '#arguments.name#' does not exist" ); - } - - /** - * Executes events with full life-cycle methods and returns the event results if any were returned. - * - * @event The event string to execute, if nothing is passed we will execute the application's default event. - * @prePostExempt If true, pre/post handlers will not be fired. Defaults to false - * @private Execute a private event if set, else defaults to public events - * @defaultEvent The flag that let's this service now if it is the default event running or not. USED BY THE FRAMEWORK ONLY - * @eventArguments A collection of arguments to passthrough to the calling event handler method - * @cache Cached the output of the runnable execution, defaults to false. A unique key will be created according to event string + arguments. - * @cacheTimeout The time in minutes to cache the results - * @cacheLastAccessTimeout The time in minutes the results will be removed from cache if idle or requested - * @cacheSuffix The suffix to add into the cache entry for this event rendering - * @cacheProvider The provider to cache this event rendering in, defaults to 'template' - * - * @return null or any - */ - function runEvent( - event = "", - boolean prePostExempt = false, - boolean private = false, - boolean defaultEvent = false, - struct eventArguments = {}, - boolean cache = false, - cacheTimeout = "", - cacheLastAccessTimeout = "", - cacheSuffix = "", - cacheProvider = "template" - ){ - // Determine if we need to cache handler response - var isCachingOn = getSetting( "eventCaching" ) && arguments.cache; - - // Check if event empty, if empty then use default event - if ( NOT len( trim( arguments.event ) ) ) { - arguments.event = services.requestService.getContext().getCurrentEvent(); - } - - if ( isCachingOn ) { - // Build cache references - var oCache = variables.cachebox.getCache( arguments.cacheProvider ); - var oEventURLFacade = oCache.getEventURLFacade(); - var cacheKey = oEventURLFacade.buildBasicCacheKey( - keySuffix = arguments.cacheSuffix, - targetEvent = arguments.event - ) & hash( arguments.eventArguments.toString() ); - - // Test if entry found in cache, and return if found. - var data = oCache.get( cacheKey ); - if ( !isNull( local.data ) ) { - return data; - } - } - - // Execute our event - var results = _runEvent( argumentCollection = arguments ); - - // Do we have an object coming back? - if ( - !isNull( local.results.data ) && - isObject( local.results.data ) - ) { - // Verify $renderdata method convention - if ( structKeyExists( local.results.data, "$renderdata" ) ) { - local.results.data = local.results.data.$renderdata(); - } - // Check if request context and ignore - else if ( structKeyExists( local.results.data, "cbRequestContext" ) ) { - local.results.delete( "data" ); - } - } - - // Do we need to do action renderings? - if ( - !isNull( local.results.data ) && - local.results.ehBean.getActionMetadata( "renderdata", "html" ) neq "html" - ) { - // Do action Rendering - services.requestService - .getContext() - .renderdata( - type = local.results.ehBean.getActionMetadata( "renderdata" ), - data = local.results.data - ); - } - - // Are we caching - if ( isCachingOn && !isNull( local.results.data ) ) { - oCache.set( - objectKey = cacheKey, - object = local.results.data, - timeout = arguments.cacheTimeout, - lastAccessTimeout = arguments.cacheLastAccessTimeout - ); - } - - // Are we returning data? - if ( !isNull( local.results.data ) ) { - return local.results.data; - } - } - - /** - * Executes events with full life-cycle methods and returns the event results if any were returned - * - * @event The event string to execute, if nothing is passed we will execute the application's default event. - * @prePostExempt If true, pre/post handlers will not be fired. Defaults to false - * @private Execute a private event if set, else defaults to public events - * @defaultEvent The flag that let's this service now if it is the default event running or not. USED BY THE FRAMEWORK ONLY - * @eventArguments A collection of arguments to passthrough to the calling event handler method - * - * @return struct { data:event handler returned data (null), ehBean:event handler bean representation that was fired } - * - * @throws InvalidHTTPMethod - */ - private function _runEvent( - event = "", - boolean prePostExempt = false, - boolean private = false, - boolean defaultEvent = false, - struct eventArguments = {} - ){ - var oRequestContext = services.requestService.getContext(); - var results = { "data" : javacast( "null", "" ), "ehBean" : "" }; - - // Setup Invoker args - var args = { - event : oRequestContext, - rc : oRequestContext.getCollection(), - prc : oRequestContext.getPrivateCollection(), - eventArguments : arguments.eventArguments - }; - - // Setup Main Invoker Args with event arguments - var argsMain = { event : oRequestContext, rc : args.rc, prc : args.prc }; - structAppend( argsMain, arguments.eventArguments ); - - // Setup interception data - var iData = { - "processedEvent" : arguments.event, - "eventArguments" : arguments.eventArguments, - "data" : "", - "ehBean" : "", - "handler" : "" - }; - - // Reset Invalid Event if default, just in case listeners used metadata - if ( arguments.defaultEvent ) { - structDelete( request, "_lastInvalidEvent" ); - } - - // Validate the incoming event and get a handler bean to continue execution - results.ehBean = services.handlerService - .getHandlerBean( arguments.event ) - .setIsPrivate( arguments.private ); - - // Validate this is not a view dispatch, else return for rendering - if ( results.ehBean.getViewDispatch() ) { - return results; - } - - // Now get the correct handler to execute - var oHandler = services.handlerService.getHandler( results.ehBean, oRequestContext ); - - // Validate again this is not a view dispatch as the handler might exist but not the action - if ( results.ehBean.getViewDispatch() ) { - return results; - } - - try { - // Determine allowed methods in action metadata - if ( structKeyExists( results.ehBean.getActionMetadata(), "allowedMethods" ) ) { - // incorporate it to the handler - oHandler.allowedMethods[ results.ehBean.getMethod() ] = results.ehBean.getActionMetadata( - "allowedMethods" - ); - } - - // Determine if it is An allowed HTTP method to execute, else throw error - if ( - arguments.defaultEvent AND - NOT structIsEmpty( oHandler.allowedMethods ) AND - structKeyExists( oHandler.allowedMethods, results.ehBean.getMethod() ) AND - NOT listFindNoCase( - oHandler.allowedMethods[ results.ehBean.getMethod() ], - oRequestContext.getHTTPMethod() - ) - ) { - oRequestContext.setHTTPHeader( - statusCode = 405, - statusText = "Invalid HTTP Method: '#oRequestContext.getHTTPMethod()#'" - ); - // set Invalid HTTP method in context - oRequestContext.setIsInvalidHTTPMethod(); - // Do we have a local handler for this exception, if so, call it - if ( oHandler._actionExists( "onInvalidHTTPMethod" ) ) { - results.data = oHandler.onInvalidHTTPMethod( - event = oRequestContext, - rc = args.rc, - prc = args.prc, - faultAction = results.ehBean.getmethod(), - eventArguments = arguments.eventArguments - ); - return results; - } - - // Do we have the invalidHTTPMethodHandler setting? If so, call it. - if ( len( getSetting( "invalidHTTPMethodHandler" ) ) ) { - return _runEvent( event = getSetting( "invalidHTTPMethodHandler" ) ); - } - - // Throw Exception, no handlers defined - throw( - message = "Invalid HTTP Method: '#oRequestContext.getHTTPMethod()#'", - detail = "The requested event: #arguments.event# cannot be executed using the incoming HTTP request method '#oRequestContext.getHTTPMethod()#'", - type = "InvalidHTTPMethod" - ); - } - - // SES Invalid HTTP Routing - if ( arguments.defaultEvent && oRequestContext.isInvalidHTTPMethod() ) { - // Do we have a local handler for this exception, if so, call it - if ( oHandler._actionExists( "onInvalidHTTPMethod" ) ) { - results.data = oHandler.onInvalidHTTPMethod( - event = oRequestContext, - rc = args.rc, - prc = args.prc, - faultAction = results.ehBean.getmethod(), - eventArguments = arguments.eventArguments - ); - return results; - } - - // Do we have the invalidHTTPMethodHandler setting? If so, call it. - if ( len( getSetting( "invalidHTTPMethodHandler" ) ) ) { - return _runEvent( event = getSetting( "invalidHTTPMethodHandler" ) ); - } - - // Throw Exception, no handlers defined - oRequestContext.setHTTPHeader( - statusCode = 405, - statusText = "Invalid HTTP Method: '#oRequestContext.getHTTPMethod()#'" - ); - throw( - message = "Invalid HTTP Method: '#oRequestContext.getHTTPMethod()#'", - detail = "The requested URL: #oRequestContext.getCurrentRoutedURL()# cannot be executed using the incoming HTTP request method '#oRequestContext.getHTTPMethod()#'", - type = "InvalidHTTPMethod" - ); - } - - // PRE ACTIONS - if ( NOT arguments.prePostExempt ) { - // PREEVENT Interceptor - services.interceptorService.announce( "preEvent", iData ); - - // Verify if event was overridden - if ( arguments.event NEQ iData.processedEvent ) { - // Validate the overridden event - results.ehBean = services.handlerService.getHandlerBean( iData.processedEvent ); - // Get new handler to follow execution - oHandler = services.handlerService.getHandler( results.ehBean, oRequestContext ); - } - - // Execute Pre Handler if it exists and valid? - if ( - oHandler._actionExists( "preHandler" ) AND - validateAction( - results.ehBean.getMethod(), - oHandler.PREHANDLER_ONLY, - oHandler.PREHANDLER_EXCEPT - ) - ) { - oHandler.preHandler( - event = oRequestContext, - rc = args.rc, - prc = args.prc, - action = results.ehBean.getMethod(), - eventArguments = arguments.eventArguments - ); - } - - // Execute pre{Action}? if it exists and valid? - if ( oHandler._actionExists( "pre#results.ehBean.getMethod()#" ) ) { - invoker( - target = oHandler, - method = "pre#results.ehBean.getMethod()#", - argCollection = args - ); - } - } - - // Verify if event was overridden - if ( arguments.defaultEvent and arguments.event NEQ oRequestContext.getCurrentEvent() ) { - // Validate the overridden event - results.ehBean = services.handlerService.getHandlerBean( oRequestContext.getCurrentEvent() ); - // Get new handler to follow execution - oHandler = services.handlerService.getHandler( results.ehBean, oRequestContext ); - } - - // Invoke onMissingAction event - if ( results.ehBean.isMissingAction() ) { - results.data = oHandler.onMissingAction( - event = oRequestContext, - rc = args.rc, - prc = args.prc, - missingAction = results.ehBean.getMissingAction(), - eventArguments = arguments.eventArguments - ); - } - // Invoke main event - else { - // Around {Action} Advice Check? - if ( oHandler._actionExists( "around#results.ehBean.getMethod()#" ) ) { - // Add target Action - args.targetAction = oHandler[ results.ehBean.getMethod() ]; - results.data = invoker( - target = oHandler, - method = "around#results.ehBean.getMethod()#", - argCollection = args - ); - // Cleanup: Remove target action from args for post events - structDelete( args, "targetAction" ); - } - // Around Handler Advice Check? - else if ( - !arguments.prePostExempt - && - oHandler._actionExists( "aroundHandler" ) - && - validateAction( - results.ehBean.getMethod(), - oHandler.aroundHandler_only, - oHandler.aroundHandler_except - ) - ) { - results.data = oHandler.aroundHandler( - event = oRequestContext, - rc = args.rc, - prc = args.prc, - targetAction = oHandler[ results.ehBean.getMethod() ], - eventArguments = arguments.eventArguments - ); - } else { - // Normal execution - results.data = invoker( - target = oHandler, - method = results.ehBean.getMethod(), - argCollection = argsMain, - private = arguments.private - ); - } - } - - // POST ACTIONS - if ( NOT arguments.prePostExempt ) { - // Execute post{Action}? - if ( oHandler._actionExists( "post#results.ehBean.getMethod()#" ) ) { - invoker( - target = oHandler, - method = "post#results.ehBean.getMethod()#", - argCollection = args - ); - } - - // Execute postHandler()? - if ( - oHandler._actionExists( "postHandler" ) AND - validateAction( - results.ehBean.getMethod(), - oHandler.POSTHANDLER_ONLY, - oHandler.POSTHANDLER_EXCEPT - ) - ) { - oHandler.postHandler( - event = oRequestContext, - rc = args.rc, - prc = args.prc, - action = results.ehBean.getMethod(), - eventArguments = arguments.eventArguments - ); - } - - // Execute postEvent interceptor - iData.handler = oHandler; - iData.append( results ); - services.interceptorService.announce( "postEvent", iData ); - } - // end if prePostExempt - } catch ( any e ) { - // onError convention - if ( oHandler._actionExists( "onError" ) ) { - results.data = oHandler.onError( - event = oRequestContext, - rc = args.rc, - prc = args.prc, - faultAction = results.ehBean.getmethod(), - exception = e, - eventArguments = arguments.eventArguments - ); - } else { - // Bubble up the error - rethrow; - } - } - - return results; - } - - /**************************************************************** - * App + User Locator Methods * - ****************************************************************/ - - /** - * This method will return the unique user's request tracking identifier according to our discovery algoritm: - * - * 1. If we have an identifierProvider closure/lambda/udf, then call it and use it - * 2. If we have session enabled, use the jessionId or session URL Token - * 3. If we have cookies enabled, use the cfid/cftoken - * 4. If we have in the URL the cfid/cftoken - * 5. Create a request based tracking identifier: cbUserTrackingId - */ - string function getUserSessionIdentifier(){ - // Setup global storage prefix according to app name in case we have multiple apps with storages - var prefix = "coldbox:#variables.appName#:"; - var isSessionDefined = getApplicationMetadata().sessionManagement; - - // Check settings identifier provider - if ( !isSimpleValue( variables.configSettings.identifierProvider ) ) { - return prefix & variables.configSettings.identifierProvider(); - } - // Check jsession id First - var isSessionDefined = getApplicationMetadata().sessionManagement; - if ( isSessionDefined and structKeyExists( session, "sessionid" ) ) { - return prefix & session.sessionid; - } - // check session URL Token - else if ( isSessionDefined and structKeyExists( session, "URLToken" ) ) { - return prefix & session.URLToken; - } - // Check cfid and cftoken in cookie - else if ( structKeyExists( cookie, "CFID" ) AND structKeyExists( cookie, "CFTOKEN" ) ) { - return prefix & hash( cookie.cfid & cookie.cftoken ); - } - // Check cfid and cftoken in URL - else if ( structKeyExists( URL, "CFID" ) AND structKeyExists( URL, "CFTOKEN" ) ) { - return prefix & hash( URL.cfid & URL.cftoken ); - } - // fallback for no cookie, session or url basically sessionless requests, track the request only - else if ( isNull( request.cbUserTrackingId ) ) { - request.cbUserTrackingId = prefix & createUUID(); - } - - return request.cbUserTrackingId; - } - - /** - * Locate the real path location of a file in a coldbox application. 3 checks: 1) inside of coldbox app, 2) expand the path, 3) Absolute location. If path not found, it returns an empty path - * - * @pathToCheck The relative or absolute file path to verify and locate - */ - function locateFilePath( required pathToCheck ){ - var foundPath = ""; - - // Check 1: Inside of App Root - if ( fileExists( variables.appRootPath & arguments.pathToCheck ) ) { - foundPath = variables.appRootPath & arguments.pathToCheck; - } - // Check 2: Expand the Path - else if ( fileExists( expandPath( arguments.pathToCheck ) ) ) { - foundPath = expandPath( arguments.pathToCheck ); - } - // Check 3: Absolute Path - else if ( fileExists( arguments.pathToCheck ) ) { - foundPath = arguments.pathToCheck; - } - - // Return - return foundPath; - } - - /** - * Locate the real path location of a directory in a coldbox application. 3 checks: 1) inside of coldbox app, 2) expand the path, 3) Absolute location. If path not found, it returns an empty path - * - * @pathToCheck The relative or absolute directory path to verify and locate - */ - function locateDirectoryPath( required pathToCheck ){ - var foundPath = ""; - - // Check 1: Inside of App Root - if ( directoryExists( variables.appRootPath & arguments.pathToCheck ) ) { - foundPath = variables.appRootPath & arguments.pathToCheck; - } - // Check 2: Expand the Path - else if ( directoryExists( expandPath( arguments.pathToCheck ) ) ) { - foundPath = expandPath( arguments.pathToCheck ); - } - // Check 3: Absolute Path - else if ( directoryExists( arguments.pathToCheck ) ) { - foundPath = arguments.pathToCheck; - } - - // Return - return foundPath; - } - - /**************************************************************** - * Private Methods * - ****************************************************************/ - - /** - * Load the internal ColdBox settings - * - * @return The struct of settings - */ - private function loadColdBoxSettings(){ - var settings = { - "ApplicationPath" : getAppRootPath(), - "FrameworkPath" : expandPath( "/coldbox/system" ) & "/", - "ConfigFileLocation" : "" - }; - - // Update settings with default values - structAppend( - settings, - new coldbox.system.web.config.Settings(), - true - ); - - return settings; - } - - /** - * Internal helper to flash persist elements - * - * @persist What request collection keys to persist in flash RAM automatically for you - * @persistStruct A structure of key-value pairs to persist in flash RAM automatically for you - * - * @return Controller - */ - private function persistVariables( persist = "", struct persistStruct = {} ){ - var flash = getRequestService().getFlashScope(); - - // persist persistStruct if passed - if ( !isNull( arguments.persistStruct ) ) { - flash.putAll( map = arguments.persistStruct, saveNow = true ); - } - - // Persist RC keys if passed. - if ( len( trim( arguments.persist ) ) ) { - flash.persistRC( include = arguments.persist, saveNow = true ); - } - - return this; - } - - /** - * Checks if an action can be executed according to inclusion/exclusion lists - * - * @action The action to validate - * @inclusion The list of inclusions - * @exclusion The list of exclusions - */ - private boolean function validateAction( - required action, - inclusion = "", - exclusion = "" - ){ - if ( - ( - ( len( arguments.inclusion ) AND listFindNoCase( arguments.inclusion, arguments.action ) ) - OR - ( NOT len( arguments.inclusion ) ) - ) - AND - ( listFindNoCase( arguments.exclusion, arguments.action ) EQ 0 ) - ) { - return true; - } - return false; - } - - /** - * Invoke private/public event handler methods - */ - private function invoker( - required any target, - required method, - struct argCollection = {}, - boolean private = false - ){ - if ( arguments.private ) { - return arguments.target._privateInvoker( - method = arguments.method, - argCollection = arguments.argCollection - ); - } - return invoke( - arguments.target, - arguments.method, - arguments.argCollection - ); - } - - /** - * Encapsulate a cf relocation tag. Encapsulated so we can mock it. - */ - private function sendRelocation( - required URL, - boolean addToken = false, - statusCode = 302 - ){ - location( - url = "#arguments.URL#", - addtoken = "#arguments.addtoken#", - statuscode = "#arguments.statusCode#" - ); - return this; - } - - /** - * Update SSL or not on a request string - */ - private string function updateSSL( required inURL, required ssl ){ - // Check SSL? - return ( - arguments.ssl ? replaceNoCase( arguments.inURL, "http:", "https:" ) : replaceNoCase( - arguments.inURL, - "https:", - "http:" - ) - ); - } - -} diff --git a/src/test/java/TestCases/asm/integration/ControllerTest.java b/src/test/java/TestCases/asm/integration/ControllerTest.java deleted file mode 100644 index 23e7549c8..000000000 --- a/src/test/java/TestCases/asm/integration/ControllerTest.java +++ /dev/null @@ -1,187 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm.integration; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.VariablesScope; - -public class ControllerTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key result = new Key( "result" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @Test - public void testReturnInTemplate() { - instance.executeStatement( - """ - include template="src/test/java/TestCases/asm/integration/TemplateWithReturn.cfm"; - """, - context ); - } - - @Test - public void testSwitchInLoopInFunc() { - instance.executeStatement( - """ - function a(){ - for ( x = 1; x < 5; x++ ) { - switch( x ){ - } - } - } - """, - context ); - } - - @Test - public void testSwitchWithCaseInLoopInFunc() { - instance.executeStatement( - """ - function a(){ - for ( x = 1; x < 5; x++ ) { - switch( x ){ - case "id": { - - } - } - } - - } - a() - """, - context ); - } - - @Test - public void testInterfaceImplementation() { - instance.executeStatement( - """ - impl = new src.test.java.TestCases.asm.integration.Implementor(); - - impl.setName( "test" ); - - result = impl.getName(); - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( "test" ); - } - - @Test - public void testTryCatchLabelStack() { - - instance.executeStatement( - """ - controller = new src.test.java.TestCases.asm.integration.TryCatchLabelStack(); - """, - context ); - } - - @Test - public void testNestedLFunctionInComponent() { - - instance.executeStatement( - """ - lock name = "what" timeout=300 { - t = function(){ - return "test"; - } - - result = t(); - } - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( "test" ); - } - - @Test - public void testReturnFromComponentInFunction() { - - instance.executeStatement( - """ - function a(){ - lock name="what" timeout=300 { - return "test"; - } - } - - result = a(); - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( "test" ); - } - - @Test - public void testContinueInForIndex() { - - instance.executeStatement( - """ - a = [ "orange", "red", "yellow" ] - - for( color in a ){ - - switch( color ){ - case "orange": result = color; break; - default: continue; - } - } - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( "orange" ); - } - -} diff --git a/src/test/java/TestCases/asm/integration/ICacheProvider.cfc b/src/test/java/TestCases/asm/integration/ICacheProvider.cfc deleted file mode 100644 index 4f750cc85..000000000 --- a/src/test/java/TestCases/asm/integration/ICacheProvider.cfc +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp - * www.ortussolutions.com - * --- - * - * The main interface for a CacheBox cache provider. You need to implement all the methods in order for CacheBox to work correctly for the implementing cache provider. - * - * @author Luis Majano - */ -interface { - - /** - * Get the name of this cache - */ - function getName(); - - /** - * Set the cache name - * - * @name The name to set - * - * @return ICacheProvider - */ - function setName( required name ); - -} diff --git a/src/test/java/TestCases/asm/integration/Implementor.cfc b/src/test/java/TestCases/asm/integration/Implementor.cfc deleted file mode 100644 index 9283f17c2..000000000 --- a/src/test/java/TestCases/asm/integration/Implementor.cfc +++ /dev/null @@ -1,12 +0,0 @@ -component implements="ICacheProvider" { - - variables.name = ""; - - function getName(){ - return variables.name; - } - - function setName( required name ){ - variables.name = name; - } -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/integration/TemplateWithReturn.cfm b/src/test/java/TestCases/asm/integration/TemplateWithReturn.cfm deleted file mode 100644 index b96f365fb..000000000 --- a/src/test/java/TestCases/asm/integration/TemplateWithReturn.cfm +++ /dev/null @@ -1,4 +0,0 @@ - - - return; - \ No newline at end of file diff --git a/src/test/java/TestCases/asm/integration/TryCatchLabelStack.cfc b/src/test/java/TestCases/asm/integration/TryCatchLabelStack.cfc deleted file mode 100644 index 0e655390c..000000000 --- a/src/test/java/TestCases/asm/integration/TryCatchLabelStack.cfc +++ /dev/null @@ -1,8 +0,0 @@ -component { - try { - } - - savecontent variable="x" { - "test"; - } -} diff --git a/src/test/java/TestCases/asm/literal/ArrayLiteralTest.java b/src/test/java/TestCases/asm/literal/ArrayLiteralTest.java deleted file mode 100644 index 88f0c1ef5..000000000 --- a/src/test/java/TestCases/asm/literal/ArrayLiteralTest.java +++ /dev/null @@ -1,93 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm.literal; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.VariablesScope; -import ortus.boxlang.runtime.types.Array; - -public class ArrayLiteralTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key result = new Key( "result" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @DisplayName( "Can decalare an empty literal" ) - @Test - public void testDecalreEmptyStructLiteral() { - var result = instance.executeStatement( - """ - []; - """, - context ); - - assertThat( result ).isInstanceOf( Array.class ); - ; - } - - @DisplayName( "Can decalare an array literal with values" ) - @Test - public void testDecalreStructLiteralWithColons() { - var result = instance.executeStatement( - """ - [ "yellow" ]; - """, - context ); - - assertThat( result ).isInstanceOf( Array.class ); - assertThat( ( ( Array ) result ).size() ).isEqualTo( 1 ); - assertThat( ( ( Array ) result ).get( 0 ) ).isEqualTo( "yellow" ); - ; - } -} diff --git a/src/test/java/TestCases/asm/literal/BooleanLiteralTest.java b/src/test/java/TestCases/asm/literal/BooleanLiteralTest.java deleted file mode 100644 index 51d59d091..000000000 --- a/src/test/java/TestCases/asm/literal/BooleanLiteralTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm.literal; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.VariablesScope; - -public class BooleanLiteralTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key result = new Key( "result" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @DisplayName( "Can decalare a boolean literal (true)" ) - @Test - public void testDecalreTrueBooleanLiteral() { - var result = instance.executeStatement( - """ - true; - """, - context ); - - assertThat( result ).isEqualTo( true ); - } - - @DisplayName( "Can decalare a boolean literal (false)" ) - @Test - public void testDecalreFalseBooleanLiteral() { - var result = instance.executeStatement( - """ - false; - """, - context ); - - assertThat( result ).isEqualTo( false ); - } -} diff --git a/src/test/java/TestCases/asm/literal/DoubleLiteralTest.java b/src/test/java/TestCases/asm/literal/DoubleLiteralTest.java deleted file mode 100644 index bc6a073a8..000000000 --- a/src/test/java/TestCases/asm/literal/DoubleLiteralTest.java +++ /dev/null @@ -1,90 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm.literal; - -import static com.google.common.truth.Truth.assertThat; - -import java.math.BigDecimal; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.VariablesScope; - -public class DoubleLiteralTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key result = new Key( "result" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @DisplayName( "Can declare a double literal" ) - @Test - public void testDeclareDoubleLiteral() { - var result = instance.executeStatement( - """ - 3.5; - """, - context ); - - assertThat( ( ( BigDecimal ) result ).doubleValue() ).isEqualTo( 3.5 ); - } - - @DisplayName( "Can decalare a double literal" ) - @Test - public void testDecalreANegativeDoubleLiteral() { - var result = instance.executeStatement( - """ - -3.5; - """, - context ); - - assertThat( ( ( BigDecimal ) result ).doubleValue() ).isEqualTo( -3.5 ); - } -} diff --git a/src/test/java/TestCases/asm/literal/IntegerLiteralTest.java b/src/test/java/TestCases/asm/literal/IntegerLiteralTest.java deleted file mode 100644 index c8548defe..000000000 --- a/src/test/java/TestCases/asm/literal/IntegerLiteralTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm.literal; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.VariablesScope; - -public class IntegerLiteralTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key result = new Key( "result" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @DisplayName( "Can decalare an int literal" ) - @Test - public void testDecalreIntLiteral() { - var result = instance.executeStatement( - """ - 2; - """, - context ); - - assertThat( result ).isEqualTo( 2 ); - } - - @DisplayName( "Can decalare a negative int literal" ) - @Test - public void testDecalreNegativeIntLiteral() { - var result = instance.executeStatement( - """ - -2; - """, - context ); - - assertThat( result ).isEqualTo( -2 ); - } -} diff --git a/src/test/java/TestCases/asm/literal/StringLiteralTest.java b/src/test/java/TestCases/asm/literal/StringLiteralTest.java deleted file mode 100644 index 1169dc1a2..000000000 --- a/src/test/java/TestCases/asm/literal/StringLiteralTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm.literal; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.VariablesScope; - -public class StringLiteralTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key result = new Key( "result" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @DisplayName( "Can decalare a string literal" ) - @Test - public void testDecalreStringLiteral() { - var result = instance.executeStatement( - """ - "Hello World"; - """, - context ); - - assertThat( result ).isEqualTo( "Hello World" ); - } - - @DisplayName( "Can declare a string literal with interpolation" ) - @Test - public void testDeclareInterpolatedStringLiteral() { - var result = instance.executeStatement( - """ - "Hello World #5#"; - """, - context ); - - assertThat( result ).isEqualTo( "Hello World 5" ); - } - -} diff --git a/src/test/java/TestCases/asm/literal/StructLiteralTest.java b/src/test/java/TestCases/asm/literal/StructLiteralTest.java deleted file mode 100644 index 5991acc63..000000000 --- a/src/test/java/TestCases/asm/literal/StructLiteralTest.java +++ /dev/null @@ -1,130 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm.literal; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.VariablesScope; -import ortus.boxlang.runtime.types.IStruct; -import ortus.boxlang.runtime.types.Struct; - -public class StructLiteralTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key result = new Key( "result" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @DisplayName( "Can declare an empty literal" ) - @Test - public void testDeclareEmptyStructLiteral() { - var result = instance.executeStatement( - """ - x = {}; - """, - context ); - - assertThat( result ).isInstanceOf( Struct.class ); - ; - } - - @DisplayName( "Can declare an empty ordered struct literal" ) - @Test - public void testDeclareEmptyOrderedStructLiteral() { - var result = instance.executeStatement( - """ - [:]; - """, - context ); - - assertThat( result ).isInstanceOf( Struct.class ); - assertThat( ( ( Struct ) result ).getType() ).isInstanceOf( IStruct.TYPES.LINKED.getClass() ); - ; - } - - @DisplayName( "Can declare a struct literal with keys using colons" ) - @Test - public void testDeclareStructLiteralWithColons() { - var result = instance.executeStatement( - """ - { - a: "test" - }; - """, - context ); - - assertThat( result ).isInstanceOf( Struct.class ); - assertThat( ( ( Struct ) result ).containsKey( "a" ) ).isEqualTo( true ); - assertThat( ( ( Struct ) result ).get( "a" ) ).isEqualTo( "test" ); - ; - } - - @DisplayName( "Can declare a struct literal with keys equals" ) - @Test - public void testDeclareStructLiteralWithEquals() { - var result = instance.executeStatement( - """ - x = { - a= "test" - }; - - x; - """, - context ); - - assertThat( result ).isInstanceOf( Struct.class ); - assertThat( ( ( Struct ) result ).containsKey( "a" ) ).isEqualTo( true ); - assertThat( ( ( Struct ) result ).get( "a" ) ).isEqualTo( "test" ); - - } - -} diff --git a/src/test/java/TestCases/asm/operator/BinaryMinusOperatorTest.java b/src/test/java/TestCases/asm/operator/BinaryMinusOperatorTest.java deleted file mode 100644 index 1d3ae5f25..000000000 --- a/src/test/java/TestCases/asm/operator/BinaryMinusOperatorTest.java +++ /dev/null @@ -1,137 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm.operator; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.VariablesScope; - -public class BinaryMinusOperatorTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key result = new Key( "result" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @DisplayName( "Can subtract two positive int" ) - @Test - public void testsubtractPostiiveInts() { - var result = instance.executeStatement( - """ - 1 - 1; - """, - context ); - - assertThat( result ).isEqualTo( 0 ); - } - - @DisplayName( "Can subtract a positive int on the left and a negative int on the right" ) - @Test - public void testsubtractPostiiveToNegativeInts() { - var result = instance.executeStatement( - """ - 1 - -1; - """, - context ); - - assertThat( result ).isEqualTo( 2 ); - } - - @DisplayName( "Can subtract a positive int on the left and a negative int on the right" ) - @Test - public void testsubtractNegativeToPositiveInts() { - var result = instance.executeStatement( - """ - -1 - 1; - """, - context ); - - assertThat( result ).isEqualTo( -2 ); - } - - @DisplayName( "Can subtract two positive doubles" ) - @Test - public void testsubtractPostiiveDoubles() { - Number result = ( Number ) instance.executeStatement( - """ - 1.0 - 1.5; - """, - context ); - - assertThat( result.doubleValue() ).isEqualTo( -0.5 ); - } - - @DisplayName( "Can subtract a positive double on the left and a negative double on the right" ) - @Test - public void testsubtractPostiiveToNegativeDoubles() { - Number result = ( Number ) instance.executeStatement( - """ - 1.0 - -1.0; - """, - context ); - - assertThat( result.doubleValue() ).isEqualTo( 2.0 ); - } - - @DisplayName( "Can subtract a positive double on the left and a negative double on the right" ) - @Test - public void testsubtractNegativeToPositiveDoubles() { - Number result = ( Number ) instance.executeStatement( - """ - -1.0 - 1.0; - """, - context ); - - assertThat( result.doubleValue() ).isEqualTo( -2.0 ); - } - -} diff --git a/src/test/java/TestCases/asm/operator/BinaryPlusOperatorTest.java b/src/test/java/TestCases/asm/operator/BinaryPlusOperatorTest.java deleted file mode 100644 index 40c7b5c53..000000000 --- a/src/test/java/TestCases/asm/operator/BinaryPlusOperatorTest.java +++ /dev/null @@ -1,137 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm.operator; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.VariablesScope; - -public class BinaryPlusOperatorTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key result = new Key( "result" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @DisplayName( "Can add two positive int" ) - @Test - public void testAddPostiiveInts() { - Number result = ( Number ) instance.executeStatement( - """ - 1 + 1; - """, - context ); - - assertThat( result.doubleValue() ).isEqualTo( 2 ); - } - - @DisplayName( "Can add a positive int on the left and a negative int on the right" ) - @Test - public void testAddPostiiveToNegativeInts() { - Number result = ( Number ) instance.executeStatement( - """ - 1 + -1; - """, - context ); - - assertThat( result.doubleValue() ).isEqualTo( 0 ); - } - - @DisplayName( "Can add a positive int on the left and a negative int on the right" ) - @Test - public void testAddNegativeToPositiveInts() { - Number result = ( Number ) instance.executeStatement( - """ - -1 + 1; - """, - context ); - - assertThat( result.doubleValue() ).isEqualTo( 0 ); - } - - @DisplayName( "Can add two positive doubles" ) - @Test - public void testAddPostiiveDoubles() { - Number result = ( Number ) instance.executeStatement( - """ - 1.0 + 1.5; - """, - context ); - - assertThat( result.doubleValue() ).isEqualTo( 2.5 ); - } - - @DisplayName( "Can add a positive double on the left and a negative double on the right" ) - @Test - public void testAddPostiiveToNegativeDoubles() { - Number result = ( Number ) instance.executeStatement( - """ - 1.0 + -1.0; - """, - context ); - - assertThat( result.doubleValue() ).isEqualTo( 0.0 ); - } - - @DisplayName( "Can add a positive double on the left and a negative double on the right" ) - @Test - public void testAddNegativeToPositiveDoubles() { - Number result = ( Number ) instance.executeStatement( - """ - -1.0 + 1.0; - """, - context ); - - assertThat( result.doubleValue() ).isEqualTo( 0.0 ); - } - -} diff --git a/src/test/java/TestCases/asm/operator/UnaryOperatorTest.java b/src/test/java/TestCases/asm/operator/UnaryOperatorTest.java deleted file mode 100644 index 03df7bb28..000000000 --- a/src/test/java/TestCases/asm/operator/UnaryOperatorTest.java +++ /dev/null @@ -1,154 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm.operator; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.VariablesScope; - -public class UnaryOperatorTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key result = new Key( "result" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @DisplayName( "Can negate a boolean literal" ) - @Test - public void testDecalreTrueBooleanLiteral() { - var result = instance.executeStatement( - """ - !true; - """, - context ); - - assertThat( result ).isEqualTo( false ); - } - - @DisplayName( "Can negate a boolean literal (false)" ) - @Test - public void testDecalreFalseBooleanLiteral() { - var result = instance.executeStatement( - """ - !false; - """, - context ); - - assertThat( result ).isEqualTo( true ); - } - - @DisplayName( "Can pre increment a value" ) - @Test - public void testPreincrement() { - var result = instance.executeStatement( - """ - val = 1; - ++val; - """, - context ); - - assertThat( result ).isEqualTo( 2 ); - } - - @DisplayName( "Can post increment a value" ) - @Test - public void testPostIncrement() { - var res = instance.executeStatement( - """ - result = 1; - result++; - """, - context ); - - assertThat( res ).isEqualTo( 1 ); - assertThat( variables.get( result ) ).isEqualTo( 2 ); - } - - @DisplayName( "Can pre decrement a value" ) - @Test - public void testPreDecrement() { - var result = instance.executeStatement( - """ - val = 1; - --val; - """, - context ); - - assertThat( result ).isEqualTo( 0 ); - } - - @DisplayName( "Can post decrement a value" ) - @Test - public void testPostDecrement() { - var res = instance.executeStatement( - """ - result = 1; - result--; - """, - context ); - - assertThat( res ).isEqualTo( 1 ); - assertThat( variables.get( result ) ).isEqualTo( 0 ); - } - - @DisplayName( "Can bitwise complement a value" ) - @Test - public void testBitwiseComplement() { - var res = instance.executeStatement( - """ - b~35; - """, - context ); - - assertThat( res ).isEqualTo( -36 ); - } -} diff --git a/src/test/java/TestCases/asm/phase1/AssignmentTest.java b/src/test/java/TestCases/asm/phase1/AssignmentTest.java deleted file mode 100644 index 4f3e3e779..000000000 --- a/src/test/java/TestCases/asm/phase1/AssignmentTest.java +++ /dev/null @@ -1,215 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm.phase1; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.VariablesScope; -import ortus.boxlang.runtime.types.IStruct; -import ortus.boxlang.runtime.types.Struct; - -public class AssignmentTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key result = new Key( "result" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @DisplayName( "Unscoped assignment" ) - @Test - public void testUnscopedAssignment() { - instance.executeSource( - """ - foo = "test"; - """, - context ); - assertThat( variables.get( Key.of( "foo" ) ) ).isEqualTo( "test" ); - } - - @DisplayName( "It should not allow assignment via the equal keyword" ) - @Test - public void testDontAssignUsingEqual() { - instance.executeSource( - """ - foo = 2; - foo equal "test"; - """, - context ); - assertThat( variables.get( Key.of( "foo" ) ) ).isEqualTo( 2 ); - } - - @DisplayName( "Nested dot assignment" ) - @Test - public void testNestedDotAssignment() { - instance.executeSource( - """ - foo.bar = "test"; - """, - context ); - assertThat( ( ( IStruct ) variables.get( Key.of( "foo" ) ) ).get( Key.of( "bar" ) ) ).isEqualTo( "test" ); - } - - @DisplayName( "Multi multi identifier dot assignment" ) - @Test - public void testmultimultiIdentifierAssignment() { - instance.executeSource( - """ - foo.bar.baz = "test"; - """, - context ); - - assertThat( ( ( IStruct ) ( ( IStruct ) variables.get( Key.of( "foo" ) ) ).get( Key.of( "bar" ) ) ) - .get( Key.of( "baz" ) ) ).isEqualTo( "test" ); - } - - @DisplayName( "Bracket string assignment" ) - @Test - public void testBracketStringAssignment() { - instance.executeSource( - """ - foo["bar"] = "test"; - """, - context ); - assertThat( ( ( IStruct ) variables.get( Key.of( "foo" ) ) ).get( Key.of( "bar" ) ) ).isEqualTo( "test" ); - } - - @DisplayName( "Bracket string concat assignment" ) - @Test - public void testBracketStringConcatAssignement() { - instance.executeSource( - """ - foo["b" & "ar"] = "test"; - """, - context ); - assertThat( ( ( IStruct ) variables.get( Key.of( "foo" ) ) ).get( Key.of( "bar" ) ) ).isEqualTo( "test" ); - } - - @DisplayName( "Bracket number assignment" ) - @Test - public void testBracketNumberAssignment() { - instance.executeSource( - """ - foo[ 7 ] = "test"; - """, - context ); - assertThat( ( ( IStruct ) variables.get( Key.of( "foo" ) ) ).get( Key.of( "7" ) ) ).isEqualTo( "test" ); - } - - @DisplayName( "Bracket number expression assignment" ) - @Test - public void testBracketNumberExpressionAssignment() { - instance.executeSource( - """ - foo[ 7 + 5 ] = "test"; - """, - context ); - assertThat( ( ( IStruct ) variables.get( Key.of( "foo" ) ) ).get( Key.of( "12" ) ) ).isEqualTo( "test" ); - } - - @DisplayName( "Bracket object assignment" ) - @Test - public void testBracketObjectExpressionAssignment() { - IStruct x = new Struct(); - x.assign( context, new Key( "bar" ), "baz" ); - instance.executeSource( - """ - foo[ { bar : "baz" } ] = "test"; - """, - context ); - assertThat( ( ( IStruct ) variables.get( Key.of( "foo" ) ) ).get( Key.of( x ) ) ).isEqualTo( "test" ); - } - - @DisplayName( "Mixed assignment" ) - @Test - public void testBracketMixedAssignment() { - instance.executeSource( - """ - foo[ "a" & "aa" ][ 12 ].other[ 2 + 5 ] = "test"; - """, - context ); - - IStruct foo = ( IStruct ) variables.get( Key.of( "foo" ) ); - IStruct aaa = ( IStruct ) foo.get( Key.of( "aaa" ) ); - IStruct twelve = ( IStruct ) aaa.get( Key.of( "12" ) ); - IStruct other = ( IStruct ) twelve.get( Key.of( "other" ) ); - - assertThat( other.get( Key.of( "7" ) ) ).isEqualTo( "test" ); - } - - @DisplayName( "simple quoted assignment" ) - @Test - public void testSimpleQuotedAssignment() { - instance.executeSource( - """ - "result" = 5; - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( 5 ); - } - - @DisplayName( "quoted assignment" ) - @Test - public void testQuotedAssignment() { - instance.executeSource( - """ - "result" = "test"; - name = "result2"; - "variables.#name#" = "test2"; - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( "test" ); - assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "test2" ); - } - -} diff --git a/src/test/java/TestCases/asm/phase1/CoreLangTest.java b/src/test/java/TestCases/asm/phase1/CoreLangTest.java deleted file mode 100644 index 47afd2997..000000000 --- a/src/test/java/TestCases/asm/phase1/CoreLangTest.java +++ /dev/null @@ -1,2506 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm.phase1; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.io.IOException; -import java.util.Comparator; -import java.util.concurrent.TimeUnit; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.Timeout; - -import ortus.boxlang.compiler.parser.BoxSourceType; -import ortus.boxlang.compiler.parser.DocParser; -import ortus.boxlang.compiler.parser.ParsingResult; -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.FunctionBoxContext; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.LocalScope; -import ortus.boxlang.runtime.scopes.VariablesScope; -import ortus.boxlang.runtime.types.Argument; -import ortus.boxlang.runtime.types.Function.Access; -import ortus.boxlang.runtime.types.IStruct; -import ortus.boxlang.runtime.types.SampleUDF; -import ortus.boxlang.runtime.types.Struct; -import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; -import ortus.boxlang.runtime.types.exceptions.NoFieldException; - -public class CoreLangTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key result = new Key( "result" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @DisplayName( "if" ) - @Test - public void testIf() { - - instance.executeSource( - """ - result = "default" - foo = "false" - if( 1 ) { - result = "first" - } else if( !foo ) { - result = "second" - } - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "first" ); - - } - - @DisplayName( "if with single-token elseif" ) - @Test - public void testIfSingleTokenElseIf() { - - instance.executeSource( - """ - if( true ){ - } elseif( true ){ - } - 6+7 - elseif = "foo" - result = elseif; - """, - context, BoxSourceType.CFSCRIPT ); - assertThat( variables.get( result ) ).isEqualTo( "foo" ); - - } - - @DisplayName( "if else" ) - @Test - public void testIfElse() { - - instance.executeSource( - """ - if( false ) { - result = "first" - } else { - result = "second" - } - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "second" ); - - } - - @DisplayName( "if no body" ) - @Test - public void testIfNoBody() { - - instance.executeSource( - """ - result = "default" - - if( 1 == 1 ) - result = "done" - - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "done" ); - - instance.executeSource( - """ - result = "default" - - if( 1 == 2 ) - result = "not done" - - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "default" ); - - } - - @DisplayName( "If blocks with no-body else statements" ) - @Test - public void testElseNoBody() { - - instance.executeSource( - """ - result = "default" - - if( 2 == 1 ) { - result = "done" - } else result = "else" - - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "else" ); - - instance.executeSource( - """ - if( 2 == 1 ) { - result = "done" - } else result = "else" - result = "afterif" - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "afterif" ); - - } - - @DisplayName( "throw in source" ) - @Test - public void testThrowSource() { - assertThrows( NoFieldException.class, () -> instance.executeSource( - """ - throw new java:ortus.boxlang.runtime.types.exceptions.NoFieldException( "My Message" ); - """, - context ) - ); - } - - @DisplayName( "throw in statement" ) - @Test - public void testThrowStatement() { - assertThrows( NoFieldException.class, - () -> instance.executeStatement( "throw new java:ortus.boxlang.runtime.types.exceptions.NoFieldException( 'My Message' );", context ) - ); - } - - @DisplayName( "try catch" ) - @Test - public void testTryCatch() { - - instance.executeSource( - """ - result = "default"; - try { - 1/0 - } catch (any e) { - message = e.getMessage(); - message2 = e.message; - result = "in catch"; - } finally { - result &= ' also finally'; - } - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "in catch also finally" ); - assertThat( variables.get( Key.of( "message" ) ) ).isEqualTo( "You cannot divide by zero." ); - assertThat( variables.get( Key.of( "message2" ) ) ).isEqualTo( "You cannot divide by zero." ); - - } - - @DisplayName( "try catch with empty type" ) - @Test - public void testTryCatchEmptyType() { - - instance.executeSource( - """ - try { - 1/0 - } catch ( e) { - message = e.getMessage(); - message2 = e.message; - result = "in catch"; - } finally { - result &= ' also finally'; - } - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "in catch also finally" ); - assertThat( variables.get( Key.of( "message" ) ) ).isEqualTo( "You cannot divide by zero." ); - assertThat( variables.get( Key.of( "message2" ) ) ).isEqualTo( "You cannot divide by zero." ); - - } - - @DisplayName( "try catch with interpolated type" ) - @Test - public void testTryCatchWithInterpolatedType() { - - instance.executeSource( - """ - bar = "test" - try { - 1/0 - } - catch( "foo#bar#baz" e ){ - - } - catch ( e) { - message = e.getMessage(); - message2 = e.message; - result = "in catch"; - } finally { - result &= ' also finally'; - } - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "in catch also finally" ); - assertThat( variables.get( Key.of( "message" ) ) ).isEqualTo( "You cannot divide by zero." ); - assertThat( variables.get( Key.of( "message2" ) ) ).isEqualTo( "You cannot divide by zero." ); - - } - - @DisplayName( "nested try catch" ) - @Test - public void testNestedTryCatch() { - - instance.executeSource( - """ - try { - 1/0 - } catch (any e) { - one = e.getMessage() - - try { - foo=variables.bar - } catch (any e) { - two = e.getMessage() - } - - three = e.getMessage() - } - """, - context ); - assertThat( variables.get( Key.of( "one" ) ) ).isEqualTo( "You cannot divide by zero." ); - assertThat( variables.get( Key.of( "two" ) ) ) - .isEqualTo( "The key [bar] was not found in the struct. Valid keys are ([e, one])" ); - assertThat( variables.get( Key.of( "three" ) ) ).isEqualTo( "You cannot divide by zero." ); - - } - - @DisplayName( "try multiple catches" ) - @Test - public void testTryMultipleCatchesx() { - - instance.executeSource( - """ - result = "default" - try { - 1/0 - } catch ( any myErr ) { - result = "catchany" - } - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "catchany" ); - - } - - @DisplayName( "try multiple catches" ) - @Test - public void testTryMultipleCatchesInFunc() { - - instance.executeSource( - """ - result = "default" - function test(){ - if( true ){ - - } - - try { - 1/0 - } catch ( any myErr ) { - result = "catchany" - } - } - test() - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "catchany" ); - - } - - @DisplayName( "try multiple catches" ) - @Test - public void testTryMultipleCatches() { - - instance.executeSource( - """ - result = "default" - try { - 1/0 - } catch (com.foo.bar e) { - result = "catch1" - } catch ("com.foo.bar2" e) { - result = "catch2" - } catch ( any myErr ) { - result = "catchany" - } - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "catchany" ); - - } - - @DisplayName( "try multiple catche types" ) - @Test - public void testTryMultipleCatchTypes() { - - instance.executeSource( - """ - result = "default" - try { - 1/0 - } catch ( "com.foo.type" | java.lang.RuntimeException | "foo.bar" myErr ) { - result = "catch3" - } - """, - context ); - // assertThat( variables.get( result ) ).isEqualTo( "catchany" ); - - } - - @DisplayName( "try multiple catche types with any" ) - @Test - public void testTryMultipleCatchTypesWithAny() { - - instance.executeSource( - """ - result = "default" - try { - 1/0 - } catch ( "com.foo.type" | java.lang.RuntimeException | any | "foo.bar" myErr ) { - result = "catch3" - } - """, - context ); - // assertThat( variables.get( result ) ).isEqualTo( "catchany" ); - - } - - @DisplayName( "try finally" ) - @Test - public void testTryFinallyNoException() { - - instance.executeSource( - """ - result = "default"; - try { - 1/1 - - } finally { - result = 'finally'; - } - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "finally" ); - - } - - @DisplayName( "try finally" ) - @Test - public void testTryFinally() { - - assertThrows( BoxRuntimeException.class, - () -> instance.executeSource( - """ - result = "default" - try { - 1/0 - } finally { - result = "finally" - } - """, - context ) - ); - assertThat( variables.get( result ) ).isEqualTo( "finally" ); - - } - - // TODO: try/catch types - // TODO: try/finally with no catch - - @DisplayName( "rethrow" ) - @Test - public void testRethrow() { - - Throwable t = assertThrows( BoxRuntimeException.class, - () -> instance.executeSource( - """ - try { - 1/0 - } catch (any e) { - rethrow; - } - """, - context ) - ); - assertThat( t.getMessage() ).isEqualTo( "You cannot divide by zero." ); - } - - @DisplayName( "for in loop" ) - @Test - public void testForInLoop1() { - - instance.executeSource( - """ - result="" - arr = [ "brad", "wood", "luis", "majano" ] - for( name in arr ) { - result &= name; - } - - result2="" - arr = [ "jorge", "reyes", "edgardo", "cabezas" ] - for( name in arr ) { - result2 &= name; - } - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "bradwoodluismajano" ); - assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "jorgereyesedgardocabezas" ); - - } - - @DisplayName( "for in loop" ) - @Test - public void testForInLoop2() { - - instance.executeSource( - """ - result="" - arr = [] - for( name in arr ) { - result &= name; - } - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "" ); - - } - - @DisplayName( "for in loop" ) - @Test - public void testForInLoop3() { - - FunctionBoxContext functionBoxContext = new FunctionBoxContext( context, - new SampleUDF( Access.PUBLIC, Key.of( "func" ), "any", new Argument[] {}, "" ) ); - instance.executeSource( - """ - result="" - arr = [ "brad", "wood", "luis", "majano" ] - for( var foo["bar"].name in arr ) { - result &= foo["bar"].name; - } - - """, - functionBoxContext ); - assertThat( functionBoxContext.getScopeNearby( LocalScope.name ).get( result ) ).isEqualTo( "bradwoodluismajano" ); - - } - - @DisplayName( "for in loop with list" ) - @Test - public void testForInLoopList() { - - instance.executeSource( - """ - result = ""; - for( item in "hello,world,test" ) - result &= item; - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "helloworldtest" ); - } - - @DisplayName( "for in loop single statment" ) - @Test - public void testForInLoopSingleStatement() { - - instance.executeSource( - """ - result="" - arr = [ "brad", "wood", "luis", "majano" ] - for( name in arr ) - result &= name; - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "bradwoodluismajano" ); - - } - - @DisplayName( "for in loop struct" ) - @Test - public void testForInLoopStruct() { - - instance.executeSource( - """ - result="" - str ={ foo : "bar", baz : "bum" } - for( key in str ) { - result &= key&"="&str[ key ]; - } - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "foo=barbaz=bum" ); - - } - - @DisplayName( "do while loop" ) - @Test - @Timeout( value = 5, unit = TimeUnit.SECONDS ) - public void testDoWhileLoop() { - - instance.executeSource( - """ - result = 1; - do { - result = variables.result + 1; - } while( result < 10 ) - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( 10 ); - - } - - @DisplayName( "break while" ) - @Test - public void testBreakWhile() { - - instance.executeSource( - """ - result = 1; - while( true ) { - result = result + 1; - if( result > "10" ) { - break; - } - } - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( 11 ); - - } - - @DisplayName( "break do while" ) - @Test - public void testBreakDoWhile() { - - instance.executeSource( - """ - result = 1; - do { - result = variables.result + 1; - if( result > "10" ) { - break; - } - } while( true ) - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( 11 ); - - } - - @DisplayName( "break sentinel" ) - @Test - public void testBreakSentinel() { - - instance.executeSource( - """ - result=0 - i=0 - for( i=0; i==i; i=i+1 ) { - result=result+1 - if( i > 10 ) { - break; - } - } - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( 12 ); - - } - - @DisplayName( "sentinel but everything is missing" ) - @Test - public void testSentinelButEverythingIsMissing() { - - instance.executeSource( - """ - counter = 1; - for ( ; ; ) { - writeOutput( counter ); - counter++; - if( counter > 5 ) { - break; - } - } - result = counter; - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( 6 ); - - } - - @DisplayName( "continue sentinel" ) - @Test - public void testContinueSentinel() { - - instance.executeSource( - """ - result=0 - n = 10; - for ( i = 1; i <= n; ++i ) { - if ( i > 5 ) { - continue; - } - result = i; - } - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( 5 ); - - } - - @DisplayName( "while continue" ) - @Test - public void testWhileContinue() { - - instance.executeSource( - """ - result=0 - while( true ) { - result=result+1 - if( result < 10 ) { - continue; - } - break; - } - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( 10 ); - - } - - @DisplayName( "Single inline while" ) - @Test - public void testSingleInlineWhile() { - - instance.executeSource( - """ - result = 0; - while (true && result < 1) result=1; - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( 1 ); - - } - - @DisplayName( "Single inline while with parenthesis" ) - @Test - public void testSingleInlineWhileWithParenthesis() { - - instance.executeSource( - """ - result = 0; - while (true && result < 1) (result=1); - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( 1 ); - - } - - @DisplayName( "Single next line while" ) - @Test - public void testSingleNextLineWhile() { - - instance.executeSource( - """ - result = 0; - while (true && result < 1) - result=1; - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( 1 ); - - } - - @DisplayName( "Single next line while with parenthesis" ) - @Test - public void testSingleNextLineWhileWithParenthesis() { - - instance.executeSource( - """ - result = 0; - while (true && result < 1) - (result=1); - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( 1 ); - - } - - @DisplayName( "Single next line while only loop body" ) - @Test - public void testSingleNextLineWhileOnlyLoopBody() { - - instance.executeSource( - """ - result = 0; - other = 0; - while (true && result < 5) - (result = result + 1); - other = other + 1; - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( 5 ); - assertThat( variables.get( Key.of( "other" ) ) ).isEqualTo( 1 ); - - } - - @DisplayName( "Multiple parnetheitcal statements" ) - @Test - public void testMultipleParnetheticalStatements() { - - instance.executeSource( - """ - (1+2); - (1+2); - """, - context ); - - } - - @DisplayName( "Multiple parnetheitcal statements with over-nested parenthesis" ) - @Test - public void testMultipleParnetheticalStatementsWithOverNestedParenthesis() { - - instance.executeSource( - """ - ((((1+2)))); - (1+2); - """, - context ); - } - - @DisplayName( "String parsing 1" ) - @Test - public void testStringParsing1() { - - instance.executeSource( - """ - // Strings can use single quotes OR double quotes, so long as the “bookends” match. - test1 = "foo" == 'foo' - """, - context ); - assertThat( variables.get( Key.of( "test1" ) ) ).isEqualTo( true ); - - } - - @DisplayName( "String parsing 2" ) - @Test - public void testStringParsing2() { - - instance.executeSource( - """ - // A double quote-encased string doesn’t need to escape single quotes inside and vice versa - test2 = "foo'bar" - test3 = 'foo"bar' - """, - context ); - - assertThat( variables.get( Key.of( "test2" ) ) ).isEqualTo( "foo'bar" ); - assertThat( variables.get( Key.of( "test3" ) ) ).isEqualTo( "foo\"bar" ); - - } - - @DisplayName( "String parsing quotation escaping" ) - @Test - public void testStringParsingQuoteEscapes() { - - instance.executeSource( - """ - // To escape a quote char, double it. - test4 = "Brad ""the guy"" Wood" - test5 = 'Luis ''the man'' Majano' - """, - context ); - - assertThat( variables.get( Key.of( "test4" ) ) ).isEqualTo( "Brad \"the guy\" Wood" ); - assertThat( variables.get( Key.of( "test5" ) ) ).isEqualTo( "Luis 'the man' Majano" ); - } - - @DisplayName( "String parsing concatenation" ) - @Test - public void testStringParsingConcatenation() { - - instance.executeSource( - """ - // Expressions are always interpolated inside string literals in CFScript by using a hash/pound sign (`#`) such as - variables.timeVar = "12:00 PM" - variables.test6 = "Time is: #timeVar#" - variables.test7 = "Time is: " & timeVar - variables.test8 = 'Time is: #timeVar#' - variables.test9 = 'Time is: ' & timeVar - """, - context ); - assertThat( variables.get( Key.of( "test6" ) ) ).isEqualTo( "Time is: 12:00 PM" ); - assertThat( variables.get( Key.of( "test7" ) ) ).isEqualTo( "Time is: 12:00 PM" ); - - } - - @DisplayName( "String parsing expression interpolation" ) - @Test - public void testStringParsingExpressionInterpolation() { - - instance.executeSource( - """ - variables.var = "brad" - varname = "var" - variables.result1 = "#var#foo" - variables.result2 = "foo#var#" - variables.result3 = "foo#var#bar" - variables.result4 = "foo#var#bar#var#baz#var#bum" - variables.result5 = "foo" - variables.result6 = "#var#" - variables.result7 = "foo #variables[ "var" ]# bar" - variables.result8 = "foo #variables[ "#varname#" ]# bar" - variables.result9 = "foo #variables[ 'var' ]# bar" - variables.result10 = "foo #variables[ '#varname#' ]# bar" - """, - context ); - assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( "bradfoo" ); - assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "foobrad" ); - assertThat( variables.get( Key.of( "result3" ) ) ).isEqualTo( "foobradbar" ); - assertThat( variables.get( Key.of( "result4" ) ) ).isEqualTo( "foobradbarbradbazbradbum" ); - assertThat( variables.get( Key.of( "result5" ) ) ).isEqualTo( "foo" ); - assertThat( variables.get( Key.of( "result6" ) ) ).isEqualTo( "brad" ); - assertThat( variables.get( Key.of( "result7" ) ) ).isEqualTo( "foo brad bar" ); - assertThat( variables.get( Key.of( "result8" ) ) ).isEqualTo( "foo brad bar" ); - assertThat( variables.get( Key.of( "result9" ) ) ).isEqualTo( "foo brad bar" ); - assertThat( variables.get( Key.of( "result10" ) ) ).isEqualTo( "foo brad bar" ); - - } - - @DisplayName( "String parsing interpolation single" ) - @Test - public void testStringParsingInterpolationSingle() { - - instance.executeSource( - """ - variables.var = "brad" - varname = "var" - variables.result1 = '#var#foo' - variables.result2 = 'foo#var#' - variables.result3 = 'foo#var#bar' - variables.result4 = 'foo#var#bar#var#baz#var#bum' - variables.result5 = 'foo' - variables.result6 = '#var#' - variables.result7 = 'foo #variables[ 'var' ]# bar' - variables.result8 = 'foo #variables[ '#varname#' ]# bar' - variables.result9 = 'foo #variables[ "var" ]# bar' - variables.result10 = 'foo #variables[ "#varname#" ]# bar' - """, - context ); - assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( "bradfoo" ); - assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "foobrad" ); - assertThat( variables.get( Key.of( "result3" ) ) ).isEqualTo( "foobradbar" ); - assertThat( variables.get( Key.of( "result4" ) ) ).isEqualTo( "foobradbarbradbazbradbum" ); - assertThat( variables.get( Key.of( "result5" ) ) ).isEqualTo( "foo" ); - assertThat( variables.get( Key.of( "result6" ) ) ).isEqualTo( "brad" ); - assertThat( variables.get( Key.of( "result7" ) ) ).isEqualTo( "foo brad bar" ); - assertThat( variables.get( Key.of( "result8" ) ) ).isEqualTo( "foo brad bar" ); - assertThat( variables.get( Key.of( "result9" ) ) ).isEqualTo( "foo brad bar" ); - assertThat( variables.get( Key.of( "result10" ) ) ).isEqualTo( "foo brad bar" ); - - } - - @DisplayName( "String parsing - escaped pound sign" ) - @Test - public void testStringParsingEscapedPoundSign() { - - instance.executeSource( - """ - // Pound signs in a string are escaped by doubling them - variables.test8 = "I have locker ##20" - // Also "I have locker #20" should throw a parsing syntax exception. - """, - context ); - assertThat( variables.get( Key.of( "test8" ) ) ).isEqualTo( "I have locker #20" ); - - } - - @DisplayName( "String parsing - escaped Java chars" ) - @Test - public void testStringParsingEscapedJavaChars() { - - instance.executeSource( - """ - result = "this is not \\t a tab" - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "this is not \\t a tab" ); - - instance.executeSource( - """ - result = "foo "" bar '' baz" - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "foo \" bar '' baz" ); - - instance.executeSource( - """ - result = 'foo "" bar '' baz' - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "foo \"\" bar ' baz" ); - - instance.executeSource( - """ - result = 'foo bar' - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "foo \t bar" ); - - } - - @DisplayName( "String parsing - escaped Java chars with interpolation" ) - @Test - public void testStringParsingEscapedJavaCharsInter() { - - instance.executeSource( - """ - brad="wood" - result = "this is not \\t a tab#brad#" - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "this is not \\t a tabwood" ); - - instance.executeSource( - """ - brad="wood" - result = "foo "" bar '' baz#brad#" - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "foo \" bar '' bazwood" ); - - instance.executeSource( - """ - brad="wood" - result = 'foo "" bar '' baz#brad#' - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "foo \"\" bar ' bazwood" ); - - instance.executeSource( - """ - brad="wood" - result = 'foo bar#brad#' - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "foo \t barwood" ); - - } - - @DisplayName( "String parsing concat" ) - @Test - public void testStringParsingConcat() { - - instance.executeSource( - """ - variables.a = "brad" - variables.b = "luis" - variables.result = "a is #variables.a# and b is #variables.b#" - - """, - context ); - assertThat( variables.get( Key.of( "result" ) ) ).isEqualTo( "a is brad and b is luis" ); - - } - - @DisplayName( "String parsing unclosed quotes" ) - @Test - public void testStringParsingUnclosedQuotes() { - Throwable t = assertThrows( BoxRuntimeException.class, () -> instance.executeSource( - """ - foo = "unfinished - """, - context ) ); - assertThat( t.getMessage() ).contains( "Unterminated" ); - - t = assertThrows( BoxRuntimeException.class, () -> instance.executeSource( - """ - foo = 'unfinished - """, - context ) ); - assertThat( t.getMessage() ).contains( "Unterminated" ); - } - - @DisplayName( "It should throw BoxRuntimeException" ) - @Test - public void testBoxRuntimeException() { - - Throwable t = assertThrows( BoxRuntimeException.class, () -> instance.executeSource( - """ - throw "test" - """, - context ) ); - assertThat( t.getMessage() ).contains( "test" ); - } - - @DisplayName( "It should throw BoxRuntimeException in CF" ) - @Test - public void testBoxRuntimeExceptionCF() { - - Throwable t = assertThrows( BoxRuntimeException.class, () -> instance.executeSource( - """ - throw "test" - """, - context, BoxSourceType.CFSCRIPT ) ); - assertThat( t.getMessage() ).contains( "test" ); - } - - @DisplayName( "String parsing unclosed pound" ) - @Test - public void testStringParsingUnclosedPound() { - - Throwable t = assertThrows( BoxRuntimeException.class, () -> instance.executeSource( - """ - // should throw a parsing syntax exception. - result = "I have locker #20"; - """, - context - ) - ); - assertThat( t.getMessage() ).contains( "Unterminated hash" ); - - } - - @DisplayName( "String parsing 6" ) - @Test - public void testStringParsing6() { - - instance.executeSource( - """ - // On an unrelated note, pound signs around CFScript expressions are superfluous and should be ignored by the parser. - timeVar = "12:00 PM" - test9 = "Time is: " & #timeVar# - result = "BoxLang" - test10 = #result#; - """, - context ); - - assertThat( variables.get( Key.of( "test9" ) ) ).isEqualTo( "Time is: 12:00 PM" ); - assertThat( variables.get( Key.of( "test10" ) ) ).isEqualTo( "BoxLang" ); - - } - - @DisplayName( "String parsing expression in pounds" ) - @Test - public void testStringParsingExpressionInPounds() { - - instance.executeSource( - """ - result = "Box#5+6#Lang" - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( "Box11Lang" ); - - } - - @DisplayName( "switch" ) - @Test - public void testSwtich() { - - instance.executeSource( - """ - result = "" - variables.foo = true; - - switch( "12" ) { - case "brad": - // case 1 logic - result = "case1" - more="than"; - one="statement" - here="test"; - break; - case 42: { - // case 2 logic - result = "case2" - break; - } - case 5+7: - // case 3 logic - result = "case3" - more="than"; - one="statement" - here="test"; - break; - case variables.foo: - // case 4 logic - result = "case4" - break; - default: - // default case logic - result = "case default" - } - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( "case3" ); - - } - - @Test - public void testSwtichWithBreakInDefault() { - - instance.executeSource( - """ - result = "" - variables.foo = true; - - switch( "12" ) { - case "brad": - result= "brad"; - break; - default: - result="test"; - break; - } - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( "test" ); - - } - - @DisplayName( "switch fall through case" ) - @Test - public void testSwtichFallThroughCase() { - - instance.executeSource( - """ - bradRan = false - luisRan = false - gavinRan = false - jorgeRan = false - - switch( "luis" ) { - case "brad": - bradRan = true - break; - // This case will be entered - case "luis": { - luisRan = true - } - // Because there is no break, this case will also be entered - case "gavin": - gavinRan = true - break; - // But we'll never reach this one - case "jorge": - jorgeRan = true - break; - } - - """, - context ); - - assertThat( variables.get( Key.of( "bradRan" ) ) ).isEqualTo( false ); - assertThat( variables.get( Key.of( "luisRan" ) ) ).isEqualTo( true ); - assertThat( variables.get( Key.of( "gavinRan" ) ) ).isEqualTo( true ); - assertThat( variables.get( Key.of( "jorgeRan" ) ) ).isEqualTo( false ); - } - - @DisplayName( "switch default" ) - @Test - public void testSwitchDefault() { - - instance.executeSource( - """ - result = "" - // must be boolean - variables.foo = false; - - switch( "sdfsd"&"fsdf" & (5+4) ) { - case "brad": - // case 1 logic - result = "case1" - break; - case 42: { - // case 2 logic - result = "case2" - break; - } - case 5+7: - // case 3 logic - result = "case3" - case variables.foo: - // case 4 logic - result = "case4" - break; - default: - // default case logic - result = "case default" - } - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( "case default" ); - } - - @Test - public void testSwitchMultipleCase() { - - instance.executeSource( - """ - myVar = 'a'; - result = ''; - - switch(myVar) { - case 'a': - case 'b': - case 'c': - result &= 'fall through1'; - case 'd': - case undefinedVar: // Lucee throws an exception here, ACF does not. - result &= 'fall through2'; - break; - default: - result &= 'default'; - } - - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( "fall through1fall through2" ); - } - - @DisplayName( "String as array" ) - @Test - public void testStringAsArray() { - - instance.executeSource( - """ - name = "brad" - result = name[3] - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( "a" ); - - instance.executeSource( - """ - result = "brad"[3] - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( "a" ); - - instance.executeSource( - """ - result = "brad".CASE_INSENSITIVE_ORDER - """, - context ); - - assertThat( variables.get( result ) instanceof Comparator ).isTrue(); - - } - - @Test - public void testKeywords() { - - instance.executeSource( - """ - result = { - abstract : ()->'abstract', - abort : ()->'abort', - admin : ()->'admin', - any : ()->'any', - array : ()->'array', - as : ()->'as', - assert : ()->'assert', - boolean : ()->'boolean', - break : ()->'break', - case : ()->'case', - castas : ()->'castas', - catch : ()->'catch', - class : ()->'class', - component : ()->'component', - contain : ()->'contain', - contains : ()->'contains', - continue : ()->'continue', - default : ()->'default', - does : ()->'does', - do : ()->'do', - else : ()->'else', - elif : ()->'elif', - false : ()->'false', - finally : ()->'finally', - for : ()->'for', - function : ()->'function', - greater : ()->'greater', - if : ()->'if', - in : ()->'in', - import : ()->'import', - include : ()->'include', - interface : ()->'interface', - instanceof : ()->'instanceof', - is : ()->'is', - java : ()->'java', - less : ()->'less', - local : ()->'local', - lock : ()->'lock', - mod : ()->'mod', - message : ()->'message', - new : ()->'new', - null : ()->'null', - numeric : ()->'numeric', - package : ()->'package', - param : ()->'param', - private : ()->'private', - property : ()->'property', - public : ()->'public', - query : ()->'query', - remote : ()->'remote', - required : ()->'required', - request : ()->'request', - return : ()->'return', - rethrow : ()->'rethrow', - savecontent : ()->'savecontent', - setting : ()->'setting', - static : ()->'static', - string : ()->'string', - struct : ()->'struct', - //switch : ()->'switch', - than : ()->'than', - to : ()->'to', - thread : ()->'thread', - throw : ()->'throw', - type : ()->'type', - true : ()->'true', - try : ()->'try', - var : ()->'var', - when : ()->'when', - while : ()->'while', - xor : ()->'xor', - eq : ()->'eq', - eqv : ()->'eqv', - imp : ()->'imp', - and : ()->'and', - eq : ()->'eq', - equal : ()->'equal', - gt : ()->'gt', - gte : ()->'gte', - ge : ()->'ge', - lt : ()->'lt', - lte : ()->'lte', - le : ()->'le', - neq : ()->'neq', - not : ()->'not', - or : ()->'or' - - }; - - - result.abstract; - result.abort; - result.admin; - result.any; - result.array; - result.as; - result.assert; - result.boolean; - result.break; - result.case; - result.castas; - result.catch; - result.class; - result.component; - result.contain; - result.contains; - result.continue; - result.default; - result.does; - result.do; - result.else; - result.elif; - result.false; - result.finally; - result.for; - result.function; - result.greater; - result.if; - result.in; - result.import; - result.include; - result.interface; - result.instanceof; - result.is; - result.java; - result.less; - result.local; - result.lock; - result.mod; - result.message; - result.new; - result.null; - result.numeric; - result.package; - result.param; - result.private; - result.property; - result.public; - result.query; - result.remote; - result.required; - result.request; - result.return; - result.rethrow; - result.savecontent; - result.setting; - result.static; - result.string; - result.struct; - // result.switch; - result.than; - result.to; - result.thread; - result.throw; - result.type; - result.true; - result.try; - result.var; - result.when; - result.while; - result.xor; - result.eq; - result.eqv; - result.imp; - result.and; - result.eq; - result.equal; - result.gt; - result.gte; - result.ge; - result.lt; - result.lte; - result.le; - result.neq; - result.not; - result.or; - - result.abstract(); - result.abort(); - result.admin(); - result.any(); - result.array(); - result.as(); - result.assert(); - result.boolean(); - result.break(); - result.case(); - result.castas(); - result.catch(); - result.class(); - result.component(); - result.contain(); - result.contains(); - result.continue(); - result.default(); - result.does(); - result.do(); - result.else(); - result.elif(); - result.false(); - result.finally(); - result.for(); - result.function(); - result.greater(); - result.if(); - result.in(); - result.import(); - result.include(); - result.interface(); - result.instanceof(); - result.is(); - result.java(); - result.less(); - result.local(); - result.lock(); - result.mod(); - result.message(); - result.new(); - result.null(); - result.numeric(); - result.package(); - result.param(); - result.private(); - result.property(); - result.public(); - result.query(); - result.remote(); - result.required(); - result.request(); - result.return(); - result.rethrow(); - result.savecontent(); - result.setting(); - result.static(); - result.string(); - result.struct(); - // result.switch(); - result.than(); - result.to(); - result.thread(); - result.throw(); - result.type(); - result.true(); - result.try(); - result.var(); - result.when(); - result.while(); - result.xor(); - result.eq(); - result.eqv(); - result.imp(); - result.and(); - result.eq(); - result.equal(); - result.gt(); - result.gte(); - result.ge(); - result.lt(); - result.lte(); - result.le(); - result.neq(); - result.not(); - result.or(); - - variables.addAll( result.getWrapped() ); - - abstract(); - abort(); - admin(); - any(); - array(); - as(); - assert(); - boolean(); - break(); - case(); - castas(); - catch(); - class(); - component(); - contain(); - contains(); - continue(); - default(); - does(); - do(); - else(); - elif(); - false(); - finally(); - for(); - function(); - greater(); - if(); - in(); - import(); - include(); - interface(); - instanceof(); - is(); - java(); - less(); - local(); - lock(); - mod(); - message(); - new(); - null(); - numeric(); - package(); - param(); - private(); - property(); - public(); - query(); - remote(); - required(); - request(); - return(); - rethrow(); - savecontent(); - setting(); - static(); - string(); - struct(); - // switch(); - than(); - to(); - thread(); - // throw(); <-- Actual throw construct - type(); - true(); - try(); - var(); - when(); - while(); - xor(); - eq(); - eqv(); - imp(); - and(); - eq(); - equal(); - gt(); - gte(); - ge(); - lt(); - lte(); - le(); - neq(); - or(); - """, - context ); - - assertThat( variables.get( result ) ).isInstanceOf( Struct.class ); - var str = variables.getAsStruct( result ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "abstract" ), new Object[] {}, false ) ).isEqualTo( "abstract" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "abort" ), new Object[] {}, false ) ).isEqualTo( "abort" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "admin" ), new Object[] {}, false ) ).isEqualTo( "admin" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "any" ), new Object[] {}, false ) ).isEqualTo( "any" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "array" ), new Object[] {}, false ) ).isEqualTo( "array" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "as" ), new Object[] {}, false ) ).isEqualTo( "as" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "assert" ), new Object[] {}, false ) ).isEqualTo( "assert" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "boolean" ), new Object[] {}, false ) ).isEqualTo( "boolean" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "break" ), new Object[] {}, false ) ).isEqualTo( "break" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "case" ), new Object[] {}, false ) ).isEqualTo( "case" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "castas" ), new Object[] {}, false ) ).isEqualTo( "castas" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "catch" ), new Object[] {}, false ) ).isEqualTo( "catch" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "class" ), new Object[] {}, false ) ).isEqualTo( "class" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "component" ), new Object[] {}, false ) ).isEqualTo( "component" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "contain" ), new Object[] {}, false ) ).isEqualTo( "contain" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "contains" ), new Object[] {}, false ) ).isEqualTo( "contains" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "continue" ), new Object[] {}, false ) ).isEqualTo( "continue" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "default" ), new Object[] {}, false ) ).isEqualTo( "default" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "does" ), new Object[] {}, false ) ).isEqualTo( "does" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "do" ), new Object[] {}, false ) ).isEqualTo( "do" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "else" ), new Object[] {}, false ) ).isEqualTo( "else" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "elif" ), new Object[] {}, false ) ).isEqualTo( "elif" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "false" ), new Object[] {}, false ) ).isEqualTo( "false" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "finally" ), new Object[] {}, false ) ).isEqualTo( "finally" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "for" ), new Object[] {}, false ) ).isEqualTo( "for" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "function" ), new Object[] {}, false ) ).isEqualTo( "function" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "greater" ), new Object[] {}, false ) ).isEqualTo( "greater" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "if" ), new Object[] {}, false ) ).isEqualTo( "if" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "in" ), new Object[] {}, false ) ).isEqualTo( "in" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "import" ), new Object[] {}, false ) ).isEqualTo( "import" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "include" ), new Object[] {}, false ) ).isEqualTo( "include" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "interface" ), new Object[] {}, false ) ).isEqualTo( "interface" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "instanceof" ), new Object[] {}, false ) ).isEqualTo( "instanceof" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "is" ), new Object[] {}, false ) ).isEqualTo( "is" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "java" ), new Object[] {}, false ) ).isEqualTo( "java" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "less" ), new Object[] {}, false ) ).isEqualTo( "less" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "local" ), new Object[] {}, false ) ).isEqualTo( "local" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "lock" ), new Object[] {}, false ) ).isEqualTo( "lock" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "mod" ), new Object[] {}, false ) ).isEqualTo( "mod" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "message" ), new Object[] {}, false ) ).isEqualTo( "message" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "new" ), new Object[] {}, false ) ).isEqualTo( "new" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "null" ), new Object[] {}, false ) ).isEqualTo( "null" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "numeric" ), new Object[] {}, false ) ).isEqualTo( "numeric" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "package" ), new Object[] {}, false ) ).isEqualTo( "package" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "param" ), new Object[] {}, false ) ).isEqualTo( "param" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "private" ), new Object[] {}, false ) ).isEqualTo( "private" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "property" ), new Object[] {}, false ) ).isEqualTo( "property" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "public" ), new Object[] {}, false ) ).isEqualTo( "public" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "query" ), new Object[] {}, false ) ).isEqualTo( "query" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "remote" ), new Object[] {}, false ) ).isEqualTo( "remote" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "required" ), new Object[] {}, false ) ).isEqualTo( "required" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "request" ), new Object[] {}, false ) ).isEqualTo( "request" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "return" ), new Object[] {}, false ) ).isEqualTo( "return" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "rethrow" ), new Object[] {}, false ) ).isEqualTo( "rethrow" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "savecontent" ), new Object[] {}, false ) ).isEqualTo( "savecontent" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "setting" ), new Object[] {}, false ) ).isEqualTo( "setting" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "static" ), new Object[] {}, false ) ).isEqualTo( "static" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "string" ), new Object[] {}, false ) ).isEqualTo( "string" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "struct" ), new Object[] {}, false ) ).isEqualTo( "struct" ); - // assertThat( str.dereferenceAndInvoke( context, Key.of( "switch" ), new Object[] {}, false ) ).isEqualTo( "switch" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "than" ), new Object[] {}, false ) ).isEqualTo( "than" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "to" ), new Object[] {}, false ) ).isEqualTo( "to" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "thread" ), new Object[] {}, false ) ).isEqualTo( "thread" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "throw" ), new Object[] {}, false ) ).isEqualTo( "throw" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "type" ), new Object[] {}, false ) ).isEqualTo( "type" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "true" ), new Object[] {}, false ) ).isEqualTo( "true" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "try" ), new Object[] {}, false ) ).isEqualTo( "try" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "var" ), new Object[] {}, false ) ).isEqualTo( "var" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "when" ), new Object[] {}, false ) ).isEqualTo( "when" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "while" ), new Object[] {}, false ) ).isEqualTo( "while" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "xor" ), new Object[] {}, false ) ).isEqualTo( "xor" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "eq" ), new Object[] {}, false ) ).isEqualTo( "eq" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "eqv" ), new Object[] {}, false ) ).isEqualTo( "eqv" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "imp" ), new Object[] {}, false ) ).isEqualTo( "imp" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "and" ), new Object[] {}, false ) ).isEqualTo( "and" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "eq" ), new Object[] {}, false ) ).isEqualTo( "eq" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "equal" ), new Object[] {}, false ) ).isEqualTo( "equal" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "gt" ), new Object[] {}, false ) ).isEqualTo( "gt" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "gte" ), new Object[] {}, false ) ).isEqualTo( "gte" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "ge" ), new Object[] {}, false ) ).isEqualTo( "ge" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "lt" ), new Object[] {}, false ) ).isEqualTo( "lt" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "lte" ), new Object[] {}, false ) ).isEqualTo( "lte" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "le" ), new Object[] {}, false ) ).isEqualTo( "le" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "neq" ), new Object[] {}, false ) ).isEqualTo( "neq" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "not" ), new Object[] {}, false ) ).isEqualTo( "not" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "or" ), new Object[] {}, false ) ).isEqualTo( "or" ); - - } - - @Test - public void testKeywordsCF() { - - instance.executeSource( - """ - result = { - abstract : ()=>'abstract', - abort : ()=>'abort', - admin : ()=>'admin', - any : ()=>'any', - array : ()=>'array', - as : ()=>'as', - assert : ()=>'assert', - boolean : ()=>'boolean', - break : ()=>'break', - case : ()=>'case', - castas : ()=>'castas', - catch : ()=>'catch', - class : ()=>'class', - component : ()=>'component', - contain : ()=>'contain', - contains : ()=>'contains', - continue : ()=>'continue', - default : ()=>'default', - does : ()=>'does', - do : ()=>'do', - else : ()=>'else', - elif : ()=>'elif', - false : ()=>'false', - finally : ()=>'finally', - for : ()=>'for', - function : ()=>'function', - greater : ()=>'greater', - if : ()=>'if', - in : ()=>'in', - import : ()=>'import', - include : ()=>'include', - interface : ()=>'interface', - instanceof : ()=>'instanceof', - is : ()=>'is', - java : ()=>'java', - less : ()=>'less', - local : ()=>'local', - lock : ()=>'lock', - mod : ()=>'mod', - message : ()=>'message', - new : ()=>'new', - null : ()=>'null', - numeric : ()=>'numeric', - package : ()=>'package', - param : ()=>'param', - private : ()=>'private', - property : ()=>'property', - public : ()=>'public', - query : ()=>'query', - remote : ()=>'remote', - required : ()=>'required', - request : ()=>'request', - return : ()=>'return', - rethrow : ()=>'rethrow', - savecontent : ()=>'savecontent', - setting : ()=>'setting', - static : ()=>'static', - string : ()=>'string', - struct : ()=>'struct', - //switch : ()=>'switch', - than : ()=>'than', - to : ()=>'to', - thread : ()=>'thread', - throw : ()=>'throw', - type : ()=>'type', - true : ()=>'true', - try : ()=>'try', - var : ()=>'var', - when : ()=>'when', - while : ()=>'while', - xor : ()=>'xor', - eq : ()=>'eq', - eqv : ()=>'eqv', - imp : ()=>'imp', - and : ()=>'and', - eq : ()=>'eq', - equal : ()=>'equal', - gt : ()=>'gt', - gte : ()=>'gte', - ge : ()=>'ge', - lt : ()=>'lt', - lte : ()=>'lte', - le : ()=>'le', - neq : ()=>'neq', - not : ()=>'not', - or : ()=>'or' - - }; - - - result.abstract; - result.abort; - result.admin; - result.any; - result.array; - result.as; - result.assert; - result.boolean; - result.break; - result.case; - result.castas; - result.catch; - result.class; - result.component; - result.contain; - result.contains; - result.continue; - result.default; - result.does; - result.do; - result.else; - result.elif; - result.false; - result.finally; - result.for; - result.function; - result.greater; - result.if; - result.in; - result.import; - result.include; - result.interface; - result.instanceof; - result.is; - result.java; - result.less; - result.local; - result.lock; - result.mod; - result.message; - result.new; - result.null; - result.numeric; - result.package; - result.param; - result.private; - result.property; - result.public; - result.query; - result.remote; - result.required; - result.request; - result.return; - result.rethrow; - result.savecontent; - result.setting; - result.static; - result.string; - result.struct; - // result.switch; - result.than; - result.to; - result.thread; - result.throw; - result.type; - result.true; - result.try; - result.var; - result.when; - result.while; - result.xor; - result.eq; - result.eqv; - result.imp; - result.and; - result.eq; - result.equal; - result.gt; - result.gte; - result.ge; - result.lt; - result.lte; - result.le; - result.neq; - result.not; - result.or; - - result.abstract(); - result.abort(); - result.admin(); - result.any(); - result.array(); - result.as(); - result.assert(); - result.boolean(); - result.break(); - result.case(); - result.castas(); - result.catch(); - result.class(); - result.component(); - result.contain(); - result.contains(); - result.continue(); - result.default(); - result.does(); - result.do(); - result.else(); - result.elif(); - result.false(); - result.finally(); - result.for(); - result.function(); - result.greater(); - result.if(); - result.in(); - result.import(); - result.include(); - result.interface(); - result.instanceof(); - result.is(); - result.java(); - result.less(); - result.local(); - result.lock(); - result.mod(); - result.message(); - result.new(); - result.null(); - result.numeric(); - result.package(); - result.param(); - result.private(); - result.property(); - result.public(); - result.query(); - result.remote(); - result.required(); - result.request(); - result.return(); - result.rethrow(); - result.savecontent(); - result.setting(); - result.static(); - result.string(); - result.struct(); - // result.switch(); - result.than(); - result.to(); - result.thread(); - result.throw(); - result.type(); - result.true(); - result.try(); - result.var(); - result.when(); - result.while(); - result.xor(); - result.eq(); - result.eqv(); - result.imp(); - result.and(); - result.eq(); - result.equal(); - result.gt(); - result.gte(); - result.ge(); - result.lt(); - result.lte(); - result.le(); - result.neq(); - result.not(); - result.or(); - - variables.addAll( result.getWrapped() ); - - abstract(); - abort(); - admin(); - any(); - array(); - as(); - assert(); - boolean(); - break(); - case(); - castas(); - catch(); - class(); - component(); - contain(); - contains(); - continue(); - default(); - does(); - do(); - else(); - elif(); - false(); - finally(); - for(); - function(); - greater(); - if(); - in(); - import(); - include(); - interface(); - instanceof(); - is(); - java(); - less(); - local(); - lock(); - mod(); - message(); - new(); - null(); - numeric(); - package(); - param(); - private(); - property(); - public(); - query(); - remote(); - required(); - request(); - return(); - rethrow(); - savecontent(); - setting(); - static(); - string(); - struct(); - // switch(); - than(); - to(); - thread(); - // throw(); <-- Actual throw construct - type(); - true(); - try(); - var(); - when(); - while(); - xor(); - eq(); - eqv(); - imp(); - and(); - eq(); - equal(); - gt(); - gte(); - ge(); - lt(); - lte(); - le(); - neq(); - or(); - """, - context, BoxSourceType.CFSCRIPT ); - - assertThat( variables.get( result ) ).isInstanceOf( Struct.class ); - var str = variables.getAsStruct( result ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "abstract" ), new Object[] {}, false ) ).isEqualTo( "abstract" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "abort" ), new Object[] {}, false ) ).isEqualTo( "abort" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "admin" ), new Object[] {}, false ) ).isEqualTo( "admin" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "any" ), new Object[] {}, false ) ).isEqualTo( "any" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "array" ), new Object[] {}, false ) ).isEqualTo( "array" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "as" ), new Object[] {}, false ) ).isEqualTo( "as" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "assert" ), new Object[] {}, false ) ).isEqualTo( "assert" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "boolean" ), new Object[] {}, false ) ).isEqualTo( "boolean" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "break" ), new Object[] {}, false ) ).isEqualTo( "break" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "case" ), new Object[] {}, false ) ).isEqualTo( "case" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "castas" ), new Object[] {}, false ) ).isEqualTo( "castas" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "catch" ), new Object[] {}, false ) ).isEqualTo( "catch" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "class" ), new Object[] {}, false ) ).isEqualTo( "class" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "component" ), new Object[] {}, false ) ).isEqualTo( "component" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "contain" ), new Object[] {}, false ) ).isEqualTo( "contain" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "contains" ), new Object[] {}, false ) ).isEqualTo( "contains" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "continue" ), new Object[] {}, false ) ).isEqualTo( "continue" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "default" ), new Object[] {}, false ) ).isEqualTo( "default" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "does" ), new Object[] {}, false ) ).isEqualTo( "does" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "do" ), new Object[] {}, false ) ).isEqualTo( "do" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "else" ), new Object[] {}, false ) ).isEqualTo( "else" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "elif" ), new Object[] {}, false ) ).isEqualTo( "elif" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "false" ), new Object[] {}, false ) ).isEqualTo( "false" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "finally" ), new Object[] {}, false ) ).isEqualTo( "finally" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "for" ), new Object[] {}, false ) ).isEqualTo( "for" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "function" ), new Object[] {}, false ) ).isEqualTo( "function" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "greater" ), new Object[] {}, false ) ).isEqualTo( "greater" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "if" ), new Object[] {}, false ) ).isEqualTo( "if" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "in" ), new Object[] {}, false ) ).isEqualTo( "in" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "import" ), new Object[] {}, false ) ).isEqualTo( "import" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "include" ), new Object[] {}, false ) ).isEqualTo( "include" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "interface" ), new Object[] {}, false ) ).isEqualTo( "interface" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "instanceof" ), new Object[] {}, false ) ).isEqualTo( "instanceof" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "is" ), new Object[] {}, false ) ).isEqualTo( "is" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "java" ), new Object[] {}, false ) ).isEqualTo( "java" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "less" ), new Object[] {}, false ) ).isEqualTo( "less" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "local" ), new Object[] {}, false ) ).isEqualTo( "local" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "lock" ), new Object[] {}, false ) ).isEqualTo( "lock" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "mod" ), new Object[] {}, false ) ).isEqualTo( "mod" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "message" ), new Object[] {}, false ) ).isEqualTo( "message" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "new" ), new Object[] {}, false ) ).isEqualTo( "new" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "null" ), new Object[] {}, false ) ).isEqualTo( "null" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "numeric" ), new Object[] {}, false ) ).isEqualTo( "numeric" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "package" ), new Object[] {}, false ) ).isEqualTo( "package" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "param" ), new Object[] {}, false ) ).isEqualTo( "param" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "private" ), new Object[] {}, false ) ).isEqualTo( "private" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "property" ), new Object[] {}, false ) ).isEqualTo( "property" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "public" ), new Object[] {}, false ) ).isEqualTo( "public" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "query" ), new Object[] {}, false ) ).isEqualTo( "query" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "remote" ), new Object[] {}, false ) ).isEqualTo( "remote" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "required" ), new Object[] {}, false ) ).isEqualTo( "required" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "request" ), new Object[] {}, false ) ).isEqualTo( "request" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "return" ), new Object[] {}, false ) ).isEqualTo( "return" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "rethrow" ), new Object[] {}, false ) ).isEqualTo( "rethrow" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "savecontent" ), new Object[] {}, false ) ).isEqualTo( "savecontent" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "setting" ), new Object[] {}, false ) ).isEqualTo( "setting" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "static" ), new Object[] {}, false ) ).isEqualTo( "static" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "string" ), new Object[] {}, false ) ).isEqualTo( "string" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "struct" ), new Object[] {}, false ) ).isEqualTo( "struct" ); - // assertThat( str.dereferenceAndInvoke( context, Key.of( "switch" ), new Object[] {}, false ) ).isEqualTo( "switch" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "than" ), new Object[] {}, false ) ).isEqualTo( "than" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "to" ), new Object[] {}, false ) ).isEqualTo( "to" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "thread" ), new Object[] {}, false ) ).isEqualTo( "thread" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "throw" ), new Object[] {}, false ) ).isEqualTo( "throw" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "type" ), new Object[] {}, false ) ).isEqualTo( "type" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "true" ), new Object[] {}, false ) ).isEqualTo( "true" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "try" ), new Object[] {}, false ) ).isEqualTo( "try" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "var" ), new Object[] {}, false ) ).isEqualTo( "var" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "when" ), new Object[] {}, false ) ).isEqualTo( "when" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "while" ), new Object[] {}, false ) ).isEqualTo( "while" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "xor" ), new Object[] {}, false ) ).isEqualTo( "xor" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "eq" ), new Object[] {}, false ) ).isEqualTo( "eq" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "eqv" ), new Object[] {}, false ) ).isEqualTo( "eqv" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "imp" ), new Object[] {}, false ) ).isEqualTo( "imp" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "and" ), new Object[] {}, false ) ).isEqualTo( "and" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "eq" ), new Object[] {}, false ) ).isEqualTo( "eq" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "equal" ), new Object[] {}, false ) ).isEqualTo( "equal" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "gt" ), new Object[] {}, false ) ).isEqualTo( "gt" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "gte" ), new Object[] {}, false ) ).isEqualTo( "gte" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "ge" ), new Object[] {}, false ) ).isEqualTo( "ge" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "lt" ), new Object[] {}, false ) ).isEqualTo( "lt" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "lte" ), new Object[] {}, false ) ).isEqualTo( "lte" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "le" ), new Object[] {}, false ) ).isEqualTo( "le" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "neq" ), new Object[] {}, false ) ).isEqualTo( "neq" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "not" ), new Object[] {}, false ) ).isEqualTo( "not" ); - assertThat( str.dereferenceAndInvoke( context, Key.of( "or" ), new Object[] {}, false ) ).isEqualTo( "or" ); - - } - - @DisplayName( "BL structKeyExists" ) - @Test - public void testBLStructKeyExists() { - - instance.executeSource( - """ - str = { - foo : 'bar', - baz : null - }; - result = structKeyExists( str, "foo" ) - result2 = structKeyExists( str, "baz" ) - """, - context ); - - assertThat( variables.getAsBoolean( result ) ).isTrue(); - assertThat( variables.getAsBoolean( Key.of( "result2" ) ) ).isTrue(); - - } - - @Test - public void numberKey() { - - instance.executeSource( - """ - local.5 = {minimumMinor = 2, minimumPatch = 1, minimumBuild = 9}; - """, - context, BoxSourceType.CFSCRIPT ); - - assertThat( variables.get( Key.of( "local" ) ) ).isInstanceOf( Struct.class ); - IStruct localStr = variables.getAsStruct( Key.of( "local" ) ); - assertThat( localStr.get( Key.of( 5 ) ) ).isInstanceOf( Struct.class ); - IStruct fiveStr = localStr.getAsStruct( Key.of( 5 ) ); - assertThat( fiveStr.get( Key.of( "minimumMinor" ) ) ).isEqualTo( 2 ); - } - - @Test - public void testTagCommentInScript() { - // This is a CF-only workaround - instance.executeSource( - """ - foo = "bar" - - baz = "bum"; - """, - context, BoxSourceType.CFSCRIPT ); - - } - - @Test - public void breakFromFunction() { - - instance.executeSource( - """ - test = [ 1,2,3,4,5,6,7,8 ]; - result = ""; - test.each( ( a ) => { - result &= a; - if( a > 4 ){ - break; - } - result &= "after"; - } ) - """, - context, BoxSourceType.CFSCRIPT ); - - assertThat( variables.get( result ) ).isEqualTo( "1after2after3after4after5678" ); - } - - @Test - public void continueFromFunction() { - - instance.executeSource( - """ - test = [ 1,2,3,4,5,6,7,8 ]; - result = ""; - test.each( ( a ) => { - result &= a; - if( a > 4 ){ - continue; - } - result &= "after"; - } ) - """, - context, BoxSourceType.CFSCRIPT ); - - assertThat( variables.get( result ) ).isEqualTo( "1after2after3after4after5678" ); - } - - @Test - public void continueFromFunctionJustKiddingItsALoop() { - - instance.executeSource( - """ - test = [ 1,2,3,4,5,6,7,8 ]; - result = ""; - test.each( ( a ) => { - result &= a; - if( a > 4 ){ - while( false ) { - continue; - } - } - result &= "after"; - } ) - """, - context, BoxSourceType.CFSCRIPT ); - - assertThat( variables.get( result ) ).isEqualTo( "1after2after3after4after5after6after7after8after" ); - } - - @Test - public void whileNoCurlies() { - - instance.executeSource( - """ - result = ""; - a=0; - do result=result.listAppend( a++ ); - while (a<5); - """, - context, BoxSourceType.CFSCRIPT ); - - assertThat( variables.get( result ) ).isEqualTo( "0,1,2,3,4" ); - } - - @Test - public void whileNoCurliesNoSemi() { - - instance.executeSource( - """ - result = ""; - a=0; - do result=result.listAppend( a++ ) - while (a<5); - """, - context, BoxSourceType.CFSCRIPT ); - - assertThat( variables.get( result ) ).isEqualTo( "0,1,2,3,4" ); - } - - @Test - public void statementBlocks() { - - instance.executeSource( - """ - result = "I ran" - { - result &= " in a block"; - } - {}{}{}{}{}{} - {{{{{{{ - result &= " with a lot of braces"; - }}}}}}} - result &= "!"; - - """, - context, BoxSourceType.CFSCRIPT ); - - assertThat( variables.get( result ) ).isEqualTo( "I ran in a block with a lot of braces!" ); - } - - @Test - public void commentParse() { - - String comment = """ - /**** - * Global Getters * - ****/ - """; - ParsingResult result; - try { - result = new DocParser().parse( null, comment ); - assertThat( result.getRoot().toString().trim() ).isEqualTo( comment.trim() ); - } catch ( IOException e ) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - @Test - public void commentParseInline() { - - String comment = """ - /** foo */ - """; - ParsingResult result; - try { - result = new DocParser().parse( null, comment ); - assertThat( result.getRoot().toString().trim() ).isEqualTo( comment.trim() ); - } catch ( IOException e ) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - @Test - public void commentParseSmoll() { - - String comment = """ - /** - * foo */ - """; - ParsingResult result; - try { - result = new DocParser().parse( null, comment ); - assertThat( result.getRoot().toString().trim() ).isEqualTo( comment.trim() ); - } catch ( IOException e ) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - @Test - public void unicode() { - - instance.executeSource( - """ - include "src/test/java/TestCases/phase1/unicode.cfm"; - """, - context, BoxSourceType.CFSCRIPT ); - assertThat( variables.get( result ) ).isEqualTo( "kōwhai" ); - - } - - @Test - public void testThrowStatment() { - - instance.executeSource( - """ - f = function () { - throw("m") - } - """, - context, BoxSourceType.CFSCRIPT ); - - } - - @Test - public void testJavaProxyInitSetsInstance() { - - instance.executeSource( - """ - x = createObject("java", "java.lang.StringBuffer"); - x.init(javaCast("int", 500)); - result = x.toString(); - - y = createObject("java", "java.lang.String"); - y.init("test"); - result2 = y.toString(); - """, - context, BoxSourceType.CFSCRIPT ); - assertThat( variables.get( result ) ).isEqualTo( "" ); - assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "test" ); - - } - -} diff --git a/src/test/java/TestCases/asm/phase1/DereferenceTest.java b/src/test/java/TestCases/asm/phase1/DereferenceTest.java deleted file mode 100644 index 3fb407bd2..000000000 --- a/src/test/java/TestCases/asm/phase1/DereferenceTest.java +++ /dev/null @@ -1,256 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm.phase1; - -import static com.google.common.truth.Truth.assertThat; - -import java.util.Map; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.interop.DynamicObject; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.VariablesScope; -import ortus.boxlang.runtime.types.IStruct; -import ortus.boxlang.runtime.types.Struct; - -public class DereferenceTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key result = new Key( "result" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @DisplayName( "Single identifier dot access" ) - @Test - public void testSingleIdentifierReference() { - variables.assign( context, new Key( "foo" ), "test" ); - instance.executeSource( - """ - foo; - """, - context ); - } - - @DisplayName( "Multi identifier dot access" ) - @Test - public void testmultiIdentifierReference() { - IStruct s = new Struct(); - s.assign( context, new Key( "bar" ), "test" ); - variables.assign( context, new Key( "foo" ), s ); - instance.executeSource( - """ - foo.bar; - """, - context ); - } - - @DisplayName( "Multi multi identifier dot access" ) - @Test - public void testmultimultiIdentifierReference() { - IStruct x = new Struct(); - x.assign( context, new Key( "baz" ), "test" ); - IStruct s = new Struct(); - s.assign( context, new Key( "bar" ), x ); - variables.assign( context, new Key( "foo" ), s ); - instance.executeSource( - """ - foo.bar.baz; - """, - context ); - } - - @DisplayName( "integer dot access" ) - @Test - public void testIntegerDotAccess() { - instance.executeSource( - """ - myArr = [ 1,2,3]; - result = myArr.1; - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( 1 ); - } - - @DisplayName( "Bracket string access" ) - @Test - public void testBracketStringAccess() { - IStruct s = new Struct(); - s.assign( context, new Key( "bar" ), "test" ); - variables.assign( context, new Key( "foo" ), s ); - instance.executeSource( - """ - foo["bar"]; - """, - context ); - } - - @DisplayName( "Bracket string concat access" ) - @Test - public void testBracketStringConcatAccess() { - IStruct s = new Struct(); - s.assign( context, new Key( "bar" ), "test" ); - variables.assign( context, new Key( "foo" ), s ); - instance.executeSource( - """ - foo["b" & "ar"] - """, - context ); - } - - @DisplayName( "Bracket number access" ) - @Test - public void testBracketNumberAccess() { - IStruct s = new Struct(); - s.assign( context, new Key( "7" ), "test" ); - variables.assign( context, new Key( "foo" ), s ); - instance.executeSource( - """ - foo[ 7 ] - """, - context ); - } - - @DisplayName( "Bracket number expression access" ) - @Test - public void testBracketNumberExpressionAccess() { - IStruct s = new Struct(); - s.assign( context, new Key( "12" ), "test" ); - variables.assign( context, new Key( "foo" ), s ); - instance.executeSource( - """ - foo[ 7 + 5 ] - """, - context ); - } - - @DisplayName( "Bracket object access" ) - @Test - public void testBracketObjectExpressionAccess() { - IStruct x = new Struct(); - x.assign( context, new Key( "bar" ), "baz" ); - IStruct s = new Struct(); - s.assign( context, new Key( "12" ), "test" ); - s.assign( context, Key.of( x ), "test" ); - variables.assign( context, new Key( "foo" ), s ); - instance.executeSource( - """ - foo[ { bar : "baz" } ]; - """, - context ); - } - - @DisplayName( "Mixed access" ) - @Test - public void testBracketMixedAccess() { - IStruct aaa = new Struct(); - IStruct twelve = new Struct(); - IStruct other = new Struct(); - IStruct foo = new Struct(); - - foo.assign( context, new Key( "aaa" ), aaa ); - aaa.assign( context, Key.of( 12 ), twelve ); - twelve.assign( context, Key.of( "other" ), other ); - other.assign( context, Key.of( 7 ), "test" ); - - variables.assign( context, new Key( "foo" ), foo ); - instance.executeSource( - """ - foo[ "a" & "aa" ][ 12 ].other[ 2 + 5 ]; - """, - context ); - } - - @DisplayName( "dereference enum key" ) - @Test - public void testDereferenceEnumKey() { - instance.executeSource( - """ - import ortus.boxlang.runtime.types.BoxLangType; - result = BoxLangType.LIST.getKey().getName(); - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "list" ); - } - - @DisplayName( "dereference enum as nested class" ) - @Test - public void testDereferenceEnumAsNestedClass() { - instance.executeSource( - """ - import ortus.boxlang.runtime.types.Struct as jStruct; - import ortus.boxlang.runtime.types.IStruct; - struct = new jStruct( IStruct.TYPES.CASE_SENSITIVE ); - """, - context ); - } - - @DisplayName( "dereference nested class" ) - @Test - public void testDereferenceNestedClass() { - instance.executeSource( - """ - import java.util.Map - result = Map.Entry; - """, - context ); - - assertThat( variables.get( result ) ).isEqualTo( Map.Entry.class ); - - instance.executeSource( - """ - import java.util.Map$Entry; - result = Entry; - """, - context ); - - assertThat( DynamicObject.unWrap( variables.get( result ) ) ).isEqualTo( Map.Entry.class ); - } - -} diff --git a/src/test/java/TestCases/asm/phase1/LabeledLoopTest.java b/src/test/java/TestCases/asm/phase1/LabeledLoopTest.java deleted file mode 100644 index 0adf7aacd..000000000 --- a/src/test/java/TestCases/asm/phase1/LabeledLoopTest.java +++ /dev/null @@ -1,261 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm.phase1; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.compiler.parser.BoxSourceType; -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.VariablesScope; - -public class LabeledLoopTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key result = new Key( "result" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @Test - public void testSimpleLabeledWhile() { - - instance.executeSource( - """ - result = 0 - mylabel : while( true ) { - result ++ - break mylabel; - result ++ - } - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( 1 ); - } - - @Test - public void testSimpleLabeledWhileContinue() { - - instance.executeSource( - """ - result = 0 - mylabel : while( true ) { - result ++ - if( result > 2 ) break; - continue mylabel; - result ++ - } - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( 3 ); - } - - @Test - public void testSimpleLabeledWhileTag() { - - instance.executeSource( - """ - - - - - - - """, - context, BoxSourceType.BOXTEMPLATE ); - assertThat( variables.get( result ) ).isEqualTo( 1 ); - } - - @Test - public void testSimpleLabeledWhileContinueTag() { - - instance.executeSource( - """ - - - - - - - - - - """, - context, BoxSourceType.BOXTEMPLATE ); - assertThat( variables.get( result ) ).isEqualTo( 3 ); - } - - @Test - public void testSimpleLabeledDoWhile() { - - instance.executeSource( - """ - result = 0 - mylabel : do { - result ++ - break mylabel; - result ++ - } while( true ) - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( 1 ); - } - - @Test - public void testSimpleLabeledForIn() { - - instance.executeSource( - """ - data = [1,2,3] - result = 0 - mylabel : for( x in data ) { - result ++ - break mylabel; - result ++ - } - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( 1 ); - } - - @Test - public void testSimpleLabeledForIndex() { - - instance.executeSource( - """ - result = 0 - mylabel : for( ; ; ) { - result ++ - break mylabel; - result ++ - } - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( 1 ); - } - - @Test - public void testSimpleLabeledLoop() { - - instance.executeSource( - """ - result = 0 - loop condition="true" label="mylabel" { - result ++ - break mylabel; - result ++ - } - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( 1 ); - } - - @Test - @Disabled - public void testSwitchInLabeledWhile() { - - instance.executeSource( - """ - result = 0 - mylabel : while( true ) { - result ++ - switch( result ) { - case 1: - break; - case 2: - break mylabel; - case 3: - break; - } - } - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( 2 ); - } - - @Test - @Disabled - public void testNestedLabeledWhiles() { - - instance.executeSource( - """ - result = 0 - outer : while( true ) { - result ++ - inner : while( true ) { - result ++ - break outer; - result ++ - } - } - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( 2 ); - } - - @Test - @Disabled - public void testTagLoop() { - - instance.executeSource( - """ - - #i# -
- - - - #i#->#i# -
-
- """, - context, BoxSourceType.CFTEMPLATE ); - } - -} diff --git a/src/test/java/TestCases/asm/phase1/ObjectCreationTest.java b/src/test/java/TestCases/asm/phase1/ObjectCreationTest.java deleted file mode 100644 index 4a4266ba6..000000000 --- a/src/test/java/TestCases/asm/phase1/ObjectCreationTest.java +++ /dev/null @@ -1,200 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm.phase1; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.interop.DynamicObject; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.VariablesScope; - -public class ObjectCreationTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key resultKey = new Key( "result" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @DisplayName( "new keyword prefix" ) - @Test - public void testNewKeywordPrefix() { - Object result = instance.executeStatement( "new java:java.lang.String( 'My String' )", context ); - assertThat( result instanceof DynamicObject ).isEqualTo( true ); - assertThat( ( ( DynamicObject ) result ).getTargetInstance() ).isEqualTo( "My String" ); - } - - @DisplayName( "new keyword no prefix" ) - @Test - public void testNewKeywordNoPrefix() { - Object result = instance.executeStatement( "new java.lang.String( 'My String' )", context ); - assertThat( result instanceof DynamicObject ).isEqualTo( true ); - assertThat( ( ( DynamicObject ) result ).getTargetInstance() ).isEqualTo( "My String" ); - } - - @DisplayName( "new keyword no prefix2" ) - @Test - public void testNewKeywordNoPrefix2() { - Object result = instance.executeStatement( "new ortus.boxlang.runtime.types.Array()", context ); - assertThat( result instanceof DynamicObject ).isEqualTo( true ); - } - - @DisplayName( "new keyword quoted" ) - @Test - public void testNewKeywordQuoted() { - Object result = instance.executeStatement( "new 'java:java.lang.String'( 'My String' );", context ); - assertThat( result instanceof DynamicObject ).isEqualTo( true ); - assertThat( ( ( DynamicObject ) result ).getTargetInstance() ).isEqualTo( "My String" ); - - instance.executeSource( - """ - classNameToCreate = 'java:java.lang.String'; - result = new "#classNameToCreate#"( 'My String' ); - """, - context ); - assertThat( variables.get( resultKey ) instanceof DynamicObject ).isEqualTo( true ); - assertThat( ( ( DynamicObject ) result ).getTargetInstance() ).isEqualTo( "My String" ); - - } - /* - * @DisplayName( "create keyword prefix" ) - * - * @Test - * public void testCreateKeywordPrefix() { - * Object result = instance.executeStatement( "create java:java.lang.System", context ); - * assertThat( result instanceof DynamicObject ).isEqualTo( true ); - * assertThat( ( ( DynamicObject ) result ).getTargetClass().getName() ).isEqualTo( "java.lang.System" ); - * } - * - * @DisplayName( "create keyword no prefix" ) - * - * @Test - * public void testCreateKeywordNoPrefix() { - * Object result = instance.executeStatement( "create java.lang.System", context ); - * assertThat( result instanceof DynamicObject ).isEqualTo( true ); - * assertThat( ( ( DynamicObject ) result ).getTargetClass().getName() ).isEqualTo( "java.lang.System" ); - * } - * - * @DisplayName( "create keyword quoted" ) - * - * @Test - * public void testCreateKeywordQuoted() { - * Object result = instance.executeStatement( "create 'java:java.lang.System';", context ); - * assertThat( result instanceof DynamicObject ).isEqualTo( true ); - * assertThat( ( ( DynamicObject ) result ).getTargetClass().getName() ).isEqualTo( "java.lang.System" ); - * - * instance.executeSource( - * """ - * classNameToCreate = 'java:java.lang.System'; - * result = create "#classNameToCreate#"; - * """, - * context ); - * assertThat( variables.get( resultKey ) instanceof DynamicObject ).isEqualTo( true ); - * assertThat( ( ( DynamicObject ) variables.get( resultKey ) ).getTargetClass().getName() ).isEqualTo( "java.lang.System" ); - * } - * - * @DisplayName( "create keyword static method call one-liner" ) - * - * @Test - * public void testCreateKeywordstaticMethodCallOneLiner() { - * instance.executeStatement( "(create java.lang.System).out.println( 2+3 )", context ); - * instance.executeSource( - * """ - * (create java.lang.System).out.println( 2+3 ) - * """, - * context ); - * - * } - */ - - @DisplayName( "imports prefix" ) - @Test - public void testImportsPrefix() { - instance.executeSource( - """ - import java:java.lang.String; - result = new java:String( 'My String' ); - """, - context ); - - assertThat( variables.get( resultKey ) instanceof DynamicObject ).isEqualTo( true ); - assertThat( ( ( DynamicObject ) variables.get( resultKey ) ).getTargetInstance() ).isEqualTo( "My String" ); - } - - @DisplayName( "imports no prefix" ) - @Test - public void testImportsNoPrefix() { - instance.executeSource( - """ - import java.lang.String; - result = new String( 'My String' ); - """, - context ); - - assertThat( variables.get( resultKey ) instanceof DynamicObject ).isEqualTo( true ); - assertThat( ( ( DynamicObject ) variables.get( resultKey ) ).getTargetInstance() ).isEqualTo( "My String" ); - } - - @DisplayName( "imports as" ) - @Test - public void testImportsAs() { - instance.executeSource( - """ - import java.lang.String as jString; - result = new jString( 'My String' ); - """, - context ); - - assertThat( variables.get( resultKey ) instanceof DynamicObject ).isEqualTo( true ); - assertThat( ( ( DynamicObject ) variables.get( resultKey ) ).getTargetInstance() ).isEqualTo( "My String" ); - - } - -} diff --git a/src/test/java/TestCases/asm/phase1/ObjectReferenceAssignmentTest.java b/src/test/java/TestCases/asm/phase1/ObjectReferenceAssignmentTest.java deleted file mode 100644 index 336307727..000000000 --- a/src/test/java/TestCases/asm/phase1/ObjectReferenceAssignmentTest.java +++ /dev/null @@ -1,396 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm.phase1; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.util.Map; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.FunctionBoxContext; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.LocalScope; -import ortus.boxlang.runtime.scopes.VariablesScope; -import ortus.boxlang.runtime.types.Argument; -import ortus.boxlang.runtime.types.Function.Access; -import ortus.boxlang.runtime.types.IStruct; -import ortus.boxlang.runtime.types.SampleUDF; -import ortus.boxlang.runtime.types.exceptions.BoxLangException; - -public class ObjectReferenceAssignmentTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key result = new Key( "result" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @DisplayName( "scope assignment" ) - @Test - public void testScopeAssignment() { - instance.executeSource( - """ - variables.result = "brad"; - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "brad" ); - - instance.executeSource( - """ - keyName = "result"; - variables[keyName] = "wood"; - - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "wood" ); - - instance.executeSource( - """ - variables['result'] = "luis"; - - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "luis" ); - - } - - @DisplayName( "unscoped assignment" ) - @Test - public void testUnscopedAssignment() { - instance.executeSource( - """ - result = "brad"; - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "brad" ); - - } - - @DisplayName( "invaid assignment" ) - @Test - public void testInvalidAssignment() { - - // These are invalid because they are trying to assign directly to an expression that can't be assigned - assertThrows( BoxLangException.class, () -> instance.executeSource( - """ - foo() = "brad"; - """, - context ) ); - - assertThrows( BoxLangException.class, () -> instance.executeSource( - """ - obj.foo() = "brad"; - """, - context ) ); - - // These are all a BoxAccess, but the "var" keyword can only come before an INITIAL identifier or BoxAccess - assertThrows( BoxLangException.class, () -> instance.executeSource( - """ - var foo().key = "brad"; - """, - context ) ); - - assertThrows( BoxLangException.class, () -> instance.executeSource( - """ - var obj.foo().key = "brad"; - """, - context ) ); - - assertThrows( BoxLangException.class, () -> instance.executeSource( - """ - var "foo".key = "brad"; - """, - context ) ); - - } - - @DisplayName( "dereference scope key" ) - @Test - public void testDereferenceScopeKey() { - instance.executeSource( - """ - variables.foo = "luis"; - result = variables.foo; - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "luis" ); - - instance.executeSource( - """ - variables.foo = "gavin"; - result = variables['foo']; - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "gavin" ); - - } - - @DisplayName( "dereference key" ) - @Test - public void testDereferenceKey() { - instance.executeSource( - """ - import java:ortus.boxlang.runtime.scopes.Key; - str = new java:ortus.boxlang.runtime.types.Struct(); - str.assign( GetBoxContext(), Key.of("name"), "Brad" ); - result = str.name; - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( "Brad" ); - - } - - @DisplayName( "dereference headless" ) - @Test - public void testDereferenceHeadless() { - instance.executeSource( - """ - variables.foo = 5; - result = foo; - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( 5 ); - - } - - @DisplayName( "dereference invoke key" ) - @Test - public void testDereferenceInvokeKey() { - instance.executeSource( - """ - ctx = new java:ortus.boxlang.runtime.context.ScriptingRequestBoxContext(); - result = variables.ctx.getDefaultAssignmentScope() - """, - context ); - assertThat( variables.get( result ) instanceof IScope ).isTrue(); - - } - - @DisplayName( "dereference invoke headless" ) - @Test - public void testDereferenceInvokeHeadless() { - instance.executeSource( - """ - ctx = new java:ortus.boxlang.runtime.context.ScriptingRequestBoxContext(); - result = ctx.getDefaultAssignmentScope() - """, - context ); - assertThat( variables.get( result ) instanceof IScope ).isTrue(); - } - - @DisplayName( "safe navigation" ) - @Test - public void testSafeNavigation() { - instance.executeSource( - """ - result = variables?.foo?.bar?.baz; - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( null ); - - Object theResult = instance.executeStatement( "variables?.foo?.bar?.baz", context ); - assertThat( theResult ).isEqualTo( null ); - - } - - @DisplayName( "var keyword for local" ) - @Test - public void testVarKeywordForLocal() { - FunctionBoxContext functionBoxContext = new FunctionBoxContext( context, - new SampleUDF( Access.PUBLIC, Key.of( "func" ), "any", new Argument[] {}, "" ) ); - instance.executeSource( - """ - var foo = 5; - local.bar = 6; - println(local.asString()) - """, - functionBoxContext ); - - IScope localScope = functionBoxContext.getScopeNearby( LocalScope.name ); - assertThat( localScope.get( Key.of( "foo" ) ) ).isEqualTo( 5 ); - assertThat( localScope.get( Key.of( "bar" ) ) ).isEqualTo( 6 ); - - } - - @DisplayName( "var keyword for local Deep" ) - @Test - public void testVarKeywordForLocalDeep() { - FunctionBoxContext functionBoxContext = new FunctionBoxContext( context, - new SampleUDF( Access.PUBLIC, Key.of( "func" ), "any", new Argument[] {}, "" ) ); - IScope localScope = functionBoxContext.getScopeNearby( LocalScope.name ); - instance.executeSource( - """ - var foo.bar = 5; - """, - functionBoxContext ); - assertThat( localScope.get( Key.of( "foo" ) ) instanceof IStruct ).isTrue(); - IStruct foo = ( IStruct ) localScope.get( Key.of( "foo" ) ); - assertThat( foo.get( Key.of( "bar" ) ) ).isEqualTo( 5 ); - - } - - @DisplayName( "var keyword for scope" ) - @Test - public void testVarKeywordForLocalForScope() { - FunctionBoxContext functionBoxContext = new FunctionBoxContext( context, - new SampleUDF( Access.PUBLIC, Key.of( "func" ), "any", new Argument[] {}, "" ) ); - IScope localScope = functionBoxContext.getScopeNearby( LocalScope.name ); - instance.executeSource( - """ - var variables = 5; - """, - functionBoxContext ); - assertThat( localScope.get( Key.of( "variables" ) ) ).isEqualTo( 5 ); - - } - - @DisplayName( "var keyword for scope deep" ) - @Test - public void testVarKeywordForLocalForScopeDeep() { - FunctionBoxContext functionBoxContext = new FunctionBoxContext( context, - new SampleUDF( Access.PUBLIC, Key.of( "func" ), "any", new Argument[] {}, "" ) ); - IScope localScope = functionBoxContext.getScopeNearby( LocalScope.name ); - instance.executeSource( - """ - var variables.bar = 5; - """, - functionBoxContext ); - assertThat( localScope.get( Key.of( "variables" ) ) instanceof IStruct ).isTrue(); - IStruct variables = ( IStruct ) localScope.get( Key.of( "variables" ) ); - assertThat( variables.get( Key.of( "bar" ) ) ).isEqualTo( 5 ); - - } - - @DisplayName( "deep assignment" ) - @Test - public void testDeepAssignment() { - instance.executeSource( - """ - variables.foo.bar.baz="brad" - """, - context ); - assertThat( variables.containsKey( Key.of( "foo" ) ) ).isEqualTo( true ); - Object foo = variables.get( Key.of( "foo" ) ); - assertThat( foo instanceof Map ).isEqualTo( true ); - assertThat( ( ( Map ) foo ).containsKey( Key.of( "bar" ) ) ).isEqualTo( true ); - Object bar = ( ( Map ) foo ).get( Key.of( "bar" ) ); - assertThat( bar instanceof Map ).isEqualTo( true ); - Object baz = ( ( Map ) bar ).get( Key.of( "baz" ) ); - assertThat( baz instanceof String ).isEqualTo( true ); - } - - @DisplayName( "assignment returns value" ) - @Test - public void testAssignmentReturnsValue() { - instance.executeSource( - """ - foo = bar = brad = "wood" - """, - context ); - - assertThat( variables.get( Key.of( "foo" ) ) ).isEqualTo( "wood" ); - assertThat( variables.get( Key.of( "bar" ) ) ).isEqualTo( "wood" ); - assertThat( variables.get( Key.of( "brad" ) ) ).isEqualTo( "wood" ); - } - - @DisplayName( "scoped assignment returns value" ) - @Test - public void testScopedAssignmentReturnsValue() { - instance.executeSource( - """ - variables.foo2 = variables.bar2 = variables.brad2 = "wood2" - """, - context ); - - assertThat( variables.get( Key.of( "foo2" ) ) ).isEqualTo( "wood2" ); - assertThat( variables.get( Key.of( "bar2" ) ) ).isEqualTo( "wood2" ); - assertThat( variables.get( Key.of( "brad2" ) ) ).isEqualTo( "wood2" ); - } - - @DisplayName( "call method on object" ) - @Test - public void testCallMethodOnObject() { - instance.executeSource( - """ - system = createObject('java', 'java.lang.System'); - system.out.println("Hello World"); - system["out"]["println"]("Hello World"); - """, - context ); - - } - - @DisplayName( "static method call on imported class" ) - @Test - public void testStaticMethodCallOnImportedClass() { - instance.executeSource( - """ - import java:java.lang.System; - - system.out.println( 2+3 ) - """, - context ); - - } - - @DisplayName( "null keyword" ) - @Test - public void testNullKeyword() { - instance.executeSource( - """ - nothing = null; - result = nothing == null; - """, - context ); - assertThat( variables.get( result ) ).isEqualTo( true ); - - } - -} diff --git a/src/test/java/TestCases/asm/phase1/OperatorsTest.java b/src/test/java/TestCases/asm/phase1/OperatorsTest.java deleted file mode 100644 index efbb97cbf..000000000 --- a/src/test/java/TestCases/asm/phase1/OperatorsTest.java +++ /dev/null @@ -1,967 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm.phase1; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.compiler.parser.BoxSourceType; -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.VariablesScope; -import ortus.boxlang.runtime.types.exceptions.ExpressionException; -import ortus.boxlang.runtime.types.exceptions.KeyNotFoundException; - -public class OperatorsTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key resultKey = new Key( "result" ); - static Key tmpKey = new Key( "tmp" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @DisplayName( "string concat" ) - @Test - public void testStringConcat() { - Object result = instance.executeStatement( "'brad' & 'wood'", context ); - assertThat( result ).isEqualTo( "bradwood" ); - } - - @DisplayName( "multi string concat" ) - @Test - public void testMutliStringConcat() { - Object result = instance.executeStatement( "'foo' & 'bar' & 'baz' & 'bum'", context ); - assertThat( result ).isEqualTo( "foobarbazbum" ); - } - - @DisplayName( "concat and eq" ) - @Test - public void testConcatAndEQ() { - Object result = instance.executeStatement( "'$' & 'foo' == '$foo'", context ); - assertThat( result ).isEqualTo( true ); - } - - @DisplayName( "string contains" ) - @Test - public void testStringContains() { - Object result = instance.executeStatement( "\"Brad Wood\" contains \"Wood\"", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "\"Brad Wood\" contains \"luis\"", context ); - assertThat( result ).isEqualTo( false ); - - result = instance.executeStatement( "\"Brad Wood\" DOES NOT CONTAIN \"Luis\"", context ); - assertThat( result ).isEqualTo( true ); - } - - @DisplayName( "math addition" ) - @Test - public void testMathAddition() { - Object result = instance.executeStatement( "+5", context ); - assertThat( result ).isEqualTo( 5 ); - - result = instance.executeStatement( "5+5", context ); - assertThat( result ).isEqualTo( 10 ); - - result = instance.executeStatement( "'5'+'2'", context ); - assertThat( result ).isEqualTo( 7 ); - } - - @DisplayName( "math subtraction" ) - @Test - public void testMathSubtraction() { - Object result = instance.executeStatement( "6-5", context ); - assertThat( result ).isEqualTo( 1 ); - } - - @DisplayName( "math negation" ) - @Test - public void testMathNegation() { - Object result = instance.executeStatement( "-5", context ); - assertThat( result ).isEqualTo( -5 ); - - result = instance.executeStatement( "-(5+5)", context ); - assertThat( result ).isEqualTo( -10 ); - - result = instance.executeStatement( "-5+5", context ); - assertThat( result ).isEqualTo( 0 ); - } - - @DisplayName( "math addition var" ) - @Test - public void testMathAdditionVar() { - Object result = instance.executeStatement( "foo=5; +foo", context ); - assertThat( result ).isEqualTo( 5 ); - } - - @DisplayName( "math negation var" ) - @Test - public void testMathNegationVar() { - Object result = instance.executeStatement( "foo=5; -foo", context ); - assertThat( result ).isEqualTo( -5 ); - } - - @DisplayName( "math division" ) - @Test - public void testMathDivision() { - Number result = ( Number ) instance.executeStatement( "10/5", context ); - assertThat( result.doubleValue() ).isEqualTo( 2 ); - } - - @DisplayName( "math int divison" ) - @Test - public void testMathIntDivision() { - Number result = ( Number ) instance.executeStatement( "10\\3", context ); - assertThat( result.doubleValue() ).isEqualTo( 3 ); - } - - @DisplayName( "math multiplication" ) - @Test - public void testMathMultiplication() { - Number result = ( Number ) instance.executeStatement( "10*5", context ); - assertThat( result.doubleValue() ).isEqualTo( 50 ); - } - - @DisplayName( "math power" ) - @Test - public void testMathPower() { - Number result = ( Number ) instance.executeStatement( "2^3", context ); - assertThat( result.doubleValue() ).isEqualTo( 8 ); - } - - @DisplayName( "math plus plus literals" ) - @Test - public void testMathPlusPlusLiterals() { - Object result = instance.executeStatement( "5++", context ); - assertThat( result ).isEqualTo( 5 ); - - result = instance.executeStatement( "++5", context ); - assertThat( result ).isEqualTo( 6 ); - - result = instance.executeStatement( "result=5++", context ); - assertThat( result ).isEqualTo( 5 ); - assertThat( variables.get( resultKey ) ).isEqualTo( 5 ); - - result = instance.executeStatement( "result=++5", context ); - assertThat( result ).isEqualTo( 6 ); - assertThat( variables.get( resultKey ) ).isEqualTo( 6 ); - - } - - @DisplayName( "math plus plus parenthetical" ) - @Test - public void testMathPlusPlusParenthetical() { - Object result = instance.executeStatement( "(5)++", context ); - assertThat( result ).isEqualTo( 5 ); - - result = instance.executeStatement( "++(5)", context ); - assertThat( result ).isEqualTo( 6 ); - - result = instance.executeStatement( "result=(5)++", context ); - assertThat( result ).isEqualTo( 5 ); - assertThat( variables.get( resultKey ) ).isEqualTo( 5 ); - - result = instance.executeStatement( "result=++(5)", context ); - assertThat( result ).isEqualTo( 6 ); - assertThat( variables.get( resultKey ) ).isEqualTo( 6 ); - - instance.executeSource( """ - myvar = 5; - result = ++(myvar); - """, context ); - assertThat( variables.get( resultKey ) ).isEqualTo( 6 ); - assertThat( variables.get( Key.of( "myvar" ) ) ).isEqualTo( 6 ); - - instance.executeSource( """ - myvar = 5; - result = (myvar)++; - """, context ); - assertThat( variables.get( resultKey ) ).isEqualTo( 5 ); - assertThat( variables.get( Key.of( "myvar" ) ) ).isEqualTo( 6 ); - - } - - @DisplayName( "math plus plus other" ) - @Test - public void testMathPlusPlusOther() { - - instance.executeSource( """ - function num() { - return 5; - } - result = ++num(); - """, context ); - assertThat( variables.get( resultKey ) ).isEqualTo( 6 ); - - instance.executeSource( """ - function num() { - return 5; - } - result = num()++; - """, context ); - assertThat( variables.get( resultKey ) ).isEqualTo( 5 ); - - } - - @DisplayName( "math plus plus scoped" ) - @Test - public void testMathPlusPlusScoped() { - instance.executeSource( - """ - tmp = 5; - result = variables.tmp++; - """, - context ); - assertThat( variables.get( resultKey ) ).isEqualTo( 5 ); - assertThat( variables.get( tmpKey ) ).isEqualTo( 6 ); - - instance.executeSource( - """ - tmp = 5; - result = ++variables.tmp; - """, - context ); - assertThat( variables.get( resultKey ) ).isEqualTo( 6 ); - assertThat( variables.get( tmpKey ) ).isEqualTo( 6 ); - } - - @DisplayName( "math plus plus invalid" ) - @Test - public void testMathPlusPlusInvalid() { - - assertThrows( ExpressionException.class, () -> instance.executeSource( "variables++", context ) ); - - } - - @DisplayName( "math plus plus unscoped" ) - @Test - public void testMathPlusPlusUnScoped() { - instance.executeSource( - """ - tmp = 5; - result = tmp++; - """, - context ); - assertThat( variables.get( resultKey ) ).isEqualTo( 5 ); - assertThat( variables.get( tmpKey ) ).isEqualTo( 6 ); - - instance.executeSource( - """ - tmp = 5; - result = ++tmp; - """, - context ); - assertThat( variables.get( resultKey ) ).isEqualTo( 6 ); - assertThat( variables.get( tmpKey ) ).isEqualTo( 6 ); - - instance.executeSource( - """ - foo.bar.baz = 5; - result = foo.bar.baz++; - tmp = foo.bar.baz; - """, - context ); - assertThat( variables.get( resultKey ) ).isEqualTo( 5 ); - assertThat( variables.get( tmpKey ) ).isEqualTo( 6 ); - - instance.executeSource( - """ - foo.bar.baz = 5; - result = ++foo.bar.baz; - tmp = foo.bar.baz; - """, - context ); - assertThat( variables.get( resultKey ) ).isEqualTo( 6 ); - assertThat( variables.get( tmpKey ) ).isEqualTo( 6 ); - } - - @DisplayName( "math minus minus literals" ) - @Test - public void testMathMinusMinusLiterals() { - Object result = instance.executeStatement( "5--", context ); - assertThat( result ).isEqualTo( 5 ); - - result = instance.executeStatement( "--5", context ); - assertThat( result ).isEqualTo( 4 ); - - result = instance.executeStatement( "result=5--", context ); - assertThat( result ).isEqualTo( 5 ); - assertThat( variables.get( resultKey ) ).isEqualTo( 5 ); - - result = instance.executeStatement( "result=--5", context ); - assertThat( result ).isEqualTo( 4 ); - assertThat( variables.get( resultKey ) ).isEqualTo( 4 ); - - } - - @DisplayName( "math minus minus parenthetical" ) - @Test - public void testMathMinusMinusParenthetical() { - Object result = instance.executeStatement( "(5)--", context ); - assertThat( result ).isEqualTo( 5 ); - - result = instance.executeStatement( "--(5)", context ); - assertThat( result ).isEqualTo( 4 ); - - result = instance.executeStatement( "result=(5)--", context ); - assertThat( result ).isEqualTo( 5 ); - assertThat( variables.get( resultKey ) ).isEqualTo( 5 ); - - result = instance.executeStatement( "result=--(5)", context ); - assertThat( result ).isEqualTo( 4 ); - assertThat( variables.get( resultKey ) ).isEqualTo( 4 ); - - instance.executeSource( """ - myvar = 5; - result = --(myvar); - """, context ); - assertThat( variables.get( resultKey ) ).isEqualTo( 4 ); - assertThat( variables.get( Key.of( "myvar" ) ) ).isEqualTo( 4 ); - - instance.executeSource( """ - myvar = 5; - result = (myvar)--; - """, context ); - assertThat( variables.get( resultKey ) ).isEqualTo( 5 ); - assertThat( variables.get( Key.of( "myvar" ) ) ).isEqualTo( 4 ); - - } - - @DisplayName( "math minus minus scoped" ) - @Test - public void testMathMinusMinusScoped() { - instance.executeSource( - """ - tmp = 5; - result = variables.tmp--; - """, - context ); - assertThat( variables.get( resultKey ) ).isEqualTo( 5 ); - assertThat( variables.get( tmpKey ) ).isEqualTo( 4 ); - - instance.executeSource( - """ - tmp = 5; - result = --variables.tmp; - """, - context ); - assertThat( variables.get( resultKey ) ).isEqualTo( 4 ); - assertThat( variables.get( tmpKey ) ).isEqualTo( 4 ); - } - - @DisplayName( "math minus minus unscoped" ) - @Test - public void testMathMinusMinusUnScoped() { - instance.executeSource( - """ - tmp = 5; - result = tmp--; - """, - context ); - assertThat( variables.get( resultKey ) ).isEqualTo( 5 ); - assertThat( variables.get( tmpKey ) ).isEqualTo( 4 ); - - instance.executeSource( - """ - tmp = 5; - result = --tmp; - """, - context ); - assertThat( variables.get( resultKey ) ).isEqualTo( 4 ); - assertThat( variables.get( tmpKey ) ).isEqualTo( 4 ); - - instance.executeSource( - """ - foo.bar.baz = 5; - result = foo.bar.baz--; - tmp = foo.bar.baz; - """, - context ); - assertThat( variables.get( resultKey ) ).isEqualTo( 5 ); - assertThat( variables.get( tmpKey ) ).isEqualTo( 4 ); - - instance.executeSource( - """ - foo.bar.baz= 5; - result = --foo.bar.baz; - tmp = foo.bar.baz; - """, - context ); - assertThat( variables.get( resultKey ) ).isEqualTo( 4 ); - assertThat( variables.get( tmpKey ) ).isEqualTo( 4 ); - } - - @DisplayName( "compound operator plus" ) - @Test - public void compoundOperatorPlus() { - instance.executeSource( - """ - result = 5; - result += 5; - """, - context ); - assertThat( variables.get( resultKey ) ).isEqualTo( 10 ); - - instance.executeSource( - """ - function foo(){ - local.result = 5; - local.result += 5; - return result; - } - result = foo(); - """, - context ); - assertThat( variables.get( resultKey ) ).isEqualTo( 10 ); - - instance.executeSource( - """ - result = 5; - variables.result += 5; - """, - context ); - assertThat( variables.get( resultKey ) ).isEqualTo( 10 ); - } - - @DisplayName( "compound operators minus" ) - @Test - public void compoundOperatorMinus() { - instance.executeSource( - """ - result = 5; - result -= 4; - """, - context ); - assertThat( variables.get( resultKey ) ).isEqualTo( 1 ); - } - - @DisplayName( "compound operator multiply" ) - @Test - public void compoundOperatorMultiply() { - instance.executeSource( - """ - result = 5; - result *= 5; - """, - context ); - assertThat( variables.getAsNumber( resultKey ).doubleValue() ).isEqualTo( 25 ); - } - - @DisplayName( "compound operator divide" ) - @Test - public void compoundOperatorDivide() { - instance.executeSource( - """ - result = 20; - result /= 5; - """, - context ); - assertThat( variables.getAsNumber( resultKey ).doubleValue() ).isEqualTo( 4 ); - } - - @DisplayName( "compound operator modulus" ) - @Test - public void compoundOperatorModulus() { - instance.executeSource( - """ - result = 5; - result %= 4; - """, - context ); - assertThat( variables.getAsNumber( resultKey ).doubleValue() ).isEqualTo( 1 ); - } - - @DisplayName( "modulus precedence" ) - @Test - public void modulusPrecedence() { - instance.executeSource( - """ - result = 1 + 1 mod 2; - result2 = 1 + 1 % 2; - """, - context ); - assertThat( variables.getAsNumber( resultKey ).doubleValue() ).isEqualTo( 2 ); - assertThat( variables.getAsNumber( Key.of( "result2" ) ).doubleValue() ).isEqualTo( 2 ); - } - - @DisplayName( "compound operator concat" ) - @Test - public void compoundOperatorConcat() { - instance.executeSource( - """ - result = "brad"; - result &= "wood"; - """, - context ); - assertThat( variables.get( resultKey ) ).isEqualTo( "bradwood" ); - } - - @DisplayName( "compound operator with var" ) - @Test - public void compoundOperatorWithVar() { - /* - * I personally think this should be invalid code, but Adobe and Lucee both allow it. Adobe turns all access to the variable into the local scope - * somehow, presumably some sort of variable hoisting. Lucee just straight up ignores the "var" keyword and modifies the variable in whatever scope - * it's already in. I've chosen Lucee's behavior for now because it's the least amount of work and this is an edge case. - */ - instance.executeSource( - """ - result = "brad"; - var result &= "wood"; - """, - context ); - assertThat( variables.get( resultKey ) ).isEqualTo( "bradwood" ); - } - - @DisplayName( "logical and" ) - @Test - public void testLogicalAnd() { - Object result = instance.executeStatement( "true and true", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "true && true", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "true and false", context ); - assertThat( result ).isEqualTo( false ); - - result = instance.executeStatement( "true && false", context ); - assertThat( result ).isEqualTo( false ); - } - - @DisplayName( "logical or" ) - @Test - public void testLogicalOr() { - Object result = instance.executeStatement( "true or true", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "true || true", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "true or false", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "true || false", context ); - assertThat( result ).isEqualTo( true ); - } - - @DisplayName( "logical not" ) - @Test - public void testLogicalNot() { - Object result = instance.executeStatement( "!true", context ); - assertThat( result ).isEqualTo( false ); - - result = instance.executeStatement( "!false", context ); - assertThat( result ).isEqualTo( true ); - } - - @DisplayName( "logical xor" ) - @Test - public void testLogicalXOR() { - Object result = instance.executeStatement( "true xor false", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "true xor true", context ); - assertThat( result ).isEqualTo( false ); - - result = instance.executeStatement( "false xor false", context ); - assertThat( result ).isEqualTo( false ); - } - - @DisplayName( "elvis" ) - @Test - public void testElvis() { - - instance.executeSource( - """ - tmp = "brad" - result = tmp ?: 'default' - """, - context ); - assertThat( variables.get( resultKey ) ).isEqualTo( "brad" ); - - Object result = instance.executeStatement( "null ?: 'default'", context ); - assertThat( result ).isEqualTo( "default" ); - - result = instance.executeStatement( "foo ?: 'default'", context ); - assertThat( result ).isEqualTo( "default" ); - - result = instance.executeStatement( "foo.bar ?: 'default'", context ); - assertThat( result ).isEqualTo( "default" ); - - result = instance.executeStatement( "foo['bar'] ?: 'default'", context ); - assertThat( result ).isEqualTo( "default" ); - - result = instance.executeStatement( "foo['bar'].baz ?: 'default'", context ); - assertThat( result ).isEqualTo( "default" ); - - result = instance.executeStatement( "foo.bar() ?: 'default'", context ); - assertThat( result ).isEqualTo( "default" ); - - } - - @DisplayName( "ternary" ) - @Test - public void testTernary() { - - Object result = instance.executeStatement( "true ? 'itwastrue' : 'itwasfalse'", context ); - assertThat( result ).isEqualTo( "itwastrue" ); - - result = instance.executeStatement( "FALSE ? 'itwastrue' : 'itwasfalse'", context ); - assertThat( result ).isEqualTo( "itwasfalse" ); - - instance.executeSource( - """ - tmp = true; - result = tmp ? 'itwastrue' : 'itwasfalse' - """, - context ); - assertThat( variables.get( resultKey ) ).isEqualTo( "itwastrue" ); - - } - - @DisplayName( "It should lazily evaluate its true branche" ) - @Test - public void testTernaryTrueLazyEvaluation() { - - Object result = instance.executeStatement( """ - "false" castas "boolean" ? "a" castas "boolean" : "0" castas "boolean"; - """, context ); - assertThat( result ).isEqualTo( false ); - } - - @DisplayName( "It should lazily evaluate its false branche" ) - @Test - public void testTernaryFalseLazyEvaluation() { - - Object result = instance.executeStatement( """ - "true" castas "boolean" ? "false" castas "boolean" : "x" castas "boolean"; - """, context ); - assertThat( result ).isEqualTo( false ); - } - - @DisplayName( "It should properly handle comparison operators" ) - @Test - public void testTernaryWithComparison() { - - Object result = instance.executeStatement( "4 < 5 ? 'itwastrue' : 'itwasfalse'", context ); - assertThat( result ).isEqualTo( "itwastrue" ); - - result = instance.executeStatement( "4 > 5 ? 'itwastrue' : 'itwasfalse'", context ); - assertThat( result ).isEqualTo( "itwasfalse" ); - - instance.executeSource( - """ - tmp = true; - result = tmp == true ? 'itwastrue' : 'itwasfalse' - """, - context ); - assertThat( variables.get( resultKey ) ).isEqualTo( "itwastrue" ); - - } - - @DisplayName( "instanceOf" ) - @Test - public void testInstanceOf() { - - Object result = instance.executeStatement( "true instanceOf 'Boolean'", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "'brad' instanceOf 'java.lang.String'", context ); - assertThat( result ).isEqualTo( true ); - - } - - @DisplayName( "castAs" ) - @Test - public void testCastAs() { - assertThrows( KeyNotFoundException.class, () -> instance.executeStatement( "5 castAs sdf", context ) ); - - Object result = instance.executeStatement( "5 castAs 'String'", context ); - assertThat( result ).isEqualTo( "5" ); - assertThat( result.getClass().getName() ).isEqualTo( "java.lang.String" ); - } - - @DisplayName( "assert" ) - @Test - public void testAssert() { - - instance.executeStatement( "assert true", context ); - instance.executeStatement( "assert true;", context ); - - instance.executeStatement( "assert 5==5", context ); - instance.executeStatement( "assert 5==5;", context ); - - assertThrows( AssertionError.class, () -> instance.executeStatement( "assert 5==6", context ) ); - assertThrows( AssertionError.class, () -> instance.executeStatement( "assert false", context ) ); - - } - - @DisplayName( "comparison equality" ) - @Test - public void testComparisonEquality() { - - Object result = instance.executeStatement( "5==5", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "'5'==5", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "'brad'=='brad'", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "'brad'==5", context ); - assertThat( result ).isEqualTo( false ); - - } - - @DisplayName( "comparison strict equality" ) - @Test - public void testComparisonStrictEquality() { - - Object result = instance.executeStatement( "5===5", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "'5'===5", context ); - assertThat( result ).isEqualTo( false ); - - result = instance.executeStatement( "'brad'==='brad'", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "'brad'===5", context ); - assertThat( result ).isEqualTo( false ); - - } - - @DisplayName( "comparison greater than" ) - @Test - public void testGreaterThan() { - - Object result = instance.executeStatement( "6 > 5", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "6 GREATER THAN 5", context ); - - result = instance.executeStatement( "'B' > 'A'", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "'B' greater than 'A'", context ); - assertThat( result ).isEqualTo( true ); - } - - @DisplayName( "comparison greater than equal" ) - @Test - public void testGreaterThanEqual() { - - Object result = instance.executeStatement( "10 >= 5", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "10 GTE 5", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "10 GREATER THAN OR EQUAL TO 5", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "10 GE 5", context ); - assertThat( result ).isEqualTo( true ); - } - - @DisplayName( "comparison less than" ) - @Test - public void testLessThan() { - - Object result = instance.executeStatement( "5 < 10", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "5 LT 10", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "5 LESS THAN 10", context ); - assertThat( result ).isEqualTo( true ); - - } - - @DisplayName( "comparison less than equal" ) - @Test - public void testLessThanEqual() { - - Object result = instance.executeStatement( "5 <= 10", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "5 LTE 10", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "5 LESS THAN OR EQUAL TO 10", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "5 LE 10", context ); - assertThat( result ).isEqualTo( true ); - - } - - @DisplayName( "parens" ) - @Test - public void testParens() { - - Number result = ( Number ) instance.executeStatement( "1 + ( 2 * 3 )", context ); - assertThat( result.doubleValue() ).isEqualTo( 7 ); - - result = ( Number ) instance.executeStatement( "( 1 + 2 ) * 3", context ); - assertThat( result.doubleValue() ).isEqualTo( 9 ); - - result = ( Number ) instance.executeStatement( "( 1 + 2 * 3 )", context ); - assertThat( result.doubleValue() ).isEqualTo( 7 ); - - } - - @DisplayName( "Order of operations" ) - @Test - public void testOrderOfOps() { - - Number result = ( Number ) instance.executeStatement( "1+2-3*4^5", context ); - assertThat( result.doubleValue() ).isEqualTo( -3069 ); - - result = ( Number ) instance.executeStatement( "2+3*4", context ); - assertThat( result.doubleValue() ).isEqualTo( 14 ); - - result = ( Number ) instance.executeStatement( "2-3+4", context ); - assertThat( result.doubleValue() ).isEqualTo( 3 ); - - result = ( Number ) instance.executeStatement( "2+3/4", context ); - assertThat( result.doubleValue() ).isEqualTo( 2.75 ); - - result = ( Number ) instance.executeStatement( "2-3/4", context ); - assertThat( result.doubleValue() ).isEqualTo( 1.25 ); - - result = ( Number ) instance.executeStatement( "2*2%3", context ); - assertThat( result.doubleValue() ).isEqualTo( 1 ); - - result = ( Number ) instance.executeStatement( "++5^--6", context ); - assertThat( result.doubleValue() ).isEqualTo( 7776 ); - - } - - @DisplayName( "It should handle equivalence" ) - @Test - public void testEQVOperator() { - - Object result = instance.executeStatement( "true EQV true", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "false EQV false", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "false EQV true", context ); - assertThat( result ).isEqualTo( false ); - - result = instance.executeStatement( "true EQV false", context ); - assertThat( result ).isEqualTo( false ); - - result = instance.executeStatement( "1 EQV 0", context ); - assertThat( result ).isEqualTo( false ); - - } - - @DisplayName( "It should handle the implies operator" ) - @Test - public void testIMPOperator() { - - Object result = instance.executeStatement( "true IMP false", context ); - assertThat( result ).isEqualTo( false ); - - result = instance.executeStatement( "false IMP false", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "false IMP true", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "true IMP true", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "1 IMP 0", context ); - assertThat( result ).isEqualTo( false ); - - } - - @DisplayName( "comparison strict not equality" ) - @Test - public void testComparisonStrictNotEquality() { - - Object result = instance.executeStatement( "5!==5", context ); - assertThat( result ).isEqualTo( false ); - - result = instance.executeStatement( "'5'!==5", context ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "'brad'!=='brad'", context ); - assertThat( result ).isEqualTo( false ); - - result = instance.executeStatement( "'brad'!==5", context ); - assertThat( result ).isEqualTo( true ); - } - - @DisplayName( "comparison strict not equality CF" ) - @Test - public void testComparisonStrictNotEqualityCF() { - - Object result = instance.executeStatement( "5!==5", context, BoxSourceType.CFSCRIPT ); - assertThat( result ).isEqualTo( false ); - - result = instance.executeStatement( "'5'!==5", context, BoxSourceType.CFSCRIPT ); - assertThat( result ).isEqualTo( true ); - - result = instance.executeStatement( "'brad'!=='brad'", context, BoxSourceType.CFSCRIPT ); - assertThat( result ).isEqualTo( false ); - - result = instance.executeStatement( "'brad'!==5", context, BoxSourceType.CFSCRIPT ); - assertThat( result ).isEqualTo( true ); - } - -} diff --git a/src/test/java/TestCases/asm/phase1/unicode.cfm b/src/test/java/TestCases/asm/phase1/unicode.cfm deleted file mode 100644 index 908462cfb..000000000 --- a/src/test/java/TestCases/asm/phase1/unicode.cfm +++ /dev/null @@ -1,2 +0,0 @@ - -kōwhai \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/AbstractClass.bx b/src/test/java/TestCases/asm/phase3/AbstractClass.bx deleted file mode 100644 index af9af4554..000000000 --- a/src/test/java/TestCases/asm/phase3/AbstractClass.bx +++ /dev/null @@ -1,9 +0,0 @@ -abstract class { - - function normal() { - return "normal"; - } - - abstract function abstractMethod() - -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/AbstractClassCF.cfc b/src/test/java/TestCases/asm/phase3/AbstractClassCF.cfc deleted file mode 100644 index dc3d808e4..000000000 --- a/src/test/java/TestCases/asm/phase3/AbstractClassCF.cfc +++ /dev/null @@ -1,9 +0,0 @@ -abstract component { - - function normal() { - return "normal"; - } - - abstract function abstractMethod() - -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/Animal.cfc b/src/test/java/TestCases/asm/phase3/Animal.cfc deleted file mode 100644 index 7fc908207..000000000 --- a/src/test/java/TestCases/asm/phase3/Animal.cfc +++ /dev/null @@ -1,36 +0,0 @@ -component { - variables.results = []; - results.append('animal pseudo ' & getFileFromPath( getCurrentTemplatePath())) - variables.inAnimal = true; - variables.inDog = false; - - function init() { - results.append('Animal init ' & getFileFromPath( getCurrentTemplatePath())) - } - - function speak() { - throw( "speak method not implemented" ); - } - - function isWarmBlooded() { - // this needs to be a reference to the bottom most class - results.append( "animal this is: " & this.$bx.meta.name ) - // We need to see the variables scope of the bottom most class - results.append( "animal sees inDog as: " & inDog ) - return true; - } - - function getScientificName() { - // this needs to be a reference to the bottom most class - results.append( "super animal sees: " & this.$bx.meta.name ) - // We need to see the variables scope of the bottom most class - results.append( "super sees inDog as: " & inDog ) - - return "Animal Kingdom"; - } - - function getResults() { - return results; - } - - } diff --git a/src/test/java/TestCases/asm/phase3/ApplicationTest.java b/src/test/java/TestCases/asm/phase3/ApplicationTest.java deleted file mode 100644 index 148cdbcc4..000000000 --- a/src/test/java/TestCases/asm/phase3/ApplicationTest.java +++ /dev/null @@ -1,242 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm.phase3; - -import static com.google.common.truth.Truth.assertThat; - -import java.nio.file.Path; -import java.time.Instant; -import java.time.temporal.ChronoUnit; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.application.Application; -import ortus.boxlang.runtime.application.BaseApplicationListener; -import ortus.boxlang.runtime.context.ApplicationBoxContext; -import ortus.boxlang.runtime.context.BaseBoxContext; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.RequestBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.dynamic.casters.DateTimeCaster; -import ortus.boxlang.runtime.scopes.ApplicationScope; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.SessionScope; -import ortus.boxlang.runtime.scopes.VariablesScope; -import ortus.boxlang.runtime.types.IStruct; - -public class ApplicationTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key result = new Key( "result" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @DisplayName( "application basics" ) - @Test - public void testBasicApplication() { - // @formatter:off - instance.executeSource( - """ - application name="myAppsdfsdfasm" sessionmanagement="true"; - - result = application; - result2 = session; - startTime = ApplicationStartTime() - """, context ); - // @formatter:on - - assertThat( variables.get( result ) ).isInstanceOf( ApplicationScope.class ); - assertThat( variables.get( Key.of( "result2" ) ) ).isInstanceOf( SessionScope.class ); - - ApplicationBoxContext appContext = context.getParentOfType( ApplicationBoxContext.class ); - Application app = appContext.getApplication(); - Instant actual = DateTimeCaster.cast( variables.get( Key.of( "startTime" ) ) ).getWrapped().toInstant(); - Instant now = Instant.now(); - long differenceInSeconds = ChronoUnit.SECONDS.between( actual, now ); - - assertThat( app.getName().getName() ).isEqualTo( "myAppsdfsdfasm" ); - assertThat( app.getSessionsCache() ).isNotNull(); - assertThat( app.getApplicationScope() ).isNotNull(); - assertThat( app.getApplicationScope().getName().getName() ).isEqualTo( "application" ); - assertThat( app.getClassLoaders() ).isNotNull(); - assertThat( app.hasStarted() ).isTrue(); - assertThat( differenceInSeconds ).isAtMost( 1L ); - } - - @Test - public void testGetAppMeta() { - // @formatter:off - instance.executeSource( - """ - application name="myAppsdfsdf2asm" sessionmanagement="true"; - result = GetApplicationMetadata(); - """, context ); - // @formatter:on - - assertThat( variables.get( result ) ).isInstanceOf( IStruct.class ); - assertThat( variables.getAsStruct( result ).get( "name" ) ).isEqualTo( "myAppsdfsdf2asm" ); - assertThat( variables.getAsStruct( result ).get( "sessionmanagement" ).toString() ).isEqualTo( "true" ); - } - - @Test - public void testGetDefaultAppMeta() { - // @formatter:off - instance.executeSource( - """ - result = GetApplicationMetadata(); - """, context ); - // @formatter:on - - assertThat( variables.get( result ) ).isInstanceOf( IStruct.class ); - assertThat( variables.getAsStruct( result ).get( "name" ) ).isEqualTo( "" ); - assertThat( variables.getAsStruct( result ).get( "sessionmanagement" ).toString() ).isEqualTo( "false" ); - } - - @DisplayName( "java settings setup" ) - @Test - public void testJavaSettings() { - // @formatter:off - instance.executeSource( - """ - application name="myJavaApp" javaSettings={ - loadPaths = [ "/src/test/resources/libs" ], - reloadOnChange = true - }; - - import com.github.benmanes.caffeine.cache.Caffeine - targetInstance = Caffeine.newBuilder() - - import org.apache.commons.lang3.ClassUtils - targetInstance2 = ClassUtils.getClass() - """, context ); - // @formatter:on - - ApplicationBoxContext appContext = context.getParentOfType( ApplicationBoxContext.class ); - Application app = appContext.getApplication(); - assertThat( app.getClassLoaderCount() ).isEqualTo( 1 ); - } - - @DisplayName( "Ad-hoc config override" ) - @Test - public void testAdHocConfigOverride() { - - context.injectParentContext( new BaseBoxContext() { - - public IStruct getConfig() { - IStruct config = super.getConfig(); - config.put( "adHocConfig", "adHocConfigValue" ); - return config; - } - } ); - - assertThat( context.getConfigItem( Key.of( "adHocConfig" ) ) ).isEqualTo( "adHocConfigValue" ); - } - - @DisplayName( "Can resolve java settings paths with a full jar/class path" ) - @Test - public void testJavaSettingsPaths() { - // @formatter:off - instance.executeSource( - """ - application name="myJavaApp" javaSettings={ - loadPaths = [ "/src/test/resources/libs/helloworld.jar" ], - reloadOnChange = true - }; - """, context ); - // @formatter:on - - ApplicationBoxContext appContext = context.getParentOfType( ApplicationBoxContext.class ); - Application app = appContext.getApplication(); - assertThat( app.getClassLoaderCount() ).isEqualTo( 1 ); - } - - @DisplayName( "Can resolve java settings paths with a full jar/class path with bad pathing" ) - @Test - public void testJavaSettingsBadPaths() { - // @formatter:off - instance.executeSource( - """ - application name="myJavaApp" javaSettings={ - loadPaths = [ "\\src\\test\\resources\\libs\\helloworld.jar" ], - reloadOnChange = true - }; - """, context ); - // @formatter:on - - ApplicationBoxContext appContext = context.getParentOfType( ApplicationBoxContext.class ); - Application app = appContext.getApplication(); - - assertThat( app.getClassLoaderCount() ).isEqualTo( 1 ); - } - - @DisplayName( "Can resolve relative paths" ) - @Test - public void testJavaSettingsRelativePaths() { - - RequestBoxContext requestContext = context.getParentOfType( RequestBoxContext.class ); - BaseApplicationListener listener = requestContext.getApplicationListener(); - - // Mock the relative path - listener.getSettings() - .put( "source", Path.of( "src/test/resources/Application.bx" ).toAbsolutePath().toString() ); - - // @formatter:off - instance.executeSource( - """ - application name="myJavaApp" javaSettings={ - loadPaths = [ "libs/helloworld.jar" ], - reloadOnChange = true - }; - """, context ); - // @formatter:on - - ApplicationBoxContext appContext = context.getParentOfType( ApplicationBoxContext.class ); - Application app = appContext.getApplication(); - assertThat( app.getClassLoaderCount() ).isEqualTo( 1 ); - } - -} diff --git a/src/test/java/TestCases/asm/phase3/CFImportTest.cfc b/src/test/java/TestCases/asm/phase3/CFImportTest.cfc deleted file mode 100644 index 1bcae0686..000000000 --- a/src/test/java/TestCases/asm/phase3/CFImportTest.cfc +++ /dev/null @@ -1,13 +0,0 @@ -import src.test.java.TestCases.phase3.Stack; - -component { - function init() { - stack = new Stack(); - } - - function doSomething() { - if (!stack.empty() ) { - // do stuff - } - } -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/CFImportTest2.cfc b/src/test/java/TestCases/asm/phase3/CFImportTest2.cfc deleted file mode 100644 index 6aff56356..000000000 --- a/src/test/java/TestCases/asm/phase3/CFImportTest2.cfc +++ /dev/null @@ -1,13 +0,0 @@ -import "src.test.java.TestCases.phase3.Stack"; - -component { - function init() { - stack = new Stack(); - } - - function doSomething() { - if (!stack.empty() ) { - // do stuff - } - } -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/Chihuahua.cfc b/src/test/java/TestCases/asm/phase3/Chihuahua.cfc deleted file mode 100644 index f1619b84d..000000000 --- a/src/test/java/TestCases/asm/phase3/Chihuahua.cfc +++ /dev/null @@ -1,17 +0,0 @@ -component extends="Dog" { - results.append('Chihuahua pseudo ' & getFileFromPath( getCurrentTemplatePath())) - - function init() { - super.init(); - results.append('Chihuahua init ' & getFileFromPath( getCurrentTemplatePath())) - } - - function speak() { - return "Yip Yip!"; - } - - function getScientificName() { - return "barkus annoyus " & super.getScientificName(); - } - - } \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/Child.cfc b/src/test/java/TestCases/asm/phase3/Child.cfc deleted file mode 100644 index ffc8ed46e..000000000 --- a/src/test/java/TestCases/asm/phase3/Child.cfc +++ /dev/null @@ -1,12 +0,0 @@ -component extends="Parent" { - - function init() { - return super.init(); - } - - private void function setupFrameworkDefaults() { - request.calls.append( "running child setupFrameworkDefaults()" ); - super.setupFrameworkDefaults(); - } - -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/ClassLeadingComment.cfc b/src/test/java/TestCases/asm/phase3/ClassLeadingComment.cfc deleted file mode 100644 index e624afcaf..000000000 --- a/src/test/java/TestCases/asm/phase3/ClassLeadingComment.cfc +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/ClassTest.java b/src/test/java/TestCases/asm/phase3/ClassTest.java deleted file mode 100644 index ba3f5d315..000000000 --- a/src/test/java/TestCases/asm/phase3/ClassTest.java +++ /dev/null @@ -1,1268 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm.phase3; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.compiler.parser.BoxSourceType; -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.interop.DynamicObject; -import ortus.boxlang.runtime.runnables.IClassRunnable; -import ortus.boxlang.runtime.runnables.RunnableLoader; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.RequestScope; -import ortus.boxlang.runtime.scopes.VariablesScope; -import ortus.boxlang.runtime.types.Array; -import ortus.boxlang.runtime.types.IStruct; -import ortus.boxlang.runtime.types.Struct; -import ortus.boxlang.runtime.types.exceptions.AbstractClassException; -import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; -import ortus.boxlang.runtime.types.meta.ClassMeta; - -public class ClassTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key result = new Key( "result" ); - static Key foo = new Key( "foo" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @DisplayName( "Test can create vanilla module config" ) - @Test - void testVanillaModuleConfig() { - - IClassRunnable cfc = ( IClassRunnable ) DynamicObject.of( RunnableLoader.getInstance().loadClass( - """ - /** - * This is the module descriptor and entry point for your module in the Runtime. - * The unique name of the moduel is the name of the directory on the modules folder. - * A BoxLang Mapping will be created for you with the name of the module. - * - * A Module can have the following folders that will be automatically registered: - * + bifs - Custom BIFs that will be registered into the runtime - * + interceptors - Custom Interceptors that will be registered into the runtime via the configure() method - * + libs - Custom Java libraries that your module leverages - * + tags - Custom tags that will be registered into the runtime - * - * Every Module will have it's own ClassLoader that will be used to load the module libs and dependencies. - */ - component{ - - /** - * -------------------------------------------------------------------------- - * Module Properties - * -------------------------------------------------------------------------- - * Here is where you define the properties of your module that the module service - * will use to register and activate your module - */ - - /** - * Your module version. Try to use semantic versioning - * @mandatory - */ - this.version = "1.0.0"; - - /** - * The BoxLang mapping for your module. All BoxLang modules are registered with an internal - * mapping prefix of : bxModules.{this.mapping}, /bxmodules/{this.mapping}. Ex: bxModules.test, /bxmodules/test - */ - this.mapping = "test"; - - /** - * Who built the module - */ - this.author = "Luis Majano"; - - /** - * The module description - */ - this.description = "This module does amazing things"; - - /** - * The module web URL - */ - this.webURL = "https://www.ortussolutions.com"; - - /** - * This boolean flag tells the module service to skip the module registration/activation process. - */ - this.disabled = false; - - /** - * -------------------------------------------------------------------------- - * Module Methods - * -------------------------------------------------------------------------- - */ - - /** - * Called by the ModuleService on module registration - * - * @moduleRecord - The module record registered in the ModuleService - * @runtime - The Runtime instance - */ - function configure( moduleRecord, runtime ){ - /** - * Every module has a settings configuration object - */ - settings = { - loadedOn : now(), - loadedBy : "Luis Majano" - }; - - /** - * The module interceptors to register into the runtime - */ - interceptors = [ - // { class="path.to.Interceptor", properties={} } - { class="bxModules.test.interceptors.Listener", properties={} } - ]; - - /** - * A list of custom interception points to register into the runtime - */ - customInterceptionPoints = [ "onBxTestModule" ]; - } - - /** - * Called by the ModuleService on module activation - * - * @moduleRecord - The module record registered in the ModuleService - * @runtime - The Runtime instance - */ - function onLoad( moduleRecord, runtime ){ - - } - - /** - * Called by the ModuleService on module deactivation - * - * @moduleRecord - The module record registered in the ModuleService - * @runtime - The Runtime instance - */ - function onUnload( moduleRecord, runtime ){ - - } - - /** - * -------------------------------------------------------------------------- - * Module Events - * -------------------------------------------------------------------------- - * You can listen to any Runtime events by creating the methods - * that match the approved Runtime Interception Points - */ - } - - """, context, BoxSourceType.CFSCRIPT ) ) - .invokeConstructor( context ) - .getTargetInstance(); - } - - @DisplayName( "Test a basic boxlang class" ) - @Test - public void testBasicBLClass() { - - // @formatter:off - IClassRunnable bxClass = ( IClassRunnable ) DynamicObject.of( RunnableLoader.getInstance().loadClass( - """ - import foo; - import java.lang.System; - - /** - * This is my class description - * - * @brad wood - * @luis - */ - @foo "bar" - class accessors=true singleton gavin="pickin" inject { - - property numeric age default=1; - property numeric test; - property testAlone; - - variables.setup=true; - System.out.println( "word" ); - request.foo="bar"; - println( request.asString()) - isInitted = false; - println( "current template is " & getCurrentTemplatePath() ); - printLn( foo() ) - - function init() { - isInitted = true; - } - - function foo() { - return "I work! #bar()# #variables.setup# #setup# #request.foo# #isInitted#"; - } - - private function bar() { - return "whee"; - } - - function getThis() { - return this; - } - - function runThisFoo() { - return this.foo(); - } - } - """, context, BoxSourceType.BOXSCRIPT ) ) - .invokeConstructor( context ) - .getTargetInstance(); - // @formatter:on - - // Test shorthand properties work - assertThat( - bxClass.dereferenceAndInvoke( context, Key.of( "getAge" ), new Object[] {}, false ) - ).isEqualTo( 1 ); - assertThat( - bxClass.dereferenceAndInvoke( context, Key.of( "getTest" ), new Object[] {}, false ) - ).isEqualTo( null ); - assertThat( - bxClass.dereferenceAndInvoke( context, Key.of( "getTestAlone" ), new Object[] {}, false ) - ).isEqualTo( null ); - var mdProperties = bxClass.getMetaData().get( Key.of( "properties" ) ); - - // execute public method - Object funcResult = bxClass.dereferenceAndInvoke( context, Key.of( "foo" ), new Object[] {}, false ); - - // private methods error - Throwable t = assertThrows( BoxRuntimeException.class, - () -> bxClass.dereferenceAndInvoke( context, Key.of( "bar" ), new Object[] {}, false ) ); - assertThat( t.getMessage().contains( "bar" ) ).isTrue(); - - // Can call public method that accesses private method, and variables, and request scope - assertThat( funcResult ).isEqualTo( "I work! whee true true bar true" ); - assertThat( context.getScope( RequestScope.name ).get( Key.of( "foo" ) ) ).isEqualTo( "bar" ); - - // This scope is reference to actual CFC instance - funcResult = bxClass.dereferenceAndInvoke( context, Key.of( "getThis" ), new Object[] {}, false ); - assertThat( funcResult ).isEqualTo( bxClass ); - - // Can call public methods on this - funcResult = bxClass.dereferenceAndInvoke( context, Key.of( "runThisFoo" ), new Object[] {}, false ); - assertThat( funcResult ).isEqualTo( "I work! whee true true bar true" ); - } - - @DisplayName( "basic class" ) - @Test - public void testBasicCFClass() { - // @formatter:off - IClassRunnable cfc = ( IClassRunnable ) DynamicObject.of( RunnableLoader.getInstance().loadClass( - """ - /** - * This is my class description - * - * @brad wood - * @luis - */ - component singleton gavin="pickin" inject foo="bar" { - - variables.setup=true; - createObject('java','java.lang.System').out.println( "word" ); - request.foo="bar"; - println( request.asString()) - isInitted = false; - println( "current template is " & getCurrentTemplatePath() ); - printLn( foo() ) - - function init() { - isInitted = true; - } - - function foo() { - return "I work! #bar()# #variables.setup# #setup# #request.foo# #isInitted#"; - } - - private function bar() { - return "whee" ; - } - - function getThis() { - return this; - } - - function runThisFoo() { - return this.foo() ; - } - } - - - """, context, BoxSourceType.CFSCRIPT ) ) - .invokeConstructor( context ) - .getTargetInstance(); - // @formatter:on - - // execute public method - Object funcResult = cfc.dereferenceAndInvoke( context, Key.of( "foo" ), new Object[] {}, false ); - - // private methods error - Throwable t = assertThrows( BoxRuntimeException.class, - () -> cfc.dereferenceAndInvoke( context, Key.of( "bar" ), new Object[] {}, false ) ); - assertThat( t.getMessage().contains( "bar" ) ).isTrue(); - - // Can call public method that accesses private method, and variables, and request scope - assertThat( funcResult ).isEqualTo( "I work! whee true true bar true" ); - assertThat( context.getScope( RequestScope.name ).get( Key.of( "foo" ) ) ).isEqualTo( "bar" ); - - // This scope is reference to actual CFC instance - funcResult = cfc.dereferenceAndInvoke( context, Key.of( "getThis" ), new Object[] {}, false ); - assertThat( funcResult ).isEqualTo( cfc ); - - // Can call public methods on this - funcResult = cfc.dereferenceAndInvoke( context, Key.of( "runThisFoo" ), new Object[] {}, false ); - assertThat( funcResult ).isEqualTo( "I work! whee true true bar true" ); - } - - @DisplayName( "basic class file" ) - @Test - public void testBasicClassFile() { - // @formatter:off - instance.executeSource( - """ - cfc = new src.test.java.TestCases.phase3.MyClass(); - // execute public method - result = cfc.foo(); - - // private methods error - try { - cfc.bar() - assert false; - } catch( BoxRuntimeException e ) { - assert e.message contains "bar"; - } - - // Can call public method that accesses private method, and variables, and request scope - assert result == "I work! whee true true bar true"; - assert request.foo == "bar"; - - // This scope is reference to actual CFC instance - assert cfc.$bx.$class.getName() == cfc.getThis().$bx.$class.getName(); - - // Can call public methods on this - assert cfc.runThisFoo() == "I work! whee true true bar true"; - """, context ); - // @formatter:on - } - - @DisplayName( "legacy meta" ) - @Test - public void testlegacyMeta() { - - instance.executeSource( - """ - cfc = new src.test.java.TestCases.phase3.MyClass(); - """, context ); - - var cfc = variables.getAsClassRunnable( Key.of( "cfc" ) ); - var meta = cfc.getMetaData(); - assertThat( meta.get( Key.of( "name" ) ) ).isEqualTo( "src.test.java.TestCases.phase3.MyClass" ); - assertThat( meta.get( Key.of( "type" ) ) ).isEqualTo( "Component" ); - assertThat( meta.get( Key.of( "fullname" ) ) ).isEqualTo( "src.test.java.TestCases.phase3.MyClass" ); - assertThat( meta.getAsString( Key.of( "path" ) ).contains( "MyClass.bx" ) ).isTrue(); - // assertThat( meta.get( Key.of( "hashcode" ) ) ).isEqualTo( cfc.hashCode() ); - assertThat( meta.get( Key.of( "properties" ) ) ).isInstanceOf( Array.class ); - assertThat( meta.getAsArray( Key.of( "properties" ) ) ).hasSize( 1 ); - Struct prop = ( Struct ) meta.getAsArray( Key.of( "properties" ) ).get( 0 ); - assertThat( prop ).doesNotContainKey( Key.of( "defaultValue" ) ); - - assertThat( meta.get( Key.of( "functions" ) ) instanceof Array ).isTrue(); - assertThat( meta.getAsArray( Key.of( "functions" ) ).size() ).isEqualTo( 5 ); - assertThat( meta.get( Key.of( "extends" ) ) ).isNull(); - assertThat( meta.get( Key.of( "output" ) ) ).isEqualTo( false ); - assertThat( meta.get( Key.of( "persisent" ) ) ).isEqualTo( false ); - assertThat( meta.get( Key.of( "accessors" ) ) ).isEqualTo( true ); - } - - @DisplayName( "legacy meta CF" ) - @Test - public void testlegacyMetaCF() { - - instance.executeSource( - """ - cfc = new src.test.java.TestCases.phase3.MyClassCF(); - """, context ); - - var cfc = variables.getAsClassRunnable( Key.of( "cfc" ) ); - var meta = cfc.getMetaData(); - assertThat( meta.get( Key.of( "name" ) ) ).isEqualTo( "src.test.java.TestCases.phase3.MyClassCF" ); - assertThat( meta.get( Key.of( "type" ) ) ).isEqualTo( "Component" ); - assertThat( meta.get( Key.of( "fullname" ) ) ).isEqualTo( "src.test.java.TestCases.phase3.MyClassCF" ); - assertThat( meta.getAsString( Key.of( "path" ) ).contains( "MyClassCF.cfc" ) ).isTrue(); - // assertThat( meta.get( Key.of( "hashcode" ) ) ).isEqualTo( cfc.hashCode() ); - assertThat( meta.get( Key.of( "properties" ) ) ).isInstanceOf( Array.class ); - assertThat( meta.get( Key.of( "functions" ) ) instanceof Array ).isTrue(); - assertThat( meta.getAsArray( Key.of( "functions" ) ).size() ).isEqualTo( 5 ); - assertThat( meta.get( Key.of( "extends" ) ) ).isNull(); - assertThat( meta.get( Key.of( "output" ) ) ).isEqualTo( true ); - assertThat( meta.get( Key.of( "persisent" ) ) ).isEqualTo( false ); - assertThat( meta.get( Key.of( "accessors" ) ) ).isEqualTo( false ); - } - - @DisplayName( "It should call onMissingMethod with pos args" ) - @Test - public void testOnMissingMethodPos() { - - instance.executeSource( - """ - cfc = new src.test.java.TestCases.phase3.OnMissingMethod(); - result = cfc.someFunc( "first", "second" ); - """, context ); - - String res = variables.getAsString( result ); - assertThat( res ).isEqualTo( "someFuncsecond" ); - } - - @DisplayName( "It should call onMissingMethod with named args" ) - @Test - public void testOnMissingMethodNamed() { - - instance.executeSource( - """ - cfc = new src.test.java.TestCases.phase3.OnMissingMethod(); - result = cfc.someFunc( foo="first", bar="second" ); - """, context ); - - String res = variables.getAsString( result ); - assertThat( res ).isEqualTo( "someFuncsecond" ); - } - - @DisplayName( "box meta" ) - @Test - public void testBoxMeta() { - - instance.executeSource( - """ - cfc = new src.test.java.TestCases.phase3.MyClass(); - """, context ); - - var cfc = variables.getAsClassRunnable( Key.of( "cfc" ) ); - var boxMeta = ( ClassMeta ) cfc.getBoxMeta(); - var meta = boxMeta.meta; - assertThat( meta.get( Key.of( "type" ) ) ).isEqualTo( "Component" ); - assertThat( meta.get( Key.of( "fullname" ) ) ).isEqualTo( "src.test.java.TestCases.phase3.MyClass" ); - assertThat( meta.getAsString( Key.of( "path" ) ).contains( "MyClass.bx" ) ).isTrue(); - assertThat( meta.get( Key.of( "hashcode" ) ) ).isEqualTo( cfc.hashCode() ); - assertThat( meta.get( Key.of( "properties" ) ) instanceof Array ).isTrue(); - assertThat( meta.get( Key.of( "functions" ) ) instanceof Array ).isTrue(); - - assertThat( meta.get( Key.of( "extends" ) ) instanceof IStruct ).isTrue(); - - assertThat( meta.getAsArray( Key.of( "functions" ) ).size() ).isEqualTo( 5 ); - var fun1 = meta.getAsArray( Key.of( "functions" ) ).get( 0 ); - assertThat( fun1 ).isInstanceOf( Struct.class ); - assertThat( ( ( IStruct ) fun1 ).containsKey( Key.of( "name" ) ) ).isTrue(); - System.out.println( meta.getAsArray( Key.of( "functions" ) ).asString() ); - - assertThat( meta.get( Key.of( "documentation" ) ) instanceof IStruct ).isTrue(); - var docs = meta.getAsStruct( Key.of( "documentation" ) ); - assertThat( docs.getAsString( Key.of( "brad" ) ).trim() ).isEqualTo( "wood" ); - assertThat( docs.get( Key.of( "luis" ) ) ).isEqualTo( "" ); - assertThat( docs.getAsString( Key.of( "hint" ) ).trim() ).isEqualTo( "This is my class description continued on this line \nand this one as well." ); - - assertThat( meta.get( Key.of( "annotations" ) ) instanceof IStruct ).isTrue(); - var annos = meta.getAsStruct( Key.of( "annotations" ) ); - assertThat( annos.getAsString( Key.of( "foo" ) ).trim() ).isEqualTo( "bar" ); - // assertThat( annos.getAsString( Key.of( "implements" ) ).trim() ).isEqualTo( "Luis,Jorge" ); - assertThat( annos.getAsString( Key.of( "singleton" ) ).trim() ).isEqualTo( "" ); - assertThat( annos.getAsString( Key.of( "gavin" ) ).trim() ).isEqualTo( "pickin" ); - assertThat( annos.getAsString( Key.of( "inject" ) ).trim() ).isEqualTo( "" ); - - } - - @DisplayName( "properties" ) - @Test - public void testProperties() { - // @formatter:off - instance.executeSource( - """ - cfc = new src.test.java.TestCases.phase3.PropertyTest(); - nameGet = cfc.getMyProperty(); - invalidSetErrored=false; - try { - // property is typed as string, an array should blow up - setResult = cfc.setMyProperty( [] ); - } catch( any e ) { - invalidSetErrored=true; - } - setResult = cfc.setMyProperty( "anotherValue" ); - nameGet2 = cfc.getMyProperty(); - test1 = cfc.getShortcutWithDefault() - test2 = cfc.getTypedShortcutWithDefault() - """, context ); - // @formatter:on - - var cfc = variables.getAsClassRunnable( Key.of( "cfc" ) ); - assertThat( variables.get( Key.of( "nameGet" ) ) ).isEqualTo( "myDefaultValue" ); - assertThat( variables.get( Key.of( "nameGet2" ) ) ).isEqualTo( "anotherValue" ); - assertThat( variables.get( Key.of( "setResult" ) ) ).isEqualTo( cfc ); - assertThat( variables.get( Key.of( "invalidSetErrored" ) ) ).isEqualTo( true ); - assertThat( variables.get( Key.of( "test1" ) ) ).isEqualTo( "myDefaultValue" ); - assertThat( variables.get( Key.of( "test2" ) ) ).isEqualTo( "myDefaultValue2" ); - - var boxMeta = ( ClassMeta ) cfc.getBoxMeta(); - var meta = boxMeta.meta; - - assertThat( meta.getAsArray( Key.of( "properties" ) ).size() ).isEqualTo( 7 ); - - var prop1 = ( IStruct ) meta.getAsArray( Key.of( "properties" ) ).get( 0 ); - assertThat( prop1.get( "name" ) ).isEqualTo( "myProperty" ); - assertThat( prop1.get( "defaultValue" ) ).isEqualTo( "myDefaultValue" ); - assertThat( prop1.get( "type" ) ).isEqualTo( "string" ); - - var prop1Annotations = prop1.getAsStruct( Key.of( "annotations" ) ); - assertThat( prop1Annotations.size() ).isEqualTo( 5 ); - - assertThat( prop1Annotations.containsKey( Key.of( "preAnno" ) ) ).isTrue(); - assertThat( prop1Annotations.get( Key.of( "preAnno" ) ) ).isEqualTo( "" ); - - assertThat( prop1Annotations.containsKey( Key.of( "inject" ) ) ).isTrue(); - assertThat( prop1Annotations.get( Key.of( "inject" ) ) ).isEqualTo( "" ); - - var prop2 = ( IStruct ) meta.getAsArray( Key.of( "properties" ) ).get( 1 ); - assertThat( prop2.get( "name" ) ).isEqualTo( "anotherprop" ); - assertThat( prop2.get( "defaultValue" ) ).isEqualTo( null ); - assertThat( prop2.get( "type" ) ).isEqualTo( "string" ); - - var prop2Annotations = prop2.getAsStruct( Key.of( "annotations" ) ); - assertThat( prop2Annotations.size() ).isEqualTo( 4 ); - - assertThat( prop2Annotations.containsKey( Key.of( "preAnno" ) ) ).isTrue(); - assertThat( prop2Annotations.get( Key.of( "preAnno" ) ) instanceof Array ).isTrue(); - Array preAnno = prop2Annotations.getAsArray( Key.of( "preAnno" ) ); - assertThat( preAnno.size() ).isEqualTo( 2 ); - assertThat( preAnno.get( 0 ) ).isEqualTo( "myValue" ); - assertThat( preAnno.get( 1 ) ).isEqualTo( "anothervalue" ); - - var prop3 = ( IStruct ) meta.getAsArray( Key.of( "properties" ) ).get( 2 ); - assertThat( prop3.get( "name" ) ).isEqualTo( "theName" ); - assertThat( prop3.get( "defaultValue" ) ).isEqualTo( null ); - assertThat( prop3.get( "type" ) ).isEqualTo( "any" ); - - var prop3Annotations = prop3.getAsStruct( Key.of( "annotations" ) ); - assertThat( prop3Annotations.size() ).isEqualTo( 4 ); - assertThat( prop3Annotations.containsKey( Key.of( "ID" ) ) ).isTrue(); - assertThat( prop3Annotations.get( Key.of( "ID" ) ) ).isEqualTo( "" ); - - var prop4 = ( IStruct ) meta.getAsArray( Key.of( "properties" ) ).get( 3 ); - assertThat( prop4.get( "name" ) ).isEqualTo( "name" ); - assertThat( prop4.get( "defaultValue" ) ).isEqualTo( null ); - assertThat( prop4.get( "type" ) ).isEqualTo( "string" ); - - var prop2Docs = prop2.getAsStruct( Key.of( "documentation" ) ); - assertThat( prop2Docs.size() ).isEqualTo( 3 ); - assertThat( prop2Docs.getAsString( Key.of( "brad" ) ).trim() ).isEqualTo( "wood" ); - assertThat( prop2Docs.getAsString( Key.of( "luis" ) ).trim() ).isEqualTo( "" ); - assertThat( prop2Docs.getAsString( Key.of( "hint" ) ).trim() ).isEqualTo( "This is my property" ); - } - - @DisplayName( "properties" ) - @Test - public void testPropertiesCF() { - - instance.executeSource( - """ - cfc = new src.test.java.TestCases.phase3.PropertyTestCF(); - nameGet = cfc.getMyProperty(); - setResult = cfc.SetMyProperty( "anotherValue" ); - nameGet2 = cfc.getMyProperty(); - test1 = cfc.getShortcutWithDefault() - test2 = cfc.getTypedShortcutWithDefault() - """, context ); - - var cfc = variables.getAsClassRunnable( Key.of( "cfc" ) ); - - assertThat( variables.get( Key.of( "nameGet" ) ) ).isEqualTo( "myDefaultValue" ); - assertThat( variables.get( Key.of( "nameGet2" ) ) ).isEqualTo( "anotherValue" ); - assertThat( variables.get( Key.of( "setResult" ) ) ).isEqualTo( cfc ); - assertThat( variables.get( Key.of( "test1" ) ) ).isEqualTo( "myDefaultValue" ); - assertThat( variables.get( Key.of( "test2" ) ) ).isEqualTo( "myDefaultValue2" ); - - var boxMeta = ( ClassMeta ) cfc.getBoxMeta(); - var meta = boxMeta.meta; - - assertThat( meta.getAsArray( Key.of( "properties" ) ).size() ).isEqualTo( 5 ); - - var prop1 = ( IStruct ) meta.getAsArray( Key.of( "properties" ) ).get( 0 ); - assertThat( prop1.get( "name" ) ).isEqualTo( "myProperty" ); - assertThat( prop1.get( "defaultValue" ) ).isEqualTo( "myDefaultValue" ); - assertThat( prop1.get( "type" ) ).isEqualTo( "string" ); - - var prop1Annotations = prop1.getAsStruct( Key.of( "annotations" ) ); - assertThat( prop1Annotations.size() ).isEqualTo( 5 ); - - assertThat( prop1Annotations.containsKey( Key.of( "preAnno" ) ) ).isTrue(); - assertThat( prop1Annotations.get( Key.of( "preAnno" ) ) ).isEqualTo( "" ); - - assertThat( prop1Annotations.containsKey( Key.of( "inject" ) ) ).isTrue(); - assertThat( prop1Annotations.get( Key.of( "inject" ) ) ).isEqualTo( "" ); - - var prop2 = ( IStruct ) meta.getAsArray( Key.of( "properties" ) ).get( 1 ); - assertThat( prop2.get( "name" ) ).isEqualTo( "anotherprop" ); - assertThat( prop2.get( "defaultValue" ) ).isEqualTo( null ); - assertThat( prop2.get( "type" ) ).isEqualTo( "string" ); - - var prop2Annotations = prop2.getAsStruct( Key.of( "annotations" ) ); - assertThat( prop2Annotations.size() ).isEqualTo( 6 ); - - assertThat( prop2Annotations.containsKey( Key.of( "preAnno" ) ) ).isTrue(); - assertThat( prop2Annotations.get( Key.of( "preAnno" ) ) instanceof Array ).isTrue(); - Array preAnno = prop2Annotations.getAsArray( Key.of( "preAnno" ) ); - assertThat( preAnno.size() ).isEqualTo( 2 ); - assertThat( preAnno.get( 0 ) ).isEqualTo( "myValue" ); - assertThat( preAnno.get( 1 ) ).isEqualTo( "anothervalue" ); - - var prop2Docs = prop2.getAsStruct( Key.of( "documentation" ) ); - assertThat( prop2Docs.size() ).isEqualTo( 3 ); - assertThat( prop2Docs.getAsString( Key.of( "brad" ) ).trim() ).isEqualTo( "wood" ); - assertThat( prop2Docs.getAsString( Key.of( "luis" ) ).trim() ).isEqualTo( "" ); - assertThat( prop2Docs.getAsString( Key.of( "hint" ) ).trim() ).isEqualTo( "This is my property" ); - - } - - @DisplayName( "Implicit Constructor named" ) - @Test - public void testImplicitConstructorNamed() { - - instance.executeSource( - """ - cfc = new src.test.java.TestCases.phase3.ImplicitConstructorTest( name="brad", age=43, favoriteColor="blue" ); - name = cfc.getName(); - age = cfc.getAge(); - favoriteColor = cfc.getFavoriteColor(); - """, context ); - - assertThat( variables.get( Key.of( "name" ) ) ).isEqualTo( "brad" ); - assertThat( variables.get( Key.of( "age" ) ) ).isEqualTo( 43 ); - assertThat( variables.get( Key.of( "favoriteColor" ) ) ).isEqualTo( "blue" ); - - } - - @DisplayName( "Implicit Constructor named argumentCollection" ) - @Test - public void testImplicitConstructorNamedArgumentCollection() { - - instance.executeSource( - """ - cfc = new src.test.java.TestCases.phase3.ImplicitConstructorTest( argumentCollection={ name="brad", age=43, favoriteColor="blue" } ); - name = cfc.getName(); - age = cfc.getAge(); - favoriteColor = cfc.getFavoriteColor(); - """, context ); - - assertThat( variables.get( Key.of( "name" ) ) ).isEqualTo( "brad" ); - assertThat( variables.get( Key.of( "age" ) ) ).isEqualTo( 43 ); - assertThat( variables.get( Key.of( "favoriteColor" ) ) ).isEqualTo( "blue" ); - - } - - @DisplayName( "Implicit Constructor positional" ) - @Test - public void testImplicitConstructorPositional() { - - instance.executeSource( - """ - cfc = new src.test.java.TestCases.phase3.ImplicitConstructorTest( {name="brad", age=43, favoriteColor="blue" }); - name = cfc.getName(); - age = cfc.getAge(); - favoriteColor = cfc.getFavoriteColor(); - """, context ); - - assertThat( variables.get( Key.of( "name" ) ) ).isEqualTo( "brad" ); - assertThat( variables.get( Key.of( "age" ) ) ).isEqualTo( 43 ); - assertThat( variables.get( Key.of( "favoriteColor" ) ) ).isEqualTo( "blue" ); - - } - - @DisplayName( "InitMethod Test" ) - @Test - public void testInitMethod() { - - instance.executeSource( - """ - cfc = new src.test.java.TestCases.phase3.InitMethodTest( ); - - result = cfc.getInittedProperly(); - """, context ); - - assertThat( variables.get( Key.of( "result" ) ) ).isEqualTo( true ); - - } - - @DisplayName( "PseudoConstructor can output" ) - @Test - public void testPseudoConstructorOutput() { - - instance.executeSource( - """ - cfc = new src.test.java.TestCases.phase3.PseudoConstructorOutput(); - result = getBoxContext().getBuffer().toString() - - """, context ); - - assertThat( variables.get( Key.of( "result" ) ) ).isEqualTo( "PseudoConstructorOutput" ); - - } - - @DisplayName( "PseudoConstructor will not output" ) - @Test - public void testPseudoConstructorNoOutput() { - - instance.executeSource( - """ - cfc = new src.test.java.TestCases.phase3.PseudoConstructorNoOutput(); - result = getBoxContext().getBuffer().toString() - - """, context ); - - assertThat( variables.get( Key.of( "result" ) ) ).isEqualTo( "" ); - - } - - @DisplayName( "can extend" ) - @Test - public void testCanExtend() { - - instance.executeSource( - """ - cfc = new src.test.java.TestCases.phase3.Chihuahua(); - result = cfc.speak() - warm = cfc.isWarmBlooded() - name = cfc.getScientificName() - results = cfc.getResults() - """, context ); - - // Polymorphism invokes overridden method - assertThat( variables.get( Key.of( "result" ) ) ).isEqualTo( "Yip Yip!" ); - // inherited method from base class - assertThat( variables.get( Key.of( "warm" ) ) ).isEqualTo( true ); - // Delegate to super.method() in parent class - assertThat( variables.get( Key.of( "name" ) ) ).isEqualTo( "barkus annoyus Canis lupus Animal Kingdom" ); - - // This array represents a specific order of operations that occur during the instantiation of our object hierachy - // as well as specific values that need to be present to ensure correct behaviors - assertThat( variables.getAsArray( Key.of( "results" ) ).toArray() ).isEqualTo( new Object[] { - // top most super class is instantiated first. getCurrentTemplate() shows that file - "animal pseudo Animal.cfc", - // Then the next super class is instantiated. getCurrentTemplate() shows that file - "Dog pseudo Dog.cfc", - // The variables scope in the Doc pseudo constructor is the "same" variables scope as the Animal pseudo constructor that ran before it - "dog sees variables.inAnimal as: true", - // And lastly, the concrete class is instantiated. getCurrentTemplate() shows that file - "Chihuahua pseudo Chihuahua.cfc", - // I'm calling super.init() first, so animal inits first. getCurrentTemplate() shows the current class. INCOMPAT WITH CF which returns concrete - // class! - "Animal init Animal.cfc", - // Then dog inits as we work backwards. getCurrentTemplate() shows the current class. INCOMPAT WITH CF which returns concrete class! - "Dog init Dog.cfc", - // Then the concrete class inits. getCurrentTemplate() shows the concrete class. - "Chihuahua init Chihuahua.cfc", - // A method inherited from a base class, sees "this" as the concrete class. - "animal this is: src.test.java.TestCases.phase3.Chihuahua", - // A method inherited from a base class, sees the top level "variables" scope. - "animal sees inDog as: true", - // A method delegated to as super.foo() sees "this" as the concrete class. - "super animal sees: src.test.java.TestCases.phase3.Chihuahua", - // A method delegated to as super.foo() sees the top level "variables" scope. - "super sees inDog as: true", - } ); - - var cfc = variables.getAsClassRunnable( Key.of( "cfc" ) ); - var boxMeta = ( ClassMeta ) cfc.getBoxMeta(); - var meta = boxMeta.meta; - - assertThat( meta.get( Key.of( "name" ) ) ).isEqualTo( "src.test.java.TestCases.phase3.Chihuahua" ); - - IStruct extendsMeta = meta.getAsStruct( Key.of( "extends" ) ); - assertThat( extendsMeta.getAsString( Key.of( "name" ) ).endsWith( ".Dog" ) ).isTrue(); - - extendsMeta = extendsMeta.getAsStruct( Key.of( "extends" ) ); - assertThat( extendsMeta.getAsString( Key.of( "name" ) ).endsWith( ".Animal" ) ).isTrue(); - - extendsMeta = extendsMeta.getAsStruct( Key.of( "extends" ) ); - assertThat( extendsMeta ).hasSize( 0 ); - - } - - @DisplayName( "class as struct" ) - @Test - public void testClassAsStruct() { - - instance.executeSource( - """ - cfc = new src.test.java.TestCases.phase3.MyClass(); - result = isStruct( cfc ) - cfc.foo = "bar" - result2 = structGet( "cfc.foo") - keyArray = structKeyArray( cfc ) - - """, context ); - - assertThat( variables.get( result ) ).isEqualTo( true ); - assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "bar" ); - assertThat( variables.get( Key.of( "keyArray" ) ) ).isInstanceOf( Array.class ); - - } - - @Test - void testJavaMeta() { - // @formatter:off - instance.executeSource( - """ - jClass = createObject( "java", "java.lang.System" ) - result = jClass.$bx.meta - clazz = jClass.$bx.$class - println( clazz ) - """, context ); - // @formatter:on - assertThat( variables.get( result ) ).isInstanceOf( IStruct.class ); - assertThat( variables.get( Key.of( "clazz" ) ) ).isEqualTo( System.class ); - } - - @Test - public void testFunctionMeta() { - // @formatter:off - instance.executeSource( - """ - cfc = new src.test.java.TestCases.phase3.FunctionMeta(); - result = cfc.$bx.meta - println( result ) - """, context ); - // @formatter:on - assertThat( variables.get( result ) ).isInstanceOf( IStruct.class ); - } - - @Test - public void testSuperHeadlessFunctionInvocationToChild() { - - instance.executeSource( - """ - request.calls = []; - cfc = new src.test.java.TestCases.phase3.Child(); - result = request.calls; - """, context ); - - assertThat( variables.getAsArray( result ) ).hasSize( 2 ); - assertThat( variables.getAsArray( result ).get( 0 ) ).isEqualTo( "running child setupFrameworkDefaults()" ); - assertThat( variables.getAsArray( result ).get( 1 ) ).isEqualTo( "running parent setupFrameworkDefaults()" ); - } - - @Test - public void testClassWrappedInScriptIsland() { - - instance.executeSource( - """ - cfc = new src.test.java.TestCases.phase3.ClassWrappedInScript(); - """, context ); - - } - - @Test - public void testClassIgnoreLeadingComment() { - - instance.executeSource( - """ - cfc = new src.test.java.TestCases.phase3.ClassLeadingComment(); - """, context ); - - } - - @Test - public void testClassIgnoreTrailingComment() { - - instance.executeSource( - """ - cfc = new src.test.java.TestCases.phase3.ClassTrailingComment(); - """, context ); - - } - - @Test - public void testCFImport() { - - instance.executeSource( - """ - foo = new src.test.java.TestCases.phase3.CFImportTest(); - foo.doSomething(); - """, context ); - - } - - @Test - public void testCFImport2() { - // This version quotes the class being imported - instance.executeSource( - """ - foo = new src.test.java.TestCases.phase3.CFImportTest2(); - foo.doSomething(); - """, context ); - - } - - @Test - public void testInlineJavaImplements() { - instance.executeSource( - """ - import java:java.lang.Thread; - jRunnable = new src.test.java.TestCases.phase3.JavaImplements(); - assert jRunnable instanceof "java.lang.Runnable" - jThread = new java:Thread( jRunnable ); - jThread.start(); - """, context ); - - } - - // disabling this as it causes a crazy concurrency issue with another test - @Test - @Disabled - public void testInlineJavaExtendsASM() { - instance.executeSource( - """ - import java.util.Timer; - myASMTask = new src.test.java.TestCases.asm.phase3.JavaExtendsAsm(); - assert myASMTask instanceof "java.util.TimerTask" - - jtimer = new Timer(); - jtimer.schedule(myASMTask, 1000); - myASMTask.cancel() - """, context ); - - } - - @Test - public void testInlineJavaExtendsField() { - instance.executeSource( - """ - myContext = new src.test.java.TestCases.phase3.JavaExtends2(); - assert myContext instanceof "ortus.boxlang.runtime.context.IBoxContext" - - println( myContext.getTemplatesYo() ) - """, context ); - - } - - @Test - public void testInlineJavaExtendsFieldPublic() { - instance.executeSource( - """ - myBIF = new src.test.java.TestCases.phase3.JavaExtends3(); - assert myBIF instanceof "ortus.boxlang.runtime.bifs.BIF" - println( myBIF.__isMemberExecution ) - println( myBIF.runtime ) - myBIF.printStuff() - """, context ); - - } - - @Test - public void testImplicitAccessor() { - instance.executeSource( - """ - clazz = new src.test.java.TestCases.phase3.ImplicitAccessor(); - clazz.name="brad"; - clazz.age=44; - name = clazz.name; - age = clazz.age; - methodsCalled = clazz.getMethodsCalled(); - """, context ); - assertThat( variables.get( Key.of( "name" ) ) ).isEqualTo( "brad" ); - assertThat( variables.get( Key.of( "age" ) ) ).isEqualTo( 44 ); - assertThat( variables.get( Key.of( "methodsCalled" ) ) ).isEqualTo( "setNamesetAgegetNamegetAge" ); - } - - @Test - public void testImplicitGeneratedAccessor() { - instance.executeSource( - """ - clazz = new src.test.java.TestCases.phase3.ImplicitGeneratedAccessor(); - clazz.name="brad"; - clazz.age=44; - name = clazz.name; - age = clazz.age; - // prove they're going in the variable scope, not this scope - keyExistsName = structKeyExists( clazz, "name") - keyExistsAge = structKeyExists( clazz, "age") - """, context ); - assertThat( variables.get( Key.of( "name" ) ) ).isEqualTo( "brad" ); - assertThat( variables.get( Key.of( "age" ) ) ).isEqualTo( 44 ); - assertThat( variables.get( Key.of( "keyExistsName" ) ) ).isEqualTo( false ); - assertThat( variables.get( Key.of( "keyExistsAge" ) ) ).isEqualTo( false ); - } - - @Test - public void testStaticInstance() { - instance.executeSource( - """ - clazz = new src.test.java.TestCases.phase3.StaticTestCF(); - result1 = clazz.foo; - result2 = clazz.myStaticFunc(); - result3 = clazz.myInstanceFunc(); - result4 = clazz.scoped; - result5 = clazz.unscoped; - result6 = clazz.again; - """, context, BoxSourceType.BOXSCRIPT ); - assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( 42 ); - assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "static42" ); - assertThat( variables.get( Key.of( "result3" ) ) ).isEqualTo( "instancestatic42" ); - assertThat( variables.get( Key.of( "result4" ) ) ).isEqualTo( "brad" ); - assertThat( variables.get( Key.of( "result5" ) ) ).isEqualTo( "wood" ); - assertThat( variables.get( Key.of( "result6" ) ) ).isEqualTo( "luis" ); - } - - @Test - @Disabled - public void testStaticStatic() { - instance.executeSource( """ - result1 = src.test.java.TestCases.phase3.StaticTest::foo; - result2 = src.test.java.TestCases.phase3.StaticTest::myStaticFunc(); - result4 = src.test.java.TestCases.phase3.StaticTest::scoped; - result5 = src.test.java.TestCases.phase3.StaticTest::unscoped; - result6 = src.test.java.TestCases.phase3.StaticTest::again; - myStaticUDF = src.test.java.TestCases.phase3.StaticTest::sayHello; - result7 = myStaticUDF(); - result8 = src.test.java.TestCases.phase3.StaticTest::123; - """, context, BoxSourceType.BOXSCRIPT ); - assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( 9000 ); - assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "static9000" ); - assertThat( variables.get( Key.of( "result4" ) ) ).isEqualTo( "brad" ); - assertThat( variables.get( Key.of( "result5" ) ) ).isEqualTo( "wood" ); - assertThat( variables.get( Key.of( "result6" ) ) ).isEqualTo( "luis" ); - assertThat( variables.get( Key.of( "result7" ) ) ).isEqualTo( "Hello" ); - assertThat( variables.get( Key.of( "result8" ) ) ).isEqualTo( 456 ); - } - - @Test - public void testStaticInstanceCF() { - instance.executeSource( - """ - clazz = new src.test.java.TestCases.phase3.StaticTestCF(); - result1 = clazz.foo; - result2 = clazz.myStaticFunc(); - result3 = clazz.myInstanceFunc(); - result4 = clazz.scoped; - result5 = clazz.unscoped; - result6 = clazz.again; - """, context, BoxSourceType.CFSCRIPT ); - assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( 42 ); - assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "static42" ); - assertThat( variables.get( Key.of( "result3" ) ) ).isEqualTo( "instancestatic42" ); - assertThat( variables.get( Key.of( "result4" ) ) ).isEqualTo( "brad" ); - assertThat( variables.get( Key.of( "result5" ) ) ).isEqualTo( "wood" ); - assertThat( variables.get( Key.of( "result6" ) ) ).isEqualTo( "luis" ); - } - - @Test - @Disabled - public void testStaticStaticCF() { - instance.executeSource( - """ - result1 = src.test.java.TestCases.phase3.StaticTest::foo; - result2 = src.test.java.TestCases.phase3.StaticTest::myStaticFunc(); - result4 = src.test.java.TestCases.phase3.StaticTest::scoped; - result5 = src.test.java.TestCases.phase3.StaticTest::unscoped; - result6 = src.test.java.TestCases.phase3.StaticTest::again; - result7 = src.test.java.TestCases.phase3.StaticTest::123; - """, context, BoxSourceType.CFSCRIPT ); - assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( 9000 ); - assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "static9000" ); - assertThat( variables.get( Key.of( "result4" ) ) ).isEqualTo( "brad" ); - assertThat( variables.get( Key.of( "result5" ) ) ).isEqualTo( "wood" ); - assertThat( variables.get( Key.of( "result6" ) ) ).isEqualTo( "luis" ); - assertThat( variables.get( Key.of( "result7" ) ) ).isEqualTo( 456 ); - } - - @Test - @Disabled - public void testStaticImport() { - instance.executeSource( - """ - import src.test.java.TestCases.phase3.StaticTest; - - result1 = StaticTest::foo; - result2 = StaticTest::myStaticFunc(); - result4 = StaticTest::scoped; - result5 = StaticTest::unscoped; - result6 = StaticTest::again; - """, context, BoxSourceType.BOXSCRIPT ); - assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( 9000 ); - assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "static9000" ); - assertThat( variables.get( Key.of( "result4" ) ) ).isEqualTo( "brad" ); - assertThat( variables.get( Key.of( "result5" ) ) ).isEqualTo( "wood" ); - assertThat( variables.get( Key.of( "result6" ) ) ).isEqualTo( "luis" ); - } - - @Test - @Disabled - public void testStaticImportDot() { - instance.executeSource( - """ - import src.test.java.TestCases.phase3.StaticTest; - - result1 = StaticTest.foo; - result2 = StaticTest.myStaticFunc(); - result4 = StaticTest.scoped; - result5 = StaticTest.unscoped; - result6 = StaticTest.again; - // instance - myInstance = new StaticTest(); - result7 = myInstance.foo; - result8 = StaticTest.foo; - result9 = myInstance.myInstanceFunc2() - """, context, BoxSourceType.BOXSCRIPT ); - assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( 9000 ); - assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "static9000" ); - assertThat( variables.get( Key.of( "result4" ) ) ).isEqualTo( "brad" ); - assertThat( variables.get( Key.of( "result5" ) ) ).isEqualTo( "wood" ); - assertThat( variables.get( Key.of( "result6" ) ) ).isEqualTo( "luis" ); - assertThat( variables.get( Key.of( "result7" ) ) ).isEqualTo( 42 ); - assertThat( variables.get( Key.of( "result8" ) ) ).isEqualTo( 42 ); - assertThat( variables.get( Key.of( "result9" ) ) ).isInstanceOf( Array.class ); - Array result9 = variables.getAsArray( Key.of( "result9" ) ); - assertThat( result9.size() ).isEqualTo( 3 ); - assertThat( result9.get( 0 ) ).isEqualTo( "brad" ); - assertThat( result9.get( 1 ) ).isEqualTo( "wood" ); - assertThat( result9.get( 2 ) ).isEqualTo( 42 ); - } - - @Test - public void testDotExtends() { - instance.executeSource( - """ - clazz = new src.test.java.TestCases.phase3.DotExtends(); - result = clazz.childUDF() - """, context ); - assertThat( variables.get( result ) ).isEqualTo( "childUDFparent" ); - } - - @Test - public void testRelativeInstantiation() { - instance.executeSource( - """ - clazz = new src.test.java.TestCases.phase3.RelativeInstantiation(); - result = clazz.findSibling() - """, context ); - assertThat( variables.get( result ) ).isEqualTo( "bar" ); - } - - @Test - public void testAbstractClass() { - Throwable t = assertThrows( AbstractClassException.class, () -> instance.executeSource( - """ - clazz = new src.test.java.TestCases.phase3.AbstractClass(); - """, context ) ); - assertThat( t.getMessage() ).contains( "Cannot instantiate an abstract class" ); - - instance.executeSource( - """ - clazz = new src.test.java.TestCases.phase3.ConcreteClass(); - result1 = clazz.normal() - result2 = clazz.abstractMethod() - """, context ); - assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( "normal" ); - assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "abstractMethod" ); - } - - @Test - public void testAbstractClassCF() { - Throwable t = assertThrows( AbstractClassException.class, () -> instance.executeSource( - """ - clazz = new src.test.java.TestCases.phase3.AbstractClassCF(); - """, context ) ); - assertThat( t.getMessage() ).contains( "Cannot instantiate an abstract class" ); - - instance.executeSource( - """ - clazz = new src.test.java.TestCases.phase3.ConcreteClassCF(); - result1 = clazz.normal() - result2 = clazz.abstractMethod() - """, context ); - assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( "normal" ); - assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "abstractMethod" ); - } - - @Test - public void testCFGetterType() { - instance.executeSource( - """ - clazz = new src.test.java.TestCases.phase3.GetterTest(); - result = clazz.getMyDate() - """, context ); - assertThat( variables.get( result ) ).isEqualTo( "" ); - } - - @Test - public void testGetterOverrideInParent() { - instance.executeSource( - """ - clazz = new src.test.java.TestCases.phase3.GeneratedGetterChild(); - result = clazz.getFoo() - """, context ); - assertThat( variables.get( result ) ).isEqualTo( "overriden" ); - } - - @Test - public void testFinalClass() { - instance.executeSource( - """ - clazz = new src.test.java.TestCases.phase3.FinalClass(); - """, context ); - Throwable t = assertThrows( BoxRuntimeException.class, - () -> instance.executeSource( - """ - clazz = new src.test.java.TestCases.phase3.IllegalFinalExtends(); - """, context ) ); - assertThat( t.getMessage() ).contains( "Cannot extend final class" ); - } - -} diff --git a/src/test/java/TestCases/asm/phase3/ClassTrailingComment.cfc b/src/test/java/TestCases/asm/phase3/ClassTrailingComment.cfc deleted file mode 100644 index a7606f5f0..000000000 --- a/src/test/java/TestCases/asm/phase3/ClassTrailingComment.cfc +++ /dev/null @@ -1,7 +0,0 @@ - - - - - diff --git a/src/test/java/TestCases/asm/phase3/ClassWrappedInScript.cfc b/src/test/java/TestCases/asm/phase3/ClassWrappedInScript.cfc deleted file mode 100644 index 813fa9c83..000000000 --- a/src/test/java/TestCases/asm/phase3/ClassWrappedInScript.cfc +++ /dev/null @@ -1,4 +0,0 @@ - - component { - } - \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/ConcreteClass.bx b/src/test/java/TestCases/asm/phase3/ConcreteClass.bx deleted file mode 100644 index da881c5b8..000000000 --- a/src/test/java/TestCases/asm/phase3/ConcreteClass.bx +++ /dev/null @@ -1,6 +0,0 @@ -class extends="AbstractClass" { - - function abstractMethod() { - return "abstractMethod"; - } -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/ConcreteClassCF.cfc b/src/test/java/TestCases/asm/phase3/ConcreteClassCF.cfc deleted file mode 100644 index 06b946605..000000000 --- a/src/test/java/TestCases/asm/phase3/ConcreteClassCF.cfc +++ /dev/null @@ -1,6 +0,0 @@ -component extends="AbstractClassCF" { - - function abstractMethod() { - return "abstractMethod"; - } -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/Dog.cfc b/src/test/java/TestCases/asm/phase3/Dog.cfc deleted file mode 100644 index efdc62ab0..000000000 --- a/src/test/java/TestCases/asm/phase3/Dog.cfc +++ /dev/null @@ -1,20 +0,0 @@ -component extends="Animal" { - results.append('Dog pseudo ' & getFileFromPath( getCurrentTemplatePath())) - variables.inDog = true; - // Our variables scope contains the variables from the parent component - results.append( "dog sees variables.inAnimal as: " & variables.inAnimal ) - - function init() { - super.init(); - results.append('Dog init ' & getFileFromPath( getCurrentTemplatePath())) - } - - function speak() { - return "Woof!"; - } - - function getScientificName() { - return "Canis lupus " & super.getScientificName(); - } - - } \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/DotExtends.cfc b/src/test/java/TestCases/asm/phase3/DotExtends.cfc deleted file mode 100644 index 28111cff1..000000000 --- a/src/test/java/TestCases/asm/phase3/DotExtends.cfc +++ /dev/null @@ -1,5 +0,0 @@ -component extends="../../TestCases/phase3/DotExtendsParent" { - function childUDF() { - return "childUDF" & super.parentUDF(); - } -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/DotExtendsParent.cfc b/src/test/java/TestCases/asm/phase3/DotExtendsParent.cfc deleted file mode 100644 index 55b42456b..000000000 --- a/src/test/java/TestCases/asm/phase3/DotExtendsParent.cfc +++ /dev/null @@ -1,5 +0,0 @@ -component { - function parentUDF() { - return "parent"; - } -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/ExceptionTest.java b/src/test/java/TestCases/asm/phase3/ExceptionTest.java deleted file mode 100644 index 80b28b71b..000000000 --- a/src/test/java/TestCases/asm/phase3/ExceptionTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm.phase3; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.VariablesScope; - -public class ExceptionTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key result = new Key( "result" ); - static Key foo = new Key( "foo" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @Test - public void testBoxMeta() { - - instance.executeStatement( - """ - include "src/test/java/TestCases/phase3/ExceptionThrower.cfs"; - """, context ); - - assertThat( "" ).isEqualTo( "" ); - - } - -} diff --git a/src/test/java/TestCases/asm/phase3/ExceptionThrower.cfs b/src/test/java/TestCases/asm/phase3/ExceptionThrower.cfs deleted file mode 100644 index 9bb637d16..000000000 --- a/src/test/java/TestCases/asm/phase3/ExceptionThrower.cfs +++ /dev/null @@ -1,6 +0,0 @@ -try { - 1/0 -} catch( any e ) { - e.printStackTrace() - println( e.tagContext ) -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/FinalClass.bx b/src/test/java/TestCases/asm/phase3/FinalClass.bx deleted file mode 100644 index dc3b92ed8..000000000 --- a/src/test/java/TestCases/asm/phase3/FinalClass.bx +++ /dev/null @@ -1,5 +0,0 @@ -final class { - - final this.CONSTANT = "constant"; - -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/FindMe.bx b/src/test/java/TestCases/asm/phase3/FindMe.bx deleted file mode 100644 index 9394e50f5..000000000 --- a/src/test/java/TestCases/asm/phase3/FindMe.bx +++ /dev/null @@ -1,5 +0,0 @@ -class { - function foo() { - return "bar"; - } -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/FunctionMeta.cfc b/src/test/java/TestCases/asm/phase3/FunctionMeta.cfc deleted file mode 100644 index 1376b2f56..000000000 --- a/src/test/java/TestCases/asm/phase3/FunctionMeta.cfc +++ /dev/null @@ -1,16 +0,0 @@ -component { - - /** - * The bit that can be used to set all tasks created by this scheduler to always run on one server - */ - property name="serverFixation" type="boolean"; - - /** - * This is my function hint - * @brad wood - * @param1.luis majano - */ - function foo( param1 ) { - - } -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/GeneratedGetterChild.bx b/src/test/java/TestCases/asm/phase3/GeneratedGetterChild.bx deleted file mode 100644 index 015a3cdaa..000000000 --- a/src/test/java/TestCases/asm/phase3/GeneratedGetterChild.bx +++ /dev/null @@ -1,2 +0,0 @@ -class accessors=true extends="GeneratedGetterParent" { -} diff --git a/src/test/java/TestCases/asm/phase3/GeneratedGetterParent.bx b/src/test/java/TestCases/asm/phase3/GeneratedGetterParent.bx deleted file mode 100644 index 80852e652..000000000 --- a/src/test/java/TestCases/asm/phase3/GeneratedGetterParent.bx +++ /dev/null @@ -1,9 +0,0 @@ -class accessors=true { - - property name="foo" type=string default="default value"; - - // overridden getter - string function getFoo() { - return "overriden"; - } -} diff --git a/src/test/java/TestCases/asm/phase3/GetterTest.cfc b/src/test/java/TestCases/asm/phase3/GetterTest.cfc deleted file mode 100644 index b85cf9750..000000000 --- a/src/test/java/TestCases/asm/phase3/GetterTest.cfc +++ /dev/null @@ -1,10 +0,0 @@ -component accessors="true" { - - property name="myDate" type="date"; - - function init() { - variables.myDate = ""; - } - - -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/IBicycle.bx b/src/test/java/TestCases/asm/phase3/IBicycle.bx deleted file mode 100644 index a67cdd373..000000000 --- a/src/test/java/TestCases/asm/phase3/IBicycle.bx +++ /dev/null @@ -1,11 +0,0 @@ -interface { - - public String function pedal( required numeric speed=3 ); - - function pedalLax( speed ); - - default function hasInnerTube() { - return true; - } - -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/IChildInterface.bx b/src/test/java/TestCases/asm/phase3/IChildInterface.bx deleted file mode 100644 index c746358a5..000000000 --- a/src/test/java/TestCases/asm/phase3/IChildInterface.bx +++ /dev/null @@ -1,9 +0,0 @@ -interface extends="IParentInterface" { - default function childDefaultMethod() { - return "childDefaultMethod"; - } - default function parentOverrideMe() { - return "parentOverrideMeChild"; - } - function childMethod(); -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/IMotorcycle.bx b/src/test/java/TestCases/asm/phase3/IMotorcycle.bx deleted file mode 100644 index a3aa0c08d..000000000 --- a/src/test/java/TestCases/asm/phase3/IMotorcycle.bx +++ /dev/null @@ -1,9 +0,0 @@ -interface { - - function shift( required numeric gear ); - - default function needsFuel() { - return false; - } - -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/IMultiChildInterface.bx b/src/test/java/TestCases/asm/phase3/IMultiChildInterface.bx deleted file mode 100644 index d73790082..000000000 --- a/src/test/java/TestCases/asm/phase3/IMultiChildInterface.bx +++ /dev/null @@ -1,12 +0,0 @@ -interface extends="IParentInterface,IUncleInterface" { - default function childDefaultMethod() { - return "childDefaultMethod"; - } - default function parentOverrideMe() { - return "parentOverrideMeChild"; - } - default function uncleOverrideMe() { - return "uncleOverrideMeChild"; - } - function childMethod(); -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/IParentInterface.bx b/src/test/java/TestCases/asm/phase3/IParentInterface.bx deleted file mode 100644 index 4f3a973b8..000000000 --- a/src/test/java/TestCases/asm/phase3/IParentInterface.bx +++ /dev/null @@ -1,12 +0,0 @@ -interface { - default function parentDefaultMethod() { - return "parentDefaultMethod"; - } - default function parentOverrideMe() { - return "parentOverrideMeParent"; - } - default function parentOverrideMe2() { - return "parentOverrideMeParent2"; - } - function parentMethod(); -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/IUncleInterface.bx b/src/test/java/TestCases/asm/phase3/IUncleInterface.bx deleted file mode 100644 index c05e1f421..000000000 --- a/src/test/java/TestCases/asm/phase3/IUncleInterface.bx +++ /dev/null @@ -1,12 +0,0 @@ -interface { - default function uncleDefaultMethod() { - return "uncleDefaultMethod"; - } - default function uncleOverrideMe() { - return "uncleOverrideMeUncle"; - } - default function uncleOverrideMe2() { - return "uncleOverrideMeUncle2"; - } - function uncleMethod(); -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/IllegalFinalExtends.bx b/src/test/java/TestCases/asm/phase3/IllegalFinalExtends.bx deleted file mode 100644 index 1442a25b7..000000000 --- a/src/test/java/TestCases/asm/phase3/IllegalFinalExtends.bx +++ /dev/null @@ -1,3 +0,0 @@ -class extends=FinalClass { - -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/ImplicitAccessor.bx b/src/test/java/TestCases/asm/phase3/ImplicitAccessor.bx deleted file mode 100644 index fc2af56a6..000000000 --- a/src/test/java/TestCases/asm/phase3/ImplicitAccessor.bx +++ /dev/null @@ -1,29 +0,0 @@ -class invokeImplicitAccessor=true { - property string name; - property integer age; - - methodsCalled = ""; - function getName() { - methodsCalled &= "getName"; - return variables.name; - } - - function setName(string name) { - methodsCalled &= "setName"; - variables.name = name; - } - - function getAge() { - methodsCalled &= "getAge"; - return variables.age; - } - - function setAge(integer age) { - methodsCalled &= "setAge"; - variables.age = age; - } - - function getMethodsCalled() { - return methodsCalled; - } -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/ImplicitConstructorTest.cfc b/src/test/java/TestCases/asm/phase3/ImplicitConstructorTest.cfc deleted file mode 100644 index 4186eec1b..000000000 --- a/src/test/java/TestCases/asm/phase3/ImplicitConstructorTest.cfc +++ /dev/null @@ -1,6 +0,0 @@ -component accessors=true { - property name="name"; - property name="age"; - property name="favoriteColor"; - -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/ImplicitGeneratedAccessor.bx b/src/test/java/TestCases/asm/phase3/ImplicitGeneratedAccessor.bx deleted file mode 100644 index 9e6d5e274..000000000 --- a/src/test/java/TestCases/asm/phase3/ImplicitGeneratedAccessor.bx +++ /dev/null @@ -1,5 +0,0 @@ -class invokeImplicitAccessor=true accessors=true { - property string name; - property integer age; - -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/InitMethodTest.bx b/src/test/java/TestCases/asm/phase3/InitMethodTest.bx deleted file mode 100644 index 60a656d12..000000000 --- a/src/test/java/TestCases/asm/phase3/InitMethodTest.bx +++ /dev/null @@ -1,9 +0,0 @@ -class initMethod=birth accessors=true { - property inittedProperly default=false; - function init() { - throw "you'd better not call me!"; - } - function birth() { - inittedProperly=true - } -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/InterfaceInheritenceTest.bx b/src/test/java/TestCases/asm/phase3/InterfaceInheritenceTest.bx deleted file mode 100644 index 1abcc38b4..000000000 --- a/src/test/java/TestCases/asm/phase3/InterfaceInheritenceTest.bx +++ /dev/null @@ -1,11 +0,0 @@ -class implements="IChildInterface" { - - public Any function childMethod() { - return "childMethod"; - } - - public Any function parentMethod() { - return "parentMethod"; - } - -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/InterfaceMultiInheritenceTest.bx b/src/test/java/TestCases/asm/phase3/InterfaceMultiInheritenceTest.bx deleted file mode 100644 index 74c0dedf9..000000000 --- a/src/test/java/TestCases/asm/phase3/InterfaceMultiInheritenceTest.bx +++ /dev/null @@ -1,23 +0,0 @@ -class implements="IMultiChildInterface" { - - public Any function childMethod() { - return "childMethod"; - } - - public Any function parentMethod() { - return "parentMethod"; - } - - public Any function uncleMethod() { - return "uncleMethod"; - } - - default function parentOverrideMe2() { - return "parentOverrideMe2Class"; - } - - default function uncleOverrideMe2() { - return "uncleOverrideMe2Class"; - } - -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/InterfaceStatic.bx b/src/test/java/TestCases/asm/phase3/InterfaceStatic.bx deleted file mode 100644 index 3c9e628e0..000000000 --- a/src/test/java/TestCases/asm/phase3/InterfaceStatic.bx +++ /dev/null @@ -1,16 +0,0 @@ -interface { - - static { - static.myVar = "brad"; - yourVar = "luis"; - } - - static function foo() { - return static.myVar; - } - - static function callStatic() { - return foo(); - } - -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/InterfaceTest.java b/src/test/java/TestCases/asm/phase3/InterfaceTest.java deleted file mode 100644 index 167dd863d..000000000 --- a/src/test/java/TestCases/asm/phase3/InterfaceTest.java +++ /dev/null @@ -1,284 +0,0 @@ -/** - * [BoxLang] - * - * Copyright [2023] [Ortus Solutions, Corp] - * - * 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 - * - * http://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. - */ -package TestCases.asm.phase3; - -import static com.google.common.truth.Truth.assertThat; - -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -import ortus.boxlang.compiler.parser.BoxSourceType; -import ortus.boxlang.runtime.BoxRuntime; -import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; -import ortus.boxlang.runtime.interop.DynamicObject; -import ortus.boxlang.runtime.runnables.BoxInterface; -import ortus.boxlang.runtime.runnables.RunnableLoader; -import ortus.boxlang.runtime.scopes.IScope; -import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.scopes.VariablesScope; - -public class InterfaceTest { - - static BoxRuntime instance; - IBoxContext context; - IScope variables; - static Key result = new Key( "result" ); - static Key foo = new Key( "foo" ); - - @BeforeAll - public static void setUp() { - instance = BoxRuntime.getInstance( true ); - } - - @AfterAll - public static void teardown() { - - } - - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - variables = context.getScopeNearby( VariablesScope.name ); - instance.useASMBoxPiler(); - } - - @AfterEach - public void teardownEach() { - // instance.useJavaBoxpiler(); - } - - @DisplayName( "basic CF interface" ) - @Test - public void testBasicCFInterface() { - - BoxInterface inter = ( BoxInterface ) DynamicObject.of( RunnableLoader.getInstance().loadClass( - """ - /** - * This is my interface description - * - * @brad wood - * @luis - */ - interface singleton gavin="pickin" inject foo="bar" { - - function init(); - - function foo(); - - private function bar(); - - default function myDefaultMethod() { - return this.foo(); - } - - } - - """, context, BoxSourceType.CFSCRIPT ), context ).unWrapBoxLangClass(); - - assertThat( inter.getMetaData().get( Key.of( "type" ) ) ).isEqualTo( "Interface" ); - assertThat( inter.getMetaData().getAsArray( Key.of( "functions" ) ) ).hasSize( 4 ); - - } - - @DisplayName( "basic interface file CF" ) - @Test - public void testBasicInterfaceFileCF() { - - instance.executeStatement( - """ - result = createObject( "component", "src.test.java.TestCases.phase3.MyInterfaceCF" ); - """, context ); - assertThat( variables.get( result ) ).isInstanceOf( BoxInterface.class ); - BoxInterface inter = variables.getAsBoxInterface( result ); - assertThat( inter.getMetaData().get( Key.of( "type" ) ) ).isEqualTo( "Interface" ); - assertThat( inter.getMetaData().getAsArray( Key.of( "functions" ) ) ).hasSize( 4 ); - - } - - @DisplayName( "basic interface file BL" ) - @Test - public void testBasicInterfaceFileBL() { - - instance.executeStatement( - """ - result = createObject( "component", "src.test.java.TestCases.phase3.MyInterfaceBL" ); - """, context ); - assertThat( variables.get( result ) ).isInstanceOf( BoxInterface.class ); - BoxInterface inter = variables.getAsBoxInterface( result ); - assertThat( inter.getMetaData().get( Key.of( "type" ) ) ).isEqualTo( "Interface" ); - assertThat( inter.getMetaData().getAsArray( Key.of( "functions" ) ) ).hasSize( 4 ); - - } - - @DisplayName( "basic interface file CF Tag" ) - @Test - public void testBasicClassFileCFTag() { - - instance.executeStatement( - """ - result = createObject( "component", "src.test.java.TestCases.phase3.MyInterfaceCFTag" ); - """, context ); - assertThat( variables.get( result ) ).isInstanceOf( BoxInterface.class ); - BoxInterface inter = variables.getAsBoxInterface( result ); - assertThat( inter.getMetaData().get( Key.of( "type" ) ) ).isEqualTo( "Interface" ); - assertThat( inter.getMetaData().getAsArray( Key.of( "functions" ) ) ).hasSize( 4 ); - - } - - @DisplayName( "moped example" ) - @Test - public void testMopedExample() { - - instance.executeStatement( - """ - boxClass = new src.test.java.TestCases.phase3.Moped(); - - // implemented method from IBicycle - result1 = boxClass.pedal( 3 ) - // impelemented method from IMotorcycle - result2 = boxClass.shift( 4 ) - // Default method from IBicycle - result3 = boxClass.hasInnerTube() - // Default method from IMotorcycle but overridden by moped - result4 = boxClass.needsFuel() - """, context ); - assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( "Pedal speed 3" ); - assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "Shift to gear 4" ); - assertThat( variables.get( Key.of( "result3" ) ) ).isEqualTo( true ); - assertThat( variables.get( Key.of( "result4" ) ) ).isEqualTo( true ); - } - - @DisplayName( "onMissingMethod example" ) - @Test - public void testOnMissingMethod() { - - instance.executeStatement( - """ - boxClass = new src.test.java.TestCases.phase3.WheeledThing(); - - // implemented method from IBicycle - result1 = boxClass.pedal( 3 ) - // impelemented method from IMotorcycle - result2 = boxClass.shift( 4 ) - // Default method from IBicycle - result3 = boxClass.hasInnerTube() - // Default method from IMotorcycle but overridden by moped - result4 = boxClass.needsFuel() - """, context ); - assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( "Pedal speed 3" ); - assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "Shift to gear 4" ); - assertThat( variables.get( Key.of( "result3" ) ) ).isEqualTo( true ); - assertThat( variables.get( Key.of( "result4" ) ) ).isEqualTo( false ); - - } - - @DisplayName( "interface inheritence example" ) - @Test - public void testInterfaceInheritence() { - - instance.executeStatement( - """ - boxClass = new src.test.java.TestCases.phase3.InterfaceInheritenceTest(); - result1 = boxClass.childMethod() - result2 = boxClass.parentMethod() - result3 = boxClass.childDefaultMethod() - result4 = boxClass.parentDefaultMethod() - result5 = boxClass.parentOverrideMe() - assert boxClass instanceof "InterfaceInheritenceTest"; - assert boxClass instanceof "IChildInterface"; - assert boxClass instanceof "IParentInterface"; - """, context ); - - assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( "childMethod" ); - assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "parentMethod" ); - assertThat( variables.get( Key.of( "result3" ) ) ).isEqualTo( "childDefaultMethod" ); - assertThat( variables.get( Key.of( "result4" ) ) ).isEqualTo( "parentDefaultMethod" ); - assertThat( variables.get( Key.of( "result5" ) ) ).isEqualTo( "parentOverrideMeChild" ); - } - - @DisplayName( "interface multi-inheritence example" ) - @Test - public void testInterfaceMultiInheritence() { - - instance.executeStatement( - """ - boxClass = new src.test.java.TestCases.phase3.InterfaceMultiInheritenceTest(); - result1 = boxClass.childMethod() - result2 = boxClass.parentMethod() - result3 = boxClass.uncleMethod() - result4 = boxClass.childDefaultMethod() - result5 = boxClass.parentDefaultMethod() - result6 = boxClass.uncleDefaultMethod() - result7 = boxClass.parentOverrideMe() - result8 = boxClass.uncleOverrideMe() - result9 = boxClass.parentOverrideMe2() - result10 = boxClass.uncleOverrideMe2() - assert boxClass instanceof "InterfaceMultiInheritenceTest"; - assert boxClass instanceof "IMultiChildInterface"; - assert boxClass instanceof "IParentInterface"; - assert boxClass instanceof "IUncleInterface"; - // println( createOBject("src.test.java.TestCases.phase3.IMultiChildInterface" ).$bx.invokeTargetMethod( getBoxContext(), "getMetaData", [].toArray() ) ) - """, - context ); - - assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( "childMethod" ); - assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "parentMethod" ); - assertThat( variables.get( Key.of( "result3" ) ) ).isEqualTo( "uncleMethod" ); - assertThat( variables.get( Key.of( "result4" ) ) ).isEqualTo( "childDefaultMethod" ); - assertThat( variables.get( Key.of( "result5" ) ) ).isEqualTo( "parentDefaultMethod" ); - assertThat( variables.get( Key.of( "result6" ) ) ).isEqualTo( "uncleDefaultMethod" ); - assertThat( variables.get( Key.of( "result7" ) ) ).isEqualTo( "parentOverrideMeChild" ); - assertThat( variables.get( Key.of( "result8" ) ) ).isEqualTo( "uncleOverrideMeChild" ); - assertThat( variables.get( Key.of( "result9" ) ) ).isEqualTo( "parentOverrideMe2Class" ); - assertThat( variables.get( Key.of( "result10" ) ) ).isEqualTo( "uncleOverrideMe2Class" ); - - } - - @DisplayName( "interface inheritence static" ) - @Test - public void testInterfaceStatic() { - - instance.executeStatement( - """ - import src.test.java.TestCases.phase3.InterfaceStatic as ist; - result1 = ist.foo() - result2 = ist.myVar; - result3 = ist.yourVar; - result4 = ist.callStatic(); - result5 = ist::yourVar; - result6 = ist::callStatic(); - result7 = src.test.java.TestCases.phase3.InterfaceStatic::yourVar; - result8 = src.test.java.TestCases.phase3.InterfaceStatic::callStatic(); - """, context ); - assertThat( variables.get( Key.of( "result1" ) ) ).isEqualTo( "brad" ); - assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( "brad" ); - assertThat( variables.get( Key.of( "result3" ) ) ).isEqualTo( "luis" ); - assertThat( variables.get( Key.of( "result4" ) ) ).isEqualTo( "brad" ); - assertThat( variables.get( Key.of( "result5" ) ) ).isEqualTo( "luis" ); - assertThat( variables.get( Key.of( "result6" ) ) ).isEqualTo( "brad" ); - assertThat( variables.get( Key.of( "result7" ) ) ).isEqualTo( "luis" ); - assertThat( variables.get( Key.of( "result8" ) ) ).isEqualTo( "brad" ); - - } - -} diff --git a/src/test/java/TestCases/asm/phase3/JavaExtends2.bx b/src/test/java/TestCases/asm/phase3/JavaExtends2.bx deleted file mode 100644 index 673799e63..000000000 --- a/src/test/java/TestCases/asm/phase3/JavaExtends2.bx +++ /dev/null @@ -1,7 +0,0 @@ -class extends="java:ortus.boxlang.runtime.context.BaseBoxContext" { - - function getTemplatesYo() { - return super.templates; - } - -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/JavaExtends3.bx b/src/test/java/TestCases/asm/phase3/JavaExtends3.bx deleted file mode 100644 index 2aef9391f..000000000 --- a/src/test/java/TestCases/asm/phase3/JavaExtends3.bx +++ /dev/null @@ -1,14 +0,0 @@ -class extends="java:ortus.boxlang.runtime.bifs.BIF" { - - @overrideJava - Object function _invoke(ortus.boxlang.runtime.context.IBoxContext c ,ortus.boxlang.runtime.scopes.ArgumentsScope args) { - - } - - function printStuff() { - println( this.__isMemberExecution ) - println( this.runtime ) - println( super.__isMemberExecution ) - println( super.runtime ) - } -} diff --git a/src/test/java/TestCases/asm/phase3/JavaExtendsAsm.bx b/src/test/java/TestCases/asm/phase3/JavaExtendsAsm.bx deleted file mode 100644 index 09ca312d3..000000000 --- a/src/test/java/TestCases/asm/phase3/JavaExtendsAsm.bx +++ /dev/null @@ -1,16 +0,0 @@ -class extends="java:java.util.TimerTask" { - - - - @overrideJava - void function run() { - println("Hello from a custom TimerTask!" ); - println( super.scheduledExecutionTime() ); - } - - @overrideJava - public long function scheduledExecutionTime() { - throw "no, not me!"; - } - -} diff --git a/src/test/java/TestCases/asm/phase3/JavaImplements.bx b/src/test/java/TestCases/asm/phase3/JavaImplements.bx deleted file mode 100644 index ba69db7f7..000000000 --- a/src/test/java/TestCases/asm/phase3/JavaImplements.bx +++ /dev/null @@ -1,7 +0,0 @@ -class implements="java:java.lang.Runnable" { - - function run() { - println("Hello from a thread!"); - } - -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/Moped.bx b/src/test/java/TestCases/asm/phase3/Moped.bx deleted file mode 100644 index 7816cd1f4..000000000 --- a/src/test/java/TestCases/asm/phase3/Moped.bx +++ /dev/null @@ -1,20 +0,0 @@ -class implements="IBicycle,IMotorcycle" { - - String function pedal( required numeric speed=3 ) { - return "Pedal speed #speed#" - } - - // This overachieving mehod has more details than the interfaces requires - Boolean function pedalLax( required numeric speed=3 ) { - } - - public Any function shift(required numeric gear) { - return "Shift to gear #gear#" - } - - @Override - default function needsFuel() { - return true; - } - -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/MyClass.bx b/src/test/java/TestCases/asm/phase3/MyClass.bx deleted file mode 100644 index 35a26f027..000000000 --- a/src/test/java/TestCases/asm/phase3/MyClass.bx +++ /dev/null @@ -1,50 +0,0 @@ -// get foo -import foo; -// get system -import java.lang.System; - -/** - * This is my class description - * continued on this line - * - * and this one - * as well. - * - * @brad wood - * @luis - */ -@foo "bar" -class singleton gavin="pickin" inject { - - property name; - - variables.setup=true; // ending comment - System.out.println( "word" ); - request.foo="bar"; - isInitted = false; - println( "current template is " & getCurrentTemplatePath() ); - printLn( foo() ) - - // my init func - function init() { - isInitted = true; - } - - function foo() { - /* return this value */ - return "I work! #bar()# #variables.setup# #setup# #request.foo# #isInitted#"; - } - - private function bar() { - return "whee"; - } - - function getThis() { - return this; - } - - function runThisFoo() { - return this.foo(); - } - - } \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/MyClassCF.cfc b/src/test/java/TestCases/asm/phase3/MyClassCF.cfc deleted file mode 100644 index 7aa68b7bb..000000000 --- a/src/test/java/TestCases/asm/phase3/MyClassCF.cfc +++ /dev/null @@ -1,36 +0,0 @@ -/** - * This is my class description - * - * @brad wood - * @luis - */ -component singleton gavin="pickin" inject foo="bar" { - - variables.setup=true; - createObject('java','java.lang.System').out.println( "word" ); - request.foo="bar"; - isInitted = false; - println( "current template is " & getCurrentTemplatePath() ); - printLn( foo() ) - - function init() { - isInitted = true; - } - - function foo() { - return "I work! #bar()# #variables.setup# #setup# #request.foo# #isInitted#"; - } - - private function bar() { - return "whee"; - } - - function getThis() { - return this; - } - - function runThisFoo() { - return this.foo(); - } - - } \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/MyInterfaceBL.bx b/src/test/java/TestCases/asm/phase3/MyInterfaceBL.bx deleted file mode 100644 index 411c83543..000000000 --- a/src/test/java/TestCases/asm/phase3/MyInterfaceBL.bx +++ /dev/null @@ -1,19 +0,0 @@ -/** -* This is my interface description -* -* @brad wood -* @luis -*/ -interface singleton gavin="pickin" inject foo="bar" { - - function init(); - - function foo(); - - private function bar(); - - default function myDefaultMethod() { - return this.foo(); - } - -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/MyInterfaceCF.cfc b/src/test/java/TestCases/asm/phase3/MyInterfaceCF.cfc deleted file mode 100644 index 411c83543..000000000 --- a/src/test/java/TestCases/asm/phase3/MyInterfaceCF.cfc +++ /dev/null @@ -1,19 +0,0 @@ -/** -* This is my interface description -* -* @brad wood -* @luis -*/ -interface singleton gavin="pickin" inject foo="bar" { - - function init(); - - function foo(); - - private function bar(); - - default function myDefaultMethod() { - return this.foo(); - } - -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/MyInterfaceCFTag.cfc b/src/test/java/TestCases/asm/phase3/MyInterfaceCFTag.cfc deleted file mode 100644 index 255d4cda0..000000000 --- a/src/test/java/TestCases/asm/phase3/MyInterfaceCFTag.cfc +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/OnMissingMethod.cfc b/src/test/java/TestCases/asm/phase3/OnMissingMethod.cfc deleted file mode 100644 index 73e5af916..000000000 --- a/src/test/java/TestCases/asm/phase3/OnMissingMethod.cfc +++ /dev/null @@ -1,5 +0,0 @@ -component { - public any function onMissingMethod( string missingMethodName, any missingMethodArguments ){ - return missingMethodName & missingMethodArguments[2]; - } -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/Parent.cfc b/src/test/java/TestCases/asm/phase3/Parent.cfc deleted file mode 100644 index 0e435dab7..000000000 --- a/src/test/java/TestCases/asm/phase3/Parent.cfc +++ /dev/null @@ -1,12 +0,0 @@ -component { - - function init() { - setupFrameworkDefaults(); - return this; - } - - private void function setupFrameworkDefaults() { - request.calls.append( "running parent setupFrameworkDefaults()" ); - } - -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/PropertyTest.bx b/src/test/java/TestCases/asm/phase3/PropertyTest.bx deleted file mode 100644 index e27a9870e..000000000 --- a/src/test/java/TestCases/asm/phase3/PropertyTest.bx +++ /dev/null @@ -1,25 +0,0 @@ -class accessors=true { - @preAnno - property name="myProperty" default="myDefaultValue" type=string inject; - - /** - * This is my property - * @brad wood - * @luis - */ - @preanno "myValue" "anothervalue" - property string anotherprop; - - @ID - property theName; - - property string name; - - property shortcutWithDefault default="myDefaultValue"; - - property String typedShortcutWithDefault default="myDefaultValue2"; - - function init() { - getMyProperty(); - } -} diff --git a/src/test/java/TestCases/asm/phase3/PropertyTestCF.cfc b/src/test/java/TestCases/asm/phase3/PropertyTestCF.cfc deleted file mode 100644 index 420a38801..000000000 --- a/src/test/java/TestCases/asm/phase3/PropertyTestCF.cfc +++ /dev/null @@ -1,18 +0,0 @@ -component accessors=true { - property name="myProperty" default="myDefaultValue" type=string inject preAnno; - - /** - * This is my property - * @brad wood - * @luis - */ - property string anotherprop preanno=["myValue", "anothervalue"]; - - property shortcutWithDefault default="myDefaultValue"; - - property String typedShortcutWithDefault default="myDefaultValue2"; - - function init() { - getMyProperty(); - } -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/PseudoConstructorNoOutput.cfc b/src/test/java/TestCases/asm/phase3/PseudoConstructorNoOutput.cfc deleted file mode 100644 index 2c844bad6..000000000 --- a/src/test/java/TestCases/asm/phase3/PseudoConstructorNoOutput.cfc +++ /dev/null @@ -1,3 +0,0 @@ -component output=false { - echo( "PseudoConstructorOutput" ); -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/PseudoConstructorOutput.cfc b/src/test/java/TestCases/asm/phase3/PseudoConstructorOutput.cfc deleted file mode 100644 index fe290b038..000000000 --- a/src/test/java/TestCases/asm/phase3/PseudoConstructorOutput.cfc +++ /dev/null @@ -1,3 +0,0 @@ -component output=true { - echo( "PseudoConstructorOutput" ); -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/RelativeInstantiation.bx b/src/test/java/TestCases/asm/phase3/RelativeInstantiation.bx deleted file mode 100644 index 4e7e792d5..000000000 --- a/src/test/java/TestCases/asm/phase3/RelativeInstantiation.bx +++ /dev/null @@ -1,5 +0,0 @@ -class { - function findSibling() { - return new FindMe().foo(); - } -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/SeniorVespa.bx b/src/test/java/TestCases/asm/phase3/SeniorVespa.bx deleted file mode 100644 index 1e1c8036b..000000000 --- a/src/test/java/TestCases/asm/phase3/SeniorVespa.bx +++ /dev/null @@ -1,3 +0,0 @@ -class extends="Moped" { - -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/Stack.cfc b/src/test/java/TestCases/asm/phase3/Stack.cfc deleted file mode 100644 index f3f5b33cd..000000000 --- a/src/test/java/TestCases/asm/phase3/Stack.cfc +++ /dev/null @@ -1,10 +0,0 @@ -component { - function init() { - stack = []; - return this; - } - - function empty() { - return stack.len() == 0; - } -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/StaticTest.bx b/src/test/java/TestCases/asm/phase3/StaticTest.bx deleted file mode 100644 index 51240db75..000000000 --- a/src/test/java/TestCases/asm/phase3/StaticTest.bx +++ /dev/null @@ -1,33 +0,0 @@ - class { - static { - static.scoped = "brad"; - unscoped = "wood" - static foo = 9000; - '123' = 456; - } - - static.foo = 42; - - static { - static.again = "luis" - } - - static function myStaticFunc() { - return "static" & static.foo; - } - - function myInstanceFunc() { - return "instance" & myStaticFunc(); - } - - array function myInstanceFunc2() { - return [static.scoped, - static.unscoped, - static.foo]; - } - - static function sayHello() { - return "Hello"; - } - - } \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/StaticTestCF.cfc b/src/test/java/TestCases/asm/phase3/StaticTestCF.cfc deleted file mode 100644 index ebfad9dc3..000000000 --- a/src/test/java/TestCases/asm/phase3/StaticTestCF.cfc +++ /dev/null @@ -1,22 +0,0 @@ -component { - static { - static.scoped = "brad"; - unscoped = "wood" - static foo = 9000; - } - - static.foo = 42; - - static { - static.again = "luis" - } - - static function myStaticFunc() { - return "static" & static.foo; - } - - function myInstanceFunc() { - return "instance" & myStaticFunc(); - } - -} \ No newline at end of file diff --git a/src/test/java/TestCases/asm/phase3/WheeledThing.bx b/src/test/java/TestCases/asm/phase3/WheeledThing.bx deleted file mode 100644 index bbbecf7cc..000000000 --- a/src/test/java/TestCases/asm/phase3/WheeledThing.bx +++ /dev/null @@ -1,13 +0,0 @@ -class implements="IBicycle,IMotorcycle" { - - function onMissingMethod( name, args ) { - switch( arguments.name ) { - case "pedal": - return "Pedal speed #args[1]#" - break; - case "shift": - return "Shift to gear #args[1]#" - break; - } - } -} \ No newline at end of file From fdfd8022e3fab064e020ba15dd75cf04108ab3e0 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 2 Dec 2024 12:17:47 -0600 Subject: [PATCH 014/193] BL-807 --- .../compiler/javaboxpiler/JavaTranspiler.java | 7 ++--- .../transformer/AbstractTransformer.java | 6 ++-- .../transformer/BoxClassTransformer.java | 4 +-- .../transformer/BoxInterfaceTransformer.java | 4 +-- .../BoxStaticAccessTransformer.java | 5 ++-- .../BoxStaticMethodInvocationTransformer.java | 3 +- .../BoxStringLiteralTransformer.java | 29 +++++++++++++------ .../BoxArgumentDeclarationTransformer.java | 4 +-- .../statement/BoxScriptTransformer.java | 4 +-- 9 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/javaboxpiler/JavaTranspiler.java b/src/main/java/ortus/boxlang/compiler/javaboxpiler/JavaTranspiler.java index 3e5289618..36b3d8096 100644 --- a/src/main/java/ortus/boxlang/compiler/javaboxpiler/JavaTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/javaboxpiler/JavaTranspiler.java @@ -41,7 +41,6 @@ import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.expr.ObjectCreationExpr; -import com.github.javaparser.ast.expr.StringLiteralExpr; import com.github.javaparser.ast.stmt.Statement; import ortus.boxlang.compiler.JavaSourceString; @@ -436,13 +435,13 @@ public Expression createAbstractMethod( BoxFunctionDeclaration bfd, AbstractTran .setType( "AbstractFunction" ) .addArgument( transformer.createKey( bfd.getName() ) ) .addArgument( argumentsArray ) - .addArgument( new StringLiteralExpr( returnTypeString ) ) + .addArgument( BoxStringLiteralTransformer.transform( returnTypeString ) ) .addArgument( new FieldAccessExpr( new FieldAccessExpr( new NameExpr( "Function" ), "Access" ), access.toString().toUpperCase() ) ) .addArgument( transformer.transformAnnotations( bfd.getAnnotations() ) ) .addArgument( transformer.transformDocumentation( bfd.getDocumentation() ) ) - .addArgument( new StringLiteralExpr( sourceObjectName ) ) - .addArgument( new StringLiteralExpr( sourceObjectType ) ) + .addArgument( BoxStringLiteralTransformer.transform( sourceObjectName ) ) + .addArgument( BoxStringLiteralTransformer.transform( sourceObjectType ) ) ); } diff --git a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/AbstractTransformer.java b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/AbstractTransformer.java index db7e7f2a4..37535a20e 100644 --- a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/AbstractTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/AbstractTransformer.java @@ -32,7 +32,6 @@ import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.expr.NameExpr; -import com.github.javaparser.ast.expr.StringLiteralExpr; import com.github.javaparser.ast.stmt.Statement; import ortus.boxlang.compiler.ast.BoxExpression; @@ -57,6 +56,7 @@ import ortus.boxlang.compiler.ast.statement.BoxWhile; import ortus.boxlang.compiler.ast.statement.component.BoxComponent; import ortus.boxlang.compiler.javaboxpiler.Transpiler; +import ortus.boxlang.compiler.javaboxpiler.transformer.expression.BoxStringLiteralTransformer; import ortus.boxlang.runtime.config.util.PlaceholderHelper; /** @@ -253,7 +253,7 @@ public Expression transformAnnotations( List annotations, Boolean value = ( Expression ) transpiler.transform( thisValue ); } else if ( onlyLiteralValues ) { // Runtime expressions we just put this place holder text in for - value = new StringLiteralExpr( "" ); + value = BoxStringLiteralTransformer.transform( "" ); } else { value = ( Expression ) transpiler.transform( thisValue ); } @@ -262,7 +262,7 @@ public Expression transformAnnotations( List annotations, Boolean value = new BooleanLiteralExpr( true ); } else { // Annotations in script with no value default to empty string (CF compat) - value = new StringLiteralExpr( "" ); + value = BoxStringLiteralTransformer.transform( "" ); } members.add( value ); } ); diff --git a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/BoxClassTransformer.java b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/BoxClassTransformer.java index 66d432a29..bb1f99047 100644 --- a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/BoxClassTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/BoxClassTransformer.java @@ -35,7 +35,6 @@ import com.github.javaparser.ast.expr.LambdaExpr; import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.expr.NameExpr; -import com.github.javaparser.ast.expr.StringLiteralExpr; import com.github.javaparser.ast.stmt.BlockStmt; import com.github.javaparser.ast.stmt.EmptyStmt; import com.github.javaparser.ast.stmt.ReturnStmt; @@ -61,6 +60,7 @@ import ortus.boxlang.compiler.ast.statement.BoxReturnType; import ortus.boxlang.compiler.ast.statement.BoxType; import ortus.boxlang.compiler.javaboxpiler.JavaTranspiler; +import ortus.boxlang.compiler.javaboxpiler.transformer.expression.BoxStringLiteralTransformer; import ortus.boxlang.runtime.config.util.PlaceholderHelper; import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; import ortus.boxlang.runtime.dynamic.casters.BooleanCaster; @@ -657,7 +657,7 @@ public Node transform( BoxNode node, TransformerContext context ) throws Illegal for ( Map.Entry entry : transpiler.getKeys().entrySet() ) { MethodCallExpr methodCallExpr = new MethodCallExpr( new NameExpr( "Key" ), "of" ); if ( entry.getValue() instanceof BoxStringLiteral str ) { - methodCallExpr.addArgument( new StringLiteralExpr( str.getValue() ) ); + methodCallExpr.addArgument( BoxStringLiteralTransformer.transform( str.getValue() ) ); } else if ( entry.getValue() instanceof BoxIntegerLiteral id ) { methodCallExpr.addArgument( new IntegerLiteralExpr( id.getValue() ) ); } else { diff --git a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/BoxInterfaceTransformer.java b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/BoxInterfaceTransformer.java index bf913cdfb..9ec96b19f 100644 --- a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/BoxInterfaceTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/BoxInterfaceTransformer.java @@ -27,7 +27,6 @@ import com.github.javaparser.ast.expr.IntegerLiteralExpr; import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.expr.NameExpr; -import com.github.javaparser.ast.expr.StringLiteralExpr; import ortus.boxlang.compiler.ast.BoxExpression; import ortus.boxlang.compiler.ast.BoxInterface; @@ -41,6 +40,7 @@ import ortus.boxlang.compiler.ast.statement.BoxFunctionDeclaration; import ortus.boxlang.compiler.ast.statement.BoxImport; import ortus.boxlang.compiler.javaboxpiler.JavaTranspiler; +import ortus.boxlang.compiler.javaboxpiler.transformer.expression.BoxStringLiteralTransformer; import ortus.boxlang.runtime.config.util.PlaceholderHelper; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.types.exceptions.ExpressionException; @@ -378,7 +378,7 @@ public Node transform( BoxNode node, TransformerContext context ) throws Illegal for ( Map.Entry entry : transpiler.getKeys().entrySet() ) { MethodCallExpr methodCallExpr = new MethodCallExpr( new NameExpr( "Key" ), "of" ); if ( entry.getValue() instanceof BoxStringLiteral str ) { - methodCallExpr.addArgument( new StringLiteralExpr( str.getValue() ) ); + methodCallExpr.addArgument( BoxStringLiteralTransformer.transform( str.getValue() ) ); } else if ( entry.getValue() instanceof BoxIntegerLiteral id ) { methodCallExpr.addArgument( new IntegerLiteralExpr( id.getValue() ) ); } else { diff --git a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxStaticAccessTransformer.java b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxStaticAccessTransformer.java index 28ca377ac..61810d220 100644 --- a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxStaticAccessTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxStaticAccessTransformer.java @@ -22,7 +22,6 @@ import com.github.javaparser.ast.Node; import com.github.javaparser.ast.expr.Expression; -import com.github.javaparser.ast.expr.StringLiteralExpr; import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.expression.BoxFQN; @@ -63,13 +62,13 @@ public Node transform( BoxNode node, TransformerContext context ) throws Illegal Expression jContext; if ( objectAccess.getContext() instanceof BoxFQN fqn ) { - jContext = new StringLiteralExpr( fqn.getValue() ); + jContext = BoxStringLiteralTransformer.transform( fqn.getValue() ); } else if ( objectAccess.getContext() instanceof BoxIdentifier id ) { // In BL code, this could be an import, but in CF it's just a string if ( transpiler.matchesImport( id.getName() ) && transpiler.getProperty( "sourceType" ).toLowerCase().startsWith( "box" ) ) { jContext = ( Expression ) transpiler.transform( id, context ); } else { - jContext = new StringLiteralExpr( id.getName() ); + jContext = BoxStringLiteralTransformer.transform( id.getName() ); } } else { // foo.bar()::baz diff --git a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxStaticMethodInvocationTransformer.java b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxStaticMethodInvocationTransformer.java index 35f18d133..7114c5e24 100644 --- a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxStaticMethodInvocationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxStaticMethodInvocationTransformer.java @@ -22,7 +22,6 @@ import com.github.javaparser.ast.Node; import com.github.javaparser.ast.expr.Expression; -import com.github.javaparser.ast.expr.StringLiteralExpr; import ortus.boxlang.compiler.ast.BoxExpression; import ortus.boxlang.compiler.ast.BoxNode; @@ -47,7 +46,7 @@ public Node transform( BoxNode node, TransformerContext context ) throws Illegal Expression expr; if ( baseObject instanceof BoxFQN fqn ) { - expr = new StringLiteralExpr( fqn.getValue() ); + expr = BoxStringLiteralTransformer.transform( fqn.getValue() ); } else if ( baseObject instanceof BoxIdentifier id ) { // TODO: What if we have foo::bar() but foo is the name of a variable AND ALSO the name of an accessible Box Class? // Do we treat "foo" as a FQN class name or a variable in that case?? diff --git a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxStringLiteralTransformer.java b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxStringLiteralTransformer.java index 50b3aba8d..11e55221d 100644 --- a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxStringLiteralTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxStringLiteralTransformer.java @@ -18,7 +18,6 @@ import java.util.List; import java.util.stream.Collectors; -import com.github.javaparser.ast.Node; import com.github.javaparser.ast.NodeList; import com.github.javaparser.ast.expr.Expression; import com.github.javaparser.ast.expr.MethodCallExpr; @@ -51,15 +50,26 @@ public BoxStringLiteralTransformer( JavaTranspiler transpiler ) { * @return generates a Java Parser string Literal or concatenation expression */ @Override - public Node transform( BoxNode node, TransformerContext context ) throws IllegalStateException { - BoxStringLiteral literal = ( BoxStringLiteral ) node; - String value = escape( literal.getValue() ); + public Expression transform( BoxNode node, TransformerContext context ) throws IllegalStateException { + BoxStringLiteral literal = ( BoxStringLiteral ) node; + return transform( literal.getValue() ); + } + + /** + * Transform just the string portion (reuseable for other purposes) + * + * @param value The input string. + * + * @return generates a Java Parser string Literal or concatenation expression + */ + public static Expression transform( String value ) throws IllegalStateException { + String escapedVal = escape( value ); - if ( value.length() > MAX_LITERAL_LENGTH ) { + if ( escapedVal.length() > MAX_LITERAL_LENGTH ) { List parts = splitStringIntoParts( value ); return createArrayJoinMethodCall( parts ); } else { - return new StringLiteralExpr( value ); + return new StringLiteralExpr( escapedVal ); } } @@ -70,7 +80,7 @@ public Node transform( BoxNode node, TransformerContext context ) throws Illegal * * @return A list of StringLiteralExpr parts. **/ - private List splitStringIntoParts( String str ) { + private static List splitStringIntoParts( String str ) { List parts = new ArrayList<>(); int length = str.length(); for ( int i = 0; i < length; i += MAX_LITERAL_LENGTH ) { @@ -88,9 +98,10 @@ private List splitStringIntoParts( String str ) { * * @return A BinaryExpr representing the concatenation of all parts. **/ - private Expression createArrayJoinMethodCall( List parts ) { + private static Expression createArrayJoinMethodCall( List parts ) { // Create a MethodCallExpr for String.join with an array of strings var args = parts.stream() + // Assumes the parts won't have so many escaped chars to put back over the limit .map( part -> ( Expression ) new StringLiteralExpr( escape( part ) ) ) // Escape quotes and create StringLiteralExpr .collect( Collectors.toCollection( NodeList::new ) ); // Collect into NodeList args.add( 0, new StringLiteralExpr( "" ) ); // Delimiter @@ -106,7 +117,7 @@ private Expression createArrayJoinMethodCall( List parts ) { * * @return The output String. **/ - private String escape( String s ) { + private static String escape( String s ) { return s.replace( "\\", "\\\\" ) .replace( "\t", "\\t" ) .replace( "\b", "\\b" ) diff --git a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/statement/BoxArgumentDeclarationTransformer.java b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/statement/BoxArgumentDeclarationTransformer.java index 45bcccbca..e0b3e031e 100644 --- a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/statement/BoxArgumentDeclarationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/statement/BoxArgumentDeclarationTransformer.java @@ -27,7 +27,6 @@ import com.github.javaparser.ast.expr.LambdaExpr; import com.github.javaparser.ast.expr.NullLiteralExpr; import com.github.javaparser.ast.expr.ObjectCreationExpr; -import com.github.javaparser.ast.expr.StringLiteralExpr; import com.github.javaparser.ast.stmt.BlockStmt; import com.github.javaparser.ast.stmt.ReturnStmt; import com.github.javaparser.ast.type.ClassOrInterfaceType; @@ -38,6 +37,7 @@ import ortus.boxlang.compiler.javaboxpiler.JavaTranspiler; import ortus.boxlang.compiler.javaboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.javaboxpiler.transformer.TransformerContext; +import ortus.boxlang.compiler.javaboxpiler.transformer.expression.BoxStringLiteralTransformer; /** * Transform a BoxArgumentDeclarationTransformer Node the equivalent Java Parser AST nodes @@ -85,7 +85,7 @@ public Node transform( BoxNode node, TransformerContext context ) throws Illegal // Create the argument list NodeList arguments = new NodeList( new BooleanLiteralExpr( boxArgument.getRequired() ), - new StringLiteralExpr( type ), + BoxStringLiteralTransformer.transform( type ), createKey( boxArgument.getName() ), defaultLiteral, defaultExpression, diff --git a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/statement/BoxScriptTransformer.java b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/statement/BoxScriptTransformer.java index 287a6d9f8..a2a0fa82d 100644 --- a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/statement/BoxScriptTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/statement/BoxScriptTransformer.java @@ -27,7 +27,6 @@ import com.github.javaparser.ast.expr.MethodCallExpr; import com.github.javaparser.ast.expr.NameExpr; import com.github.javaparser.ast.expr.NullLiteralExpr; -import com.github.javaparser.ast.expr.StringLiteralExpr; import com.github.javaparser.ast.stmt.BlockStmt; import com.github.javaparser.ast.stmt.EmptyStmt; import com.github.javaparser.ast.stmt.ExpressionStmt; @@ -46,6 +45,7 @@ import ortus.boxlang.compiler.javaboxpiler.JavaTranspiler; import ortus.boxlang.compiler.javaboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.javaboxpiler.transformer.TransformerContext; +import ortus.boxlang.compiler.javaboxpiler.transformer.expression.BoxStringLiteralTransformer; import ortus.boxlang.runtime.config.util.PlaceholderHelper; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.types.exceptions.ExpressionException; @@ -287,7 +287,7 @@ public Node transform( BoxNode node, TransformerContext context ) throws Illegal for ( Map.Entry entry : transpiler.getKeys().entrySet() ) { MethodCallExpr methodCallExpr = new MethodCallExpr( new NameExpr( "Key" ), "of" ); if ( entry.getValue() instanceof BoxStringLiteral str ) { - methodCallExpr.addArgument( new StringLiteralExpr( str.getValue() ) ); + methodCallExpr.addArgument( BoxStringLiteralTransformer.transform( str.getValue() ) ); } else if ( entry.getValue() instanceof BoxIntegerLiteral id ) { methodCallExpr.addArgument( new IntegerLiteralExpr( id.getValue() ) ); } else { From e3686f36432e3335e11c2625879ee0e755a5d0ab Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Mon, 2 Dec 2024 12:36:47 -0600 Subject: [PATCH 015/193] BL-808 fully enable asm tests --- build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle b/build.gradle index 9571ddf9f..d7b019f6d 100644 --- a/build.gradle +++ b/build.gradle @@ -235,6 +235,8 @@ shadowJar { } test { + systemProperty 'boxlang.experimental.compiler', System.getProperty('boxlang.experimental.compiler', "java") + testLogging { events "FAILED", "STANDARD_ERROR" } From e7490dbac5c66e585063912a8242a0784ca9ac1e Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 2 Dec 2024 14:45:39 -0600 Subject: [PATCH 016/193] BL-788 --- .../application/ApplicationClassListener.java | 11 ++++ .../application/BaseApplicationListener.java | 2 + .../boxlang/runtime/config/Configuration.java | 22 ++++++++ .../runtime/context/RequestBoxContext.java | 13 +++++ .../runtime/loader/resolvers/BoxResolver.java | 54 ++++++++++++++++++- .../ortus/boxlang/runtime/scopes/Key.java | 2 + src/main/resources/config/boxlang.json | 4 +- src/test/java/TestCases/phase3/ClassTest.java | 35 ++++++++++++ 8 files changed, 141 insertions(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/application/ApplicationClassListener.java b/src/main/java/ortus/boxlang/runtime/application/ApplicationClassListener.java index 4a12f411f..86b6d7921 100644 --- a/src/main/java/ortus/boxlang/runtime/application/ApplicationClassListener.java +++ b/src/main/java/ortus/boxlang/runtime/application/ApplicationClassListener.java @@ -24,11 +24,13 @@ import ortus.boxlang.runtime.dynamic.casters.StringCaster; import ortus.boxlang.runtime.runnables.IClassRunnable; import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.Array; import ortus.boxlang.runtime.types.Function; import ortus.boxlang.runtime.types.Struct; import ortus.boxlang.runtime.types.exceptions.AbortException; import ortus.boxlang.runtime.types.util.BLCollector; import ortus.boxlang.runtime.util.EncryptionUtil; +import ortus.boxlang.runtime.util.FileSystemUtil; /** * I represent an Application listener that wraps an Application class instance, delegting to it, where possible and providing default @@ -57,6 +59,15 @@ public ApplicationClassListener( IClassRunnable listener, RequestBoxContext cont this.settings.put( Key.source, listener.getRunnablePath().absolutePath().toString() ); this.settings.put( Key._CLASS, listener.getRunnablePath().absolutePath().toString() ); + // Expand classPaths in Application.bx. They will be relative to the Application.bx file if not starting with / + Array classPaths = this.settings.getAsArray( Key.classPaths ); + classPaths = classPaths.stream() + .map( String::valueOf ) + .map( ( cp ) -> { + return FileSystemUtil.expandPath( context, cp, listener.getRunnablePath() ); + + } ).collect( BLCollector.toArray() ); + // If there is no application name or if it's empty, make one up. String appName = StringCaster.cast( this.settings.get( Key._NAME ) ); if ( appName == null || appName.isBlank() ) { diff --git a/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java b/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java index 103b6d91c..363e55168 100644 --- a/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java +++ b/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java @@ -127,6 +127,8 @@ public abstract class BaseApplicationListener { "clientStorage", "cookie", "clientTimeout", 1, // END: CLIENT + "classPaths", new Array(), + // TODO: move this logic to compat "componentPaths", new Array(), "customTagPaths", new Array(), "datasource", runtime.getConfiguration().defaultDatasource, diff --git a/src/main/java/ortus/boxlang/runtime/config/Configuration.java b/src/main/java/ortus/boxlang/runtime/config/Configuration.java index a0ae2e536..fa7a96edc 100644 --- a/src/main/java/ortus/boxlang/runtime/config/Configuration.java +++ b/src/main/java/ortus/boxlang/runtime/config/Configuration.java @@ -187,6 +187,11 @@ public class Configuration implements IConfigSegment { public List customTagsDirectory = new ArrayList<>( Arrays.asList( BoxRuntime.getInstance().getRuntimeHome().toString() + "/customTags" ) ); + /** + * An array of directories where box classes are located and loaded from. + */ + public List classPaths = new ArrayList<>(); + /** * An array of directories where jar files will be loaded from at runtime. */ @@ -421,6 +426,22 @@ public Configuration process( IStruct config ) { } } + // process classPaths directories + if ( config.containsKey( Key.classPaths ) ) { + if ( config.get( Key.classPaths ) instanceof List castedList ) { + // iterate and add to the original list if it doesn't exist + castedList.forEach( item -> { + var resolvedItem = PlaceholderHelper.resolve( item ); + // Verify or add the path + if ( !this.classPaths.contains( resolvedItem ) ) { + this.classPaths.add( resolvedItem ); + } + } ); + } else { + logger.warn( "The [classPaths] configuration is not a JSON Array, ignoring it." ); + } + } + // Process javaLibraryPaths directories if ( config.containsKey( Key.javaLibraryPaths ) ) { if ( config.get( Key.javaLibraryPaths ) instanceof List castedList ) { @@ -862,6 +883,7 @@ public IStruct asStruct() { Key.caches, cachesCopy, Key.classGenerationDirectory, this.classGenerationDirectory, Key.customTagsDirectory, Array.fromList( this.customTagsDirectory ), + Key.classPaths, Array.fromList( this.classPaths ), Key.datasources, datsourcesCopy, Key.debugMode, this.debugMode, Key.defaultCache, this.defaultCache.toStruct(), diff --git a/src/main/java/ortus/boxlang/runtime/context/RequestBoxContext.java b/src/main/java/ortus/boxlang/runtime/context/RequestBoxContext.java index 1311a7615..df81f5467 100644 --- a/src/main/java/ortus/boxlang/runtime/context/RequestBoxContext.java +++ b/src/main/java/ortus/boxlang/runtime/context/RequestBoxContext.java @@ -376,6 +376,19 @@ public IStruct getConfig() { config.getAsArray( Key.customTagsDirectory ).addAll( customTagPaths ); } + // Add in classPaths and componentPaths (for CF compat) to the classPaths array + Array classPaths = appSettings.getAsArray( Key.classPaths ); + if ( classPaths != null && !classPaths.isEmpty() ) { + // Expand each path in case it's relative + config.getAsArray( Key.classPaths ).addAll( classPaths ); + } + // TODO: move componentPaths logic to compat + Array componentPaths = appSettings.getAsArray( Key.componentPaths ); + if ( componentPaths != null && !componentPaths.isEmpty() ) { + // Expand each path in case it's relative + config.getAsArray( Key.classPaths ).addAll( componentPaths ); + } + // OTHER OVERRIDES go here // Announce it so modules can do their own overrides and such diff --git a/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java b/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java index 480d63ab5..33ebe61ce 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java +++ b/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java @@ -287,8 +287,12 @@ public Optional findFromLocal( IBoxContext context, String fullyQ // Try to find the class using: // 1. Relative to the current template // 2. A mapping + // 3. Custom tag directory or component/class path return findByRelativeLocation( context, finalSlashName, name, imports, loadClass ) - .or( () -> findByMapping( context, finalSlashName, name, imports, loadClass ) ); + // TODO: both of these method call context.getConfig(), but ideally we just call it once + // For classes found in a lookup directory, it will result in getConfig() being called twice. + .or( () -> findByMapping( context, finalSlashName, name, imports, loadClass ) ) + .or( () -> findByLookupDirectory( context, finalSlashName, name, imports, loadClass ) ); } /** @@ -433,6 +437,54 @@ private Optional findByRelativeLocation( return Optional.empty(); } + /** + * Find a class in lookup directories. This includes custom tag paths and component/class paths + * + * @param context The current context of execution + * @param slashName The name of the class to find using slahes instead of dots + * @param name The original dot notation name of the class to find + * @param imports The list of imports to use + * @param loadClass When false, the class location is returned with informatino about where the class was found, but the class is not loaded and will be null. + * + * @return An Optional of {@link ClassLocation} if found, {@link Optional#empty()} otherwise + */ + private Optional findByLookupDirectory( + IBoxContext context, + String slashName, + String name, + List imports, + boolean loadClass ) { + + List lookupPaths = new ArrayList(); + lookupPaths + .addAll( context.getConfig().getAsArray( Key.customTagsDirectory ).stream().map( String::valueOf ).map( Path::of ).toList() ); + lookupPaths.addAll( context.getConfig().getAsArray( Key.classPaths ).stream().map( String::valueOf ).map( Path::of ).toList() ); + + Optional result = lookupPaths + .stream() + .map( cp -> findExistingPathWithValidExtension( cp, slashName ) ) + .filter( path -> path != null ) + .findFirst(); + + if ( !result.isPresent() ) { + return Optional.empty(); + } + Path foundPath = result.get(); + + ResolvedFilePath newResolvedFilePath = ResolvedFilePath.of( "", "", slashName, foundPath ); + + return Optional.of( new ClassLocation( + newResolvedFilePath.getBoxFQN().getClassName(), + foundPath.toAbsolutePath().toString(), + newResolvedFilePath.getBoxFQN().getPackageString(), + ClassLocator.TYPE_BX, + loadClass ? RunnableLoader.getInstance().loadClass( newResolvedFilePath, context ) : null, + "", + false + ) ); + + } + /** * Find an existing path with a valid extension * diff --git a/src/main/java/ortus/boxlang/runtime/scopes/Key.java b/src/main/java/ortus/boxlang/runtime/scopes/Key.java index a2f10ff3c..1e13fb66d 100644 --- a/src/main/java/ortus/boxlang/runtime/scopes/Key.java +++ b/src/main/java/ortus/boxlang/runtime/scopes/Key.java @@ -165,6 +165,7 @@ public class Key implements Comparable, Serializable { public static final Key childname = Key.of( "childname" ); public static final Key classGenerationDirectory = Key.of( "classGenerationDirectory" ); public static final Key className = Key.of( "className" ); + public static final Key classPaths = Key.of( "classPaths" ); public static final Key cli = Key.of( "cli" ); public static final Key clazz = Key.of( "clazz" ); public static final Key clientCert = Key.of( "clientCert" ); @@ -184,6 +185,7 @@ public class Key implements Comparable, Serializable { public static final Key columnTypeList = Key.of( "columnTypeList" ); public static final Key compiler = Key.of( "compiler" ); public static final Key component = Key.of( "component" ); + public static final Key componentPaths = Key.of( "componentPaths" ); public static final Key componentService = Key.of( "componentService" ); public static final Key compression = Key.of( "compression" ); public static final Key condition = Key.of( "condition" ); diff --git a/src/main/resources/config/boxlang.json b/src/main/resources/config/boxlang.json index 77729773b..b35113014 100644 --- a/src/main/resources/config/boxlang.json +++ b/src/main/resources/config/boxlang.json @@ -68,6 +68,8 @@ "customTagsDirectory": [ "${boxlang-home}/customTags" ], + // A collection of directories to lookup box classes in (.bx files), they must be absolute paths + "classPaths": [], // A collection of directories we will class load all Java *.jar files from "javaLibraryPaths": [ "${boxlang-home}/lib" @@ -406,4 +408,4 @@ // } // } } -} +} \ No newline at end of file diff --git a/src/test/java/TestCases/phase3/ClassTest.java b/src/test/java/TestCases/phase3/ClassTest.java index 70b6979b8..d1a1126c3 100644 --- a/src/test/java/TestCases/phase3/ClassTest.java +++ b/src/test/java/TestCases/phase3/ClassTest.java @@ -392,6 +392,41 @@ public void testBasicClassFile() { // @formatter:on } + @DisplayName( "basic class file via component path" ) + @Test + public void testBasicClassFileViaComponentPath() { + // @formatter:off + instance.executeSource( + """ + newClassPaths = getApplicationMetadata().classPaths.append( expandPath( "/src/test/java/TestCases/phase3" ) ); + application classPaths=newClassPaths; + cfc = new MyClass(); + // execute public method + result = cfc.foo(); + + assert result == "I work! whee true true bar true"; + """, context ); + // @formatter:on + } + + @DisplayName( "basic class file via component path subdir" ) + @Test + public void testBasicClassFileViaComponentPathSubDir() { + // @formatter:off + instance.executeSource( + """ + newClassPaths = getApplicationMetadata().classPaths.append( expandPath( "/src/test/java/TestCases" ) ); + application classPaths=newClassPaths; + import phase3.MyClass as bradClass; + cfc = new bradClass(); + // execute public method + result = cfc.foo(); + + assert result == "I work! whee true true bar true"; + """, context ); + // @formatter:on + } + @DisplayName( "legacy meta" ) @Test public void testlegacyMeta() { From 66c7aa6ec3dcbbfe263845dd425c07c95962ab32 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 2 Dec 2024 21:48:19 +0100 Subject: [PATCH 017/193] BL-784 WIP --- .../java/ortus/boxlang/runtime/util/FQN.java | 34 +-- .../boxlang/runtime/util/RegexBuilder.java | 213 ++++++++++++++++++ 2 files changed, 232 insertions(+), 15 deletions(-) create mode 100644 src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java diff --git a/src/main/java/ortus/boxlang/runtime/util/FQN.java b/src/main/java/ortus/boxlang/runtime/util/FQN.java index 434270319..1d3558f90 100644 --- a/src/main/java/ortus/boxlang/runtime/util/FQN.java +++ b/src/main/java/ortus/boxlang/runtime/util/FQN.java @@ -22,6 +22,8 @@ import java.util.HashSet; import java.util.Set; +import org.apache.commons.lang3.StringUtils; + /** * This class represents a java fully qualified name (FQN) for a class or package. * It handles all the edge cases of dealing with file paths and package names. @@ -186,8 +188,11 @@ protected String[] parseParts( String fqn, boolean allPackage ) { fqn = normalizeDots( fqn ); // Remove any non alpha-numeric chars. - fqn = fqn.replaceAll( "[^a-zA-Z0-9$\\.]", "__" ); + fqn = RegexBuilder + .of( fqn, RegexBuilder.PACKAGE_NAMES ) + .replaceAllAndGet( "__" ); + // Short circuit if empty if ( fqn.isEmpty() ) { return new String[] {}; } @@ -214,7 +219,7 @@ protected String[] parseParts( String fqn, boolean allPackage ) { // parse fqn into array, loop over array and clean/normalize parts return Arrays.stream( fqn.split( "\\." ) ) // if starts with number, prefix with _ - .map( s -> s.matches( "^\\d.*" ) ? "_" + s : s ) + .map( s -> RegexBuilder.of( s, RegexBuilder.STARTS_WITH_DIGIT ).matches() ? "_" + s : s ) .map( s -> { if ( RESERVED_WORDS.contains( s ) ) { return "_" + s; @@ -229,18 +234,20 @@ protected String[] parseParts( String fqn, boolean allPackage ) { * - Remove any double dots. * - Trim trailing period. * - Trim leading period. - * + * * @param fqn The string to normalize. - * + * * @return The normalized string. */ protected String normalizeDots( String fqn ) { // Replace .. with . - fqn = fqn.replaceAll( "\\.\\.", "." ); + fqn = RegexBuilder.of( fqn, RegexBuilder.TWO_DOTS ).replaceAllAndGet( "." ); + // trim trailing period if ( fqn.endsWith( "." ) ) { fqn = fqn.substring( 0, fqn.length() - 1 ); } + // trim leading period if ( fqn.startsWith( "." ) ) { fqn = fqn.substring( 1 ); @@ -276,17 +283,14 @@ protected String parseFromFile( Path file ) { fqn = fqn.substring( 0, fqn.length() - 1 ); } - // Take out periods in folder names - fqn = fqn.replaceAll( "\\.", "" ); - // Replace / with . - fqn = fqn.replaceAll( "/", "." ); - // Remove any : from Windows drives - fqn = fqn.replaceAll( ":", "_" ); - // Replace \ with . - fqn = fqn.replaceAll( "\\\\", "." ); - + // Replace all periods with an emtpy string in fqn + fqn = StringUtils.remove( fqn, "." ); + // Take out slashes to . and backslashes to . + fqn = StringUtils.replace( fqn, "/", "." ); + fqn = StringUtils.replace( fqn, "\\", "." ); + // Take out colons to _ + fqn = StringUtils.replace( fqn, ":", "_" ); return fqn; - } /** diff --git a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java new file mode 100644 index 000000000..cf5b5be84 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java @@ -0,0 +1,213 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.util; + +import java.util.Objects; +import java.util.regex.Pattern; + +/** + * This class can be used as a utility to handle regular expressions. + * Add as many compiled patterns as needed and make sure you give them a meaningful name. + */ +public class RegexBuilder { + + /** + * Pattern Dictionary + * Add as many patterns as needed, but make sure they are in all caps and alphabetically ordered. + */ + public static final Pattern BACKSLASH = Pattern.compile( "\\\\" ); + public static final Pattern COLON = Pattern.compile( ":" ); + public static final Pattern PACKAGE_NAMES = Pattern.compile( "[^a-zA-Z0-9$\\.]" ); + public static final Pattern PERIOD = Pattern.compile( "\\." ); + public static final Pattern SLASH = Pattern.compile( "/" ); + public static final Pattern STARTS_WITH_DIGIT = Pattern.compile( "^\\d.*" ); + public static final Pattern TWO_DOTS = Pattern.compile( "\\.{2}" ); + public static final Pattern WHITESPACE = Pattern.compile( "\\s" ); + + /** + * Build a matcher for the given pattern lookup + * + * @param input The input string to match against + * + * @return A new matcher instance + */ + public static RegexMatcher of( String input ) { + return new RegexMatcher( input ); + } + + /** + * Build a matcher for the given input and string pattern + * + * @param input The input string to match against + * @param pattern The pattern to match against + * + * @return A new matcher instance + */ + public static RegexMatcher of( String input, String pattern ) { + return new RegexMatcher( input ).match( pattern ); + } + + /** + * Build a matcher for the given input and string pattern + * + * @param input The input string to match against + * @param pattern The pattern to match against + * + * @return A new matcher instance + */ + public static RegexMatcher of( String input, Pattern pattern ) { + return new RegexMatcher( input ).match( pattern ); + } + + /** + * The RegexMatcher class is used to match a pattern against an input string + * by either using a pre-complied pattern or a string pattern that will be compiled on the fly. + */ + public static class RegexMatcher { + + private Pattern pattern; + private String input; + private final String original; + + /** + * Create a new matcher instance + * + * @param input The input string to match against + */ + private RegexMatcher( String input ) { + this.input = input; + this.original = input; + } + + /** + * Compile the pattern from the given pattern + * + * @param pattern The pattern to compile + * + * @return The matcher instance + */ + public RegexMatcher match( Pattern pattern ) { + this.pattern = pattern; + return this; + } + + /** + * Compile the pattern from the given string + * + * @param pattern The pattern to compile + * + * @return The matcher instance + */ + public RegexMatcher match( String pattern ) { + Objects.requireNonNull( pattern, "Pattern cannot be null" ); + if ( pattern.isEmpty() ) { + throw new IllegalArgumentException( "Pattern cannot be empty" ); + } + this.pattern = Pattern.compile( pattern ); + return this; + } + + /** + * Check if the input string matches the pattern + * + * @return True if the input string matches the pattern, false otherwise + */ + public Boolean matches() { + return this.pattern.matcher( this.input ).matches(); + } + + /** + * Replace all occurrences of the pattern in the input string with the replacement string + * + * @param replacement The replacement string + * + * @return The input string with all occurrences of the pattern replaced with the replacement string + */ + public RegexMatcher replaceAll( String replacement ) { + Objects.requireNonNull( replacement, "Replacement cannot be null" ); + if ( replacement.isEmpty() ) { + throw new IllegalArgumentException( "Replacement cannot be empty" ); + } + this.input = this.pattern + .matcher( this.input ) + .replaceAll( replacement ); + + return this; + } + + /** + * Replace all occurrences of the pattern in the input string with the replacement string + * + * @param pattern The pattern to match against + * @param replacement The replacement string + * + * @return The input string with all occurrences of the pattern replaced with the replacement string + */ + public RegexMatcher replaceAll( Pattern pattern, String replacement ) { + Objects.requireNonNull( pattern, "Pattern cannot be null" ); + Objects.requireNonNull( replacement, "Replacement cannot be null" ); + this.input = pattern + .matcher( this.input ) + .replaceAll( replacement ); + return this; + } + + /** + * Replace all occurrences of the pattern in the input string with the replacement string + * + * @param replacement The replacement string + * + * @return The input string with all occurrences of the pattern replaced with the replacement string + */ + public String replaceAllAndGet( String replacement ) { + return this.replaceAll( replacement ).get(); + } + + /** + * Replace all occurrences of the pattern in the input string with the replacement string + * + * @param replacement The replacement string + * + * @return The input string with all occurrences of the pattern replaced with the replacement string + */ + public String replaceAllAndGet( Pattern pattern, String replacement ) { + return this.replaceAll( pattern, replacement ).get(); + } + + /** + * Get's the input string as modified by the matcher + * + * @return The input string + */ + public String get() { + return this.input; + } + + /** + * Reset the input string to the original value + * + * @return The matcher instance + */ + public RegexMatcher reset() { + this.input = this.original; + return this; + } + + } + +} From da062b343c0499ca75275c39810784cbb7c46d7e Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 2 Dec 2024 21:59:14 +0100 Subject: [PATCH 018/193] BL-784 validation done --- src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java | 3 +++ src/main/java/ortus/boxlang/runtime/util/ValidationUtil.java | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java index cf5b5be84..684eb4745 100644 --- a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java +++ b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java @@ -32,6 +32,9 @@ public class RegexBuilder { */ public static final Pattern BACKSLASH = Pattern.compile( "\\\\" ); public static final Pattern COLON = Pattern.compile( ":" ); + // cardNumber contains characters other than digit, space, or underscore + public static final Pattern CREDIT_CARD_NUMBERS = Pattern.compile( "[0-9 ,_-]+" ); + public static final Pattern NO_DIGITS = Pattern.compile( "\\D" ); public static final Pattern PACKAGE_NAMES = Pattern.compile( "[^a-zA-Z0-9$\\.]" ); public static final Pattern PERIOD = Pattern.compile( "\\." ); public static final Pattern SLASH = Pattern.compile( "/" ); diff --git a/src/main/java/ortus/boxlang/runtime/util/ValidationUtil.java b/src/main/java/ortus/boxlang/runtime/util/ValidationUtil.java index 53561c0d8..0e8f453fa 100644 --- a/src/main/java/ortus/boxlang/runtime/util/ValidationUtil.java +++ b/src/main/java/ortus/boxlang/runtime/util/ValidationUtil.java @@ -103,11 +103,11 @@ public class ValidationUtil { public static boolean isValidCreditCard( String cardNumber ) { int shortestValidCardLength = 12; int longestValidCardLength = 19; - if ( cardNumber == null || !cardNumber.matches( "[0-9 ,_-]+" ) ) { + if ( cardNumber == null || !RegexBuilder.of( cardNumber, RegexBuilder.CREDIT_CARD_NUMBERS ).matches() ) { // cardNumber contains characters other than digit, space, or underscore return false; } - String sanitized = cardNumber.replaceAll( "[^0-9]", "" ); + String sanitized = RegexBuilder.of( cardNumber, RegexBuilder.NO_DIGITS ).replaceAllAndGet( "" ); if ( sanitized.length() < shortestValidCardLength || sanitized.length() > longestValidCardLength ) { return false; } From 865dd6fdc40f3e9857ecf4590f15b0728abae474 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 2 Dec 2024 22:18:28 +0100 Subject: [PATCH 019/193] BL-784 regex performance tuning on runtime --- .../boxlang/runtime/modules/ModuleRecord.java | 10 ++++----- .../runtime/services/FunctionService.java | 5 ++++- .../ortus/boxlang/runtime/types/Array.java | 9 ++++---- .../ortus/boxlang/runtime/types/Struct.java | 3 ++- .../runtime/types/StructMapWrapper.java | 3 ++- .../runtime/types/util/StringUtil.java | 15 ++++++------- .../boxlang/runtime/util/RegexBuilder.java | 21 ++++++++++--------- 7 files changed, 37 insertions(+), 29 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java b/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java index 05b0a6c65..af8d0fbc6 100644 --- a/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java +++ b/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java @@ -57,7 +57,6 @@ import ortus.boxlang.runtime.loader.DynamicClassLoader; import ortus.boxlang.runtime.runnables.IClassRunnable; import ortus.boxlang.runtime.runnables.RunnableLoader; -import ortus.boxlang.runtime.types.Argument; import ortus.boxlang.runtime.scopes.ArgumentsScope; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.scopes.ThisScope; @@ -67,6 +66,7 @@ import ortus.boxlang.runtime.services.IService; import ortus.boxlang.runtime.services.InterceptorService; import ortus.boxlang.runtime.services.ModuleService; +import ortus.boxlang.runtime.types.Argument; import ortus.boxlang.runtime.types.Array; import ortus.boxlang.runtime.types.BoxLangType; import ortus.boxlang.runtime.types.DynamicFunction; @@ -304,7 +304,7 @@ public ModuleRecord loadDescriptor( IBoxContext context ) { ResolvedFilePath.of( null, null, - packageName.replaceAll( "\\.", Matcher.quoteReplacement( File.separator ) ) + File.separator + ModuleService.MODULE_DESCRIPTOR, + packageName.replace( ".", Matcher.quoteReplacement( File.separator ) ) + File.separator + ModuleService.MODULE_DESCRIPTOR, descriptorPath ), context @@ -975,7 +975,7 @@ private Array discoverMemberMethods( IClassRunnable targetBIF, Key className ) { return Array.of( Struct.of( // Default member name for class ArrayFoo with BoxType of Array is just foo() - Key._NAME, className.getNameNoCase().replaceAll( boxType.getKey().getNameNoCase(), "" ), + Key._NAME, className.getNameNoCase().replace( boxType.getKey().getNameNoCase(), "" ), Key.objectArgument, "", Key.type, boxType ) @@ -1009,7 +1009,7 @@ private Array discoverMemberMethods( IClassRunnable targetBIF, Key className ) { // Prepare the record now BoxLangType boxType = BoxLangType.valueOf( type.getNameNoCase() ); memberRecord.put( Key.type, boxType ); - memberRecord.computeIfAbsent( Key._NAME, k -> className.getNameNoCase().replaceAll( type.getNameNoCase(), "" ) + memberRecord.computeIfAbsent( Key._NAME, k -> className.getNameNoCase().replace( type.getNameNoCase(), "" ) ); memberRecord.putIfAbsent( Key.objectArgument, "" ); result.push( memberRecord ); @@ -1075,7 +1075,7 @@ private IClassRunnable loadClassRunnable( File targetFile, String conventionsPat null, null, ( this.invocationPath + "." + conventionsPath ) - .replaceAll( "\\.", Matcher.quoteReplacement( File.separator ) ) + .replace( ".", Matcher.quoteReplacement( File.separator ) ) + File.separator + FilenameUtils.getBaseName( targetFile.getAbsolutePath() ), targetFile.toPath() diff --git a/src/main/java/ortus/boxlang/runtime/services/FunctionService.java b/src/main/java/ortus/boxlang/runtime/services/FunctionService.java index baa593dee..05f976a42 100644 --- a/src/main/java/ortus/boxlang/runtime/services/FunctionService.java +++ b/src/main/java/ortus/boxlang/runtime/services/FunctionService.java @@ -24,6 +24,7 @@ import java.util.ServiceLoader; import java.util.concurrent.ConcurrentHashMap; +import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -408,7 +409,9 @@ public void processBIFRegistration( Class BIFClass, BIF function, String modu Key memberKey; if ( member.name().equals( "" ) ) { // Default member name for class ArrayFoo with BoxType of Array is just foo() - memberKey = Key.of( className.toLowerCase().replaceAll( member.type().name().toLowerCase(), "" ) ); + memberKey = Key.of( + StringUtils.replace( className.toLowerCase(), member.type().name().toLowerCase(), "" ) + ); } else { memberKey = Key.of( member.name() ); } diff --git a/src/main/java/ortus/boxlang/runtime/types/Array.java b/src/main/java/ortus/boxlang/runtime/types/Array.java index b3f7fe158..384aa6390 100644 --- a/src/main/java/ortus/boxlang/runtime/types/Array.java +++ b/src/main/java/ortus/boxlang/runtime/types/Array.java @@ -57,6 +57,7 @@ import ortus.boxlang.runtime.types.meta.IListenable; import ortus.boxlang.runtime.types.unmodifiable.UnmodifiableArray; import ortus.boxlang.runtime.types.util.BLCollector; +import ortus.boxlang.runtime.util.RegexBuilder; /** * The primary array class in BoxLang. This class wraps a Java List and provides additional functionality for BoxLang. @@ -286,7 +287,7 @@ public List toList() { /** * Because toList() can't be called from BL code due to the arrayToList() BIF - * + * * @return */ public List asList() { @@ -546,7 +547,7 @@ public String asString() { sb.append( "[\n " ); sb.append( wrapped.stream() .map( value -> ( value instanceof IType t ? t.asString() : ( value == null ? "[null]" : value.toString() ) ) ) - .map( line -> line.replaceAll( "(?m)^", " " ) ) // Add an indent to the start of each line + .map( line -> RegexBuilder.of( line, RegexBuilder.MULTILINE_START_OF_LINE ).replaceAllAndGet( " " ) ) // Add an indent to the start of each line .collect( java.util.stream.Collectors.joining( ",\n" ) ) ); sb.append( "\n]" ); return sb.toString(); @@ -988,10 +989,10 @@ public static int validateAndGetIntForAssign( Key key, int size, boolean isNativ /** * Get an integer from a key, returning null if the key is not an integer unless safe is false. - * + * * @param key The key to get the integer from * @param safe Whether to return null if the key is not an integer - * + * * @return The integer or null */ public static Integer getIntFromKey( Key key, boolean safe ) { diff --git a/src/main/java/ortus/boxlang/runtime/types/Struct.java b/src/main/java/ortus/boxlang/runtime/types/Struct.java index bd5a033da..006cf2d75 100644 --- a/src/main/java/ortus/boxlang/runtime/types/Struct.java +++ b/src/main/java/ortus/boxlang/runtime/types/Struct.java @@ -54,6 +54,7 @@ import ortus.boxlang.runtime.types.meta.IListenable; import ortus.boxlang.runtime.types.meta.StructMeta; import ortus.boxlang.runtime.types.unmodifiable.UnmodifiableStruct; +import ortus.boxlang.runtime.util.RegexBuilder; /** * This type provides the core map class for Boxlang. Structs are highly versatile and are used for organizing and managing related data. @@ -776,7 +777,7 @@ public String asString() { } return line; } ) - .map( line -> line.replaceAll( "(?m)^", " " ) ) // Add an indent to the start of each line + .map( line -> RegexBuilder.of( line, RegexBuilder.MULTILINE_START_OF_LINE ).replaceAllAndGet( " " ) ) // Add an indent to the start of each line .collect( java.util.stream.Collectors.joining( ",\n" ) ) ); sb.append( size() > 0 ? "\n}" : "}" ); return sb.toString(); diff --git a/src/main/java/ortus/boxlang/runtime/types/StructMapWrapper.java b/src/main/java/ortus/boxlang/runtime/types/StructMapWrapper.java index 31635e54e..74a11f740 100644 --- a/src/main/java/ortus/boxlang/runtime/types/StructMapWrapper.java +++ b/src/main/java/ortus/boxlang/runtime/types/StructMapWrapper.java @@ -46,6 +46,7 @@ import ortus.boxlang.runtime.types.meta.IListenable; import ortus.boxlang.runtime.types.meta.StructMeta; import ortus.boxlang.runtime.types.unmodifiable.UnmodifiableStruct; +import ortus.boxlang.runtime.util.RegexBuilder; /** * I wrap a Map to allow it to be used as a Struct, but without needing to make a copy of the original Map. @@ -564,7 +565,7 @@ public String asString() { } return line; } ) - .map( line -> line.replaceAll( "(?m)^", " " ) ) // Add an indent to the start of each line + .map( line -> RegexBuilder.of( line, RegexBuilder.MULTILINE_START_OF_LINE ).replaceAllAndGet( " " ) ) // Add an indent to the start of each line .collect( java.util.stream.Collectors.joining( ",\n" ) ) ); sb.append( size() > 0 ? "\n}" : "}" ); return sb.toString(); diff --git a/src/main/java/ortus/boxlang/runtime/types/util/StringUtil.java b/src/main/java/ortus/boxlang/runtime/types/util/StringUtil.java index f62c15823..be4ba7f34 100644 --- a/src/main/java/ortus/boxlang/runtime/types/util/StringUtil.java +++ b/src/main/java/ortus/boxlang/runtime/types/util/StringUtil.java @@ -22,6 +22,8 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; +import org.apache.commons.lang3.StringUtils; + import com.fasterxml.jackson.jr.ob.JSON; /** @@ -100,15 +102,14 @@ public static String slugify( String str ) { */ public static String slugify( String str, int maxLength, String allow ) { // Replace multiple spaces with a single space - String slug = str.trim().toLowerCase() - .replace( "\\s+", "-" ) - .replaceAll( "ä", "ae" ) - .replaceAll( "ü", "ue" ) - .replaceAll( "ö", "oe" ) - .replaceAll( "ß", "ss" ); + String slug = str.trim().toLowerCase().replace( "\\s+", "-" ); + + // Strip accents + slug = StringUtils.stripAccents( slug ); + slug = StringUtils.replace( slug, "ß", "ss" ); // More cleanup - slug = slug.toLowerCase().replaceAll( "[^a-z0-9" + allow + "]", "-" ).replaceAll( "-+", "-" ); + slug = slug.replaceAll( "[^a-z0-9" + allow + "]", "-" ).replaceAll( "-+", "-" ); // is there a max length restriction if ( maxLength != 0 && slug.length() > maxLength ) { diff --git a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java index 684eb4745..0a34465fb 100644 --- a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java +++ b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java @@ -30,17 +30,18 @@ public class RegexBuilder { * Pattern Dictionary * Add as many patterns as needed, but make sure they are in all caps and alphabetically ordered. */ - public static final Pattern BACKSLASH = Pattern.compile( "\\\\" ); - public static final Pattern COLON = Pattern.compile( ":" ); + public static final Pattern BACKSLASH = Pattern.compile( "\\\\" ); + public static final Pattern COLON = Pattern.compile( ":" ); // cardNumber contains characters other than digit, space, or underscore - public static final Pattern CREDIT_CARD_NUMBERS = Pattern.compile( "[0-9 ,_-]+" ); - public static final Pattern NO_DIGITS = Pattern.compile( "\\D" ); - public static final Pattern PACKAGE_NAMES = Pattern.compile( "[^a-zA-Z0-9$\\.]" ); - public static final Pattern PERIOD = Pattern.compile( "\\." ); - public static final Pattern SLASH = Pattern.compile( "/" ); - public static final Pattern STARTS_WITH_DIGIT = Pattern.compile( "^\\d.*" ); - public static final Pattern TWO_DOTS = Pattern.compile( "\\.{2}" ); - public static final Pattern WHITESPACE = Pattern.compile( "\\s" ); + public static final Pattern CREDIT_CARD_NUMBERS = Pattern.compile( "[0-9 ,_-]+" ); + public static final Pattern MULTILINE_START_OF_LINE = Pattern.compile( "(?m)^" ); + public static final Pattern NO_DIGITS = Pattern.compile( "\\D" ); + public static final Pattern PACKAGE_NAMES = Pattern.compile( "[^a-zA-Z0-9$\\.]" ); + public static final Pattern PERIOD = Pattern.compile( "\\." ); + public static final Pattern SLASH = Pattern.compile( "/" ); + public static final Pattern STARTS_WITH_DIGIT = Pattern.compile( "^\\d.*" ); + public static final Pattern TWO_DOTS = Pattern.compile( "\\.{2}" ); + public static final Pattern WHITESPACE = Pattern.compile( "\\s" ); /** * Build a matcher for the given pattern lookup From 7e5f5562cc54c9869b2801d2dc80f610472defc3 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 2 Dec 2024 22:19:13 +0100 Subject: [PATCH 020/193] making sure replacement can be empty but not null BL-784 --- src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java index 0a34465fb..1981bd8d3 100644 --- a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java +++ b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java @@ -144,9 +144,6 @@ public Boolean matches() { */ public RegexMatcher replaceAll( String replacement ) { Objects.requireNonNull( replacement, "Replacement cannot be null" ); - if ( replacement.isEmpty() ) { - throw new IllegalArgumentException( "Replacement cannot be empty" ); - } this.input = this.pattern .matcher( this.input ) .replaceAll( replacement ); From bff250d91bcfad735ae6618541825f17b20026ca Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 3 Dec 2024 13:21:34 -0600 Subject: [PATCH 021/193] BL-788 --- src/test/java/TestCases/phase3/ClassTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/TestCases/phase3/ClassTest.java b/src/test/java/TestCases/phase3/ClassTest.java index d1a1126c3..05a45aef2 100644 --- a/src/test/java/TestCases/phase3/ClassTest.java +++ b/src/test/java/TestCases/phase3/ClassTest.java @@ -404,7 +404,6 @@ public void testBasicClassFileViaComponentPath() { // execute public method result = cfc.foo(); - assert result == "I work! whee true true bar true"; """, context ); // @formatter:on } From d921aa0a5575563bc8035056973ca43e4ca9ab7a Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 3 Dec 2024 13:49:16 -0600 Subject: [PATCH 022/193] BL-784 --- src/test/java/ortus/boxlang/runtime/util/StringUtilTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/ortus/boxlang/runtime/util/StringUtilTest.java b/src/test/java/ortus/boxlang/runtime/util/StringUtilTest.java index 5f8eafd2a..44ed8c9ca 100644 --- a/src/test/java/ortus/boxlang/runtime/util/StringUtilTest.java +++ b/src/test/java/ortus/boxlang/runtime/util/StringUtilTest.java @@ -39,7 +39,7 @@ void testSlug() { @Test void testSlugWithSpecialChars() { String slug = StringUtil.slugify( "This is ä ü test ß" ); - assertThat( slug ).isEqualTo( "this-is-ae-ue-test-ss" ); + assertThat( slug ).isEqualTo( "this-is-a-u-test-ss" ); } @DisplayName( "Can pretty print sql" ) From ccafd43c7034c3474979870e9df7cfca57eafb04 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Tue, 3 Dec 2024 22:30:22 -0600 Subject: [PATCH 023/193] Add support for XML Dump templates and structKeyExists on XML special keys --- .../java/ortus/boxlang/runtime/types/XML.java | 66 +++++++++++++++++-- .../ortus/boxlang/runtime/util/DumpUtil.java | 3 + src/main/resources/dump/html/XML.bxm | 52 +++++++++++++++ .../ortus/boxlang/runtime/types/XMLTest.java | 19 ++++++ 4 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 src/main/resources/dump/html/XML.bxm diff --git a/src/main/java/ortus/boxlang/runtime/types/XML.java b/src/main/java/ortus/boxlang/runtime/types/XML.java index 66bcd22cc..dd36cfb6c 100644 --- a/src/main/java/ortus/boxlang/runtime/types/XML.java +++ b/src/main/java/ortus/boxlang/runtime/types/XML.java @@ -23,6 +23,7 @@ import java.io.StringWriter; import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -340,13 +341,65 @@ public String getXMLType() { } } + public Set getReferencableKeys() { + return getReferencableKeys( true ); + } + + public Set getReferencableKeys( boolean withChildren ) { + Set keys = new HashSet<>(); + switch ( node.getNodeType() ) { + case Node.DOCUMENT_NODE : + keys.add( Key.XMLRoot ); + keys.add( Key.XMLComment ); + return keys; + case Node.ELEMENT_NODE : + keys.add( Key.XMLText ); + keys.add( Key.XMLChildren ); + keys.add( Key.XMLAttributes ); + keys.add( Key.XMLNsPrefix ); + keys.add( Key.XMLNsURI ); + keys.add( Key.XMLComment ); + if ( withChildren ) { + keys.addAll( getXMLChildrenAsList().stream().map( XML::getXMLName ).map( Key::of ).collect( Collectors.toSet() ) ); + } + return keys; + case Node.ATTRIBUTE_NODE : + return getXMLAttributes().keySet(); + case Node.TEXT_NODE : + return keys; + case Node.CDATA_SECTION_NODE : + return keys; + case Node.ENTITY_REFERENCE_NODE : + return keys; + case Node.ENTITY_NODE : + return keys; + case Node.PROCESSING_INSTRUCTION_NODE : + return keys; + case Node.COMMENT_NODE : + return keys; + case Node.DOCUMENT_TYPE_NODE : + return keys; + case Node.DOCUMENT_FRAGMENT_NODE : + return keys; + case Node.NOTATION_NODE : + return keys; + default : + return keys; + } + } + + public Object getDumpRepresentation() { + IStruct dump = new Struct(); + getReferencableKeys( false ).stream().forEach( key -> dump.put( key, dereference( null, key, true ) ) ); + return dump; + } + /*************************** * IReferencable implementation ****************************/ @Override public Object dereference( IBoxContext context, Key name, Boolean safe ) { - // Special check for $bx if ( name.equals( BoxMeta.key ) ) { return getBoxMeta(); @@ -577,7 +630,7 @@ public Object get( Object key ) { @Override public Object getRaw( Key key ) { - return getFirstChildOfName( key.getName() ); + return dereference( null, key, true ); } @Override @@ -597,7 +650,7 @@ public boolean isEmpty() { @Override public boolean containsKey( Object key ) { - return getFirstChildOfName( KeyCaster.cast( key ).getName() ) != null; + return containsKey( KeyCaster.cast( key ) ); } @Override @@ -654,7 +707,7 @@ public void clear() { @Override public Set keySet() { - return getXMLChildrenAsList().stream().map( XML::getXMLName ).map( Key::of ).collect( Collectors.toSet() ); + return getReferencableKeys( true ); } @Override @@ -699,12 +752,13 @@ public Object getOrDefault( Key key, Object defaultValue ) { @Override public boolean containsKey( String key ) { - return getFirstChildOfName( key ) != null; + return containsKey( KeyCaster.cast( key ) ); } @Override public boolean containsKey( Key key ) { - return containsKey( key.getName() ); + Set keys = keySet(); + return keys.contains( key ); } @Override diff --git a/src/main/java/ortus/boxlang/runtime/util/DumpUtil.java b/src/main/java/ortus/boxlang/runtime/util/DumpUtil.java index 89a050d8a..60e675a8f 100644 --- a/src/main/java/ortus/boxlang/runtime/util/DumpUtil.java +++ b/src/main/java/ortus/boxlang/runtime/util/DumpUtil.java @@ -60,6 +60,7 @@ import ortus.boxlang.runtime.types.IType; import ortus.boxlang.runtime.types.NullValue; import ortus.boxlang.runtime.types.Struct; +import ortus.boxlang.runtime.types.XML; import ortus.boxlang.runtime.types.exceptions.AbortException; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.types.exceptions.ExceptionUtil; @@ -407,6 +408,8 @@ private static String discoverTemplateName( Object target ) { return "Null.bxm"; } else if ( target instanceof Throwable ) { return "Throwable.bxm"; + } else if ( target instanceof XML ) { + return "XML.bxm"; } else if ( target instanceof Query ) { return "Query.bxm"; } else if ( target instanceof Function ) { diff --git a/src/main/resources/dump/html/XML.bxm b/src/main/resources/dump/html/XML.bxm new file mode 100644 index 000000000..a6ccf4f93 --- /dev/null +++ b/src/main/resources/dump/html/XML.bxm @@ -0,0 +1,52 @@ + + expandRoot = expand ?: true; + + +
+ + + + + + class="d-none" + > + + theCollection = var.getDumpRepresentation(); + for ( key in theCollection ) { + ``` + + + + + + + ``` + } + + +
open aria-expanded="true"aria-expanded="false" + data-bx-toggle="siblings" + > + + #label# - + XML #var.getXMLType()# + +
+ #encodeForHTML( key )# + +
+ +
+
+
+
diff --git a/src/test/java/ortus/boxlang/runtime/types/XMLTest.java b/src/test/java/ortus/boxlang/runtime/types/XMLTest.java index 7e10bf8f8..d750cf870 100644 --- a/src/test/java/ortus/boxlang/runtime/types/XMLTest.java +++ b/src/test/java/ortus/boxlang/runtime/types/XMLTest.java @@ -18,6 +18,7 @@ package ortus.boxlang.runtime.types; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertTrue; import static org.junit.jupiter.api.Assertions.assertEquals; import org.junit.jupiter.api.AfterAll; @@ -267,4 +268,22 @@ void testXMLNodeNamedArray() { } + @DisplayName( "Can check for XMLChildren with StructKeyExists" ) + @Test + void textStructKeyExistsXmlChildren() { + instance.executeSource( + """ + myXML = XMLParse( + ' + + + ' + ); + result = structKeyExists( myXML.xmlRoot, "XmlChildren" ); + """, + context ); + Object r = variables.get( result ); + assertTrue( variables.getAsBoolean( result ) ); + } + } From 8e71a666819d35526378c0ce1a0d5d3733624401 Mon Sep 17 00:00:00 2001 From: Jon Clausen Date: Wed, 4 Dec 2024 10:26:22 -0600 Subject: [PATCH 024/193] add getCompiler method to return the compiler in use --- src/main/java/ortus/boxlang/compiler/BXCompiler.java | 3 +-- src/main/java/ortus/boxlang/runtime/BoxRuntime.java | 7 +++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/BXCompiler.java b/src/main/java/ortus/boxlang/compiler/BXCompiler.java index 658080c3c..b327c40ce 100644 --- a/src/main/java/ortus/boxlang/compiler/BXCompiler.java +++ b/src/main/java/ortus/boxlang/compiler/BXCompiler.java @@ -25,7 +25,6 @@ import java.nio.file.Paths; import java.util.List; -import ortus.boxlang.compiler.javaboxpiler.JavaBoxpiler; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.types.exceptions.ParseException; @@ -181,7 +180,7 @@ public static void compileFile( Path sourcePath, Path targetPath, Boolean stopOn // calculate relative path by replacing the base path with an empty string Path relativePath = basePath.relativize( sourcePath ); // remove file name - bytesList = JavaBoxpiler.getInstance() + bytesList = runtime.getCompiler() .compileTemplateBytes( ResolvedFilePath.of( mapping, basePath.toString(), relativePath.toString(), sourcePath ) ); } catch ( ParseException e ) { if ( stopOnError ) { diff --git a/src/main/java/ortus/boxlang/runtime/BoxRuntime.java b/src/main/java/ortus/boxlang/runtime/BoxRuntime.java index ecb07f506..75072ff15 100644 --- a/src/main/java/ortus/boxlang/runtime/BoxRuntime.java +++ b/src/main/java/ortus/boxlang/runtime/BoxRuntime.java @@ -673,6 +673,13 @@ public static Boolean hasInstance() { return instance != null; } + /** + * Returns the compiler instance to use based on the configuration + */ + public IBoxpiler getCompiler() { + return this.boxpiler; + } + /** * -------------------------------------------------------------------------- * Service Access Methods From d2986a2f5a67438467b1e75798b781b510bb5fb6 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Wed, 4 Dec 2024 10:40:14 -0600 Subject: [PATCH 025/193] BL-806 ASM fixes --- .../compiler/asmboxpiler/AsmHelper.java | 5 +- .../expression/BoxClosureTransformer.java | 26 +- .../expression/BoxContinueTransformer.java | 4 + .../expression/BoxLambdaTransformer.java | 1 + .../expression/BoxReturnTransformer.java | 3 + .../statement/BoxBufferOutputTransformer.java | 3 + .../statement/BoxComponentTransformer.java | 40 +- .../statement/BoxForInTransformer.java | 2 + .../statement/BoxIfElseTransformer.java | 31 +- .../statement/BoxTryTransformer.java | 7 + .../statement/BoxWhileTransformer.java | 35 +- .../java/ortus/boxlang/compiler/Assertion.cfc | 1469 +++++++++++++++++ .../java/ortus/boxlang/compiler/Child.cfc | 6 + .../boxlang/compiler/FileTesterTest.java | 141 ++ .../java/ortus/boxlang/compiler/Parent.cfc | 5 + .../java/ortus/boxlang/compiler/Scheduler.cfc | 20 + .../ortus/boxlang/compiler/SimpleReporter.cfc | 55 + .../java/ortus/boxlang/compiler/Testbox.cfc | 918 ++++++++++ .../java/ortus/boxlang/compiler/template.cfm | 12 + 19 files changed, 2735 insertions(+), 48 deletions(-) create mode 100644 src/test/java/ortus/boxlang/compiler/Assertion.cfc create mode 100644 src/test/java/ortus/boxlang/compiler/Child.cfc create mode 100644 src/test/java/ortus/boxlang/compiler/FileTesterTest.java create mode 100644 src/test/java/ortus/boxlang/compiler/Parent.cfc create mode 100644 src/test/java/ortus/boxlang/compiler/Scheduler.cfc create mode 100644 src/test/java/ortus/boxlang/compiler/SimpleReporter.cfc create mode 100644 src/test/java/ortus/boxlang/compiler/Testbox.cfc create mode 100644 src/test/java/ortus/boxlang/compiler/template.cfm diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java index c20b5447e..8909fb5c5 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java @@ -242,6 +242,7 @@ public static List getDefaultExpression( AsmTranspiler transpi + "$Lambda_" + transpiler.incrementAndGetLambdaCounter() + ";" ); ClassNode classNode = new ClassNode(); + classNode.visitSource( transpiler.getProperty( "filePath" ), null ); classNode.visit( Opcodes.V17, @@ -836,8 +837,8 @@ public static List transformBodyExpressions( Transpiler transp return new ArrayList<>(); } - ReturnValueContext bodyContext = finalReturnValueContext == ReturnValueContext.VALUE_OR_NULL ? ReturnValueContext.EMPTY_UNLESS_JUMPING - : ReturnValueContext.EMPTY; + ReturnValueContext bodyContext = finalReturnValueContext == ReturnValueContext.EMPTY ? ReturnValueContext.EMPTY + : ReturnValueContext.EMPTY_UNLESS_JUMPING; List nodes = statements.stream().limit( statements.size() - 1 ) .flatMap( child -> transpiler.transform( child, context, bodyContext ).stream() ) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxClosureTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxClosureTransformer.java index 8e2216574..f4ab5c9c1 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxClosureTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxClosureTransformer.java @@ -33,6 +33,7 @@ import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.expression.BoxClosure; +import ortus.boxlang.compiler.ast.statement.BoxExpressionStatement; import ortus.boxlang.compiler.ast.statement.BoxStatementBlock; import ortus.boxlang.compiler.parser.BoxSourceType; import ortus.boxlang.runtime.context.FunctionBoxContext; @@ -63,6 +64,7 @@ public List transform( BoxNode node, TransformerContext contex + "$Closure_" + transpiler.incrementAndGetLambdaCounter() + ";" ); ClassNode classNode = new ClassNode(); + classNode.visitSource( transpiler.getProperty( "filePath" ), null ); AsmHelper.init( classNode, true, type, Type.getType( Closure.class ), methodVisitor -> { } ); @@ -145,11 +147,27 @@ public List transform( BoxNode node, TransformerContext contex int componentCounter = transpiler.getComponentCounter(); transpiler.setComponentCounter( 0 ); transpiler.incrementfunctionBodyCounter(); + + ReturnValueContext closureReturnContext = isBlock ? ReturnValueContext.EMPTY : ReturnValueContext.VALUE_OR_NULL; AsmHelper.methodWithContextAndClassLocator( classNode, "_invoke", Type.getType( FunctionBoxContext.class ), Type.getType( Object.class ), false, transpiler, isBlock, - () -> boxClosure.getBody().getChildren().stream() - .flatMap( statement -> transpiler.transform( statement, TransformerContext.NONE, ReturnValueContext.VALUE_OR_NULL ).stream() ) - .toList() ); + () -> { + List bodyNodes = new ArrayList(); + + BoxNode body = boxClosure.getBody(); + + if ( body instanceof BoxExpressionStatement boxExpr ) { + bodyNodes.addAll( transpiler.transform( boxExpr.getExpression(), TransformerContext.NONE, closureReturnContext ) ); + } else { + bodyNodes.addAll( transpiler.transform( body, TransformerContext.NONE, closureReturnContext ) ); + } + + if ( isBlock ) { + bodyNodes.add( 0, new InsnNode( Opcodes.ACONST_NULL ) ); + } + + return bodyNodes; + } ); transpiler.decrementfunctionBodyCounter(); transpiler.setComponentCounter( componentCounter ); @@ -170,7 +188,7 @@ public List transform( BoxNode node, TransformerContext contex Type.getDescriptor( String.class ) ); AsmHelper.array( Type.getType( Argument.class ), - boxClosure.getArgs().stream().map( decl -> transpiler.transform( decl, TransformerContext.NONE ) ).toList() ) + boxClosure.getArgs().stream().map( decl -> transpiler.transform( decl, TransformerContext.NONE, ReturnValueContext.VALUE ) ).toList() ) .forEach( abstractInsnNode -> abstractInsnNode.accept( methodVisitor ) ); methodVisitor.visitFieldInsn( Opcodes.PUTSTATIC, type.getInternalName(), diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxContinueTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxContinueTransformer.java index b1e2e364f..d03dbee01 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxContinueTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxContinueTransformer.java @@ -61,6 +61,10 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); } + // if ( returnContext == ReturnValueContext.VALUE || returnContext == ReturnValueContext.VALUE_OR_NULL || exitsAllowed.equals( ExitsAllowed.FUNCTION ) ) { + // nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); + // } + MethodContextTracker tracker = transpiler.getCurrentMethodContextTracker().get(); BoxNode labelTarget = tracker.getStringLabel( continueNode.getLabel() ); LabelNode currentBreak = tracker.getContinue( labelTarget != null ? labelTarget : getTargetAncestor( continueNode ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxLambdaTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxLambdaTransformer.java index 1429b41cf..4dc08cd85 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxLambdaTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxLambdaTransformer.java @@ -58,6 +58,7 @@ public List transform( BoxNode node, TransformerContext contex + "$Lambda_" + transpiler.incrementAndGetLambdaCounter() + ";" ); ClassNode classNode = new ClassNode(); + classNode.visitSource( transpiler.getProperty( "filePath" ), null ); AsmHelper.init( classNode, true, type, Type.getType( Lambda.class ), methodVisitor -> { } ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxReturnTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxReturnTransformer.java index f02319be8..7c91c1552 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxReturnTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxReturnTransformer.java @@ -47,6 +47,9 @@ public List transform( BoxNode node, TransformerContext contex if ( !transpiler.canReturn() ) { nodes.add( new InsnNode( Opcodes.RETURN ) ); + if ( returnContext.nullable ) { + nodes.add( new InsnNode( Opcodes.ARETURN ) ); + } return nodes; } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxBufferOutputTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxBufferOutputTransformer.java index e984cef7b..a4dbc8230 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxBufferOutputTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxBufferOutputTransformer.java @@ -27,6 +27,7 @@ import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.VarInsnNode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; @@ -59,6 +60,8 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.POP ) ); } + AsmHelper.addDebugLabel( nodes, "BoxBufferOutput - end" ); + return nodes; } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java index 9e24d53d7..6d4b992b0 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java @@ -85,17 +85,17 @@ public List transform( BoxNode node, TransformerContext contex Type.getType( Component.ComponentBody.class ) ), true ) ); - if ( boxComponent.getBody() == null || boxComponent.getBody().size() == 0 ) { - // if ( returnContext != ReturnValueContext.VALUE && returnContext != ReturnValueContext.VALUE_OR_NULL ) { - // nodes.add( new InsnNode( Opcodes.POP ) ); - // } + // if ( boxComponent.getBody() == null || boxComponent.getBody().size() == 0 ) { + // if ( returnContext != ReturnValueContext.VALUE && returnContext != ReturnValueContext.VALUE_OR_NULL ) { + // nodes.add( new InsnNode( Opcodes.POP ) ); + // } - // transpiler.decrementComponentCounter(); + // transpiler.decrementComponentCounter(); - // return nodes; - // TODO: this causes CoreLangTest.unicode - // return AsmHelper.addLineNumberLabels( nodes, node ); - } + // return nodes; + // TODO: this causes CoreLangTest.unicode + // return AsmHelper.addLineNumberLabels( nodes, node ); + // } if ( transpiler.isInsideComponent() ) { LabelNode ifLabel = new LabelNode(); @@ -116,8 +116,13 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.ARETURN ) ); + AsmHelper.addDebugLabel( nodes, "BoxComponent - isInsideComponent ifLabel" ); nodes.add( ifLabel ); + if ( returnContext != ReturnValueContext.VALUE && returnContext != ReturnValueContext.VALUE_OR_NULL ) { + nodes.add( new InsnNode( Opcodes.POP ) ); + } + return nodes; } else if ( transpiler.canReturn() ) { LabelNode ifLabel = new LabelNode(); @@ -134,6 +139,7 @@ public List transform( BoxNode node, TransformerContext contex ) ); + AsmHelper.addDebugLabel( nodes, "BoxComponent - canReturn ifeq ifLabel" ); nodes.add( new JumpInsnNode( Opcodes.IFEQ, ifLabel ) ); nodes.add( @@ -148,15 +154,20 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.ARETURN ) ); + AsmHelper.addDebugLabel( nodes, "BoxComponent - canReturn ifLabel" ); nodes.add( ifLabel ); - } - - if ( returnContext != ReturnValueContext.VALUE && returnContext != ReturnValueContext.VALUE_OR_NULL ) { - nodes.add( new InsnNode( Opcodes.POP ) ); + if ( returnContext != ReturnValueContext.VALUE && returnContext != ReturnValueContext.VALUE_OR_NULL ) { + nodes.add( new InsnNode( Opcodes.POP ) ); + } + } else { + // remove the body result because we decided not to use it. + if ( returnContext != ReturnValueContext.VALUE && returnContext != ReturnValueContext.VALUE_OR_NULL ) { + nodes.add( new InsnNode( Opcodes.POP ) ); + } } // transpiler.decrementComponentCounter(); - + AsmHelper.addDebugLabel( nodes, "BoxComponent - done" ); return nodes; // TODO: this causes CoreLangTest.unicode // return AsmHelper.addLineNumberLabels( nodes, node ); @@ -193,6 +204,7 @@ private Type defineBodyLambdaClass( List body ) { + "$ComponentBodyLambda_" + transpiler.incrementAndGetLambdaCounter() + ";" ); ClassNode classNode = new ClassNode(); + classNode.visitSource( transpiler.getProperty( "filePath" ), null ); AsmHelper.init( classNode, false, type, Type.getType( Object.class ), methodVisitor -> { }, Type.getType( Component.ComponentBody.class ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java index 7028aa960..bf01d05a2 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java @@ -253,6 +253,8 @@ public List transform( BoxNode node, TransformerContext contex ) ); nodes.add( unRegisterQueryLabel ); + AsmHelper.addDebugLabel( nodes, "BoxForIn - end" ); + return nodes; } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxIfElseTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxIfElseTransformer.java index 4c08827c8..c49b1bce5 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxIfElseTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxIfElseTransformer.java @@ -58,27 +58,28 @@ public List transform( BoxNode node, TransformerContext contex Type.getMethodDescriptor( Type.BOOLEAN_TYPE ), false ) ); LabelNode ifLabel = new LabelNode(); + AsmHelper.addDebugLabel( nodes, "BoxIfElse - goto iflabel" ); nodes.add( new JumpInsnNode( Opcodes.IFEQ, ifLabel ) ); nodes.addAll( transpiler.transform( ifElse.getThenBody(), TransformerContext.NONE, returnContext ) ); - if ( ifElse.getElseBody() == null ) { - LabelNode elseLabel = new LabelNode(); - nodes.add( new JumpInsnNode( Opcodes.GOTO, elseLabel ) ); - nodes.add( ifLabel ); - if ( returnContext == ReturnValueContext.VALUE_OR_NULL ) { - nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); - } - nodes.add( elseLabel ); - } else if ( ifElse.getElseBody() != null ) { - LabelNode elseLabel = new LabelNode(); - nodes.add( new JumpInsnNode( Opcodes.GOTO, elseLabel ) ); - nodes.add( ifLabel ); + LabelNode elseLabel = new LabelNode(); + AsmHelper.addDebugLabel( nodes, "BoxIfElse - goto elselabel" ); + nodes.add( new JumpInsnNode( Opcodes.GOTO, elseLabel ) ); + + AsmHelper.addDebugLabel( nodes, "BoxIfElse - ifLabel" ); + nodes.add( ifLabel ); + + if ( ifElse.getElseBody() != null ) { nodes.addAll( transpiler.transform( ifElse.getElseBody(), TransformerContext.NONE, returnContext ) ); - nodes.add( elseLabel ); - } else { - nodes.add( ifLabel ); + } else if ( returnContext == ReturnValueContext.VALUE_OR_NULL ) { + nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); } + AsmHelper.addDebugLabel( nodes, "BoxIfElse - elseLabel" ); + nodes.add( elseLabel ); + + AsmHelper.addDebugLabel( nodes, "BoxIfElse - end" ); + return nodes; } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTryTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTryTransformer.java index bcc71e40a..343b36137 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTryTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTryTransformer.java @@ -68,12 +68,15 @@ public List transform( BoxNode node, TransformerContext contex LabelNode finallyStartLabel = new LabelNode(); LabelNode finallyEndLabel = new LabelNode(); + AsmHelper.addDebugLabel( nodes, "BoxTryBlock" ); + nodes.add( tryStartLabel ); nodes.addAll( generateBodyNodesWithInlinedFinally( context, returnValueContext, boxTry.getTryBody(), boxTry.getFinallyBody(), () -> tryEndLabel ) ); // if we hit this instruction we have successfully executed the try body and inlined finally code // we can skip to the end of this construct + AsmHelper.addDebugLabel( nodes, "BoxTryBlock goto finallyEndLabel" ); nodes.add( new JumpInsnNode( Opcodes.GOTO, finallyEndLabel ) ); if ( boxTry.getCatches().size() > 0 ) { @@ -117,6 +120,7 @@ public List transform( BoxNode node, TransformerContext contex null ); tracker.addTryCatchBlock( catchHandler ); + AsmHelper.addDebugLabel( nodes, "BoxTry - finallyStartLabel" ); nodes.add( finallyStartLabel ); var errorVarStore = tracker.storeNewVariable( Opcodes.ASTORE ); @@ -128,6 +132,7 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.ATHROW ) ); + AsmHelper.addDebugLabel( nodes, "BoxTry - FinallyEndLabel" ); nodes.add( finallyEndLabel ); tracker.addTryCatchBlock( new TryCatchBlockNode( tryStartLabel, tryEndLabel, finallyStartLabel, null ) ); @@ -179,6 +184,8 @@ private List generateBodyNodesWithInlinedFinally( returnValueContext ) ); + AsmHelper.addDebugLabel( nodes, "BoxTryBlock - END" ); + return nodes; } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxWhileTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxWhileTransformer.java index 01e1c478b..3ab60d6bf 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxWhileTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxWhileTransformer.java @@ -25,6 +25,7 @@ import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.MethodInsnNode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.MethodContextTracker; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; @@ -59,18 +60,18 @@ public List transform( BoxNode node, TransformerContext contex // push two nulls onto the stack in order to initialize our strategy for keeping the stack height consistent // this is to allow the statement to return an expression in the case of a BoxScript execution - if ( returnContext == ReturnValueContext.VALUE_OR_NULL ) { - nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); - nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); - } + // if ( returnContext == ReturnValueContext.VALUE_OR_NULL ) { + nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); + nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); + // } nodes.add( start ); // every iteration we will swap the values and pop in order to remove the older value - if ( returnContext == ReturnValueContext.VALUE_OR_NULL ) { - nodes.add( new InsnNode( Opcodes.SWAP ) ); - nodes.add( new InsnNode( Opcodes.POP ) ); - } + // if ( returnContext == ReturnValueContext.VALUE_OR_NULL ) { + nodes.add( new InsnNode( Opcodes.SWAP ) ); + nodes.add( new InsnNode( Opcodes.POP ) ); + // } nodes.addAll( transpiler.transform( boxWhile.getCondition(), TransformerContext.RIGHT, ReturnValueContext.VALUE ) ); nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, @@ -83,20 +84,28 @@ public List transform( BoxNode node, TransformerContext contex "booleanValue", Type.getMethodDescriptor( Type.BOOLEAN_TYPE ), false ) ); + + AsmHelper.addDebugLabel( nodes, "BoxWhile - jump end" ); nodes.add( new JumpInsnNode( Opcodes.IFEQ, end ) ); - nodes.addAll( transpiler.transform( boxWhile.getBody(), TransformerContext.NONE, returnContext ) ); + nodes.addAll( transpiler.transform( boxWhile.getBody(), TransformerContext.NONE, ReturnValueContext.VALUE_OR_NULL ) ); + AsmHelper.addDebugLabel( nodes, "BoxWhile - jump start" ); nodes.add( new JumpInsnNode( Opcodes.GOTO, start ) ); + AsmHelper.addDebugLabel( nodes, "BoxWhile - breakTarget" ); nodes.add( breakTarget ); + + nodes.add( new InsnNode( Opcodes.SWAP ) ); + nodes.add( new InsnNode( Opcodes.POP ) ); + + AsmHelper.addDebugLabel( nodes, "BoxWhile - end label" ); + nodes.add( end ); + // every iteration we will swap the values and pop in order to remove the older value - if ( returnContext == ReturnValueContext.VALUE_OR_NULL ) { - nodes.add( new InsnNode( Opcodes.SWAP ) ); + if ( returnContext == ReturnValueContext.EMPTY || returnContext == ReturnValueContext.EMPTY_UNLESS_JUMPING ) { nodes.add( new InsnNode( Opcodes.POP ) ); } - nodes.add( end ); - return nodes; } } diff --git a/src/test/java/ortus/boxlang/compiler/Assertion.cfc b/src/test/java/ortus/boxlang/compiler/Assertion.cfc new file mode 100644 index 000000000..748ad94c2 --- /dev/null +++ b/src/test/java/ortus/boxlang/compiler/Assertion.cfc @@ -0,0 +1,1469 @@ +/** + * Copyright Since 2005 TestBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * This object represents our Assertion style DSL for Unit style testing + */ +component { + + /** + * Fail assertion + * + * @message The message to send in the failure + * @detail The detail to add in the exception + */ + function fail( message = "", detail = "" ){ + arguments.message = ( len( arguments.message ) ? arguments.message : "A test failure occurred" ); + throw( + type = "TestBox.AssertionFailed", + message = arguments.message, + detail = arguments.detail + ); + } + + /** + * Skip a Test + * + * @message The message to send in the skip information dialog + * @detail The detail to add in the exception + */ + function skip( message = "", detail = "" ){ + arguments.message = ( len( arguments.message ) ? arguments.message : "Test was skipped" ); + throw( + type = "TestBox.SkipSpec", + message = arguments.message, + detail = arguments.detail + ); + } + + /** + * Assert that the passed expression is true + * + * @expression The expression to test + * @message The message to send in the failure + */ + function assert( required boolean expression, message = "" ){ + return isTrue( arguments.expression, arguments.message ); + } + + /** + * Assert something is true + * + * @actual The actual data to test + * @message The message to send in the failure + */ + function isTrue( required boolean actual, message = "" ){ + arguments.message = ( + len( arguments.message ) ? arguments.message : "Expected [#arguments.actual#] to be true" + ); + if ( NOT arguments.actual ) { + fail( arguments.message ); + } + return this; + } + + /** + * Assert something is false + * + * @actual The actual data to test + * @message The message to send in the failure + */ + function isFalse( required boolean actual, message = "" ){ + arguments.message = ( + len( arguments.message ) ? arguments.message : "Expected [#arguments.actual#] to be false" + ); + if ( arguments.actual ) { + fail( arguments.message ); + } + return this; + } + + /** + * Assert something is equal to each other, no case is required + * + * @expected The expected data + * @actual The actual data to test + * @message The message to send in the failure + */ + function isEqual( any expected, any actual, message = "" ){ + // validate equality + if ( equalize( argumentCollection = arguments ) ) { + return this; + } + arguments.message = ( + len( arguments.message ) ? arguments.message & ". Expected [#getStringName( arguments.expected )#] Actual [#getStringName( arguments.actual )#]" : "Expected [#getStringName( arguments.expected )#] but received [#getStringName( arguments.actual )#]" + ); + // if we reach here, nothing is equal man! + fail( arguments.message ); + } + + /** + * Assert something is not equal to each other, no case is required + * + * @expected The expected data + * @actual The actual data to test + * @message The message to send in the failure + */ + function isNotEqual( any expected, any actual, message = "" ){ + arguments.message = ( + len( arguments.message ) ? arguments.message & ". Expected [#getStringName( arguments.expected )#] Actual [#getStringName( arguments.actual )#]" : "Expected [#getStringName( arguments.expected )#] to not be [#getStringName( arguments.actual )#]" + ); + // validate equality + if ( !equalize( argumentCollection = arguments ) ) { + return this; + } + // if we reach here, they are equal! + fail( arguments.message ); + } + + /** + * Assert an object is the same instance as another object + * + * @expected The expected data + * @actual The actual data to test + * @message The message to send in the failure + */ + function isSameInstance( + required any expected, + required any actual, + message = "" + ){ + var expectedIdentityHashCode = getIdentityHashCode( arguments.expected ); + var actualIdentityHashCode = getIdentityHashCode( arguments.actual ); + + // validate same object + if ( expectedIdentityHashCode == actualIdentityHashCode ) { + return this; + } + arguments.message = ( + len( arguments.message ) ? arguments.message : "Expected [#getStringName( arguments.expected )#:#expectedIdentityHashCode#] but received [#getStringName( arguments.actual )#:#actualIdentityHashCode#]" + ); + // if we reach here, the objects weren't the same + fail( arguments.message ); + } + + /** + * Assert an object is not the same instance as another object + * + * @expected The expected data + * @actual The actual data to test + * @message The message to send in the failure + */ + function isNotSameInstance( + required any expected, + required any actual, + message = "" + ){ + var expectedIdentityHashCode = getIdentityHashCode( arguments.expected ); + var actualIdentityHashCode = getIdentityHashCode( arguments.actual ); + + // validate not same object + if ( expectedIdentityHashCode != actualIdentityHashCode ) { + return this; + } + arguments.message = ( + len( arguments.message ) ? arguments.message : "Expected [#getStringName( arguments.expected )#:#expectedIdentityHashCode#] to not be [#getStringName( arguments.actual )#:#actualIdentityHashCode#]" + ); + // if we reach here, they are equal! + fail( arguments.message ); + } + + /** + * Assert strings are equal to each other with case. + * + * @expected The expected data + * @actual The actual data to test + * @message The message to send in the failure + */ + function isEqualWithCase( string expected, string actual, message = "" ){ + arguments.message = ( + len( arguments.message ) ? arguments.message : "Expected [#getStringName( arguments.expected )#] but received [#getStringName( arguments.actual )#]" + ); + // null check + if ( isNull( arguments.expected ) && isNull( arguments.actual ) ) { + return this; + } + if ( isNull( arguments.expected ) || isNull( arguments.actual ) ) { + fail( arguments.message ); + } + // equalize with case + if ( compare( arguments.expected, arguments.actual ) eq 0 ) { + return this; + } + // if we reach here, nothing is equal man! + fail( arguments.message ); + } + + /** + * Assert something is null + * + * @actual The actual data to test + * @message The message to send in the failure + */ + function null( any actual, message = "" ){ + // equalize with case + if ( isNull( arguments.actual ) ) { + return this; + } + arguments.message = ( + len( arguments.message ) ? arguments.message : "Expected a null value but got #getStringName( arguments.actual )#" + ); + // if we reach here, nothing is equal man! + fail( arguments.message ); + } + + + /** + * Assert something is not null + * + * @actual The actual data to test + * @message The message to send in the failure + */ + function notNull( any actual, message = "" ){ + // equalize with case + if ( !isNull( arguments.actual ) ) { + return this; + } + arguments.message = ( + len( arguments.message ) ? arguments.message : "Expected the actual value to be NOT null but it was null" + ); + // if we reach here, nothing is equal man! + fail( arguments.message ); + } + + /** + * Assert the type of the incoming actual data, it uses the internal ColdFusion isValid() function behind the scenes + * + * @type The type to check, valid types are: array, binary, boolean, component, date, time, float, numeric, integer, query, string, struct, url, uuid + * @actual The actual data to check + * @message The message to send in the failure + */ + function typeOf( + required string type, + required any actual, + message = "" + ){ + arguments.message = ( + len( arguments.message ) ? arguments.message : "Actual data [#getStringName( arguments.actual )#] is not of this type: [#arguments.type#]" + ); + if ( isValid( arguments.type, arguments.actual ) ) { + return this; + } + fail( arguments.message ); + } + + /** + * Assert that is NOT a type of the incoming actual data, it uses the internal ColdFusion isValid() function behind the scenes + * + * @type The type to check, valid types are: array, binary, boolean, component, date, time, float, numeric, integer, query, string, struct, url, uuid + * @actual The actual data to check + * @message The message to send in the failure + */ + function notTypeOf( + required string type, + required any actual, + message = "" + ){ + arguments.message = ( + len( arguments.message ) ? arguments.message : "Actual data [#getStringName( arguments.actual )#] is actually of this type: [#arguments.type#]" + ); + if ( !isValid( arguments.type, arguments.actual ) ) { + return this; + } + fail( arguments.message ); + } + + /** + * Assert that the actual object is of the expected instance type + * + * @actual The actual data to check + * @typeName The typename to check + * @message The message to send in the failure + */ + function instanceOf( + required any actual, + required string typeName, + message = "" + ){ + var md = getMetadata( arguments.actual ); + var actualType = isStruct( md ) && md.keyExists( "name" ) ? md.name : actual.getClass().getName(); + arguments.message = ( + len( arguments.message ) ? arguments.message : "The actual is of type [#actualType#] which is not the expected type of [#arguments.typeName#]" + ); + + if ( isInstanceOf( arguments.actual, arguments.typeName ) ) { + return this; + } + + fail( arguments.message ); + } + + /** + * Assert that the actual object is NOT of the expected instance type + * + * @actual The actual data to check + * @typeName The typename to check + * @message The message to send in the failure + */ + function notInstanceOf( + required any actual, + required string typeName, + message = "" + ){ + var md = getMetadata( arguments.actual ); + var actualType = isStruct( md ) && md.keyExists( "name" ) ? md.name : actual.getClass().getName(); + arguments.message = ( + len( arguments.message ) ? arguments.message : "The actual is of type [#actualType#] which is the expected type of [#arguments.typeName#]" + ); + if ( !isInstanceOf( arguments.actual, arguments.typeName ) ) { + return this; + } + fail( arguments.message ); + } + + /** + * Assert that the actual data matches the incoming regular expression with no case sensitivity + * + * @actual The actual data to check + * @regex The regex to check with + * @message The message to send in the failure + */ + function match( + required string actual, + required string regex, + message = "" + ){ + arguments.message = ( + len( arguments.message ) ? arguments.message : "The actual [#arguments.actual.toString()#] does not match [#arguments.regex#]" + ); + if ( arrayLen( reMatchNoCase( arguments.regex, arguments.actual ) ) gt 0 ) { + return this; + } + fail( arguments.message ); + } + + /** + * Assert that the actual data matches the incoming regular expression with case sensitivity + * + * @actual The actual data to check + * @regex The regex to check with + * @message The message to send in the failure + */ + function matchWithCase( + required string actual, + required string regex, + message = "" + ){ + arguments.message = ( + len( arguments.message ) ? arguments.message : "The actual [#arguments.actual.toString()#] does not match [#arguments.regex#]" + ); + if ( arrayLen( reMatch( arguments.regex, arguments.actual ) ) gt 0 ) { + return this; + } + fail( arguments.message ); + } + + /** + * Assert that the actual data does NOT match the incoming regular expression with case sensitivity + * + * @actual The actual data to check + * @regex The regex to check with + * @message The message to send in the failure + */ + function notMatchWithCase( + required string actual, + required string regex, + message = "" + ){ + arguments.message = ( + len( arguments.message ) ? arguments.message : "The actual [#arguments.actual.toString()#] does not match [#arguments.regex#]" + ); + if ( arrayLen( reMatch( arguments.regex, arguments.actual ) ) eq 0 ) { + return this; + } + fail( arguments.message ); + } + + /** + * Assert that the actual data does NOT match the incoming regular expression with no case sensitivity + * + * @actual The actual data to check + * @regex The regex to check with + * @message The message to send in the failure + */ + function notMatch( + required string actual, + required string regex, + message = "" + ){ + arguments.message = ( + len( arguments.message ) ? arguments.message : "The actual [#arguments.actual.toString()#] actually matches [#arguments.regex#]" + ); + if ( arrayLen( reMatchNoCase( arguments.regex, arguments.actual ) ) eq 0 ) { + return this; + } + fail( arguments.message ); + } + + /** + * Assert that a given key exists in the passed in struct/object + * + * @target The target object/struct + * @key The key to check for existence + * @message The message to send in the failure + * @caseSensitive If the key check is case sensitive + */ + function key( + required any target, + required string key, + message = "", + boolean caseSensitive = false + ){ + arguments.target = normalizeToStruct( arguments.target ); + + arguments.message = ( + len( arguments.message ) ? arguments.message : "The key(s) [#arguments.key#] does not exist in the target object. Found keys are [#structKeyArray( arguments.target ).toString()#]" + ); + + // Inflate Key and process + if ( + arguments.key + .listToArray() + .filter( function( thisKey ){ + if ( caseSensitive ) { + return target.keyList().find( arguments.thisKey ); + } else { + return structKeyExists( target, arguments.thisKey ); + } + } ) + .len() != listLen( arguments.key ) + ) { + fail( arguments.message ); + } + return this; + } + + /** + * Assert that a given key DOES NOT exist in the passed in struct/object + * + * @target The target object/struct + * @key The key to check for existence + * @message The message to send in the failure + * @caseSensitive If the key check is case sensitive + */ + function notKey( + required any target, + required string key, + message = "", + boolean caseSensitive = false + ){ + arguments.target = normalizeToStruct( arguments.target ); + arguments.message = ( + len( arguments.message ) ? arguments.message : "The key [#arguments.key#] exists in the target object. Found keys are [#structKeyArray( arguments.target ).toString()#]" + ); + + // Inflate Key and process + if ( + arguments.key + .listToArray() + .filter( function( thisKey ){ + if ( caseSensitive ) { + return target.keyList().find( arguments.thisKey ); + } else { + return structKeyExists( target, arguments.thisKey ); + } + } ) + .len() > 0 + ) { + fail( arguments.message ); + } + return this; + } + + /** + * Assert that a given key exists in the passed in struct by searching the entire nested structure + * + * @target The target object/struct + * @key The key to check for existence anywhere in the nested structure + * @message The message to send in the failure + */ + function deepKey( + required struct target, + required string key, + message = "" + ){ + arguments.target = normalizeToStruct( arguments.target ); + arguments.message = ( + len( arguments.message ) ? arguments.message : "The key [#arguments.key#] does not exist anywhere in the target object." + ); + if ( arrayLen( structFindKey( arguments.target, arguments.key ) ) GT 0 ) { + return this; + } + fail( arguments.message ); + } + + /** + * Assert that a given key DOES NOT exists in the passed in struct by searching the entire nested structure + * + * @target The target object/struct + * @key The key to check for existence anywhere in the nested structure + * @message The message to send in the failure + */ + function notDeepKey( + required struct target, + required string key, + message = "" + ){ + arguments.target = normalizeToStruct( arguments.target ); + var results = structFindKey( arguments.target, arguments.key ); + // check if not found? + if ( arrayLen( results ) EQ 0 ) { + return this; + } + // found, so throw it + arguments.message = ( + len( arguments.message ) ? arguments.message : "The key [#arguments.key#] actually exists in the target object: #results.toString()#" + ); + fail( arguments.message ); + } + + /** + * Assert the size of a given string, array, structure or query + * + * @target The target object to check the length for, this can be a string, array, structure or query + * @length The length to check + * @message The message to send in the failure + */ + function lengthOf( + required any target, + required string length, + message = "" + ){ + var aLength = getTargetLength( arguments.target ); + // validate it + if ( aLength eq arguments.length ) { + return this; + } + + // found, so throw it + arguments.message = ( + len( arguments.message ) ? arguments.message : "The expected length [#arguments.length#] is different than the actual length [#aLength#]" + ); + fail( arguments.message ); + } + + /** + * Assert the size of a given string, array, structure or query + * + * @target The target object to check the length for, this can be a string, array, structure or query + * @length The length to check + * @message The message to send in the failure + */ + function notLengthOf( + required any target, + required string length, + message = "" + ){ + var aLength = getTargetLength( arguments.target ); + // validate it + if ( aLength neq arguments.length ) { + return this; + } + + // found, so throw it + arguments.message = ( + len( arguments.message ) ? arguments.message : "The expected length [#arguments.length#] is equal than the actual length [#aLength#]" + ); + fail( arguments.message ); + } + + /** + * Assert that a a given string, array, structure or query is empty + * + * @target The target object to check the length for, this can be a string, array, structure or query + * @message The message to send in the failure + */ + function isEmpty( required any target, message = "" ){ + var aLength = getTargetLength( arguments.target ); + // validate it + if ( aLength eq 0 ) { + return this; + } + + // found, so throw it + arguments.message = ( + len( arguments.message ) ? arguments.message : "The expected value is not empty, actual size [#aLength#]" + ); + fail( arguments.message ); + } + + /** + * Assert that a a given string, array, structure or query is not empty + * + * @target The target object to check the length for, this can be a string, array, structure or query + * @message The message to send in the failure + */ + function isNotEmpty( required any target, message = "" ){ + var aLength = getTargetLength( arguments.target ); + // validate it + if ( aLength GT 0 ) { + return this; + } + + // found, so throw it + arguments.message = ( len( arguments.message ) ? arguments.message : "The expected target is empty." ); + fail( arguments.message ); + } + + /** + * Assert that the passed in function will throw an exception + * + * @target The target function to execute and check for exceptions + * @type Match this type with the exception thrown + * @regex Match this regex against the message + detail of the exception + * @message The message to send in the failure + */ + function throws( + required any target, + type = "", + regex = ".*", + message = "" + ){ + var detail = ""; + + try { + arguments.target(); + arguments.message = ( + len( arguments.message ) ? arguments.message : "The incoming function did not throw an expected exception. Type=[#arguments.type#], Regex=[#arguments.regex#]" + ); + } catch ( Any e ) { + // If no type, message expectations, just throw flag + if ( !len( arguments.type ) && arguments.regex eq ".*" ) { + return this; + } + + // determine if the expected 'type' matches the actual exception 'type' + var typeMatches = len( arguments.type ) == 0 OR e.type eq arguments.type; + + // determine if the expected 'regex' matches the actual exception 'message' or 'detail' + var regexMatches = arguments.regex eq ".*" OR ( + arrayLen( reMatchNoCase( arguments.regex, e.message ) ) OR arrayLen( + reMatchNoCase( arguments.regex, e.detail ) + ) + ); + + // this assertion passes if the expected type and regex match the actual exception data + if ( typeMatches && regexMatches ) { + return this; + } + // diff message types + arguments.message = ( + len( arguments.message ) ? arguments.message : "The incoming function threw exception [type: #e.type#] [message: #e.message#] [#e.detail#] different than expected params type=[#arguments.type#], regex=[#arguments.regex#]" + ); + detail = e.stackTrace; + } + + // found, so throw it + fail( arguments.message, detail ); + } + + /** + * Assert that the passed in function will NOT throw an exception, an exception of a specified type or exception message regex + * + * @target The target function to execute and check for exceptions + * @type Match this type with the exception thrown + * @regex Match this regex against the message+detail of the exception + * @message The message to send in the failure + */ + function notThrows( + required any target, + type = "", + regex = "", + message = "" + ){ + try { + arguments.target(); + } catch ( Any e ) { + arguments.message = ( + len( arguments.message ) ? arguments.message : "The incoming function DID throw an exception of type [#e.type#] with message [#e.message#] detail [#e.detail#]" + ); + + // If type passed and matches, then its ok + if ( len( arguments.type ) AND e.type neq arguments.type ) { + return this; + } + + // Message+Detail regex must not match + if ( + len( arguments.regex ) AND + ( + !arrayLen( reMatchNoCase( arguments.regex, e.message ) ) OR !arrayLen( + reMatchNoCase( arguments.regex, e.detail ) + ) + ) + ) { + return this; + } + + fail( arguments.message ); + } + + return this; + } + + /** + * Assert that the passed in actual number or date is expected to be close to it within +/- a passed delta and optional datepart + * + * @expected The expected number or date + * @actual The actual number or date + * @delta The +/- delta to range it + * @datepart If passed in values are dates, then you can use the datepart to evaluate it + * @message The message to send in the failure + */ + function closeTo( + required any expected, + required any actual, + required any delta, + datePart = "", + message = "" + ){ + arguments.message = ( + len( arguments.message ) ? arguments.message : "The actual [#arguments.actual#] is not in range of [#arguments.expected#] by +/- [#arguments.delta#]" + ); + + if ( isNumeric( arguments.actual ) ) { + if ( + isValid( + "range", + arguments.actual, + ( arguments.expected - arguments.delta ), + ( arguments.expected + arguments.delta ) + ) + ) { + return this; + } + } else if ( isDate( arguments.actual ) ) { + if ( !listFindNoCase( "yyyy,q,m,ww,w,y,d,h,n,s,l", arguments.datePart ) ) { + fail( "The passed in datepart [#arguments.datepart#] is not valid." ); + } + + if ( + abs( + dateDiff( + arguments.datePart, + arguments.actual, + arguments.expected + ) + ) lt arguments.delta + ) { + return this; + } + } + + fail( arguments.message ); + } + + /** + * Assert that the passed in actual number or date is between the passed in min and max values + * + * @actual The actual number or date to evaluate + * @min The expected min number or date + * @max The expected max number or date + * @message The message to send in the failure + */ + function between( + required any actual, + required any min, + required any max, + message = "" + ){ + arguments.message = ( + len( arguments.message ) ? arguments.message : "The actual [#arguments.actual#] is not between [#arguments.min#] and [#arguments.max#]" + ); + + // numeric between + if ( isNumeric( arguments.actual ) ) { + if ( + isValid( + "range", + arguments.actual, + arguments.min, + arguments.max + ) + ) { + return this; + } + } else if ( isDate( arguments.actual ) ) { + // check min/max dates first + if ( dateCompare( arguments.min, arguments.max ) NEQ -1 ) { + fail( "The passed in min [#arguments.min#] is either equal or later than max [#arguments.max#]" ); + } + + // To pass, ( actual > min && actual < max ) + if ( + ( dateCompare( arguments.actual, arguments.min ) EQ 1 ) AND + ( dateCompare( arguments.actual, arguments.max ) EQ -1 ) + ) { + return this; + } + } + + fail( arguments.message ); + } + + /** + * Assert that the given "needle" argument exists in the incoming string or array with no case-sensitivity + * + * @target The target object to check if the incoming needle exists in. This can be a string or array + * @needle The substring to find in a string or the value to find in an array + * @message The message to send in the failure + */ + function includes( + required any target, + required any needle, + message = "" + ){ + arguments.message = ( + len( arguments.message ) ? arguments.message : "The needle [#arguments.needle#] was not found in [#arguments.target.toString()#]" + ); + + // string + if ( isSimpleValue( arguments.target ) AND findNoCase( arguments.needle, arguments.target ) ) { + return this; + } + // array + if ( isArray( arguments.target ) AND arrayFindNoCase( arguments.target, arguments.needle ) ) { + return this; + } + + fail( arguments.message ); + } + + /** + * Assert that the given "needle" argument exists in the incoming string or array with case-sensitivity + * + * @target The target object to check if the incoming needle exists in. This can be a string or array + * @needle The substring to find in a string or the value to find in an array + * @message The message to send in the failure + */ + function includesWithCase( + required any target, + required any needle, + message = "" + ){ + arguments.message = ( + len( arguments.message ) ? arguments.message : "The needle [#arguments.needle#] was not found in [#arguments.target.toString()#]" + ); + + // string + if ( isSimpleValue( arguments.target ) AND find( arguments.needle, arguments.target ) ) { + return this; + } + // array + if ( isArray( arguments.target ) AND arrayContains( arguments.target, arguments.needle ) ) { + return this; + } + + fail( arguments.message ); + } + + /** + * Assert that the given "needle" argument does not exist in the incoming string or array with case-sensitivity + * + * @target The target object to check if the incoming needle exists in. This can be a string or array + * @needle The substring to find in a string or the value to find in an array + * @message The message to send in the failure + */ + function notIncludesWithCase( + required any target, + required any needle, + message = "" + ){ + arguments.message = ( + len( arguments.message ) ? arguments.message : "The needle [#arguments.needle#] was found in [#arguments.target.toString()#]" + ); + + // string + if ( isSimpleValue( arguments.target ) AND !find( arguments.needle, arguments.target ) ) { + return this; + } + // array + if ( isArray( arguments.target ) AND !arrayContains( arguments.target, arguments.needle ) ) { + return this; + } + + fail( arguments.message ); + } + + /** + * Assert that the given "needle" argument exists in the incoming string or array with no case-sensitivity + * + * @target The target object to check if the incoming needle exists in. This can be a string or array + * @needle The substring to find in a string or the value to find in an array + * @message The message to send in the failure + */ + function notIncludes( + required any target, + required any needle, + message = "" + ){ + arguments.message = ( + len( arguments.message ) ? arguments.message : "The needle [#arguments.needle#] was found in [#arguments.target.toString()#]" + ); + + // string + if ( isSimpleValue( arguments.target ) AND !findNoCase( arguments.needle, arguments.target ) ) { + return this; + } + // array + if ( isArray( arguments.target ) AND !arrayFindNoCase( arguments.target, arguments.needle ) ) { + return this; + } + + fail( arguments.message ); + } + + /** + * Assert that the given target string starts with the given needle string with no case-sensitivity + * + * $assert.startsWith( "hello world", "hello" ); + * + * @target The target string to check + * @needle The starts with string + * @message The message to send in the failure + */ + function startsWith( + required string target, + required string needle, + message = "" + ){ + arguments.message = ( + len( arguments.message ) ? arguments.message : "[#arguments.target#] doesn't start with [#arguments.needle#]]" + ); + + if ( toString( lCase( arguments.target ) ).startsWith( lCase( arguments.needle ) ) ) { + return this; + } + + fail( arguments.message ); + } + + /** + * Assert that the given target string doesn't start with the given needle string with no case-sensitivity + * + * $assert.notStartsWith( "hello world", "hello" ); + * + * @target The target string to check + * @needle The starts with string + * @message The message to send in the failure + */ + function notStartsWith( + required string target, + required string needle, + message = "" + ){ + try { + startsWith( argumentCollection = arguments ); + arguments.message = ( + len( arguments.message ) ? arguments.message : "[#arguments.target#] actually starts with [#arguments.needle#]]" + ); + fail( arguments.message ); + } catch ( "TestBox.AssertionFailed" e ) { + return this; + } + } + + /** + * Assert that the given target string starts with the given needle string with case-sensitivity + * + * $assert.startsWith( "hello world", "hello" ); + * + * @target The target string to check + * @needle The starts with string + * @message The message to send in the failure + */ + function startsWithCase( + required string target, + required string needle, + message = "" + ){ + arguments.message = ( + len( arguments.message ) ? arguments.message : "[#arguments.target#] doesn't start with [#arguments.needle#]]" + ); + + if ( toString( arguments.target ).startsWith( arguments.needle ) ) { + return this; + } + + fail( arguments.message ); + } + + /** + * Assert that the given target string doesn't start with the given needle string with case-sensitivity + * + * $assert.notStartsWith( "hello world", "hello" ); + * + * @target The target string to check + * @needle The starts with string + * @message The message to send in the failure + */ + function notStartsWithCase( + required string target, + required string needle, + message = "" + ){ + try { + startsWithCase( argumentCollection = arguments ); + arguments.message = ( + len( arguments.message ) ? arguments.message : "[#arguments.target#] actually starts with [#arguments.needle#]]" + ); + fail( arguments.message ); + } catch ( "TestBox.AssertionFailed" e ) { + return this; + } + } + + /** + * Assert that the given target string ends with the given needle string with no case-sensitivity + * + * $assert.endsWith( "hello world", "World" ); + * + * @target The target string to check + * @needle The starts with string + * @message The message to send in the failure + */ + function endsWith( + required string target, + required string needle, + message = "" + ){ + arguments.message = ( + len( arguments.message ) ? arguments.message : "[#arguments.target#] doesn't end with [#arguments.needle#]]" + ); + + if ( toString( lCase( arguments.target ) ).endsWith( lCase( arguments.needle ) ) ) { + return this; + } + + fail( arguments.message ); + } + + /** + * Assert that the given target string doesn't end with the given needle string with no case-sensitivity + * + * $assert.notEndsWith( "hello world", "peace" ); + * + * @target The target string to check + * @needle The starts with string + * @message The message to send in the failure + */ + function notEndsWith( + required string target, + required string needle, + message = "" + ){ + try { + endsWith( argumentCollection = arguments ); + arguments.message = ( + len( arguments.message ) ? arguments.message : "[#arguments.target#] actually ends with [#arguments.needle#]]" + ); + fail( arguments.message ); + } catch ( "TestBox.AssertionFailed" e ) { + return this; + } + } + + /** + * Assert that the given target string ends with the given needle string with case-sensitivity + * + * $assert.endsWith( "hello world", "ld" ); + * + * @target The target string to check + * @needle The starts with string + * @message The message to send in the failure + */ + function endsWithCase( + required string target, + required string needle, + message = "" + ){ + arguments.message = ( + len( arguments.message ) ? arguments.message : "[#arguments.target#] doesn't end with [#arguments.needle#]]" + ); + + if ( toString( arguments.target ).endsWith( arguments.needle ) ) { + return this; + } + + fail( arguments.message ); + } + + /** + * Assert that the given target string doesn't end with the given needle string with case-sensitivity + * + * $assert.notEndsWith( "hello world", "ld" ); + * + * @target The target string to check + * @needle The starts with string + * @message The message to send in the failure + */ + function notEndsWithCase( + required string target, + required string needle, + message = "" + ){ + try { + endsWithCase( argumentCollection = arguments ); + arguments.message = ( + len( arguments.message ) ? arguments.message : "[#arguments.target#] actually ends with [#arguments.needle#]]" + ); + fail( arguments.message ); + } catch ( "TestBox.AssertionFailed" e ) { + return this; + } + } + + /** + * Assert that the actual value is greater than the target value + * + * @actual The actual value + * @target The target value + * @message The message to send in the failure + */ + function isGT( + required any actual, + required any target, + message = "" + ){ + arguments.message = ( + len( arguments.message ) ? arguments.message : "The actual [#arguments.actual#] is not greater than [#arguments.target#]" + ); + + if ( arguments.actual gt arguments.target ) { + return this; + } + + fail( arguments.message ); + } + + /** + * Assert that the actual value is greater than or equal the target value + * + * @actual The actual value + * @target The target value + * @message The message to send in the failure + */ + function isGTE( + required any actual, + required any target, + message = "" + ){ + arguments.message = ( + len( arguments.message ) ? arguments.message : "The actual [#arguments.actual#] is not greater than or equal to [#arguments.target#]" + ); + + if ( arguments.actual gte arguments.target ) { + return this; + } + + fail( arguments.message ); + } + + /** + * Assert that the actual value is less than the target value + * + * @actual The actual value + * @target The target value + * @message The message to send in the failure + */ + function isLT( + required any actual, + required any target, + message = "" + ){ + arguments.message = ( + len( arguments.message ) ? arguments.message : "The actual [#arguments.actual#] is not less than [#arguments.target#]" + ); + + if ( arguments.actual lt arguments.target ) { + return this; + } + + fail( arguments.message ); + } + + /** + * Assert that the actual value is less than or equal the target value + * + * @actual The actual value + * @target The target value + * @message The message to send in the failure + */ + function isLTE( + required any actual, + required any target, + message = "" + ){ + arguments.message = ( + len( arguments.message ) ? arguments.message : "The actual [#arguments.actual#] is not less than or equal to [#arguments.target#]" + ); + + if ( arguments.actual lte arguments.target ) { + return this; + } + + fail( arguments.message ); + } + + + /** + * Get a string name representation of an incoming object. + */ + function getStringName( obj ){ + if ( isNull( arguments.obj ) ) { + return "null"; + } + if ( isSimpleValue( arguments.obj ) ) { + return arguments.obj; + } + if ( isObject( arguments.obj ) ) { + try { + return getMetadata( arguments.obj ).name; + } catch ( any e ) { + return "Unknown Object"; + } + } + return arguments.obj.toString(); + } + + /** + * Assert something is JSON + * + * @actual The actual data to test + * @message The message to send in the failure + */ + function isJSON( required any actual, message = "" ){ + arguments.message = ( + len( arguments.message ) ? arguments.message : "Expected [#arguments.actual#] to be json" + ); + if ( !isJSON( arguments.actual ) ) { + fail( arguments.message ); + } + return this; + } + + /*********************************** PRIVATE Methods ***********************************/ + + private boolean function equalize( any expected, any actual ){ + // Null values + if ( isNull( arguments.expected ) && isNull( arguments.actual ) ) { + return true; + } + + if ( isNull( arguments.expected ) || isNull( arguments.actual ) ) { + return false; + } + + // Numerics + if ( + isNumeric( arguments.actual ) && isNumeric( arguments.expected ) && toString( arguments.actual ) eq toString( + arguments.expected + ) + ) { + return true; + } + + // Simple values + if ( + isSimpleValue( arguments.actual ) && isSimpleValue( arguments.expected ) && arguments.actual eq arguments.expected + ) { + return true; + } + + // Queries + if ( isQuery( arguments.actual ) && isQuery( arguments.expected ) ) { + // Check number of records + if ( arguments.actual.recordCount != arguments.expected.recordCount ) { + return false; + } + + // Get both column lists and sort them the same + var actualColumnList = listSort( arguments.actual.columnList, "textNoCase" ); + var expectedColumnList = listSort( arguments.expected.columnList, "textNoCase" ); + + // Check column lists + if ( actualColumnList != expectedColumnList ) { + return false; + } + + // Loop over each row + var i = 0; + while ( ++i <= arguments.actual.recordCount ) { + // Loop over each column + for ( var column in listToArray( actualColumnList ) ) { + // Compare each value + if ( arguments.actual[ column ][ i ] != arguments.expected[ column ][ i ] ) { + // At the first sign of trouble, bail! + return false; + } + } + } + + // We made it here so nothing looked wrong + return true; + } + + // UDFs + if ( + isCustomFunction( arguments.actual ) && isCustomFunction( arguments.expected ) && + arguments.actual.toString() eq arguments.expected.toString() + ) { + return true; + } + + // XML + if ( + isXMLDoc( arguments.actual ) && isXMLDoc( arguments.expected ) && + toString( arguments.actual ) eq toString( arguments.expected ) + ) { + return true; + } + + // Arrays + if ( isArray( arguments.actual ) && isArray( arguments.expected ) ) { + // Confirm both arrays are the same length + if ( arrayLen( arguments.actual ) neq arrayLen( arguments.expected ) ) { + return false; + } + + for ( var i = 1; i lte arrayLen( arguments.actual ); i++ ) { + // check for both being defined + if ( arrayIsDefined( arguments.actual, i ) and arrayIsDefined( arguments.expected, i ) ) { + // check for both nulls + if ( isNull( arguments.actual[ i ] ) and isNull( arguments.expected[ i ] ) ) { + continue; + } + // check if one is null mismatch + if ( isNull( arguments.actual[ i ] ) OR isNull( arguments.expected[ i ] ) ) { + return false; + } + // And make sure they match + if ( !equalize( arguments.actual[ i ], arguments.expected[ i ] ) ) { + return false; + } + continue; + } + // check if both not defined, then continue to next element + if ( !arrayIsDefined( arguments.actual, i ) and !arrayIsDefined( arguments.expected, i ) ) { + continue; + } else { + return false; + } + } + + // If we made it here, we couldn't find anything different + return true; + } + + // Structs / Object + if ( isStruct( arguments.actual ) && isStruct( arguments.expected ) ) { + var actualKeys = listSort( structKeyList( arguments.actual ), "textNoCase" ); + var expectedKeys = listSort( structKeyList( arguments.expected ), "textNoCase" ); + var key = ""; + + // Confirm both structs have the same keys + if ( actualKeys neq expectedKeys ) { + return false; + } + + // Loop over each key + for ( key in arguments.actual ) { + // check for both nulls + if ( isNull( arguments.actual[ key ] ) and isNull( arguments.expected[ key ] ) ) { + continue; + } + // check if one is null mismatch + if ( isNull( arguments.actual[ key ] ) OR isNull( arguments.expected[ key ] ) ) { + return false; + } + // And make sure they match when actual values exist + if ( !equalize( arguments.actual[ key ], arguments.expected[ key ] ) ) { + return false; + } + } + + // If we made it here, we couldn't find anything different + return true; + } + + return false; + } + + /** + * Returns the length of the target based on its type + * + * @target The target to get the length of + */ + private function getTargetLength( required any target ){ + var aLength = 0; + + if ( isSimpleValue( arguments.target ) ) { + aLength = len( arguments.target ); + } + if ( isArray( arguments.target ) ) { + aLength = arrayLen( arguments.target ); + } + if ( isStruct( arguments.target ) ) { + aLength = structCount( arguments.target ); + } + if ( isQuery( arguments.target ) ) { + aLength = arguments.target.recordcount; + } + + if ( listFirst( server.coldfusion.productversion ) lt 10 ) { + if ( isCustomFunction( arguments.target ) ) { + throw( type = "InvalidType", message = "You sent an invalid type for length checking (function)" ); + } + } else { + if ( isCustomFunction( arguments.target ) or isClosure( arguments.target ) ) { + throw( + type = "InvalidType", + message = "You sent an invalid type for length checking (closure/function)" + ); + } + } + + return aLength; + } + + /** + * Get the identity hash code for the target. + * + * @target The target to get the hash code for + */ + private function getIdentityHashCode( required any target ){ + var system = createObject( "java", "java.lang.System" ); + return system.identityHashCode( arguments.target ); + } + + /** + * Normalize the target to a struct if possible. + * + * @target The target to normalize + * + * @throws InvalidTargetType - If we can normalize it to a struct + */ + private function normalizeToStruct( any target ){ + if ( isStruct( arguments.target ) ) { + return arguments.target; + } + if ( isQuery( arguments.target ) ) { + return getMetadata( arguments.target ).reduce( ( results, item ) => { + results[ item.name ] = {}; + return results; + }, {} ); + } + throw( "InvalidTargetType", "The target is not a struct or query" ); + } + +} diff --git a/src/test/java/ortus/boxlang/compiler/Child.cfc b/src/test/java/ortus/boxlang/compiler/Child.cfc new file mode 100644 index 000000000..43676ddb0 --- /dev/null +++ b/src/test/java/ortus/boxlang/compiler/Child.cfc @@ -0,0 +1,6 @@ +component extends="Parent" { + public function init( num ){ + super.init( argumentCollection = arguments ); + return this; + } +} \ No newline at end of file diff --git a/src/test/java/ortus/boxlang/compiler/FileTesterTest.java b/src/test/java/ortus/boxlang/compiler/FileTesterTest.java new file mode 100644 index 000000000..1c7951205 --- /dev/null +++ b/src/test/java/ortus/boxlang/compiler/FileTesterTest.java @@ -0,0 +1,141 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.compiler; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import ortus.boxlang.runtime.BoxRuntime; +import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; +import ortus.boxlang.runtime.scopes.IScope; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.scopes.VariablesScope; + +public class FileTesterTest { + + static BoxRuntime instance; + IBoxContext context; + IScope variables; + static Key result = new Key( "result" ); + + @BeforeAll + public static void setUp() { + instance = BoxRuntime.getInstance( true ); + } + + @AfterAll + public static void teardown() { + + } + + @BeforeEach + public void setupEach() { + context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); + variables = context.getScopeNearby( VariablesScope.name ); + } + + @DisplayName( "Test valueList() to queryColumnData().toList()" ) + @Test + public void testValueListToQueryColumnDataToListDot() { + instance.executeSource( + """ + result = new src.test.java.ortus.boxlang.compiler.Assertion(); + // result.startup(); + """, + context ); + } + + @DisplayName( "Test valueList() to queryColumnData().toList()" ) + @Test + public void testTestbox() { + // instance.useJavaBoxpiler(); + instance.executeSource( + """ + function(){ + /* + + */ + // do nothing + } + """, + context ); + } + + @DisplayName( "Test valueList() to queryColumnData().toList()" ) + @Test + public void closureArg() { + // instance.useJavaBoxpiler(); + instance.executeSource( + """ + a = [ "null" ]; + + a.map( ( item ) => item.replaceNoCase( 'ModuleConfig.cfc', '' ) ) + .each( ( path ) => { + len( + 'testbox' & arguments.path + .replaceNoCase( "", '' ) + .reReplace( '[\\/]', '.', 'all' ) + .reReplace( '\\.$', '', 'all' ) + ); + } ); + """, + context ); + } + + @DisplayName( "Test valueList() to queryColumnData().toList()" ) + @Test + public void testSaveContent() { + // instance.useJavaBoxpiler(); + instance.executeSource( + """ + y = [] + savecontent variable="x" { + if( true ){ + loop array="#y#" index = "i" { + + } + } + } + """, + context ); + } + + @DisplayName( "Test valueList() to queryColumnData().toList()" ) + @Test + public void testSuperInit() { + // instance.useJavaBoxpiler(); + instance.executeSource( + """ + a = new src.test.java.ortus.boxlang.compiler.Child(); + """, + context ); + } + + @DisplayName( "Test valueList() to queryColumnData().toList()" ) + @Test + public void x() { + // instance.useJavaBoxpiler(); + instance.executeSource( + """ + a = expand ?: true + """, + context ); + } + +} diff --git a/src/test/java/ortus/boxlang/compiler/Parent.cfc b/src/test/java/ortus/boxlang/compiler/Parent.cfc new file mode 100644 index 000000000..3af8f022d --- /dev/null +++ b/src/test/java/ortus/boxlang/compiler/Parent.cfc @@ -0,0 +1,5 @@ +component { + public function init(){ + + } +} \ No newline at end of file diff --git a/src/test/java/ortus/boxlang/compiler/Scheduler.cfc b/src/test/java/ortus/boxlang/compiler/Scheduler.cfc new file mode 100644 index 000000000..5279a087b --- /dev/null +++ b/src/test/java/ortus/boxlang/compiler/Scheduler.cfc @@ -0,0 +1,20 @@ +component { + + Scheduler function startup(){ + variables.tasks = []; + variables.tasks.each( function( taskName, taskRecord ){ + if ( true ) { + + } else { + + } + + try { + + } catch ( any e ) { + + } + } ); + } + +} diff --git a/src/test/java/ortus/boxlang/compiler/SimpleReporter.cfc b/src/test/java/ortus/boxlang/compiler/SimpleReporter.cfc new file mode 100644 index 000000000..9157d0ee0 --- /dev/null +++ b/src/test/java/ortus/boxlang/compiler/SimpleReporter.cfc @@ -0,0 +1,55 @@ +/** + * Copyright Since 2005 TestBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * A simple HTML reporter + */ +component { + + /** + * Get the name of the reporter + */ + function getName(){ + return "Simple"; + } + + /** + * Do the reporting thing here using the incoming test results + * The report should return back in whatever format they desire and should set any + * Specific browser types if needed. + * + * @results The instance of the TestBox TestResult object to build a report on + * @testbox The TestBox core object + * @options A structure of options this reporter needs to build the report with + * @justReturn Boolean flag that if set just returns the content with no content type and buffer reset + */ + any function runReport(){ + if ( true ) { + // content type + // getPageContextResponse().setContentType( "text/html" ); + } + + // bundle stats + variables.bundleStats = ""; + + // prepare base links + // variables.baseURL = "?"; + // if ( structKeyExists( url, "method" ) ) { + // variables.baseURL &= "method=#urlEncodedFormat( url.method )#"; + // } + // if ( structKeyExists( url, "output" ) ) { + // variables.baseURL &= "output=#urlEncodedFormat( url.output )#"; + // } + + // prepare incoming params + // prepareIncomingParams(); + + // prepare the report + savecontent variable="local.report" { + include "template.cfm"; + } + + return local.report; + } + +} diff --git a/src/test/java/ortus/boxlang/compiler/Testbox.cfc b/src/test/java/ortus/boxlang/compiler/Testbox.cfc new file mode 100644 index 000000000..d86b725e9 --- /dev/null +++ b/src/test/java/ortus/boxlang/compiler/Testbox.cfc @@ -0,0 +1,918 @@ +/** + * Copyright Since 2005 TestBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * Welcome to the next generation of BDD and xUnit testing for BoxLang & CFML applications + * The TestBox core class allows you to execute all kinds of test bundles, directories and more. + */ +component accessors="true" { + + // The version + property name="version"; + // The codename + property name="codename"; + // The main utility object + property name="utility"; + // The class bundles to test + property name="bundles"; + // The labels used for the testing + property name="labels"; + // The labels excluded from testing + property name="excludes"; + // The reporter attached to this runner + property name="reporter"; + // The configuration options attached to this runner + property name="options"; + // Last TestResult in case runner wants to inspect it + property name="result"; + // Code Coverage Service + property name="coverageService"; + // TestBox Modules Registry + property name="modules"; + // A list of globbing patterns to match bundles to test ONLY! Ex: *Spec|*Test + property name="bundlesPattern"; + + // Static Variables + variables.TESTBOX_PATH = expandPath( "/testbox" ); + variables.IS_BOXLANG = server.keyExists( "boxlang" ); + variables.IS_CLI = variables.IS_BOXLANG && server.boxlang.cliMode ? true : false; + variables.DEFAULT_REPORTER = variables.IS_CLI ? "text" : "simple"; + variables.DEFAULT_BUNDLES_PATTERN = "*Spec*.cfc|*Test*.cfc|*Spec*.bx|*Test*.bx"; + // TestBox Info : Modified by the build process. + variables.VERSION = "6.0.0+8"; + variables.CODENAME = ""; + + /** + * Constructor + * + * @bundles The path, list of paths or array of paths of the spec bundle classes to run and test + * @directory The directory to test which can be a simple mapping path or a struct with the following options: [ mapping = the path to the directory using dot notation (myapp.testing.specs), recurse = boolean, filter = closure that receives the path of the class found, it must return true to process or false to continue process ] + * @directories Same as @directory, but accepts an array or list + * @reporter The type of reporter to use for the results, by default is uses our 'simple' report. You can pass in a core reporter string type or an instance of a testbox.system.reports.IReporter + * @labels The list or array of labels that a suite or spec must have in order to execute. + * @excludes The list or array of labels that a suite or spec must not have in order to execute. + * @options A structure of configuration options that are optionally used to configure a runner. + * @bundlesPattern A globbing pattern list to match bundles to test ONLY, matches directoryList() filters! Ex: *Spec|*Test + */ + // any function init( + // any bundles = [], + // any directory = {}, + // any directories = {}, + // any reporter = variables.DEFAULT_REPORTER, + // any labels = [], + // any excludes = [], + // struct options = {}, + // string bundlesPattern = variables.DEFAULT_BUNDLES_PATTERN + // ){ + // // Bundles pattern + // if ( !len( arguments.bundlesPattern ) ) { + // arguments.bundlesPattern = variables.DEFAULT_BUNDLES_PATTERN; + // } + // variables.bundlesPattern = arguments.bundlesPattern; + // // Utility and mappings + // variables.utility = new testbox.system.util.Util(); + // // Coverage Service + // if ( !structKeyExists( arguments.options, "coverage" ) ) { + // arguments.options.coverage = {}; + // } + // variables.coverageService = new testbox.system.coverage.CoverageService( arguments.options.coverage ); + // // reporter + // variables.reporter = arguments.reporter; + // // options + // variables.options = arguments.options; + // // Empty bundles to start + // variables.bundles = []; + // // Modules Init + // variables.modules = { "mappings" : {}, "registry" : structNew( "ordered" ) }; + + // // inflate labels + // inflateLabels( arguments.labels ); + // // inflate excludes + // inflateExcludes( arguments.excludes ); + // // Add bundles given (if any) + // addBundles( arguments.bundles ); + // // Add directory given (if any) + // addDirectory( arguments.directory ); + // // Add directories given (if any) + // addDirectories( arguments.directories ); + // // Load TestBox Modules + // loadTestBoxModules(); + + // return this; + // } + + /** + * Load the TestBox Modules + */ + function loadTestBoxModules(){ + var modulesPath = variables.TESTBOX_PATH & "/system/modules"; + + // Register Modules + directoryList( modulesPath, true, "path", "ModuleConfig.cfc" ) + .map( ( item ) => item.replaceNoCase( "ModuleConfig.cfc", "" ) ) + .each( ( path ) => { + writeDump( arguments ); + abort; + registerModule( + "testbox" & arguments.path + .replaceNoCase( variables.TESTBOX_PATH, "" ) + .reReplace( "[\\\/]", ".", "all" ) + .reReplace( "\.$", "", "all" ) + ); + } ); + + directoryList( modulesPath, true, "path", "ModuleConfig.bx" ) + .map( ( item ) => item.replaceNoCase( "ModuleConfig.bx", "" ) ) + .each( ( path ) => { + registerModule( + "testbox" & arguments.path + .replaceNoCase( variables.TESTBOX_PATH, "" ) + .reReplace( "[\\\/]", ".", "all" ) + .reReplace( "\.$", "", "all" ) + ); + } ); + + // Activate Modules + variables.modules.registry.each( ( moduleName, config ) => activateModule( moduleName ) ); + + // Register Global Mappings + variables.utility.addMapping( mappings: variables.modules.mappings ); + } + + /** + * Register a module in TestBox + * + * @path The invocationPath path to the module. Ex: tests.resources.myModule + */ + function registerModule( required path ){ + var moduleName = listLast( arguments.path, "." ); + var absolutePath = expandPath( "/" & arguments.path.replace( ".", "/", "all" ) ); + // Register Mapping + variables.modules.mappings[ moduleName ] = absolutePath; + // Register Module + variables.modules.registry[ moduleName ] = { + "name" : moduleName, + "settings" : {}, + "moduleConfig" : "", + "path" : absolutePath, + "loadTime" : now(), + "activationTime" : 0, + "active" : false, + "activationFailure" : {}, + "mapping" : moduleName, + "invocationPath" : arguments.path + }; + return this; + } + + /** + * Activate a module in TestBox + * + * @name The name of the module to activate + * + * @throws ModuleNotRegisteredException - If the module is not registered + */ + function activateModule( required name ){ + // Verify it + if ( !variables.modules.registry.keyExists( arguments.name ) ) { + throw( "ModuleNotRegisteredException", "The module #arguments.name# is not registered in TestBox" ); + } + + // If active, skip activation + var moduleRecord = variables.modules.registry[ arguments.name ]; + if ( moduleRecord.active ) { + return this; + } + + // Create and Decorate ModuleConfig + moduleRecord.moduleConfig = variables.utility + .getMixerUtil() + .start( createObject( "component", moduleRecord.invocationPath & ".ModuleConfig" ) ); + + // Inject properties + moduleRecord.moduleConfig + .injectPropertyMixin( "testbox", this ) + .injectPropertyMixin( "testboxVersion", variables.version ) + .injectPropertyMixin( "moduleMapping", moduleRecord.invocationPath ) + .injectPropertyMixin( "modulePath", moduleRecord.path ) + .injectPropertyMixin( "getJavaSystem", getEnv().getJavaSystem ) + .injectPropertyMixin( "getSystemSetting", getEnv().getSystemSetting ) + .injectPropertyMixin( "getSystemProperty", getEnv().getSystemProperty ) + .injectPropertyMixin( "getEnv", getEnv().getEnv ); + + // Activate it + try { + moduleRecord.moduleConfig.configure(); + moduleRecord.settings = moduleRecord.moduleConfig.getPropertyMixin( "settings", "variables", {} ); + moduleRecord.active = true; + moduleRecord.activationTime = now(); + moduleRecord.moduleConfig.onLoad(); + } catch ( any e ) { + moduleRecord.activationFailure = e; + // writeDump( + // var = "**** Error activating (#arguments.name#) TestBox Module: #e.message & e.detail#", + // output = "console" + // ); + } + + return this; + } + + /** + * Register and activate a TestBox module. You must pass the full invocation + * path in order to register and activate the module. + * + * @path The invocationPath path to the module. Ex: tests.resources.myModule + */ + function registerAndActivateModule( required path ){ + var moduleName = listLast( arguments.path, "." ); + registerModule( arguments.path ).activateModule( moduleName ); + variables.utility.addMapping( name: moduleName, path: variables.modules.registry[ moduleName ].path ); + return this; + } + + /** + * Get only the activated modules registry + * + * @return The struct of activated modules + */ + struct function getActiveModules(){ + return variables.modules.registry.filter( ( moduleName, config ) => { + return config.active; + } ); + } + + /** + * Get the modules registry + * + * @return The struct of registered modules regardless if they are activated or not + */ + struct function getModuleRegistry(){ + return variables.modules.registry; + } + + /** + * Get the TestBox Env object + * + * @return testbox.system.util.Env + */ + function getEnv(){ + // Lazy Load it + if ( isNull( variables.env ) ) { + variables.env = new testbox.system.util.Env(); + } + return variables.env; + } + + /** + * Register a directory to test + * + * @directory A directory to test which can be a simple mapping path or a struct with the following options: [ mapping = the path to the directory using dot notation (myapp.testing.specs), recurse = boolean, filter = closure that receives the path of the class found, it must return true to process or false to continue process ] + */ + any function addDirectory( required any directory, boolean recurse = true ){ + // inflate directory? + if ( isSimpleValue( arguments.directory ) ) { + arguments.directory = { + mapping : arguments.directory, + recurse : arguments.recurse + }; + } + // directory passed? + if ( !structIsEmpty( arguments.directory ) ) { + for ( var bundle in getSpecPaths( arguments.directory ) ) { + arrayAppend( variables.bundles, bundle ); + } + } + return this; + } + + /** + * Constructor + * + * @directories A set of directories to test which can be a list of simple mapping paths or an array of structs with the following options: [ mapping = the path to the directory using dot notation (myapp.testing.specs), recurse = boolean, filter = closure that receives the path of the class found, it must return true to process or false to continue process ] + */ + any function addDirectories( required any directories, boolean recurse = true ){ + if ( isSimpleValue( arguments.directories ) ) { + arguments.directories = listToArray( arguments.directories ); + } + for ( var dir in arguments.directories ) { + addDirectory( dir, arguments.recurse ); + } + return this; + } + + /** + * Add bundles to the TestBox `bundles` target to test + * + * @bundles The path, list of paths or array of paths of the spec bundle classess to run and test + */ + any function addBundles( required any bundles ){ + if ( isSimpleValue( arguments.bundles ) ) { + arguments.bundles = listToArray( arguments.bundles ); + } + for ( var bundle in arguments.bundles ) { + arrayAppend( variables.bundles, bundle ); + } + return this; + } + + /** + * Run me some testing goodness, this can use the constructed object variables or the ones + * you can send right here. + * + * @bundles The path, list of paths or array of paths of the spec bundle classes to run and test + * @directory The directory to test which can be a simple mapping path or a struct with the following options: [ mapping = the path to the directory using dot notation (myapp.testing.specs), recurse = boolean, filter = closure that receives the path of the class found, it must return true to process or false to continue process ] + * @reporter The type of reporter to use for the results, by default is uses our 'simple' report. You can pass in a core reporter string type or an instance of a testbox.system.reports.IReporter. You can also pass a struct if the reporter requires options: {type="", options={}} + * @labels The list or array of labels that a suite or spec must have in order to execute. + * @excludes The list or array of labels that a suite or spec must not have in order to execute. + * @options A structure of configuration options that are optionally used to configure a runner. + * @testBundles A list or array of bundle names that are the ones that will be executed ONLY! + * @testSuites A list or array of suite names that are the ones that will be executed ONLY! + * @testSpecs A list or array of test names that are the ones that will be executed ONLY! + * @callbacks A struct of listener callbacks or a class with callbacks for listening to progress of the testing: onBundleStart,onBundleEnd,onSuiteStart,onSuiteEnd,onSpecStart,onSpecEnd + * @eagerFailure If this boolean is set to true, then execution of more bundle tests will stop once the first failure/error is detected. By default this is false. + */ + any function run( + any bundles, + any directory, + any reporter, + any labels, + any excludes, + struct options, + any testBundles = [], + any testSuites = [], + any testSpecs = [], + any callbacks = {}, + boolean eagerFailure = false + ){ + // reporter passed? + if ( !isNull( arguments.reporter ) ) { + variables.reporter = arguments.reporter; + } + // run it and get results + var results = runRaw( argumentCollection = arguments ); + // store latest results + variables.result = results; + // return report + var report = produceReport( results ); + // set response headers + sendStatusHeaders( results ); + + return report; + } + + /** + * Run me some testing goodness but give you back the raw TestResults object instead of a report + * + * @bundles The path, list of paths or array of paths of the spec bundle classes to run and test + * @directory The directory to test which can be a simple mapping path or a struct with the following options: [ mapping = the path to the directory using dot notation (myapp.testing.specs), recurse = boolean, filter = closure that receives the path of the class found, it must return true to process or false to continue process ] + * @labels The list or array of labels that a suite or spec must have in order to execute. + * @excludes The list or array of labels that a suite or spec must not have in order to execute. + * @options A structure of configuration options that are optionally used to configure a runner. + * @testBundles A list or array of bundle names that are the ones that will be executed ONLY! + * @testSuites A list or array of suite names that are the ones that will be executed ONLY! + * @testSpecs A list or array of test names that are the ones that will be executed ONLY! + * @callbacks A struct of listener callbacks or a class with callbacks for listening to progress of the testing: onBundleStart,onBundleEnd,onSuiteStart,onSuiteEnd,onSpecStart,onSpecEnd + * @eagerFailure If this boolean is set to true, then execution of more bundle tests will stop once the first failure/error is detected. By default this is false. + */ + testbox.system.TestResult function runRaw( + any bundles, + any directory, + any labels, + any excludes, + struct options, + any testBundles = [], + any testSuites = [], + any testSpecs = [], + any callbacks = {}, + boolean eagerFailure = false + ){ + // inflate options if passed + if ( !isNull( arguments.options ) ) { + variables.options = arguments.options; + } + // inflate directory? + if ( !isNull( arguments.directory ) && isSimpleValue( arguments.directory ) ) { + arguments.directory = { mapping : arguments.directory, recurse : true }; + } + // inflate test bundles, suites and specs from incoming variables. + arguments.testBundles = ( + isSimpleValue( arguments.testBundles ) ? listToArray( arguments.testBundles ) : arguments.testBundles + ); + arguments.testSuites = ( + isSimpleValue( arguments.testSuites ) ? listToArray( arguments.testSuites ) : arguments.testSuites + ); + arguments.testSpecs = ( + isSimpleValue( arguments.testSpecs ) ? listToArray( arguments.testSpecs ) : arguments.testSpecs + ); + + // Verify URL conventions for bundle, suites and specs exclusions. + if ( !isNull( url.testBundles ) ) { + testBundles.addAll( listToArray( urlDecode( url.testBundles ) ) ); + } + if ( !isNull( url.testSuites ) ) { + arguments.testSuites.addAll( listToArray( urlDecode( url.testSuites ) ) ); + } + if ( !isNull( url.testSpecs ) ) { + arguments.testSpecs.addAll( listToArray( urlDecode( url.testSpecs ) ) ); + } + if ( !isNull( url.testMethod ) ) { + arguments.testSpecs.addAll( listToArray( urlDecode( url.testMethod ) ) ); + } + + // Using a directory runner? + if ( !isNull( arguments.directory ) && !structIsEmpty( arguments.directory ) ) { + arguments.bundles = getSpecPaths( arguments.directory ); + } + + // Inflate labels if passed + if ( !isNull( arguments.labels ) ) { + inflateLabels( arguments.labels ); + } + // Inflate excludes if passed + if ( !isNull( arguments.excludes ) ) { + inflateExcludes( arguments.excludes ); + } + // If bundles passed, inflate those as the target + if ( !isNull( arguments.bundles ) ) { + inflateBundles( arguments.bundles ); + } + + // create results object + var results = new testbox.system.TestResult( + bundleCount = arrayLen( variables.bundles ), + labels = variables.labels, + excludes = variables.excludes, + testBundles = arguments.testBundles, + testSuites = arguments.testSuites, + testSpecs = arguments.testSpecs + ); + + coverageService.beginCapture(); + + // iterate and run the test bundles + for ( var thisBundlePath in variables.bundles ) { + // Skip interfaces, they are not testable + if ( getComponentMetadata( thisBundlePath ).type eq "interface" ) { + continue; + } + + // Execute Bundle + testBundle( + bundlePath = thisBundlePath, + testResults = results, + callbacks = arguments.callbacks + ); + + // Eager Failures on Bundle? + if ( arguments.eagerFailure ) { + var failuresDetected = results + .getBundleStats() + // Get stats for running bundle + .filter( function( item ){ + return ( item.path == thisBundlePath ? true : false ); + } ) + .reduce( function( result, item ){ + return ( item.totalError + item.totalFail ) > 0; + }, false ); + + if ( failuresDetected ) { + // Hard skip iterations + break; + } + } + } + + // mark end of testing bundles + results.end(); + // mark end of code coverage + coverageService.processCoverage( results = results, testbox = this ); + coverageService.endCapture( true ); + // Store results + variable.result = results; + + // Unload Modules + variables.modules.registry.each( ( moduleName, config ) => { + if ( config.active ) { + config.moduleConfig.onUnload( results ); + } + } ); + + return results; + } + + /** + * Run me some testing goodness, remotely via SOAP, Flex, REST, URL + * + * @bundles The path or list of paths of the spec bundle classes to run and test + * @directory The directory mapping to test: directory = the path to the directory using dot notation (myapp.testing.specs) + * @recurse Recurse the directory mapping or not, by default it does + * @reporter The type of reporter to use for the results, by default is uses our 'simple' report. You can pass in a core reporter string type or a class path to the reporter to use. + * @reporterOptions A JSON struct literal of options to pass into the reporter + * @labels The list of labels that a suite or spec must have in order to execute. + * @options A JSON struct literal of configuration options that are optionally used to configure a runner. + * @testBundles A list or array of bundle names that are the ones that will be executed ONLY! + * @testSuites A list of suite names that are the ones that will be executed ONLY! + * @testSpecs A list of test names that are the ones that will be executed ONLY! + * @eagerFailure If this boolean is set to true, then execution of more bundle tests will stop once the first failure/error is detected. By default this is false. + */ + remote function runRemote( + string bundles, + string directory, + boolean recurse = true, + string reporter = "simple", + string reporterOptions = "{}", + string labels = "", + string excludes = "", + string options, + string testBundles = "", + string testSuites = "", + string testSpecs = "", + boolean eagerFailure = false + ) output=true{ + // local init + init(); + + // simple to complex + arguments.labels = listToArray( arguments.labels ); + arguments.excludes = listToArray( arguments.excludes ); + arguments.testBundles = listToArray( arguments.testBundles ); + arguments.testSuites = listToArray( arguments.testSuites ); + arguments.testSpecs = listToArray( arguments.testSpecs ); + + // options inflate from JSON + if ( !isNull( arguments.options ) and isJSON( arguments.options ) ) { + arguments.options = deserializeJSON( arguments.options ); + } else { + arguments.options = {}; + } + + // Inflate directory? + if ( !isNull( arguments.directory ) and len( arguments.directory ) ) { + arguments.directory = { + mapping : arguments.directory, + recurse : arguments.recurse + }; + } + + // reporter options inflate from JSON + if ( !isNull( arguments.reporterOptions ) and isJSON( arguments.reporterOptions ) ) { + arguments.reporterOptions = deserializeJSON( arguments.reporterOptions ); + } else { + arguments.reporterOptions = {}; + } + + // setup reporter + if ( !isNull( arguments.reporter ) and len( arguments.reporter ) ) { + variables.reporter = { + type : arguments.reporter, + options : arguments.reporterOptions + }; + } + + // run it and get results + variables.result = runRaw( argumentCollection = arguments ); + + // check if reporter is "raw" and if raw, just return it else output the results + if ( variables.reporter.type == "raw" ) { + return produceReport( variables.result ); + } else { + writeOutput( produceReport( variables.result ) ); + } + + // create status headers + sendStatusHeaders( variables.result ); + } + + /** + * Send some status headers + */ + private function sendStatusHeaders( required results ){ + try { + var response = getPageContext().getResponse(); + + response.addHeader( "x-testbox-totalDuration", javacast( "string", results.getTotalDuration() ) ); + response.addHeader( "x-testbox-totalBundles", javacast( "string", results.getTotalBundles() ) ); + response.addHeader( "x-testbox-totalSuites", javacast( "string", results.getTotalSuites() ) ); + response.addHeader( "x-testbox-totalSpecs", javacast( "string", results.getTotalSpecs() ) ); + response.addHeader( "x-testbox-totalPass", javacast( "string", results.getTotalPass() ) ); + response.addHeader( "x-testbox-totalFail", javacast( "string", results.getTotalFail() ) ); + response.addHeader( "x-testbox-totalError", javacast( "string", results.getTotalError() ) ); + response.addHeader( "x-testbox-totalSkipped", javacast( "string", results.getTotalSkipped() ) ); + } catch ( Any e ) { + writeLog( + type = "error", + text = "Error sending TestBox headers: #e.message# #e.detail# #e.stackTrace#", + file = "testbox.log" + ); + } + + return this; + } + + /************************************** REPORTING COMMON METHODS *********************************************/ + + /** + * Build a report according to this runner's setup reporter, which can be anything. + * + * @results The results object to use to produce a report + */ + private any function produceReport( required results ){ + var iData = { type : "", options : {} }; + + // If the type is a simple value then inflate it + if ( isSimpleValue( variables.reporter ) ) { + iData = { type : buildReporter( variables.reporter ), options : {} }; + } + // If the incoming reporter is an object. + else if ( isObject( variables.reporter ) ) { + iData = { type : variables.reporter, options : {} }; + } + // Do we have reporter type and options + else if ( isStruct( variables.reporter ) ) { + iData.type = buildReporter( variables.reporter.type ); + if ( structKeyExists( variables.reporter, "options" ) ) { + iData.options = variables.reporter.options; + } + } + // build the report from the reporter + return iData.type.runReport( arguments.results, this, iData.options ); + } + + /** + * Build a reporter according to passed in reporter type or class path + * + * @reporter The reporter type to build. + */ + any function buildReporter( required reporter ){ + switch ( arguments.reporter ) { + case "json": { + return new "testbox.system.reports.JSONReporter"( ); + } + case "xml": { + return new "testbox.system.reports.XMLReporter"( ); + } + case "raw": { + return new "testbox.system.reports.RawReporter"( ); + } + case "simple": { + return new "testbox.system.reports.SimpleReporter"( ); + } + case "dot": { + return new "testbox.system.reports.DotReporter"( ); + } + case "text": { + return new "testbox.system.reports.TextReporter"( ); + } + case "junit": { + return new "testbox.system.reports.JUnitReporter"( ); + } + case "antjunit": { + return new "testbox.system.reports.ANTJUnitReporter"( ); + } + case "console": { + return new "testbox.system.reports.ConsoleReporter"( ); + } + case "min": { + return new "testbox.system.reports.MinReporter"( ); + } + case "mintext": { + return new "testbox.system.reports.MinTextReporter"( ); + } + case "tap": { + return new "testbox.system.reports.TapReporter"( ); + } + case "doc": { + return new "testbox.system.reports.DocReporter"( ); + } + case "codexwiki": { + return new "testbox.system.reports.CodexWikiReporter"( ); + } + default: { + return new "#arguments.reporter#"( ); + } + } + } + + /** + * Announce an event to all modules + * + * @event The name of the event to announce + * @args The arguments to pass to the event: struct or array + */ + function announceToModules( required event, args = {} ){ + getActiveModules().each( ( moduleName, config ) => { + if ( structKeyExists( config.moduleConfig, event ) ) { + invoke( config.moduleConfig, event, args ); + } + } ); + } + + /***************************************** PRIVATE ************************************************************/ + + /** + * This method executes the tests in a bundle class according to type. If the testing was ok a true is returned. + * + * @bundlePath The path of the Bundle class to test. + * @testResults The testing results object to keep track of results + * @callbacks The callbacks struct or class + * + * @throws BundleRunnerMajorException + */ + private function testBundle( + required bundlePath, + required testResults, + required callbacks + ){ + // create new target bundle and get its metadata + try { + var target = getBundle( arguments.bundlePath ); + } + // CFML + catch ( "AbstractComponentException" e ) { + // Skip abstract components + return this; + } + // BoxLang + catch ( "AbstractClassException" e ) { + // Skip abstract components + return this; + } + + // verify call backs + if ( structKeyExists( arguments.callbacks, "onBundleStart" ) ) { + arguments.callbacks.onBundleStart( target, testResults ); + } + // Module call backs + announceToModules( "onBundleStart", { target : target, testResults : testResults } ); + + try { + // Discover type? + if ( structKeyExists( target, "run" ) ) { + // Run via BDD Style + new testbox.system.runners.BDDRunner( options = variables.options, testbox = this ).run( + target, + arguments.testResults, + arguments.callbacks + ); + } else { + // Run via xUnit Style + new testbox.system.runners.UnitRunner( options = variables.options, testbox = this ).run( + target, + arguments.testResults, + arguments.callbacks + ); + } + } catch ( Any e ) { + throw( + message = "Error executing bundle - #arguments.bundlePath# message: #e.message# #e.detail#", + detail = e.stackTrace, + type = "BundleRunnerMajorException" + ); + } + + // Store debug buffer for this bundle + arguments.testResults.storeDebugBuffer( target.getDebugBuffer() ); + + // verify call backs + if ( structKeyExists( arguments.callbacks, "onBundleEnd" ) ) { + arguments.callbacks.onBundleEnd( target, testResults ); + } + // Module call backs + announceToModules( "onBundleEnd", { target : target, testResults : testResults } ); + + return this; + } + + /** + * Creates and returns a bundle class with spec capabilities if not inherited. + * + * @bundlePath The path to the Bundle class + * + * @throws AbstractComponentException - When an abstract component exists as a spec + */ + private any function getBundle( required bundlePath ){ + try { + var bundle = createObject( "component", "#arguments.bundlePath#" ); + } catch ( "Application" e ) { + if ( findNoCase( "abstract component", e.message ) ) { + throw( type: "AbstractComponentException", message: "Skip abstract components" ); + } + rethrow; + } + var familyPath = "testbox.system.BaseSpec"; + + // check if base spec assigned + if ( isInstanceOf( bundle, familyPath ) ) { + return bundle; + } + + // Else virtualize it + var baseObject = new testbox.system.BaseSpec(); + var excludedProperties = ""; + + // Mix it up baby + variables.utility.getMixerUtil().start( bundle ); + + // Mix in the virtual methods + for ( var key in baseObject ) { + // If target has overriden method, then don't override it with mixin, simulated inheritance + if ( NOT structKeyExists( bundle, key ) AND NOT listFindNoCase( excludedProperties, key ) ) { + bundle.injectMixin( key, baseObject[ key ] ); + } + } + + // Mix in virtual super class just in case we need it + bundle.$super = baseObject; + + return bundle; + } + + /** + * Get an array of spec paths from a directory + * + * @directory The directory information struct to test: [ mapping = the path to the directory using dot notation (myapp.testing.specs), recurse = boolean, filter = closure that receives the path of the class found, it must return true to process or false to continue process ] + */ + private function getSpecPaths( required directory ){ + var results = []; + var pathPatternMatcher = new testbox.system.modules.globber.models.PathPatternMatcher(); + + // recurse default + arguments.directory.recurse = ( + structKeyExists( arguments.directory, "recurse" ) ? arguments.directory.recurse : true + ); + // clean up paths + var bundleExpandedPath = expandPath( "/" & replace( arguments.directory.mapping, ".", "/", "all" ) ); + bundleExpandedPath = replace( bundleExpandedPath, "\", "/", "all" ); + + // search directory with filters + var bundlesFound = directoryList( + bundleExpandedPath, + arguments.directory.recurse, + "path", + "", + "asc", + "file" + ).filter( ( path ) => { + return pathPatternMatcher.matchPatterns( variables.bundlesPattern.listToArray( "|" ), path ); + } ); + + // cleanup paths and store them for usage + for ( var x = 1; x lte arrayLen( bundlesFound ); x++ ) { + // filter closure exists and the filter does not match the path + if ( + structKeyExists( arguments.directory, "filter" ) && !arguments.directory.filter( + bundlesFound[ x ] + ) + ) { + continue; + } + + // If not in BoxLang, skip bx bundles + if ( !variables.IS_BOXLANG and bundlesFound[ x ].listLast( "." ) eq "bx" ) { + continue; + } + + // standardize paths + bundlesFound[ x ] = reReplace( + reReplaceNoCase( bundlesFound[ x ], "\.(bx|cfc)", "" ), + "(\\|/)", + "/", + "all" + ); + // clean base out of them + bundlesFound[ x ] = replace( bundlesFound[ x ], bundleExpandedPath, "" ); + // Clean out slashes and append the mapping. + bundlesFound[ x ] = arguments.directory.mapping & reReplace( bundlesFound[ x ], "(\\|/)", ".", "all" ); + arrayAppend( results, bundlesFound[ x ] ); + } + + return results; + } + + /** + * Inflate incoming labels from a simple string as a standard array + */ + private function inflateLabels( required any labels ){ + variables.labels = ( isSimpleValue( arguments.labels ) ? listToArray( arguments.labels ) : arguments.labels ); + } + + /** + * Inflate incoming excludes from a simple string as a standard array + */ + private function inflateExcludes( required any excludes ){ + variables.excludes = ( + isSimpleValue( arguments.excludes ) ? listToArray( arguments.excludes ) : arguments.excludes + ); + } + + /** + * Inflate incoming bundles from a simple string as a standard array + */ + private function inflateBundles( required any bundles ){ + variables.bundles = ( + isSimpleValue( arguments.bundles ) ? listToArray( arguments.bundles ) : arguments.bundles + ); + } + +} diff --git a/src/test/java/ortus/boxlang/compiler/template.cfm b/src/test/java/ortus/boxlang/compiler/template.cfm new file mode 100644 index 000000000..578c4fba9 --- /dev/null +++ b/src/test/java/ortus/boxlang/compiler/template.cfm @@ -0,0 +1,12 @@ + + + + + + + + + + + + From de784fa5b11ee55aff40ea5265e82220a3d5a299 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Wed, 4 Dec 2024 10:52:03 -0600 Subject: [PATCH 026/193] BL-766 Make ASM default compiler --- src/main/java/ortus/boxlang/runtime/BoxRuntime.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/BoxRuntime.java b/src/main/java/ortus/boxlang/runtime/BoxRuntime.java index 75072ff15..a020065e7 100644 --- a/src/main/java/ortus/boxlang/runtime/BoxRuntime.java +++ b/src/main/java/ortus/boxlang/runtime/BoxRuntime.java @@ -1657,7 +1657,7 @@ private IBoxContext ensureRequestTypeContext( IBoxContext context, URI template * @return The Boxpiler implementation to use */ private IBoxpiler chooseBoxpiler() { - switch ( ( String ) this.configuration.experimental.getOrDefault( "compiler", "java" ) ) { + switch ( ( String ) this.configuration.experimental.getOrDefault( "compiler", "asm" ) ) { case "asm" : useASMBoxPiler(); return ASMBoxpiler.getInstance(); From 2092d822cc31b38904245e7e93c1d30c168a4f6c Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Wed, 4 Dec 2024 11:06:11 -0600 Subject: [PATCH 027/193] BL-811 implement compileTemplateBytes --- .../boxlang/compiler/asmboxpiler/ASMBoxpiler.java | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java index b4b13e98e..253310a35 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java @@ -3,6 +3,7 @@ import java.io.File; import java.io.PrintStream; import java.io.PrintWriter; +import java.nio.file.Path; import java.util.List; import java.util.function.BiConsumer; @@ -18,6 +19,7 @@ import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.BoxScript; import ortus.boxlang.compiler.ast.visitor.QueryEscapeSingleQuoteVisitor; +import ortus.boxlang.compiler.parser.Parser; import ortus.boxlang.compiler.parser.ParsingResult; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.util.ResolvedFilePath; @@ -151,6 +153,17 @@ private ParsingResult parseClassInfo( ClassInfo info ) { @Override public List compileTemplateBytes( ResolvedFilePath resolvedFilePath ) { - throw new UnsupportedOperationException( "Unimplemented method 'compileTemplateBytes'" ); + Path path = resolvedFilePath.absolutePath(); + ClassInfo classInfo = null; + // file extension is .bx or .cfc + if ( path.toString().endsWith( ".bx" ) || path.toString().endsWith( ".cfc" ) ) { + classInfo = ClassInfo.forClass( resolvedFilePath, Parser.detectFile( path.toFile() ), this ); + } else { + classInfo = ClassInfo.forTemplate( resolvedFilePath, Parser.detectFile( path.toFile() ), this ); + } + var classPool = getClassPool( classInfo.classPoolName() ); + classPool.putIfAbsent( classInfo.fqn().toString(), classInfo ); + compileClassInfo( classInfo.classPoolName(), classInfo.fqn().toString() ); + return diskClassUtil.readClassBytes( classInfo.classPoolName(), classInfo.fqn().toString() ); } } From 38ee229f33e1bebc8fa97368c53f17387f47702a Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Wed, 4 Dec 2024 12:05:14 -0600 Subject: [PATCH 028/193] Add ASM debug code --- .../compiler/asmboxpiler/ASMBoxpiler.java | 26 +++- .../compiler/asmboxpiler/AsmTranspiler.java | 135 +++++++++++------- 2 files changed, 102 insertions(+), 59 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java index 253310a35..e8a2b3ca9 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java @@ -3,6 +3,7 @@ import java.io.File; import java.io.PrintStream; import java.io.PrintWriter; +import java.io.StringWriter; import java.nio.file.Path; import java.util.List; import java.util.function.BiConsumer; @@ -103,13 +104,26 @@ private void doWriteClassInfo( BoxNode node, ClassInfo classInfo ) { node.accept( new QueryEscapeSingleQuoteVisitor() ); doCompileClassInfo( transpiler( classInfo ), classInfo, node, ( fqn, classNode ) -> { ClassWriter classWriter = new ClassWriter( ClassWriter.COMPUTE_FRAMES ); - if ( DEBUG ) { - classNode.accept( new CheckClassAdapter( new TraceClassVisitor( classWriter, new PrintWriter( System.out ) ) ) ); - } else { - classNode.accept( classWriter ); + try { + if ( DEBUG ) { + classNode.accept( new CheckClassAdapter( new TraceClassVisitor( classWriter, new PrintWriter( System.out ) ) ) ); + } else { + classNode.accept( classWriter ); + } + byte[] bytes = classWriter.toByteArray(); + diskClassUtil.writeBytes( classInfo.classPoolName(), fqn, "class", bytes ); + } catch ( Exception e ) { + StringWriter out = new StringWriter(); + classNode.accept( new CheckClassAdapter( new TraceClassVisitor( classWriter, new PrintWriter( out ) ) ) ); + + try { + e.printStackTrace( new PrintWriter( out ) ); + this.logger.error( out.toString() ); + } catch ( Exception ex ) { + this.logger.error( "Unabel to output ASM error info: " + ex.getMessage() ); + } + throw e; } - byte[] bytes = classWriter.toByteArray(); - diskClassUtil.writeBytes( classInfo.classPoolName(), fqn, "class", bytes ); } ); } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index 8e4f09853..5a0949e08 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -13,11 +13,11 @@ import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.InsnNode; -import org.objectweb.asm.tree.InvokeDynamicInsnNode; import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.MethodInsnNode; -import org.objectweb.asm.tree.MultiANewArrayInsnNode; import org.objectweb.asm.tree.TypeInsnNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; import ortus.boxlang.compiler.asmboxpiler.transformer.Transformer; @@ -142,6 +142,7 @@ import ortus.boxlang.compiler.ast.statement.BoxWhile; import ortus.boxlang.compiler.ast.statement.component.BoxComponent; import ortus.boxlang.compiler.ast.statement.component.BoxTemplateIsland; +import ortus.boxlang.compiler.javaboxpiler.JavaBoxpiler; import ortus.boxlang.compiler.parser.BoxSourceType; import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.dynamic.casters.BooleanCaster; @@ -163,6 +164,8 @@ public class AsmTranspiler extends Transpiler { + protected static final Logger logger = LoggerFactory.getLogger( JavaBoxpiler.class ); + private static final int[] STACK_SIZE_DELTA = { 0, // nop = 0 (0x0) 1, // aconst_null = 1 (0x1) @@ -569,63 +572,89 @@ public ClassNode transpile( BoxInterface boxClass ) throws BoxRuntimeException { public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnValueContext ) { Transformer transformer = registry.get( node.getClass() ); if ( transformer != null ) { - List nodes = transformer.transform( node, context, returnValueContext ); - if ( ASMBoxpiler.DEBUG ) { - int delta = 0; - for ( AbstractInsnNode value : nodes ) { - if ( value.getOpcode() == -1 ) { - continue; - } else if ( value instanceof FieldInsnNode fieldInsnNode ) { - Type type = Type.getType( fieldInsnNode.desc ); - delta += switch ( fieldInsnNode.getOpcode() ) { - case Opcodes.GETSTATIC -> type.getSize(); - case Opcodes.PUTSTATIC -> -type.getSize(); - case Opcodes.GETFIELD -> type.getSize() - 1; - case Opcodes.PUTFIELD -> -type.getSize() - 1; - default -> throw new IllegalStateException(); - }; - } else if ( value instanceof MethodInsnNode methodInsnNode ) { - Type type = Type.getMethodType( methodInsnNode.desc ); - for ( Type argument : type.getArgumentTypes() ) { - delta -= argument.getSize(); - } - delta += type.getReturnType().getSize(); - delta += switch ( methodInsnNode.getOpcode() ) { - case Opcodes.INVOKESTATIC -> 0; - case Opcodes.INVOKEVIRTUAL, Opcodes.INVOKEINTERFACE, Opcodes.INVOKESPECIAL -> -1; - default -> throw new IllegalStateException(); - }; - } else if ( value instanceof InvokeDynamicInsnNode invokeDynamicInsnNode ) { - Type type = Type.getMethodType( invokeDynamicInsnNode.desc ); - for ( Type argument : type.getArgumentTypes() ) { - delta -= argument.getSize(); - } - delta += type.getReturnType().getSize(); - } else if ( value instanceof LdcInsnNode ldcInsnNode ) { - delta += ldcInsnNode.cst instanceof Double || ldcInsnNode.cst instanceof Long ? 2 : 1; - } else if ( value instanceof MultiANewArrayInsnNode multiANewArrayInsnNode ) { - delta -= multiANewArrayInsnNode.dims + 1; - } else { - if ( STACK_SIZE_DELTA[ value.getOpcode() ] == Integer.MIN_VALUE ) { - throw new IllegalStateException(); - } - delta += STACK_SIZE_DELTA[ value.getOpcode() ]; - } + try { + List nodes = transformer.transform( node, context, returnValueContext ); + + return nodes; + } catch ( Exception e ) { + this.logger.error( "Error transforming:" + node.getClass().toString() ); + if ( getProperty( "filePath" ) != null ) { + this.logger.error( " file: " + getProperty( "filePath" ) ); + } else { + this.logger.error( " file: unkown" ); } - int expectation = switch ( returnValueContext ) { - case EMPTY, EMPTY_UNLESS_JUMPING -> 0; - case VALUE, VALUE_OR_NULL -> 1; - }; - if ( node instanceof BoxReturn ) { - expectation = 0; + if ( node.getPosition() != null ) { + this.logger.error( " position: " + node.getPosition().toString() ); + } else { + this.logger.error( " position: unkown" ); } - if ( expectation != delta ) { - throw new IllegalStateException( node.getClass() + " with " + returnValueContext + " yielded a stack delta of " + delta ); + if ( node.getSourceText() != null ) { + this.logger.error( " source: " + node.getSourceText() ); + } else { + this.logger.error( " source: unkown" ); } + + this.logger.error( e.getMessage() ); + throw e; } - return nodes; + // if ( ASMBoxpiler.DEBUG ) { + // int delta = 0; + // for ( AbstractInsnNode value : nodes ) { + // if ( value.getOpcode() == -1 ) { + // continue; + // } else if ( value instanceof FieldInsnNode fieldInsnNode ) { + // Type type = Type.getType( fieldInsnNode.desc ); + // delta += switch ( fieldInsnNode.getOpcode() ) { + // case Opcodes.GETSTATIC -> type.getSize(); + // case Opcodes.PUTSTATIC -> -type.getSize(); + // case Opcodes.GETFIELD -> type.getSize() - 1; + // case Opcodes.PUTFIELD -> -type.getSize() - 1; + // default -> throw new IllegalStateException(); + // }; + // } else if ( value instanceof MethodInsnNode methodInsnNode ) { + // Type type = Type.getMethodType( methodInsnNode.desc ); + // for ( Type argument : type.getArgumentTypes() ) { + // delta -= argument.getSize(); + // } + // delta += type.getReturnType().getSize(); + // delta += switch ( methodInsnNode.getOpcode() ) { + // case Opcodes.INVOKESTATIC -> 0; + // case Opcodes.INVOKEVIRTUAL, Opcodes.INVOKEINTERFACE, Opcodes.INVOKESPECIAL -> -1; + // default -> throw new IllegalStateException(); + // }; + // } else if ( value instanceof InvokeDynamicInsnNode invokeDynamicInsnNode ) { + // Type type = Type.getMethodType( invokeDynamicInsnNode.desc ); + // for ( Type argument : type.getArgumentTypes() ) { + // delta -= argument.getSize(); + // } + // delta += type.getReturnType().getSize(); + // } else if ( value instanceof LdcInsnNode ldcInsnNode ) { + // delta += ldcInsnNode.cst instanceof Double || ldcInsnNode.cst instanceof Long ? 2 : 1; + // } else if ( value instanceof MultiANewArrayInsnNode multiANewArrayInsnNode ) { + // delta -= multiANewArrayInsnNode.dims + 1; + // } else { + // if ( STACK_SIZE_DELTA[ value.getOpcode() ] == Integer.MIN_VALUE ) { + // throw new IllegalStateException(); + // } + // delta += STACK_SIZE_DELTA[ value.getOpcode() ]; + // } + // } + // int expectation = switch ( returnValueContext ) { + // case EMPTY, EMPTY_UNLESS_JUMPING -> 0; + // case VALUE, VALUE_OR_NULL -> 1; + // }; + + // if ( node instanceof BoxReturn ) { + // expectation = 0; + // } + + // if ( expectation != delta ) { + // throw new IllegalStateException( node.getClass() + " with " + returnValueContext + " yielded a stack delta of " + delta ); + // } + // } + // return nodes; } throw new IllegalStateException( "unsupported: " + node.getClass().getSimpleName() + " : " + node.getSourceText() ); } From 58641a084541fb2d37f3a2c6bfdd2aa9c5b85e70 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Wed, 4 Dec 2024 11:14:08 -0600 Subject: [PATCH 029/193] Typos --- .../ortus/boxlang/compiler/ast/expression/BoxAssignment.java | 4 ++-- .../java/ortus/boxlang/runtime/scopes/ArgumentsScope.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/ast/expression/BoxAssignment.java b/src/main/java/ortus/boxlang/compiler/ast/expression/BoxAssignment.java index 7f0a3a57a..eb0b85796 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/expression/BoxAssignment.java +++ b/src/main/java/ortus/boxlang/compiler/ast/expression/BoxAssignment.java @@ -39,8 +39,8 @@ public class BoxAssignment extends BoxExpression { /** * Constructor * - * @param left left side of the assigment - * @param right right side of the assigment + * @param left left side of the assignment + * @param right right side of the assignment * @param position position of the expression in the source code * @param sourceText source code of the expression */ diff --git a/src/main/java/ortus/boxlang/runtime/scopes/ArgumentsScope.java b/src/main/java/ortus/boxlang/runtime/scopes/ArgumentsScope.java index 223071879..832f4a0d9 100644 --- a/src/main/java/ortus/boxlang/runtime/scopes/ArgumentsScope.java +++ b/src/main/java/ortus/boxlang/runtime/scopes/ArgumentsScope.java @@ -89,7 +89,7 @@ public IStruct asStruct() { @Override public boolean containsKey( Key key ) { - // Leave this unneccessary override as a reminder. All the other get/put methods translate between numeric and named keys, but contains key + // Leave this unnecessary override as a reminder. All the other get/put methods translate between numeric and named keys, but contains key // will ONLY look for real live actual keys. This is largely for CF compat as `arguments[ 1 ]` works but `structKeyExists( arguments, 1 )` returns false. // It sort of makes sense if you think about it as the numeric keys only really existing when using the arguments as an array. // containsKey() is what powers structKeyExists(), and when using the arguments scope as a struct, it should only return true for actual keys and ignore the "spoofed" From e663e4b1dc53bfdb7fe8222db7358f4ae494c751 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Wed, 4 Dec 2024 12:08:35 -0600 Subject: [PATCH 030/193] Fix for de-serializing classes with mappings in the path --- .../boxlang/runtime/util/conversion/BoxClassState.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/util/conversion/BoxClassState.java b/src/main/java/ortus/boxlang/runtime/util/conversion/BoxClassState.java index 27ed2937d..10cb63083 100644 --- a/src/main/java/ortus/boxlang/runtime/util/conversion/BoxClassState.java +++ b/src/main/java/ortus/boxlang/runtime/util/conversion/BoxClassState.java @@ -23,6 +23,7 @@ import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.context.RequestBoxContext; import ortus.boxlang.runtime.dynamic.casters.BooleanCaster; import ortus.boxlang.runtime.loader.ClassLocator; import ortus.boxlang.runtime.runnables.IClassRunnable; @@ -121,8 +122,11 @@ private Boolean isSerializable( Array properties, Key property ) { * @throws ObjectStreamException */ private Object readResolve() throws ObjectStreamException { - IBoxContext context = BoxRuntime.getInstance().getRuntimeContext(); - IClassRunnable boxClass = ( IClassRunnable ) ClassLocator + IBoxContext context = RequestBoxContext.getCurrent(); + if ( context == null ) { + context = BoxRuntime.getInstance().getRuntimeContext(); + } + IClassRunnable boxClass = ( IClassRunnable ) ClassLocator .getInstance() .load( context, From e728290fe513be865298a19f82fbc7562fcfe85f Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 4 Dec 2024 12:26:11 -0600 Subject: [PATCH 031/193] BL-784 MORE tests on regex --- .../ortus/boxlang/runtime/scopes/Key.java | 1 + .../boxlang/runtime/types/util/RegexUtil.java | 38 ++++++----- .../runtime/types/util/StringUtil.java | 6 +- .../boxlang/runtime/util/RegexBuilder.java | 27 +++++++- src/main/resources/config/boxlang.json | 22 ++++++- .../runtime/util/EncryptionUtilTest.java | 1 - .../ortus/boxlang/runtime/util/FQNTest.java | 17 +++++ .../runtime/util/RegexBuilderTest.java | 63 +++++++++++++++++++ 8 files changed, 148 insertions(+), 27 deletions(-) create mode 100644 src/test/java/ortus/boxlang/runtime/util/RegexBuilderTest.java diff --git a/src/main/java/ortus/boxlang/runtime/scopes/Key.java b/src/main/java/ortus/boxlang/runtime/scopes/Key.java index 1e13fb66d..ba62aa89f 100644 --- a/src/main/java/ortus/boxlang/runtime/scopes/Key.java +++ b/src/main/java/ortus/boxlang/runtime/scopes/Key.java @@ -129,6 +129,7 @@ public class Key implements Comparable, Serializable { public static final Key boxCacheProvider = Key.of( "BoxCacheProvider" ); public static final Key boxlang = Key.of( "boxlang" ); public static final Key bxSessions = Key.of( "bxSessions" ); + public static final Key bxRegex = Key.of( "bxRegex" ); public static final Key boxMember = Key.of( "BoxMember" ); public static final Key boxRuntime = Key.of( "boxRuntime" ); public static final Key buffer = Key.of( "buffer" ); diff --git a/src/main/java/ortus/boxlang/runtime/types/util/RegexUtil.java b/src/main/java/ortus/boxlang/runtime/types/util/RegexUtil.java index 6aa6c236b..9803bc2b3 100644 --- a/src/main/java/ortus/boxlang/runtime/types/util/RegexUtil.java +++ b/src/main/java/ortus/boxlang/runtime/types/util/RegexUtil.java @@ -22,6 +22,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import ortus.boxlang.runtime.util.RegexBuilder; + public class RegexUtil { // POSIX Pattern @@ -89,37 +91,30 @@ public static String posixReplace( String expression, Boolean noCase ) { * Perl regex allows abitrary curly braces in the regex. This function escapes the curly braces that are not part of valid quantifiers. * Ex: {{foobar}} * which is not a valid quantifier, will be escaped to \{\{foobar\}\} - * + * * @param input The regular expression string - * + * * @return The escaped regular expression string */ /** * @param input The regular expression string - * + * * @return The escaped regular expression string */ public static String replaceNonQuantiferCurlyBraces( String input ) { // Regular expression to match valid quantifiers like {2,4}, {3}, {,5}, etc. - String quantifierRegex = "\\{\\d*,?\\d*\\}"; - - // Pattern to match valid quantifiers - Pattern quantifierPattern = Pattern.compile( quantifierRegex ); - - // Matcher for the input string - Matcher matcher = quantifierPattern.matcher( input ); - + Matcher matcher = RegexBuilder.of( input, RegexBuilder.REGEX_QUANTIFIER ).matcher(); // Create a StringBuilder to build the final output - StringBuilder escapedString = new StringBuilder(); - + StringBuilder escapedString = new StringBuilder(); // Index to keep track of the position in the input string - int lastIndex = 0; + int lastIndex = 0; while ( matcher.find() ) { // Append text between matches and escape curly braces in that portion - String betweenMatches = input.substring( lastIndex, matcher.start() ) - .replaceAll( "(? maxLength ) { diff --git a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java index 1981bd8d3..6df4ae2cb 100644 --- a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java +++ b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java @@ -18,8 +18,12 @@ package ortus.boxlang.runtime.util; import java.util.Objects; +import java.util.regex.Matcher; import java.util.regex.Pattern; +import ortus.boxlang.runtime.BoxRuntime; +import ortus.boxlang.runtime.scopes.Key; + /** * This class can be used as a utility to handle regular expressions. * Add as many compiled patterns as needed and make sure you give them a meaningful name. @@ -32,12 +36,15 @@ public class RegexBuilder { */ public static final Pattern BACKSLASH = Pattern.compile( "\\\\" ); public static final Pattern COLON = Pattern.compile( ":" ); - // cardNumber contains characters other than digit, space, or underscore public static final Pattern CREDIT_CARD_NUMBERS = Pattern.compile( "[0-9 ,_-]+" ); public static final Pattern MULTILINE_START_OF_LINE = Pattern.compile( "(?m)^" ); + public static final Pattern MULTIPLE_SPACES = Pattern.compile( "\\s+" ); public static final Pattern NO_DIGITS = Pattern.compile( "\\D" ); public static final Pattern PACKAGE_NAMES = Pattern.compile( "[^a-zA-Z0-9$\\.]" ); public static final Pattern PERIOD = Pattern.compile( "\\." ); + public static final Pattern REGEX_QUANTIFIER = Pattern.compile( "\\{\\d*,?\\d*\\}" ); + public static final Pattern REGEX_QUANTIFIER_END = Pattern.compile( "(? Pattern.compile( pattern ) ); + return this; } @@ -135,6 +149,15 @@ public Boolean matches() { return this.pattern.matcher( this.input ).matches(); } + /** + * Get the matcher instance for the input string and pattern + * + * @return The matcher instance + */ + public Matcher matcher() { + return this.pattern.matcher( this.input ); + } + /** * Replace all occurrences of the pattern in the input string with the replacement string * diff --git a/src/main/resources/config/boxlang.json b/src/main/resources/config/boxlang.json index b35113014..c219a3f04 100644 --- a/src/main/resources/config/boxlang.json +++ b/src/main/resources/config/boxlang.json @@ -174,7 +174,7 @@ "experimental": { // This choose the compiler to use for the runtime // Valid values are: "java", "asm" - "compiler": "java", + "compiler": "asm", // If enabled, it will generate AST JSON data under the project's /grapher/data folder "ASTCapture": false }, @@ -287,6 +287,24 @@ "useLastAccessTimeouts": false } }, + // Stores all dynamic regular expressions used in the runtime + "bxRegex": { + "provider": "BoxCacheProvider", + "properties": { + "evictCount": 1, + "evictionPolicy": "LRU", + "freeMemoryPercentageThreshold": 0, + "maxObjects": 500, + // 30 minutes ifnot used + "defaultLastAccessTimeout": 1800, + // 60 minutes default + "defaultTimeout": 3600, + "objectStore": "ConcurrentSoftReferenceStore", + "reapFrequency": 120, + "resetTimeoutOnAccess": false, + "useLastAccessTimeouts": true + } + }, "bxImports": { "provider": "BoxCacheProvider", "properties": { @@ -408,4 +426,4 @@ // } // } } -} \ No newline at end of file +} diff --git a/src/test/java/ortus/boxlang/runtime/util/EncryptionUtilTest.java b/src/test/java/ortus/boxlang/runtime/util/EncryptionUtilTest.java index 4162eafea..77b5af278 100644 --- a/src/test/java/ortus/boxlang/runtime/util/EncryptionUtilTest.java +++ b/src/test/java/ortus/boxlang/runtime/util/EncryptionUtilTest.java @@ -15,7 +15,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package ortus.boxlang.runtime.util; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/src/test/java/ortus/boxlang/runtime/util/FQNTest.java b/src/test/java/ortus/boxlang/runtime/util/FQNTest.java index 0a0a3415e..62ffc097f 100644 --- a/src/test/java/ortus/boxlang/runtime/util/FQNTest.java +++ b/src/test/java/ortus/boxlang/runtime/util/FQNTest.java @@ -1,3 +1,20 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ package ortus.boxlang.runtime.util; import static org.junit.jupiter.api.Assertions.assertEquals; diff --git a/src/test/java/ortus/boxlang/runtime/util/RegexBuilderTest.java b/src/test/java/ortus/boxlang/runtime/util/RegexBuilderTest.java new file mode 100644 index 000000000..1cfaeed4b --- /dev/null +++ b/src/test/java/ortus/boxlang/runtime/util/RegexBuilderTest.java @@ -0,0 +1,63 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.util; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import ortus.boxlang.runtime.BoxRuntime; +import ortus.boxlang.runtime.cache.providers.ICacheProvider; +import ortus.boxlang.runtime.scopes.Key; + +public class RegexBuilderTest { + + static BoxRuntime instance; + + @BeforeAll + public static void setUp() { + instance = BoxRuntime.getInstance( true ); + } + + @DisplayName( "Build a new regex matcher of a string only" ) + @Test + public void testOf() { + var matcher = RegexBuilder.of( "test" ); + assertThat( matcher ).isNotNull(); + } + + @DisplayName( "Build a new regex matcher of a string and a pattern" ) + @Test + public void testOfWithPattern() { + var matcher = RegexBuilder.of( "test", RegexBuilder.BACKSLASH ); + assertThat( matcher ).isNotNull(); + } + + @DisplayName( "Build a new regex matcher of a string and a string pattern" ) + @Test + public void testOfWithStringPattern() { + ICacheProvider regexCache = instance.getCacheService().getCache( Key.bxRegex ); + regexCache.clearAll(); + + var matcher = RegexBuilder.of( "test", "\\\\" ); + assertThat( matcher ).isNotNull(); + assertThat( regexCache.getSize() ).isEqualTo( 1 ); + } +} From a277a561e86735c8156406791b1732acb3cc009f Mon Sep 17 00:00:00 2001 From: Jon Clausen Date: Wed, 4 Dec 2024 12:30:19 -0600 Subject: [PATCH 032/193] fix logger class --- .../java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index 5a0949e08..674471b18 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -142,7 +142,6 @@ import ortus.boxlang.compiler.ast.statement.BoxWhile; import ortus.boxlang.compiler.ast.statement.component.BoxComponent; import ortus.boxlang.compiler.ast.statement.component.BoxTemplateIsland; -import ortus.boxlang.compiler.javaboxpiler.JavaBoxpiler; import ortus.boxlang.compiler.parser.BoxSourceType; import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.dynamic.casters.BooleanCaster; @@ -164,7 +163,7 @@ public class AsmTranspiler extends Transpiler { - protected static final Logger logger = LoggerFactory.getLogger( JavaBoxpiler.class ); + protected static final Logger logger = LoggerFactory.getLogger( ASMBoxpiler.class ); private static final int[] STACK_SIZE_DELTA = { 0, // nop = 0 (0x0) From 9270f7c1dc982acc47e65e2235ac81d569e60445 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 4 Dec 2024 12:32:10 -0600 Subject: [PATCH 033/193] BL-812 --- .../boxlang/runtime/dynamic/Referencer.java | 19 ++++++++++ .../TestCases/phase2/UDFFunctionTest.java | 37 +++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/src/main/java/ortus/boxlang/runtime/dynamic/Referencer.java b/src/main/java/ortus/boxlang/runtime/dynamic/Referencer.java index ee5eebbed..4bdc3e1b3 100644 --- a/src/main/java/ortus/boxlang/runtime/dynamic/Referencer.java +++ b/src/main/java/ortus/boxlang/runtime/dynamic/Referencer.java @@ -21,11 +21,14 @@ import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.context.IBoxContext.ScopeSearchResult; +import ortus.boxlang.runtime.dynamic.casters.CastAttempt; +import ortus.boxlang.runtime.dynamic.casters.StructCaster; import ortus.boxlang.runtime.interop.DynamicInteropService; import ortus.boxlang.runtime.interop.DynamicObject; import ortus.boxlang.runtime.runnables.IClassRunnable; import ortus.boxlang.runtime.scopes.IScope; import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.Struct; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; @@ -200,6 +203,22 @@ public static Object setDeep( IBoxContext context, boolean isFinal, Key mustBeSc throw new BoxRuntimeException( "Scope [" + mustBeScopeName.getName() + "] is not available in this context." ); } + // Catch the case where we're assigning an actual scope + // arguments = someStruct + if ( object instanceof IScope os && keys.length == 0 ) { + CastAttempt castedStruct = StructCaster.attempt( value ); + if ( castedStruct.wasSuccessful() ) { + os.clear(); + os.putAll( castedStruct.get() ); + return value; + } else { + // If this isn't a struct, then we're going to just create a key in the default scope. + // Adobe does this. Lucee just blows up. We're going to be more like Adobe. For now. Maybe. We'll see. + keys = new Key[] { os.getName() }; + object = context.getDefaultAssignmentScope(); + } + } + for ( int i = 0; i <= keys.length - 1; i++ ) { Key key = keys[ i ]; // At the final key, just assign our value and we're done diff --git a/src/test/java/TestCases/phase2/UDFFunctionTest.java b/src/test/java/TestCases/phase2/UDFFunctionTest.java index cb817c67b..70ce7c7bd 100644 --- a/src/test/java/TestCases/phase2/UDFFunctionTest.java +++ b/src/test/java/TestCases/phase2/UDFFunctionTest.java @@ -972,4 +972,41 @@ function foo( headers={}, flag=false ) { } + @Test + public void testOverwriteArgumentsScope() { + instance.executeSource( + """ + function foo() { + // Keys in argument scope are replaced with contents of this struct + arguments = { brad : 'wood' }; + return arguments; + } + result = foo( luis = 'majano' ); + """, + context ); + assertThat( variables.getAsStruct( result ) ).containsKey( Key.of( "brad" ) ); + assertThat( variables.getAsStruct( result ) ).doesNotContainKey( Key.of( "luis" ) ); + assertThat( variables.getAsStruct( result ).get( Key.of( "brad" ) ) ).isEqualTo( "wood" ); + } + + @Test + public void testOverwriteArgumentsScopeNonStruct() { + instance.executeSource( + """ + function foo() { + // Not assigning a struct, so we just set local.arguments as a normal variable + arguments = "hello"; + variables.localRef = local; + return arguments; + } + result = foo( luis = 'majano' ); + """, + context ); + assertThat( variables.getAsStruct( result ) ).containsKey( Key.of( "luis" ) ); + assertThat( variables.getAsStruct( result ) ).doesNotContainKey( Key.of( "brad" ) ); + assertThat( variables.getAsStruct( result ).get( Key.of( "luis" ) ) ).isEqualTo( "majano" ); + assertThat( variables.getAsStruct( Key.of( "localRef" ) ) ).containsKey( Key.of( "arguments" ) ); + assertThat( variables.getAsStruct( Key.of( "localRef" ) ).get( Key.of( "arguments" ) ) ).isEqualTo( "hello" ); + } + } From f8f36fdaab0429fbca9371a25f67179dda903606 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 4 Dec 2024 12:40:27 -0600 Subject: [PATCH 034/193] BL-784 more updates --- .../runtime/types/util/StringUtil.java | 20 +++++++++---------- .../boxlang/runtime/util/RegexBuilder.java | 3 +++ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/types/util/StringUtil.java b/src/main/java/ortus/boxlang/runtime/types/util/StringUtil.java index 82c88b554..de12a8fa2 100644 --- a/src/main/java/ortus/boxlang/runtime/types/util/StringUtil.java +++ b/src/main/java/ortus/boxlang/runtime/types/util/StringUtil.java @@ -151,19 +151,19 @@ public static String prettySql( String target ) { // trim it .map( String::trim ) // comma spacing - .map( item -> item.replaceAll( "\\s*(?![^()]*\\))(,)\\s*", "," + NEW_LINE + INDENT ) ) + .map( item -> RegexBuilder.of( item, RegexBuilder.SQL_COMMA_SPACING ).replaceAllAndGet( "," + NEW_LINE + INDENT ) ) // parenthesis spacing - .map( item -> item.replaceAll( "\\((\\w|\\'|\"|\\`)", "( $1" ) ) - .map( item -> item.replaceAll( "(\\w|\\'|\"|\\`)\\)", "$1 )" ) ) + .map( item -> RegexBuilder.of( item, RegexBuilder.SQL_PARENTHESIS_START ).replaceAllAndGet( "( $1" ) ) + .map( item -> RegexBuilder.of( item, RegexBuilder.SQL_PARENTHESIS_END ).replaceAllAndGet( "$1 )" ) ) // Keyword spacing - .map( item -> item.replaceAll( "(?i)(\s)*(" + SQL_KEYWORDS_REGEX + ")(\s)+", NEW_LINE + "$2" + NEW_LINE + INDENT ).toUpperCase() ) + .map( item -> RegexBuilder.of( item, "(?i)(\s)*(" + SQL_KEYWORDS_REGEX + ")(\s)+" ).replaceAllAndGet( NEW_LINE + "$2" + NEW_LINE + INDENT ) + .toUpperCase() ) // Indented Keyword spacing - .map( item -> item.replaceAll( "(?i)(" + SQL_INDENTED_KEYWORDS_REGEX + ")", NEW_LINE + INDENT + "$1" ).toUpperCase() ) + .map( item -> RegexBuilder.of( item, "(?i)(" + SQL_INDENTED_KEYWORDS_REGEX + ")" ).replaceAllAndGet( NEW_LINE + INDENT + "$1" ).toUpperCase() ) // Add a line break after a SQL_LOGICAL_OPERATORS and upper case the logical operator - .map( item -> item.replaceAll( "(?i)(" + SQL_LOGICAL_OPERATORS_REGEX + ")", "$1" + NEW_LINE ).toUpperCase() ) - + .map( item -> RegexBuilder.of( item, "(?i)(" + SQL_LOGICAL_OPERATORS_REGEX + ")" ).replaceAllAndGet( "$1" + NEW_LINE ).toUpperCase() ) // Add spacing before an after a SQL_OPERATORS_REGEX - .map( item -> item.replaceAll( "(?i)(" + SQL_OPERATORS_REGEX + ")", " $1 " ) ) + .map( item -> RegexBuilder.of( item, "(?i)(" + SQL_OPERATORS_REGEX + ")" ).replaceAllAndGet( " $1 " ) ) // Collect to a list of strings with a newline for each line .collect( StringBuilder::new, ( sb, s ) -> sb.append( s ).append( NEW_LINE ), StringBuilder::append ) .toString(); @@ -217,7 +217,7 @@ public static String lcFirst( String target ) { * @return The string in kebab-case */ public static String kebabCase( String target ) { - return target.toLowerCase().replaceAll( "\\s+", "-" ); + return RegexBuilder.of( target.toLowerCase(), RegexBuilder.MULTIPLE_SPACES ).replaceAllAndGet( "-" ); } /** @@ -228,7 +228,7 @@ public static String kebabCase( String target ) { * @return The string in snake_case */ public static String snakeCase( String target ) { - return target.toLowerCase().replaceAll( "\\s+", "_" ); + return RegexBuilder.of( target.toLowerCase(), RegexBuilder.MULTIPLE_SPACES ).replaceAllAndGet( "_" ); } /** diff --git a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java index 6df4ae2cb..aa2166365 100644 --- a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java +++ b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java @@ -46,6 +46,9 @@ public class RegexBuilder { public static final Pattern REGEX_QUANTIFIER_END = Pattern.compile( "(? Date: Wed, 4 Dec 2024 12:43:21 -0600 Subject: [PATCH 035/193] BL-784 more wip --- .../java/ortus/boxlang/runtime/loader/DiskClassLoader.java | 7 ++++--- src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/loader/DiskClassLoader.java b/src/main/java/ortus/boxlang/runtime/loader/DiskClassLoader.java index cea08df71..a1f78d153 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/DiskClassLoader.java +++ b/src/main/java/ortus/boxlang/runtime/loader/DiskClassLoader.java @@ -29,6 +29,7 @@ import ortus.boxlang.compiler.ClassInfo; import ortus.boxlang.compiler.IBoxpiler; import ortus.boxlang.runtime.BoxRuntime; +import ortus.boxlang.runtime.util.RegexBuilder; /** * Disk Class Loader for our Class Infos @@ -67,7 +68,7 @@ public DiskClassLoader( URL[] urls, ClassLoader parent, Path diskStore, IBoxpile super( urls, parent ); this.boxPiler = boxpiler; this.classPoolName = classPoolName; - this.classPoolDiskPrefix = classPoolName.replaceAll( "[^a-zA-Z0-9]", "_" ); + this.classPoolDiskPrefix = RegexBuilder.of( classPoolName, RegexBuilder.NON_ALPHANUMERIC ).replaceAllAndGet( "_" ); this.diskStore = diskStore.resolve( classPoolDiskPrefix ); // Init disk store @@ -112,12 +113,12 @@ protected synchronized Class findClass( String name ) throws ClassNotFoundExc /** * Abstract out the logic for determining if a class needs to be compiled - * + * * @param classInfo the class info, may be null if there is none * @param diskPath the path to the class file on disk, may not exist * @param name the fully qualified class name * @param baseName the base name of the class - * + * * @return true if the class needs to be compiled */ private boolean needsCompile( ClassInfo classInfo, Path diskPath, String name, String baseName ) { diff --git a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java index aa2166365..afa71f7d6 100644 --- a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java +++ b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java @@ -40,6 +40,7 @@ public class RegexBuilder { public static final Pattern MULTILINE_START_OF_LINE = Pattern.compile( "(?m)^" ); public static final Pattern MULTIPLE_SPACES = Pattern.compile( "\\s+" ); public static final Pattern NO_DIGITS = Pattern.compile( "\\D" ); + public static final Pattern NON_ALPHANUMERIC = Pattern.compile( "[^a-zA-Z0-9]" ); public static final Pattern PACKAGE_NAMES = Pattern.compile( "[^a-zA-Z0-9$\\.]" ); public static final Pattern PERIOD = Pattern.compile( "\\." ); public static final Pattern REGEX_QUANTIFIER = Pattern.compile( "\\{\\d*,?\\d*\\}" ); From 5b9ff7ce25275cbd0760eed9c28073db132e2a1e Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 4 Dec 2024 12:48:09 -0600 Subject: [PATCH 036/193] BL-784 more work on regex --- .../java/ortus/boxlang/runtime/jdbc/PendingQuery.java | 2 +- .../java/ortus/boxlang/runtime/jdbc/QueryParameter.java | 9 ++++++--- .../java/ortus/boxlang/runtime/util/RegexBuilder.java | 1 + 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/PendingQuery.java b/src/main/java/ortus/boxlang/runtime/jdbc/PendingQuery.java index 4be89cdfd..c2f787148 100644 --- a/src/main/java/ortus/boxlang/runtime/jdbc/PendingQuery.java +++ b/src/main/java/ortus/boxlang/runtime/jdbc/PendingQuery.java @@ -40,8 +40,8 @@ import ortus.boxlang.runtime.services.InterceptorService; import ortus.boxlang.runtime.types.Array; import ortus.boxlang.runtime.types.IStruct; -import ortus.boxlang.runtime.types.Struct; import ortus.boxlang.runtime.types.Query; +import ortus.boxlang.runtime.types.Struct; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.types.exceptions.DatabaseException; import ortus.boxlang.runtime.types.util.ListUtil; diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/QueryParameter.java b/src/main/java/ortus/boxlang/runtime/jdbc/QueryParameter.java index 1203c704b..1977302d0 100644 --- a/src/main/java/ortus/boxlang/runtime/jdbc/QueryParameter.java +++ b/src/main/java/ortus/boxlang/runtime/jdbc/QueryParameter.java @@ -14,19 +14,20 @@ */ package ortus.boxlang.runtime.jdbc; +import ortus.boxlang.runtime.dynamic.casters.BigIntegerCaster; import ortus.boxlang.runtime.dynamic.casters.BooleanCaster; +import ortus.boxlang.runtime.dynamic.casters.CastAttempt; import ortus.boxlang.runtime.dynamic.casters.DateTimeCaster; import ortus.boxlang.runtime.dynamic.casters.DoubleCaster; import ortus.boxlang.runtime.dynamic.casters.IntegerCaster; import ortus.boxlang.runtime.dynamic.casters.StringCaster; -import ortus.boxlang.runtime.dynamic.casters.BigIntegerCaster; -import ortus.boxlang.runtime.dynamic.casters.CastAttempt; import ortus.boxlang.runtime.dynamic.casters.StructCaster; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.QueryColumnType; import ortus.boxlang.runtime.types.Struct; import ortus.boxlang.runtime.types.util.ListUtil; +import ortus.boxlang.runtime.util.RegexBuilder; /** * Represents a parameter to a SQL query created via QueryExecute or the Query component. @@ -88,7 +89,9 @@ private QueryParameter( IStruct param ) { } this.value = this.isNullParam ? null : v; - this.type = QueryColumnType.fromString( sqltype.replaceAll( "(?i)CF_SQL_", "" ) ); + this.type = QueryColumnType.fromString( + RegexBuilder.of( sqltype, RegexBuilder.CF_SQL ).replaceAllAndGet( "" ) + ); this.maxLength = param.getAsInteger( Key.maxLength ); this.scale = param.getAsInteger( Key.scale ); } diff --git a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java index afa71f7d6..1cf1b601d 100644 --- a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java +++ b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java @@ -36,6 +36,7 @@ public class RegexBuilder { */ public static final Pattern BACKSLASH = Pattern.compile( "\\\\" ); public static final Pattern COLON = Pattern.compile( ":" ); + public static final Pattern CF_SQL = Pattern.compile( "(?i)CF_SQL_" ); public static final Pattern CREDIT_CARD_NUMBERS = Pattern.compile( "[0-9 ,_-]+" ); public static final Pattern MULTILINE_START_OF_LINE = Pattern.compile( "(?m)^" ); public static final Pattern MULTIPLE_SPACES = Pattern.compile( "\\s+" ); From 3f4ca1f407e4ac1398449a32eb49ef320b94f595 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 4 Dec 2024 12:52:43 -0600 Subject: [PATCH 037/193] BL-784 more wip --- .../ortus/boxlang/runtime/config/util/PlaceholderHelper.java | 3 ++- src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/config/util/PlaceholderHelper.java b/src/main/java/ortus/boxlang/runtime/config/util/PlaceholderHelper.java index f640e6637..b842f9a8e 100644 --- a/src/main/java/ortus/boxlang/runtime/config/util/PlaceholderHelper.java +++ b/src/main/java/ortus/boxlang/runtime/config/util/PlaceholderHelper.java @@ -26,6 +26,7 @@ import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.Struct; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; +import ortus.boxlang.runtime.util.RegexBuilder; /** * A helper class for resolving placeholders in configuration files @@ -174,7 +175,7 @@ public static String resolve( Object input ) { */ @SuppressWarnings( "unused" ) private static String escapeReplacementMetaChars( String input ) { - return input.replaceAll( "([\\\\$])", "\\\\$1" ); + return RegexBuilder.of( input, RegexBuilder.REGEX_META ).replaceAllAndGet( "\\\\$1" ); } } diff --git a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java index 1cf1b601d..bbdc9800c 100644 --- a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java +++ b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java @@ -44,6 +44,7 @@ public class RegexBuilder { public static final Pattern NON_ALPHANUMERIC = Pattern.compile( "[^a-zA-Z0-9]" ); public static final Pattern PACKAGE_NAMES = Pattern.compile( "[^a-zA-Z0-9$\\.]" ); public static final Pattern PERIOD = Pattern.compile( "\\." ); + public static final Pattern REGEX_META = Pattern.compile( "([\\\\$])" ); public static final Pattern REGEX_QUANTIFIER = Pattern.compile( "\\{\\d*,?\\d*\\}" ); public static final Pattern REGEX_QUANTIFIER_END = Pattern.compile( "(? Date: Wed, 4 Dec 2024 13:20:00 -0600 Subject: [PATCH 038/193] giving tests more time to complete --- .../bifs/global/system/ApplicationRestartTest.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/system/ApplicationRestartTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/system/ApplicationRestartTest.java index 94ad4d99f..c55b9591c 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/system/ApplicationRestartTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/system/ApplicationRestartTest.java @@ -66,22 +66,23 @@ void setupEach() { @Test void testItCanStopAnApplication() { - instance.executeSource( - """ - application name="unit-test1" sessionmanagement="true"; - """, - context ); + instance.executeSource( """ + application name="unit-test1" sessionmanagement="true"; + """, context ); Application targetApp = context.getParentOfType( ApplicationBoxContext.class ).getApplication(); assertThat( targetApp.hasStarted() ).isTrue(); Instant startTime = targetApp.getStartTime(); + // @formatter:off instance.executeSource( """ - applicationRestart(); + sleep( 1000 ); + applicationRestart(); """, context ); + // @formatter:on assertThat( targetApp.hasStarted() ).isTrue(); // Verify the new start time is after the old start time From 1ac1e10d89a7c9cd831ab1268f6034256b4ca485 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 4 Dec 2024 13:20:40 -0600 Subject: [PATCH 039/193] change to replace chars instead of regex --- .../boxlang/runtime/bifs/global/format/NumberFormat.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/format/NumberFormat.java b/src/main/java/ortus/boxlang/runtime/bifs/global/format/NumberFormat.java index be8468e14..7c1016d2d 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/format/NumberFormat.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/format/NumberFormat.java @@ -87,8 +87,8 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { } else if ( format.equals( "ls$" ) ) { formatter = LocalizationUtil.localizedCurrencyFormatter( locale ); } else { - format = format.replaceAll( "9", "0" ) - .replaceAll( "_", "#" ); + format = format.replace( "9", "0" ) + .replace( "_", "#" ); if ( format.substring( 0, 1 ).equals( "L" ) ) { format = format.substring( 1, format.length() ); } else if ( format.substring( 0, 1 ).equals( "C" ) ) { From 612ae2eddd87858cb7253e3f048a675aed94a5be Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 4 Dec 2024 13:25:15 -0600 Subject: [PATCH 040/193] BL-784 more regex work --- .../runtime/bifs/global/string/Wrap.java | 37 ++++++++++++++++++- .../boxlang/runtime/util/RegexBuilder.java | 1 + 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/string/Wrap.java b/src/main/java/ortus/boxlang/runtime/bifs/global/string/Wrap.java index 243e5ff7e..faa2853ef 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/string/Wrap.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/string/Wrap.java @@ -1,3 +1,17 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ package ortus.boxlang.runtime.bifs.global.string; import ortus.boxlang.runtime.bifs.BIF; @@ -8,6 +22,7 @@ import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.Argument; import ortus.boxlang.runtime.types.BoxLangType; +import ortus.boxlang.runtime.util.RegexBuilder; @BoxBIF @BoxMember( type = BoxLangType.STRING, name = "Wrap" ) @@ -22,18 +37,38 @@ public Wrap() { }; } + /** + * Wraps a string at the specified limit, breaking at the last space within the limit. + * + * @param context The context in which the BIF is being invoked. + * @param arguments Argument scope for the BIF. + * + * @argument.string The string to wrap. + * + * @argument.limit The character limit at which to wrap the string. + * + * @argument.strip If true, replaces all line endings with spaces before wrapping. Default is false. + */ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { String input = arguments.getAsString( Key.string ); int limit = arguments.getAsInteger( Key.limit ); boolean strip = arguments.getAsBoolean( Key.strip ); if ( strip ) { - input = input.replaceAll( "\\r?\\n", " " ); + input = RegexBuilder.of( input, RegexBuilder.LINE_ENDINGS ).replaceAllAndGet( " " ); } return wrapText( input, limit ); } + /** + * Wraps the given text at the specified limit, breaking at the last space within the limit. + * + * @param text The text to wrap. + * @param limit The character limit at which to wrap the text. + * + * @return The wrapped text. + */ private String wrapText( String text, int limit ) { if ( text == null ) { return ""; diff --git a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java index bbdc9800c..9977a1d06 100644 --- a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java +++ b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java @@ -38,6 +38,7 @@ public class RegexBuilder { public static final Pattern COLON = Pattern.compile( ":" ); public static final Pattern CF_SQL = Pattern.compile( "(?i)CF_SQL_" ); public static final Pattern CREDIT_CARD_NUMBERS = Pattern.compile( "[0-9 ,_-]+" ); + public static final Pattern LINE_ENDINGS = Pattern.compile( "\\r?\\n" ); public static final Pattern MULTILINE_START_OF_LINE = Pattern.compile( "(?m)^" ); public static final Pattern MULTIPLE_SPACES = Pattern.compile( "\\s+" ); public static final Pattern NO_DIGITS = Pattern.compile( "\\D" ); From 83fe9922fcfa93dc39cb5e2a9b5506cf2224b631 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 4 Dec 2024 14:53:29 -0600 Subject: [PATCH 041/193] BL-784 more wip --- .../ortus/boxlang/compiler/DiskClassUtil.java | 42 ++++++++++++++----- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/DiskClassUtil.java b/src/main/java/ortus/boxlang/compiler/DiskClassUtil.java index a984e2f31..611144858 100644 --- a/src/main/java/ortus/boxlang/compiler/DiskClassUtil.java +++ b/src/main/java/ortus/boxlang/compiler/DiskClassUtil.java @@ -26,6 +26,7 @@ import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.types.util.JSONUtil; +import ortus.boxlang.runtime.util.RegexBuilder; /** * Contains some utilities for working with non-class files in the class generation dir @@ -57,10 +58,29 @@ public boolean hasLineNumbers( String classPoolName, String name ) { return generateDiskpath( classPoolName, name, "json" ).toFile().exists(); } + /** + * Generate a disk path for a class file + * + * @param classPoolName class pool name + * @param name class name + * @param extension file extension + * + * @return The path to the file + */ private Path generateDiskpath( String classPoolName, String name, String extension ) { - return Paths.get( diskStore.toString(), classPoolName.replaceAll( "[^a-zA-Z0-9]", "_" ), name.replace( ".", File.separator ) + "." + extension ); + return Paths.get( + diskStore.toString(), + RegexBuilder.of( classPoolName, RegexBuilder.NON_ALPHANUMERIC ).replaceAllAndGet( "_" ), + new StringBuilder( name.replace( ".", File.separator ) ).append( "." ).append( extension ).toString() + ); } + /** + * Write line numbers to disk + * + * @param fqn The fully qualified name of the class + * @param lineNumberJSON The JSON representation of the line numbers + */ public void writeLineNumbers( String classPoolName, String fqn, String lineNumberJSON ) { if ( lineNumberJSON == null ) { return; @@ -74,7 +94,6 @@ public void writeLineNumbers( String classPoolName, String fqn, String lineNumbe * * @returns array of maps. Null if not found. */ - @SuppressWarnings( "unchecked" ) public SourceMap readLineNumbers( String classPoolName, String fqn ) { if ( !hasLineNumbers( classPoolName, fqn ) ) { return null; @@ -117,9 +136,9 @@ public void writeBytes( String classPoolName, String fqn, String extension, byte /** * Read the bytes from the class file and all inner classes from disk and return them - * + * * @param fqn The fully qualified name of the class - * + * * @return A list of byte arrays, one for each class file */ public List readClassBytes( String classPoolName, String fqn ) { @@ -149,6 +168,13 @@ public List readClassBytes( String classPoolName, String fqn ) { return bytes; } + /** + * Checkf if the file is a Java bytecode file or source file + * + * @param sourceFile The file to check + * + * @return true if the file is a Java bytecode file + */ public boolean isJavaBytecode( File sourceFile ) { try ( FileInputStream fis = new FileInputStream( sourceFile ); DataInputStream dis = new DataInputStream( fis ) ) { @@ -156,12 +182,8 @@ public boolean isJavaBytecode( File sourceFile ) { if ( dis.available() < 4 ) { return false; } - if ( dis.readInt() == 0xCAFEBABE ) { - // The class file does start with the magic number - return true; - } - - return false; + // Are we the Java Magic number? + return dis.readInt() == 0xCAFEBABE; } catch ( IOException e ) { throw new RuntimeException( "Failed to read file", e ); } From c0775d25ea66abaabf6c293ffab649a421e40e51 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Wed, 4 Dec 2024 13:35:59 -0600 Subject: [PATCH 042/193] Fix try catch inside closure --- .../expression/BoxClosureTransformer.java | 2 +- .../java/ortus/boxlang/compiler/BDDTest.cfc | 11 + .../java/ortus/boxlang/compiler/BaseSpec.cfc | 1863 +++++++++++++++++ .../boxlang/compiler/FileTesterTest.java | 9 + 4 files changed, 1884 insertions(+), 1 deletion(-) create mode 100644 src/test/java/ortus/boxlang/compiler/BDDTest.cfc create mode 100644 src/test/java/ortus/boxlang/compiler/BaseSpec.cfc diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxClosureTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxClosureTransformer.java index f4ab5c9c1..dc28b952f 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxClosureTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxClosureTransformer.java @@ -163,7 +163,7 @@ public List transform( BoxNode node, TransformerContext contex } if ( isBlock ) { - bodyNodes.add( 0, new InsnNode( Opcodes.ACONST_NULL ) ); + bodyNodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); } return bodyNodes; diff --git a/src/test/java/ortus/boxlang/compiler/BDDTest.cfc b/src/test/java/ortus/boxlang/compiler/BDDTest.cfc new file mode 100644 index 000000000..4867bb880 --- /dev/null +++ b/src/test/java/ortus/boxlang/compiler/BDDTest.cfc @@ -0,0 +1,11 @@ +component { + + function run(){ + function() { + try { + } catch ( any e ) { + } + }; + } + +} diff --git a/src/test/java/ortus/boxlang/compiler/BaseSpec.cfc b/src/test/java/ortus/boxlang/compiler/BaseSpec.cfc new file mode 100644 index 000000000..8ffc0b8b2 --- /dev/null +++ b/src/test/java/ortus/boxlang/compiler/BaseSpec.cfc @@ -0,0 +1,1863 @@ +/** + * Copyright Since 2005 TestBox Framework by Luis Majano and Ortus Solutions, Corp + * www.ortussolutions.com + * --- + * This is a base spec object that is used to test XUnit and BDD style specification methods + */ +component { + + // Param default URL method runner when it runs remotely + param name="url.method" default="runRemote"; + + // Assertions object + variables.$assert = this.$assert = new src.test.java.ortus.boxlang.compiler.Assertion(); + // Custom Matchers + this.$customMatchers = {}; + // BDD Test Suites are stored here as an array so they are executed in order of definition + this.$suites = []; + // A reverse lookup for the suite definitions + this.$suiteReverseLookup = {}; + // The suite context, denotes the current suite being defined + this.$suiteContext = ""; + // ExpectedException Annotation + this.$exceptionAnnotation = "expectedException"; + // Expected Exception holder, only use on synchronous testing. + this.$expectedException = {}; + // Internal testing ID + this.$testID = hash( getTickCount() + randRange( 1, 10000000 ) ); + // Debug buffer + this.$debugBuffer = []; + // Current Executing Spec + this.$currentExecutingSpec = ""; + // Focused Structures + this.$focusedTargets = { "suites" : [], "specs" : [] }; + // Setup Request Utilities struct + if ( !request.keyExists( "testbox" ) ) { + request.testbox = {}; + } + // Setup request lookbacks for debugging purposes. + request.$testID = this.$testID; + + /************************************** BDD & EXPECTATIONS METHODS *********************************************/ + + /** + * Constructor + */ + remote function init(){ + return this; + } + + /** + * Expect an exception from the testing spec + * + * @type The type to expect + * @regex Optional exception message regular expression to match, by default it matches .* + */ + function expectedException( type = "", regex = ".*" ){ + this.$expectedException = arguments; + return this; + } + + /** + * Assert that the passed expression is true + * + * @facade + */ + function assert( required expression, message = "" ){ + return this.$assert.assert( argumentCollection = arguments ); + } + + /** + * Fail an assertion + * + * @facade + */ + function fail( message = "", detail = "" ){ + this.$assert.fail( argumentCollection = arguments ); + } + + /** + * Skip a test + * + * @message The message to send in the skip information dialog + * @detail The detail to add in the exception + */ + function skip( message = "", detail = "" ){ + this.$assert.skip( argumentCollection = arguments ); + } + + + /** + * This function is used for BDD test suites to store the beforeEach() function to execute for a test suite group + * + * @body The closure function + * @data Data bindings + */ + function beforeEach( required any body, struct data = {} ){ + this.$suitesReverseLookup[ this.$suiteContext ].beforeEach = arguments.body; + this.$suitesReverseLookup[ this.$suiteContext ].beforeEachData = arguments.data; + return this; + } + + /** + * This function is used for BDD test suites to store the afterEach() function to execute for a test suite group + * + * @body The closure function + * @data Data bindings + */ + function afterEach( required any body, struct data = {} ){ + this.$suitesReverseLookup[ this.$suiteContext ].afterEach = arguments.body; + this.$suitesReverseLookup[ this.$suiteContext ].afterEachData = arguments.data; + return this; + } + + /** + * This is used to surround a spec with your own closure code to provide a nice around decoration advice + * + * @body The closure function + * @data Data bindings + */ + function aroundEach( required any body, struct data = {} ){ + this.$suitesReverseLookup[ this.$suiteContext ].aroundEach = arguments.body; + this.$suitesReverseLookup[ this.$suiteContext ].aroundEachData = arguments.data; + return this; + } + + /** + * Focused Describe, only this should run + * + * @title The name of this test suite + * @body The closure that represents the test suite + * @labels The list or array of labels this suite group belongs to + * @asyncAll If you want to parallelize the execution of the defined specs in this suite group. + * @skip A flag or a closure that tells TestBox to skip this suite group from testing if true. If this is a closure it must return boolean. + */ + any function fdescribe( + required string title, + required any body, + any labels = [], + boolean asyncAll = false, + any skip = false + ){ + arguments.focused = true; + return this.describe( argumentCollection = arguments ); + } + + /** + * The way to describe BDD test suites in TestBox. The title is usually what you are testing or grouping of tests. + * The body is the function that implements the suite. + * + * @title The name of this test suite + * @body The closure that represents the test suite + * @labels The list or array of labels this suite group belongs to + * @asyncAll If you want to parallelize the execution of the defined specs in this suite group. + * @skip A flag or a closure that tells TestBox to skip this suite group from testing if true. If this is a closure it must return boolean. + * @focused A flag that tells TestBox to only run this suite and no other + */ + any function describe( + required string title, + required any body, + any labels = [], + boolean asyncAll = false, + any skip = false, + boolean focused = false + ){ + // closure checks + if ( !isClosure( arguments.body ) && !isCustomFunction( arguments.body ) ) { + throw( + type = "TestBox.InvalidBody", + message = "The body of this test suite must be a closure and you did not give me one, what's up with that!" + ); + } + + var suite = { + // The unique id of the spec that can be reproducible + "id" : hash( this.$suiteContext & arguments.title ), + // suite name + "name" : arguments.title, + // async flag + "asyncAll" : arguments.asyncAll, + // skip suite testing + "skip" : arguments.skip, + // labels attached to the suite for execution + "labels" : ( isSimpleValue( arguments.labels ) ? listToArray( arguments.labels ) : arguments.labels ), + // the test specs for this suite + "specs" : [], + // the recursive suites + "suites" : [], + // the beforeEach closure + "beforeEach" : variables.closureStub, + "beforeEachData" : {}, + // the afterEach closure + "afterEach" : variables.closureStub, + "afterEachData" : {}, + // the aroundEach closure, init to empty to distinguish + "aroundEach" : variables.aroundStub, + "aroundEachData" : {}, + // the parent suite + "parent" : "", + // the parent ref + "parentRef" : "", + // hierarchy slug + "slug" : "", + // Focused + "focused" : arguments.focused + }; + + // skip constraint for suite as a closure + // Todo: move this to the runner, so it can be delayed. + if ( isClosure( arguments.skip ) || isCustomFunction( arguments.skip ) ) { + suite.skip = arguments.skip( + title = arguments.title, + body = arguments.body, + labels = arguments.labels, + asyncAll = arguments.asyncAll, + suite = suite + ); + } + + // Are we in a nested describe() block + if ( len( this.$suiteContext ) and this.$suiteContext neq arguments.title ) { + // Append this suite to the nested suite. + arrayAppend( this.$suitesReverseLookup[ this.$suiteContext ].suites, suite ); + this.$suitesReverseLookup[ arguments.title ] = suite; + + // Setup parent reference + suite.parent = this.$suiteContext; + suite.parentRef = this.$suitesReverseLookup[ this.$suiteContext ]; + + // Build hierarchy slug separated by / + suite.slug = this.$suitesReverseLookup[ this.$suiteContext ].slug & "/" & this.$suiteContext; + if ( left( suite.slug, 1 ) != "/" ) { + suite.slug = "/" & suite.slug; + } + + // Store parent context + var parentContext = this.$suiteContext; + var parentSpecIndex = this.$specOrderIndex; + // Switch contexts and go deep + this.$suiteContext = arguments.title; + this.$specOrderIndex = 1; + // execute the test suite definition with this context now. + arguments.body(); + // switch back the context to parent + this.$suiteContext = parentContext; + this.$specOrderIndex = parentSpecIndex; + } else { + // Append this spec definition to the master root to track it + arrayAppend( this.$suites, suite ); + // setup pivot context now and reverse lookups + this.$suiteContext = arguments.title; + this.$specOrderIndex = 1; + this.$suitesReverseLookup[ arguments.title ] = suite; + // execute the test suite definition with this context now. + arguments.body(); + // reset context, finalized it already. + this.$suiteContext = ""; + } + + // Are we focused? + if ( arguments.focused ) { + arrayAppend( this.$focusedTargets.suites, suite.slug & "/" & suite.name ); + } + + // Restart spec index + this.$specOrderIndex = 1; + + return this; + } + + /** + * The way to describe BDD test suites in TestBox. The story is an alias for describe usually use when you are writing using Gherkin-esque language + * The body is the function that implements the suite. + * + * @story The name of this test suite + * @body The closure that represents the test suite + * @labels The list or array of labels this suite group belongs to + * @asyncAll If you want to parallelize the execution of the defined specs in this suite group. + * @skip A flag or a closure that tells TestBox to skip this suite group from testing if true. If this is a closure it must return boolean. + */ + any function story( + required string story, + required any body, + any labels = [], + boolean asyncAll = false, + any skip = false + ){ + return describe( argumentCollection = arguments, title = "Story: " & arguments.story ); + } + + /** + * A focused Story + * + * @story The name of this test suite + * @body The closure that represents the test suite + * @labels The list or array of labels this suite group belongs to + * @asyncAll If you want to parallelize the execution of the defined specs in this suite group. + * @skip A flag or a closure that tells TestBox to skip this suite group from testing if true. If this is a closure it must return boolean. + */ + any function fstory( + required string story, + required any body, + any labels = [], + boolean asyncAll = false, + any skip = false + ){ + return fdescribe( argumentCollection = arguments, title = "Story: " & arguments.story ); + } + + /** + * The way to describe BDD test suites in TestBox. The feature is an alias for describe usually use when you are writing in a Given-When-Then style + * The body is the function that implements the suite. + * + * @feature The name of this test suite + * @body The closure that represents the test suite + * @labels The list or array of labels this suite group belongs to + * @asyncAll If you want to parallelize the execution of the defined specs in this suite group. + * @skip A flag or a closure that tells TestBox to skip this suite group from testing if true. If this is a closure it must return boolean. + */ + any function feature( + required string feature, + required any body, + any labels = [], + boolean asyncAll = false, + any skip = false + ){ + return describe( argumentCollection = arguments, title = "Feature: " & arguments.feature ); + } + + /** + * A Focused Feature + * + * @feature The name of this test suite + * @body The closure that represents the test suite + * @labels The list or array of labels this suite group belongs to + * @asyncAll If you want to parallelize the execution of the defined specs in this suite group. + * @skip A flag or a closure that tells TestBox to skip this suite group from testing if true. If this is a closure it must return boolean. + */ + any function ffeature( + required string feature, + required any body, + any labels = [], + boolean asyncAll = false, + any skip = false + ){ + return fdescribe( argumentCollection = arguments, title = "Feature: " & arguments.feature ); + } + + /** + * The way to describe BDD test suites in TestBox. The given is an alias for describe usually use when you are writing in a Given-When-Then style + * The body is the function that implements the suite. + * + * @feature The name of this test suite + * @body The closure that represents the test suite + * @labels The list or array of labels this suite group belongs to + * @asyncAll If you want to parallelize the execution of the defined specs in this suite group. + * @skip A flag or a closure that tells TestBox to skip this suite group from testing if true. If this is a closure it must return boolean. + */ + any function given( + required string given, + required any body, + any labels = [], + boolean asyncAll = false, + any skip = false + ){ + return describe( argumentCollection = arguments, title = "Given " & arguments.given ); + } + + /** + * A focused given + * + * @feature The name of this test suite + * @body The closure that represents the test suite + * @labels The list or array of labels this suite group belongs to + * @asyncAll If you want to parallelize the execution of the defined specs in this suite group. + * @skip A flag or a closure that tells TestBox to skip this suite group from testing if true. If this is a closure it must return boolean. + */ + any function fgiven( + required string given, + required any body, + any labels = [], + boolean asyncAll = false, + any skip = false + ){ + return fdescribe( argumentCollection = arguments, title = "Given " & arguments.given ); + } + + /** + * The way to describe BDD test suites in TestBox. The scenario is an alias for describe usually use when you are writing in a Given-When-Then style + * The body is the function that implements the suite. + * + * @feature The name of this test suite + * @body The closure that represents the test suite + * @labels The list or array of labels this suite group belongs to + * @asyncAll If you want to parallelize the execution of the defined specs in this suite group. + * @skip A flag or a closure that tells TestBox to skip this suite group from testing if true. If this is a closure it must return boolean. + */ + any function scenario( + required string scenario, + required any body, + any labels = [], + boolean asyncAll = false, + any skip = false + ){ + return describe( argumentCollection = arguments, title = "Scenario: " & arguments.scenario ); + } + + /** + * A focused scenario + * + * @feature The name of this test suite + * @body The closure that represents the test suite + * @labels The list or array of labels this suite group belongs to + * @asyncAll If you want to parallelize the execution of the defined specs in this suite group. + * @skip A flag or a closure that tells TestBox to skip this suite group from testing if true. If this is a closure it must return boolean. + */ + any function fscenario( + required string scenario, + required any body, + any labels = [], + boolean asyncAll = false, + any skip = false + ){ + return fdescribe( argumentCollection = arguments, title = "Scenario: " & arguments.scenario ); + } + + /** + * The way to describe BDD test suites in TestBox. The when is an alias for scenario usually use when you are writing in a Given-When-Then style + * The body is the function that implements the suite. + * + * @feature The name of this test suite + * @body The closure that represents the test suite + * @labels The list or array of labels this suite group belongs to + * @asyncAll If you want to parallelize the execution of the defined specs in this suite group. + * @skip A flag or a closure that tells TestBox to skip this suite group from testing if true. If this is a closure it must return boolean. + */ + any function when( + required string when, + required any body, + any labels = [], + boolean asyncAll = false, + any skip = false + ){ + return describe( argumentCollection = arguments, title = "When " & arguments.when ); + } + + /** + * A focused when + * + * @feature The name of this test suite + * @body The closure that represents the test suite + * @labels The list or array of labels this suite group belongs to + * @asyncAll If you want to parallelize the execution of the defined specs in this suite group. + * @skip A flag or a closure that tells TestBox to skip this suite group from testing if true. If this is a closure it must return boolean. + */ + any function fwhen( + required string when, + required any body, + any labels = [], + boolean asyncAll = false, + any skip = false + ){ + return fdescribe( argumentCollection = arguments, title = "When " & arguments.when ); + } + + /** + * A focused `it` where only focused it's are executed + * + * @title The title of this spec + * @body The closure that represents the test + * @labels The list or array of labels this spec belongs to + * @skip A flag or a closure that tells TestBox to skip this spec test from testing if true. If this is a closure it must return boolean. + * @data A struct of data you would like to bind into the spec so it can be later passed into the executing body function + */ + any function fit( + required string title, + required any body, + any labels = [], + any skip = false, + struct data = {} + ){ + arguments.focused = true; + return this.it( argumentCollection = arguments ); + } + + /** + * The it() function describes a spec or a test in TestBox. The body argument is the closure that implements + * the test which usually contains one or more expectations that test the state of the code under test. + * + * @title The title of this spec + * @body The closure that represents the test + * @labels The list or array of labels this spec belongs to + * @skip A flag or a closure that tells TestBox to skip this spec test from testing if true. If this is a closure it must return boolean. + * @data A struct of data you would like to bind into the spec so it can be later passed into the executing body function + * @focused A flag that tells TestBox to only run this spec and no other + * + * @throws TestBox.InvalidBody If the body is not a closure + * @throws TestBox.InvalidContext If the spec is not defined within a suite + */ + any function it( + required string title, + required any body, + any labels = [], + any skip = false, + struct data = {}, + boolean focused = false + ){ + // closure checks + if ( !isClosure( arguments.body ) && !isCustomFunction( arguments.body ) ) { + throw( + type = "TestBox.InvalidBody", + message = "The body of this test suite must be a closure and you did not give me one, what's up with that!" + ); + } + + // context checks + if ( !len( this.$suiteContext ) ) { + throw( + type = "TestBox.InvalidContext", + message = "You cannot define a spec without a test suite! This it() must exist within a describe() body! Go fix it :)" + ); + } + + // define the spec + var spec = { + // The unique id of the spec that can be reproducible + "id" : hash( this.$suiteContext & arguments.title ), + // the spec body + "body" : arguments.body, + // the data binding + "data" : arguments.data, + // Focus flag + "focused" : arguments.focused, + // labels attached to the spec for execution + "labels" : ( isSimpleValue( arguments.labels ) ? listToArray( arguments.labels ) : arguments.labels ), + // spec title + "name" : arguments.title, + // Display name + "displayName" : arguments.title, + // the order of execution + "order" : this.$specOrderIndex++, + // skip spec testing + "skip" : arguments.skip + }; + + // Executes the skip constraint for the spec and stores it's value + // TODO: move this to the runner, so it can be delayed. + if ( isClosure( arguments.skip ) || isCustomFunction( arguments.skip ) ) { + spec.skip = arguments.skip( + title = arguments.title, + body = arguments.body, + labels = arguments.labels, + spec = spec + ); + } + + // Attach this spec to the incoming context array of specs + arrayAppend( this.$suitesReverseLookup[ this.$suiteContext ].specs, spec ); + + // Are we focused? + if ( arguments.focused ) { + var thisSuite = this.$suitesReverseLookup[ this.$suiteContext ]; + arrayAppend( this.$focusedTargets.specs, thisSuite.slug & "/" & thisSuite.name & "/" & spec.name ); + } + return this; + } + + /** + * An alias to the it() function to provide a more natural language for BDD. + * + * @title The title of this test + * @body The closure that represents the test + * @labels The list or array of labels this spec belongs to + * @skip A flag or a closure that tells TestBox to skip this spec test from testing if true. If this is a closure it must return boolean. + * @data A struct of data you would like to bind into the spec so it can be later passed into the executing body function + * @focused A flag that tells TestBox to only run this spec and no other + */ + any function test( + required string title, + required any body, + any labels = [], + any skip = false, + struct data = {}, + boolean focused = false + ){ + return this.it( argumentCollection = arguments ); + } + + /** + * A focused alias to the fit() function to provide a more natural language for BDD. + * + * @then The title of this spec + * @body The closure that represents the test + * @labels The list or array of labels this spec belongs to + * @skip A flag or a closure that tells TestBox to skip this spec test from testing if true. If this is a closure it must return boolean. + * @data A struct of data you would like to bind into the spec so it can be later passed into the executing body function + * @focused A flag that tells TestBox to only run this spec and no other + */ + any function ftest( + required string title, + required any body, + any labels = [], + any skip = false, + struct data = {}, + boolean focused = false + ){ + return this.fit( argumentCollection = arguments ); + } + + /** + * The then() function describes a spec or a test in TestBox and is an alias for it. The body argument is the closure that implements + * the test which usually contains one or more expectations that test the state of the code under test. + * + * @then The title of this spec + * @body The closure that represents the test + * @labels The list or array of labels this spec belongs to + * @skip A flag or a closure that tells TestBox to skip this spec test from testing if true. If this is a closure it must return boolean. + * @data A struct of data you would like to bind into the spec so it can be later passed into the executing body function + * @focused A flag that tells TestBox to only run this spec and no other + */ + any function then( + required string then, + required any body, + any labels = [], + any skip = false, + struct data = {}, + boolean focused = false + ){ + return it( argumentCollection = arguments, title = "Then " & arguments.then ); + } + + /** + * A focused then + * + * @then The title of this spec + * @body The closure that represents the test + * @labels The list or array of labels this spec belongs to + * @skip A flag or a closure that tells TestBox to skip this spec test from testing if true. If this is a closure it must return boolean. + * @data A struct of data you would like to bind into the spec so it can be later passed into the executing body function + * @focused A flag that tells TestBox to only run this spec and no other + */ + any function fthen( + required string then, + required any body, + any labels = [], + any skip = false, + struct data = {}, + boolean focused = false + ){ + return fit( argumentCollection = arguments, title = "Then " & arguments.then ); + } + + /** + * This is a convenience method that makes sure the test suite is skipped from execution + * + * @title The name of this test suite + * @body The closure that represents the test suite + * @labels The list or array of labels this suite group belongs to + * @asyncAll If you want to parallelize the execution of the defined specs in this suite group. + */ + any function xdescribe( + required string title, + required any body, + any labels = [], + boolean asyncAll = false + ){ + arguments.skip = true; + return this.describe( argumentCollection = arguments ); + } + + /** + * This is a convenience method that makes sure the test spec is skipped from execution + * + * @title The title of this spec + * @body The closure that represents the test + * @labels The list or array of labels this spec belongs to + * @data A struct of data you would like to bind into the spec so it can be later passed into the executing body function + */ + any function xit( + required string title, + required any body, + any labels = [], + struct data = {} + ){ + arguments.skip = true; + return this.it( argumentCollection = arguments ); + } + + /** + * The way to describe BDD test suites in TestBox. The story is an alias for describe usually use when you are writing using Gherkin-esque language + * The body is the function that implements the suite. + * + * @story The name of this test suite + * @body The closure that represents the test suite + * @labels The list or array of labels this suite group belongs to + * @asyncAll If you want to parallelize the execution of the defined specs in this suite group. + * @skip A flag or a closure that tells TestBox to skip this suite group from testing if true. If this is a closure it must return boolean. + */ + any function xstory( + required string story, + required any body, + any labels = [], + boolean asyncAll = false + ){ + arguments.skip = true; + return this.story( argumentCollection = arguments ); + } + + /** + * The way to describe BDD test suites in TestBox. The feature is an alias for describe usually use when you are writing in a Given-When-Then style + * The body is the function that implements the suite. + * + * @feature The name of this test suite + * @body The closure that represents the test suite + * @labels The list or array of labels this suite group belongs to + * @asyncAll If you want to parallelize the execution of the defined specs in this suite group. + * @skip A flag or a closure that tells TestBox to skip this suite group from testing if true. If this is a closure it must return boolean. + */ + any function xfeature( + required string feature, + required any body, + any labels = [], + boolean asyncAll = false + ){ + arguments.skip = true; + return this.feature( argumentCollection = arguments ); + } + + /** + * The way to describe BDD test suites in TestBox. The given is an alias for describe usually use when you are writing in a Given-When-Then style + * The body is the function that implements the suite. + * + * @feature The name of this test suite + * @body The closure that represents the test suite + * @labels The list or array of labels this suite group belongs to + * @asyncAll If you want to parallelize the execution of the defined specs in this suite group. + * @skip A flag or a closure that tells TestBox to skip this suite group from testing if true. If this is a closure it must return boolean. + */ + any function xgiven( + required string given, + required any body, + any labels = [], + boolean asyncAll = false + ){ + arguments.skip = true; + return this.given( argumentCollection = arguments ); + } + + /** + * The way to describe BDD test suites in TestBox. The scenario is an alias for describe usually use when you are writing in a Given-When-Then style + * The body is the function that implements the suite. + * + * @feature The name of this test suite + * @body The closure that represents the test suite + * @labels The list or array of labels this suite group belongs to + * @asyncAll If you want to parallelize the execution of the defined specs in this suite group. + * @skip A flag or a closure that tells TestBox to skip this suite group from testing if true. If this is a closure it must return boolean. + */ + any function xscenario( + required string scenario, + required any body, + any labels = [], + boolean asyncAll = false + ){ + arguments.skip = true; + return this.scenario( argumentCollection = arguments ); + } + + /** + * The way to describe BDD test suites in TestBox. The when is an alias for scenario usually use when you are writing in a Given-When-Then style + * The body is the function that implements the suite. + * + * @feature The name of this test suite + * @body The closure that represents the test suite + * @labels The list or array of labels this suite group belongs to + * @asyncAll If you want to parallelize the execution of the defined specs in this suite group. + * @skip A flag or a closure that tells TestBox to skip this suite group from testing if true. If this is a closure it must return boolean. + */ + any function xwhen( + required string when, + required any body, + any labels = [], + boolean asyncAll = false + ){ + arguments.skip = true; + return this.when( argumentCollection = arguments ); + } + + /** + * This is a convenience method that makes sure the test spec is skipped from execution + * + * @title The title of this spec + * @body The closure that represents the test + * @labels The list or array of labels this spec belongs to + * @data A struct of data you would like to bind into the spec so it can be later passed into the executing body function + */ + any function xthen( + required string then, + required any body, + any labels = [], + struct data = {} + ){ + arguments.skip = true; + return this.then( argumentCollection = arguments ); + } + + /** + * This is a convenience method that makes sure the test spec is skipped from execution + * + * @title The title of this spec + * @body The closure that represents the test + * @labels The list or array of labels this spec belongs to + * @data A struct of data you would like to bind into the spec so it can be later passed into the executing body function + * @focused A flag that tells TestBox to only run this spec and no other + */ + any function xtest( + required string title, + required any body, + any labels = [], + struct data = {}, + boolean focused = false + ){ + arguments.skip = true; + return this.test( argumentCollection = arguments ); + } + + /** + * Start an expectation expression. This returns an instance of Expectation so you can work with its matchers. + * + * @actual The actual value, it is not required as it can be null. + */ + Expectation function expect( any actual ){ + // build an expectation + var oExpectation = new Expectation( spec = this, assertions = this.$assert ); + + // Store the actual data + if ( !isNull( arguments.actual ) ) { + oExpectation.actual = arguments.actual; + } else { + oExpectation.actual = javacast( "null", "" ); + } + + // Do we have any custom matchers to add to this expectation? + if ( !structIsEmpty( this.$customMatchers ) ) { + for ( var thisMatcher in this.$customMatchers ) { + oExpectation.registerMatcher( thisMatcher, this.$customMatchers[ thisMatcher ] ); + } + } + + return oExpectation; + } + + /** + * Start a collection expectation expression. This returns an instance of CollectionExpectation + * so you can work with its collection-unrolling matches (delegating to Expectation). + * + * @actual The actual value, it should be an array or a struct. + */ + CollectionExpectation function expectAll( required any actual ){ + return new CollectionExpectation( + spec = this, + assertions = this.$assert, + collection = arguments.actual + ); + } + + /** + * Add custom matchers to your expectations + * + * @matchers The structure of custom matcher functions to register or a path or instance of a class containing all the matcher functions to register + */ + function addMatchers( required any matchers ){ + // register structure + if ( isStruct( arguments.matchers ) ) { + // register the custom matchers with override + structAppend( this.$customMatchers, arguments.matchers, true ); + return this; + } + + // Build the Matcher + var oMatchers = ""; + if ( isSimpleValue( arguments.matchers ) ) { + oMatchers = new "#arguments.matchers#"( ); + } else if ( isObject( arguments.matchers ) ) { + oMatchers = arguments.matchers; + } else { + throw( + type = "TestBox.InvalidCustomMatchers", + message = "The matchers argument you sent is not valid, it must be a struct, string or object" + ); + } + + // Register the methods into our custom matchers struct + var matcherArray = structKeyArray( oMatchers ); + for ( var thisMatcher in matcherArray ) { + this.$customMatchers[ thisMatcher ] = oMatchers[ thisMatcher ]; + } + + return this; + } + + /** + * Add custom assertions to the $assert object + * + * @assertions The structure of custom assertion functions to register or a path or instance of a class containing all the assertion functions to register + */ + function addAssertions( required any assertions ){ + // register structure + if ( isStruct( arguments.assertions ) ) { + // register the custom matchers with override + structAppend( this.$assert, arguments.assertions, true ); + return this; + } + + // Build the Custom Assertion + var oAssertions = ""; + if ( isSimpleValue( arguments.assertions ) ) { + oAssertions = new "#arguments.assertions#"( ); + } else if ( isObject( arguments.assertions ) ) { + oAssertions = arguments.assertions; + } else { + throw( + type = "TestBox.InvalidCustomAssertions", + message = "The assertions argument you sent is not valid, it must be a struct, string or object" + ); + } + + // Register the methods into our custom assertions struct + var methodArray = structKeyArray( oAssertions ); + for ( var thisMethod in methodArray ) { + this.$assert[ thisMethod ] = oAssertions[ thisMethod ]; + } + + return this; + } + + /************************************** RUN BDD METHODS *********************************************/ + + /** + * Run a test remotely, only useful if the spec inherits from this class. Useful for remote executions. + * + * @testSuites A list or array of suite names that are the ones that will be executed ONLY! + * @testSpecs A list or array of test names that are the ones that will be executed ONLY! + * @reporter The type of reporter to run the test with + * @labels A list or array of labels to apply to the testing. + */ + remote function runRemote( + string testSpecs = "", + string testSuites = "", + string reporter = "simple", + string labels = "" + ) output=true{ + setting requesttimeout=99999999; + + // content type defaulted, to avoid dreaded wddx default + getPageContext().getResponse().setContentType( "text/html" ); + + // run tests + var runner = new testbox.system.TestBox( + bundles : "#getMetadata( this ).name#", + labels : arguments.labels, + reporter: arguments.reporter, + options : { coverage : { enabled : false } } + ); + // Produce report + writeOutput( runner.run( testSuites = arguments.testSuites, testSpecs = arguments.testSpecs ) ); + } + + /** + * Run a BDD test in this target + * + * @spec The spec definition to test + * @suite The suite definition this spec belongs to + * @testResults The testing results object + * @suiteStats The suite stats that the incoming spec definition belongs to + * @runner The runner calling this BDD test + */ + function runSpec( + required spec, + required suite, + required testResults, + required suiteStats, + required runner + ){ + try { + // init spec tests + var specStats = arguments.testResults.startSpecStats( arguments.spec, arguments.suiteStats ); + // init consolidated spec labels + var consolidatedLabels = arguments.spec.labels; + var md = getMetadata( this ); + param md.labels = ""; + consolidatedLabels.addAll( listToArray( md.labels ) ); + // Build labels from nested suites, so suites inherit from parent suite labels + var parentSuite = arguments.suite; + while ( !isSimpleValue( parentSuite ) ) { + consolidatedLabels.addAll( parentSuite.labels ); + parentSuite = parentSuite.parentref; + } + + // Is the spec focused + var isSpecFocused = function( name ){ + return ( + // We have no focused suites or specs + ( arrayLen( this.$focusedTargets.specs ) == 0 && arrayLen( this.$focusedTargets.suites ) == 0 ) + || + ( + // Are we in the focused Spec? + arrayLen( this.$focusedTargets.specs ) && arrayFindNoCase( + this.$focusedTargets.specs, + name + ) + || + // Are we in the focused Suite? + arrayLen( this.$focusedTargets.suites ) && runner.isSuiteFocused( + suite = suite, + target = this, + checkChildren = false + ) + ) + ); + }; + + // Verify we can execute + if ( + !arguments.spec.skip && // Not skipping + isSpecFocused( arguments.suite.slug & "/" & arguments.suite.name & "/" & arguments.spec.name ) && // Is the spec focused + arguments.runner.canRunLabel( consolidatedLabels, arguments.testResults ) && // In label or no labels + arguments.runner.canRunSpec( arguments.spec, arguments.testResults ) // In test results + ) { + // setup the current executing spec for debug purposes + this.$currentExecutingSpec = arguments.suite.slug & "/" & arguments.suite.name & "/" & arguments.spec.name; + // Run beforeEach closures + runBeforeEachClosures( arguments.suite, arguments.spec ); + + try { + runAroundEachClosures( arguments.suite, arguments.spec ); + } catch ( any e ) { + rethrow; + } finally { + runAfterEachClosures( arguments.suite, arguments.spec ); + } + + // store spec status + specStats.status = "Passed"; + // Increment recursive pass stats + arguments.testResults.incrementSpecStat( type = "pass", stats = specStats ); + } else { + // store spec status + specStats.status = "Skipped"; + // Increment recursive pass stats + arguments.testResults.incrementSpecStat( type = "skipped", stats = specStats ); + } + } + // Catch Skip() calls + catch ( "TestBox.SkipSpec" e ) { + // store spec status + specStats.status = "Skipped"; + specStats.failMessage = e.message; + specStats.failDetail = e.detail; + // Increment recursive pass stats + arguments.testResults.incrementSpecStat( type = "skipped", stats = specStats ); + // Module call backs + arguments.runner + .getTestBox() + .announceToModules( + "onSpecSkipped", + [ + arguments.spec, + specStats, + arguments.suite, + arguments.suiteStats, + arguments.testResults + ] + ); + } + // Catch Fail() calls + catch ( "TestBox.AssertionFailed" e ) { + // store spec status and debug data + specStats.status = "Failed"; + specStats.failMessage = e.message; + specStats.failDetail = e.detail; + specStats.failExtendedInfo = e.extendedInfo; + specStats.failStacktrace = e.stackTrace; + specStats.failOrigin = e.tagContext; + specStats.debugBuffer = duplicate( this.$debugBuffer ); + + // Increment recursive pass stats + arguments.testResults.incrementSpecStat( type = "fail", stats = specStats ); + // Module call backs + arguments.runner + .getTestBox() + .announceToModules( + "onSpecFailure", + [ + e, + arguments.spec, + specStats, + arguments.suite, + arguments.suiteStats, + arguments.testResults + ] + ); + } + // Catch errors + catch ( any e ) { + // store spec status and debug data + specStats.status = "Error"; + specStats.error = e; + specStats.failOrigin = e.tagContext; + specStats.failMessage = e.message; + specStats.failDetail = e.detail; + specStats.failExtendedInfo = e.extendedInfo; + specStats.failStacktrace = e.stackTrace; + specStats.debugBuffer = duplicate( this.$debugBuffer ); + + // Increment recursive pass stats + arguments.testResults.incrementSpecStat( type = "error", stats = specStats ); + + // Module call backs + arguments.runner + .getTestBox() + .announceToModules( + "onSpecError", + [ + e, + arguments.spec, + specStats, + arguments.suite, + arguments.suiteStats, + arguments.testResults + ] + ); + } finally { + // Complete spec testing + arguments.testResults.endStats( specStats ); + } + + return this; + } + + /** + * Execute the before each closures in order for a suite and spec + * + * @suite The suite definition + * @spec The spec definition + */ + BaseSpec function runBeforeEachClosures( required suite, required spec ){ + // re-bind request utilities to the currently executing test before they may be invoked + request.testbox.console = () => variables.console( argumentCollection = arguments ); + request.testbox.debug = () => variables.debug( argumentCollection = arguments ); + request.testbox.clearDebugBuffer = () => variables.clearDebugBuffer( argumentCollection = arguments ); + request.testbox.print = () => variables.print( argumentCollection = arguments ); + request.testbox.println = () => variables.println( argumentCollection = arguments ); + + var reverseTree = []; + + // do we have nested suites? If so, traverse the tree to build reverse execution map + var parentSuite = arguments.suite.parentRef; + while ( !isSimpleValue( parentSuite ) ) { + arrayAppend( + reverseTree, + { + beforeEach : parentSuite.beforeEach, + beforeEachData : parentSuite.beforeEachData + } + ); + parentSuite = parentSuite.parentRef; + } + + // Incorporate annotated methods + arrayEach( getUtility().getAnnotatedMethods( annotation = "beforeEach", metadata = getMetadata( this ) ), function( item ){ + arrayAppend( + reverseTree, + { + beforeEach : this[ arguments.item.name ], + beforeEachData : {} + } + ); + } ); + + // sort tree backwards + arraySort( reverseTree, function( a, b ){ + return -1; + } ); + + // Execute it + arrayEach( reverseTree, function( item ){ + item.beforeEach( currentSpec = spec.name, data = item.beforeEachData ); + } ); + + // execute beforeEach() + arguments.suite.beforeEach( currentSpec = arguments.spec.name, data = arguments.suite.beforeEachData ); + + return this; + } + + /** + * Execute the around each closures in order for a suite and spec + * + * @suite The suite definition + * @spec The spec definition + */ + BaseSpec function runAroundEachClosures( required suite, required spec ){ + var reverseTree = [ + { + name : arguments.suite.name, + body : arguments.suite.aroundEach, + data : arguments.suite.aroundEachData, + labels : arguments.suite.labels, + order : 0, + skip : arguments.suite.skip + } + ]; + + // do we have nested suites? If so, traverse the tree to build reverse execution map + var parentSuite = arguments.suite.parentRef; + while ( !isSimpleValue( parentSuite ) ) { + arrayAppend( + reverseTree, + { + name : parentSuite.name, + body : parentSuite.aroundEach, + data : parentSuite.aroundEachData, + labels : parentSuite.labels, + order : 0, + skip : parentSuite.skip + } + ); + // go deep + parentSuite = parentSuite.parentRef; + } + + // Discover annotated methods and add to reverseTree + arrayEach( getUtility().getAnnotatedMethods( annotation = "aroundEach", metadata = getMetadata( this ) ), function( item ){ + arrayAppend( + reverseTree, + { + name : arguments.item.name, + body : this[ arguments.item.name ], + data : {}, + labels : {}, + order : 0, + skip : false + } + ); + } ); + + // Sort the closures from the oldest parent down to the current spec + arraySort( reverseTree, function( a, b ){ + return -1; + } ); + + // Build a function that will execute down the tree + var specStack = generateAroundEachClosuresStack( + closures = reverseTree, + suite = arguments.suite, + spec = arguments.spec + ); + // Run the specs + specStack(); + + return this; + } + + /** + * Generates a specs stack for executions + * + * @closures The array of closures data to build + * @suite The target suite + * @spec The target spec + */ + function generateAroundEachClosuresStack( + array closures, + required suite, + required spec, + closureIndex = 1 + ){ + thread.closures = arguments.closures; + thread.suite = arguments.suite; + thread.spec = arguments.spec; + + // Get closure data from stack and pop it + var nextClosure = thread.closures[ arguments.closureIndex ]; + + // Check if we have more in the stack or empty + if ( arrayLen( thread.closures ) == arguments.closureIndex ) { + // Return the closure of execution for a single spec ONLY + return function(){ + // Execute the body of the spec + nextClosure.body( + spec = thread.spec, + suite = thread.suite, + data = nextClosure.data + ); + }; + } + + // Get next Spec in stack + var nextSpecInfo = thread.closures[ ++arguments.closureIndex ]; + // Return generated closure + return function(){ + nextClosure.body( + spec = { + name : nextSpecInfo.name, + body : generateAroundEachClosuresStack( + thread.closures, + thread.suite, + thread.spec, + closureIndex + ), + data : nextSpecInfo.data, + labels : nextSpecInfo.labels, + order : nextSpecInfo.order, + skip : nextSpecInfo.skip + }, + suite = thread.suite, + data = nextClosure.data + ); + }; + } + + /** + * Execute the after each closures in order for a suite and spec + * + * @suite The suite definition + * @spec The spec definition + */ + BaseSpec function runAfterEachClosures( required suite, required spec ){ + // execute nearest afterEach() + arguments.suite.afterEach( currentSpec = arguments.spec.name, data = arguments.suite.afterEachData ); + + // do we have nested suites? If so, traverse and execute life-cycle methods up the tree backwards + var parentSuite = arguments.suite.parentRef; + while ( !isSimpleValue( parentSuite ) ) { + parentSuite.afterEach( currentSpec = arguments.spec.name, data = parentSuite.afterEachData ); + parentSuite = parentSuite.parentRef; + } + + arrayEach( getUtility().getAnnotatedMethods( annotation = "afterEach", metadata = getMetadata( this ) ), function( item ){ + invoke( + this, + item.name, + { currentSpec : spec.name, data : {} } + ); + } ); + + return this; + } + + /** + * Runs a xUnit style test method in this target + * + * @spec The spec definition to test + * @testResults The testing results object + * @suiteStats The suite stats that the incoming spec definition belongs to + * @runner The runner calling this BDD test + */ + function runTestMethod( + required spec, + required testResults, + required suiteStats, + required runner + ){ + try { + // init spec tests + var specStats = arguments.testResults.startSpecStats( arguments.spec, arguments.suiteStats ); + + // Verify we can execute + if ( + !arguments.spec.skip && + arguments.runner.canRunLabel( arguments.spec.labels, arguments.testResults ) && + arguments.runner.canRunSpec( arguments.spec, arguments.testResults ) + ) { + // Reset expected exceptions: Only works on synchronous testing. + this.$expectedException = {}; + // setup the current executing spec for debug purposes + this.$currentExecutingSpec = arguments.spec.name; + + // execute setup() + if ( structKeyExists( this, "setup" ) ) { + this.setup( currentMethod = arguments.spec.name ); + } + + // Execute Spec + try { + invoke( this, arguments.spec.name ); + + // Where we expecting an exception and it did not throw? + if ( hasExpectedException( arguments.spec.name, arguments.runner ) ) { + $assert.fail( + "Method did not throw expected exception: [#this.$expectedException.toString()#]" + ); + } + // else all good. + } catch ( Any e ) { + // do we have expected exception? else rethrow it + if ( !hasExpectedException( arguments.spec.name, arguments.runner ) ) { + rethrow; + } + // if not the expected exception, then fail it + if ( !isExpectedException( e, arguments.spec.name, arguments.runner ) ) { + $assert.fail( + "Method did not throw expected exception: [#this.$expectedException.toString()#], actual exception [type:#e.type#][message:#e.message#]" + ); + } + } finally { + // execute teardown() + if ( structKeyExists( this, "teardown" ) ) { + this.teardown( currentMethod = arguments.spec.name ); + } + } + + // store spec status + specStats.status = "Passed"; + // Increment recursive pass stats + arguments.testResults.incrementSpecStat( type = "pass", stats = specStats ); + } else { + // store spec status + specStats.status = "Skipped"; + // Increment recursive pass stats + arguments.testResults.incrementSpecStat( type = "skipped", stats = specStats ); + } + } + // Catch skip() calls + catch ( "TestBox.SkipSpec" e ) { + // store spec status + specStats.status = "Skipped"; + specStats.failMessage = e.message; + specStats.failDetail = e.detail; + // Increment recursive pass stats + arguments.testResults.incrementSpecStat( type = "skipped", stats = specStats ); + } + // Catch Fail() calls + catch ( "TestBox.AssertionFailed" e ) { + // store spec status and debug data + specStats.status = "Failed"; + specStats.failMessage = e.message; + specStats.failExtendedInfo = e.extendedInfo; + specStats.failStacktrace = e.stackTrace; + specStats.failOrigin = e.tagContext; + specStats.debugBuffer = duplicate( this.$debugBuffer ); + + // Increment recursive pass stats + arguments.testResults.incrementSpecStat( type = "fail", stats = specStats ); + } + // Catch errors + catch ( any e ) { + // store spec status and debug data + specStats.status = "Error"; + specStats.error = e; + specStats.failOrigin = e.tagContext; + // Increment recursive pass stats + arguments.testResults.incrementSpecStat( type = "error", stats = specStats ); + } finally { + // Complete spec testing + arguments.testResults.endStats( specStats ); + } + + return this; + } + + /************************************** UTILITY METHODS *********************************************/ + + /** + * Send some information to the console via writedump( output="console" ) + * + * @var The data to send + * @top Apply a top to the dump, by default it does 9999 levels + * @showUDFs Show UDFs in the dump, by default it does not + * @label A label to add to the console output + */ + any function console( + required var, + numeric top = 9999, + boolean showUDFs = false, + string label = "" + ){ + writeDump( + output = "console", + var = arguments.var, + label = "TestBox Console: #arguments.label#", + top = arguments.top, + showUDFs = arguments.showUDFs + ); + return this; + } + + /** + * Debug some information into the TestBox debugger array buffer + * + * @var The data to debug + * @label The label to add to the debug entry + * @deepCopy By default we do not duplicate the incoming information, but you can :) + * @top The top numeric number to dump on the screen in the report, defaults to 999 + */ + any function debug( + any var, + string label = "", + boolean deepCopy = false, + numeric top = "999", + boolean showUDFs = false + ){ + // null check + if ( isNull( arguments.var ) ) { + arrayAppend( this.$debugBuffer, "null" ); + return; + } + + // duplication control + var newVar = ( arguments.deepCopy ? duplicate( arguments.var ) : arguments.var ); + // compute label? + if ( !len( trim( arguments.label ) ) ) { + // Check if executing spec is set, else most likely this is called from a request scoped debug method + arguments.label = !isNull( this.$currentExecutingSpec ) ? this.$currentExecutingSpec : "request"; + } + + // add to debug output + arrayAppend( + this.$debugBuffer, + { + "data" : newVar, + "label" : arguments.label, + "timestamp" : now(), + "top" : arguments.top, + "showUDFs" : arguments.showUDFs + } + ); + return this; + } + + /** + * Clear the debug array buffer + */ + any function clearDebugBuffer(){ + arrayClear( this.$debugBuffer ); + return this; + } + + /** + * Get the debug array buffer from scope + */ + array function getDebugBuffer(){ + return this.$debugBuffer; + } + + /** + * Write some output to the ColdFusion output buffer + */ + any function print( required message ) output=true{ + writeOutput( arguments.message ); + return this; + } + + /** + * Write some output to the ColdFusion output buffer using a
attached + */ + any function println( required message ) output=true{ + return print( arguments.message & "
" ); + } + + /************************************** MOCKING METHODS *********************************************/ + + /** + * Make a private method on a class public with or without a new name and returns the target object + * + * @target The target object to expose the method + * @method The private method to expose + * @newName If passed, it will expose the method with this name, else just uses the same name + */ + any function makePublic( + required any target, + required string method, + string newName = "" + ){ + // decorate it + getUtility().getMixerUtil().start( arguments.target ); + // expose it + arguments.target.exposeMixin( arguments.method, arguments.newName ); + + return arguments.target; + } + + /** + * Get a private property + * + * @target The target to get a property from + * @name The name of the property to retrieve + * @scope The scope to get it from, defaults to 'variables' scope + * @defaultValue A default value if the property does not exist + */ + any function getProperty( + required target, + required name, + scope = "variables", + defaultValue + ){ + // stupid cf10 parser + if ( structKeyExists( arguments, "defaultValue" ) ) { + arguments.default = arguments.defaultValue; + } + return prepareMock( arguments.target ).$getProperty( argumentCollection = arguments ); + } + + /** + * First line are the query columns separated by commas. Then do a consequent rows separated by line breaks separated by | to denote columns. + */ + function querySim( required queryData ){ + return getMockBox().querySim( arguments.queryData ); + } + + /** + * Use MockDataCFC to mock whatever data you want by executing the `mock()` function in MockDataCFC + * + * @return The mock data you desire sir! + */ + function mockData(){ + return getMockDataCFC().mock( argumentCollection = arguments ); + } + + /** + * Get the MockData CFC object + * + * @return testbox.system.modules.mockdatacfc.models.MockData + */ + function getMockDataCFC(){ + // Lazy Load it + if ( isNull( variables.$mockDataCFC ) ) { + variables.$mockDataCFC = new testbox.system.modules.mockdatacfc.models.MockData(); + } + return variables.$mockDataCFC; + } + + /** + * Get the TestBox utility object + * + * @return testbox.system.util.Util + */ + function getUtility(){ + // Lazy Load it + if ( isNull( variables.$utility ) ) { + variables.$utility = new testbox.system.util.Util(); + } + return variables.$utility; + } + + /** + * Get the TestBox Env object + * + * @return testbox.system.util.Env + */ + function getEnv(){ + // Lazy Load it + if ( isNull( variables.$env ) ) { + variables.$env = new testbox.system.util.Env(); + } + return variables.$env; + } + + /** + * Get a reference to the MockBox Engine + * + * @generationPath The path to generate the mocks if passed, else uses default location. + * + * @return testbox.system.MockBox + */ + function getMockBox( string generationPath = "" ){ + // Lazy Load it + if ( isNull( this.$mockbox ) ) { + variables.$mockbox = this.$mockbox = new testbox.system.MockBox( arguments.generationPath ); + } else { + // Generation path updates + if ( len( arguments.generationPath ) ) { + this.$mockBox.setGenerationPath( arguments.generationPath ); + } + } + + return this.$mockBox; + } + + /** + * Create an empty mock + * + * @className The class name of the object to mock. The mock factory will instantiate it for you + * @object The object to mock, already instantiated + * @callLogging Add method call logging for all mocked methods. Defaults to true + */ + function createEmptyMock( + string className, + any object, + boolean callLogging = true + ){ + return getMockBox().createEmptyMock( argumentCollection = arguments ); + } + + /** + * Create a mock with or without clearing implementations, usually not clearing means you want to build object spies + * + * @className The class name of the object to mock. The mock factory will instantiate it for you + * @object The object to mock, already instantiated + * @clearMethods If true, all methods in the target mock object will be removed. You can then mock only the methods that you want to mock. Defaults to false + * @callLogging Add method call logging for all mocked methods. Defaults to true + */ + function createMock( + string className, + any object, + boolean clearMethods = false + boolean callLogging =true + ){ + return getMockBox().createMock( argumentCollection = arguments ); + } + + /** + * Prepares an already instantiated object to act as a mock for spying and much more + * + * @object The object to mock, already instantiated + * @callLogging Add method call logging for all mocked methods. Defaults to true + */ + function prepareMock( any object, boolean callLogging = true ){ + return getMockBox().prepareMock( argumentCollection = arguments ); + } + + /** + * Create an empty stub object that you can use for mocking + * + * @callLogging Add method call logging for all mocked methods. Defaults to true + * @extends Make the stub extend from certain class + * @implements Make the stub adhere to an interface + */ + function createStub( + boolean callLogging = true, + string extends = "", + string implements = "" + ){ + return getMockBox().createStub( argumentCollection = arguments ); + } + + // Closure Stub + function closureStub(){ + } + + // Around Stub + function aroundStub( spec ){ + arguments.spec.body( arguments.spec.data ); + } + + /** + * Check if an expected exception is defined + */ + boolean function hasExpectedException( required specName, required runner ){ + // do we have an expected annotation? + var eAnnotation = arguments.runner.getMethodAnnotation( + this[ arguments.specName ], + this.$exceptionAnnotation, + "false" + ); + + if ( eAnnotation != false ) { + // incorporate it. + this.$expectedException = { + type : ( eAnnotation == "true" ? "" : listFirst( eAnnotation, ":" ) ), + regex : ( find( ":", eAnnotation ) ? listLast( eAnnotation, ":" ) : ".*" ) + }; + } + + return ( structIsEmpty( this.$expectedException ) ? false : true ); + } + + /** + * Check if the incoming exception is expected or not. + */ + boolean function isExpectedException( + required exception, + required specName, + required runner + ){ + var results = false; + + // normalize expected exception + if ( hasExpectedException( arguments.specName, arguments.runner ) ) { + // If no type, message expectations + if ( !len( this.$expectedException.type ) && this.$expectedException.regex eq ".*" ) { + results = true; + } + // Type expectation then + else if ( + len( this.$expectedException.type ) && + arguments.exception.type eq this.$expectedException.type && + ( + this.$expectedException.regex == ".*" + || arrayLen( reMatchNoCase( this.$expectedException.regex, arguments.exception.message ) ) + ) + ) { + results = true; + } + // Message regex then only + else if ( + this.$expectedException.regex neq ".*" && + arrayLen( reMatchNoCase( this.$expectedException.regex, arguments.exception.message ) ) + ) { + results = true; + } + } + + return results; + } + + /** + * ------------------------------------------------------------------ + * Environment Skipping Helpers + * ------------------------------------------------------------------ + */ + + function isAdobe(){ + return server.keyExists( "coldfusion" ) && server.coldfusion.productName.findNoCase( "ColdFusion Server" ); + } + + function isLucee(){ + return server.keyExists( "lucee" ); + } + + function isBoxLang(){ + return server.keyExists( "boxlang" ); + } + + function isWindows(){ + return server.keyExists( "os" ) && server.os.name.findNoCase( "windows" ); + } + + function isLinux(){ + return server.keyExists( "os" ) && server.os.name.findNoCase( "unix" ); + } + + function isMac(){ + return server.keyExists( "os" ) && server.os.name.findNoCase( "mac" ); + } + + /** + * ------------------------------------------------------------------ + * Pass-through assertions + * ------------------------------------------------------------------ + */ + + /** + * On missing method handler for dynamic assertions + * + * @missingMethodName The name of the missing method + * @missingMethodArguments The arguments passed to the missing method + */ + function onMissingMethod( missingMethodName, missingMethodArguments ){ + // If the method follows the pattern "assert{target}" then get the target into a variable + if ( left( arguments.missingMethodName, 6 ) == "assert" && len( arguments.missingMethodName ) > 6 ) { + var target = right( arguments.missingMethodName, len( arguments.missingMethodName ) - 6 ); + return invoke( + this.$assert, + target, + arguments.missingMethodArguments + ); + } + + // Throw an exception + throw( type = "InvalidMethod", message = "The method #arguments.missingMethodName# does not exist." ); + } + +} diff --git a/src/test/java/ortus/boxlang/compiler/FileTesterTest.java b/src/test/java/ortus/boxlang/compiler/FileTesterTest.java index 1c7951205..1b0bdc92f 100644 --- a/src/test/java/ortus/boxlang/compiler/FileTesterTest.java +++ b/src/test/java/ortus/boxlang/compiler/FileTesterTest.java @@ -138,4 +138,13 @@ public void x() { context ); } + @DisplayName( "Test bddTest" ) + @Test + public void testBDDTest() { + instance.executeSource( + """ + result = new src.test.java.ortus.boxlang.compiler.BDDTest(); + """, + context ); + } } From fdf7b7862f61e6809be26d790c9b3bad43aa4793 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Wed, 4 Dec 2024 15:02:53 -0600 Subject: [PATCH 043/193] Hoisting in ASM --- .../compiler/asmboxpiler/AsmTranspiler.java | 21 +++++++- .../statement/BoxClassTransformer.java | 16 ++---- .../BoxFunctionDeclarationTransformer.java | 14 +++-- .../statement/BoxInterfaceTransformer.java | 53 ++++++++----------- .../boxlang/compiler/FileTesterTest.java | 15 ++++++ 5 files changed, 73 insertions(+), 46 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index 674471b18..2037467e3 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -373,6 +373,9 @@ public class AsmTranspiler extends Transpiler { private static HashMap, Transformer> registry = new HashMap<>(); private static final String EXTENDS_ANNOTATION_MARKER = "overrideJava"; + private List UDFDeclarations = new ArrayList<>(); + private List staticUDFDeclarations = new ArrayList<>(); + public AsmTranspiler() { // TODO: instance write to static field. Seems like an oversight in Java version (retained until clarified). registry.put( BoxStringLiteral.class, new BoxStringLiteralTransformer( this ) ); @@ -495,8 +498,14 @@ public ClassNode transpile( BoxScript boxScript ) throws BoxRuntimeException { false, this, false, - () -> AsmHelper.transformBodyExpressions( this, boxScript.getStatements(), TransformerContext.NONE, - returnType == Type.VOID_TYPE ? ReturnValueContext.EMPTY : ReturnValueContext.VALUE_OR_NULL ) + () -> { + List nodes = new ArrayList<>(); + List body = AsmHelper.transformBodyExpressions( this, boxScript.getStatements(), TransformerContext.NONE, + returnType == Type.VOID_TYPE ? ReturnValueContext.EMPTY : ReturnValueContext.VALUE_OR_NULL ); + nodes.addAll( getUDFDeclarations() ); + nodes.addAll( body ); + return nodes; + } ); AsmHelper.complete( classNode, type, methodVisitor -> { @@ -1070,4 +1079,12 @@ private List generateSetOfCompileTimeMethodNames( BoxClass box return nodes; } + + public List getStaticUDFDeclarations() { + return staticUDFDeclarations; + } + + public List getUDFDeclarations() { + return UDFDeclarations; + } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java index 1d6efd7f0..cc11a9f67 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java @@ -41,6 +41,7 @@ import org.objectweb.asm.tree.VarInsnNode; import ortus.boxlang.compiler.asmboxpiler.AsmHelper; +import ortus.boxlang.compiler.asmboxpiler.AsmTranspiler; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; @@ -468,20 +469,13 @@ public static ClassNode transpile( Transpiler transpiler, BoxClass boxClass ) th AsmHelper.methodWithContextAndClassLocator( classNode, "_pseudoConstructor", Type.getType( IBoxContext.class ), Type.VOID_TYPE, false, transpiler, false, () -> { - List psuedoBody = boxClass.getBody() + List psuedoBody = new ArrayList<>(); + List body = boxClass.getBody() .stream() - .sorted( ( a, b ) -> { - if ( a instanceof BoxFunctionDeclaration && ! ( b instanceof BoxFunctionDeclaration ) ) { - return -1; - } else if ( b instanceof BoxFunctionDeclaration && ! ( a instanceof BoxFunctionDeclaration ) ) { - return 1; - } - - return 0; - - } ) .flatMap( statement -> transpiler.transform( statement, TransformerContext.NONE, ReturnValueContext.EMPTY ).stream() ) .collect( Collectors.toList() ); + psuedoBody.addAll( ( ( AsmTranspiler ) transpiler ).getUDFDeclarations() ); + psuedoBody.addAll( body ); psuedoBody.add( new VarInsnNode( Opcodes.ALOAD, 0 ) ); psuedoBody.add( new VarInsnNode( Opcodes.ALOAD, 1 ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java index 838f8b1ee..95315c011 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java @@ -225,8 +225,12 @@ public List transform( BoxNode node, TransformerContext contex Type.getDescriptor( List.class ) ); } ); - List nodes = new ArrayList(); - + List nodes; + if ( function.getModifiers().contains( BoxMethodDeclarationModifier.STATIC ) ) { + nodes = new ArrayList<>(); + } else { + nodes = ( ( AsmTranspiler ) transpiler ).getUDFDeclarations(); + } nodes.addAll( transpiler.getCurrentMethodContextTracker().get().loadCurrentContext() ); nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, @@ -247,6 +251,10 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); } - return nodes; + if ( function.getModifiers().contains( BoxMethodDeclarationModifier.STATIC ) ) { + return nodes; + } else { + return new ArrayList<>(); + } } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java index 0e7ed7471..d5e622591 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java @@ -31,6 +31,7 @@ import org.objectweb.asm.tree.MethodInsnNode; import ortus.boxlang.compiler.asmboxpiler.AsmHelper; +import ortus.boxlang.compiler.asmboxpiler.AsmTranspiler; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; @@ -66,10 +67,9 @@ public static ClassNode transpile( Transpiler transpiler, BoxInterface boxInterf String mappingName = transpiler.getProperty( "mappingName" ); String mappingPath = transpiler.getProperty( "mappingPath" ); String relativePath = transpiler.getProperty( "relativePath" ); - String fileName = source instanceof SourceFile file && file.getFile() != null ? file.getFile().getName() : "unknown"; String filePath = source instanceof SourceFile file && file.getFile() != null ? file.getFile().getAbsolutePath() : "unknown"; // trim leading . if exists - String boxInterfacename = transpiler.getProperty( "boxFQN" ); + String boxInterfaceName = transpiler.getProperty( "boxFQN" ); String sourceType = transpiler.getProperty( "sourceType" ); Type type = Type.getType( "L" + packageName.replace( '.', '/' ) @@ -337,36 +337,29 @@ public static ClassNode transpile( Transpiler transpiler, BoxInterface boxInterf AsmHelper.methodWithContextAndClassLocator( classNode, "_pseudoConstructor", Type.getType( IBoxContext.class ), Type.VOID_TYPE, false, transpiler, false, () -> { - return boxInterface.getBody() + List nodes = new ArrayList<>(); + List body = boxInterface.getBody() .stream() - .sorted( ( a, b ) -> { - if ( a instanceof BoxFunctionDeclaration && ! ( b instanceof BoxFunctionDeclaration ) ) { - return -1; - } else if ( b instanceof BoxFunctionDeclaration && ! ( a instanceof BoxFunctionDeclaration ) ) { - return 1; - } - - return 0; - - } ) .flatMap( statement -> { - - if ( ! ( statement instanceof BoxFunctionDeclaration ) - && ! ( statement instanceof BoxImport ) - && ! ( statement instanceof BoxStaticInitializer ) ) { - throw new ExpressionException( - "Statement type not supported in an interface: " + statement.getClass().getSimpleName(), - statement - ); - } - - if ( statement instanceof BoxFunctionDeclaration bfd && bfd.getBody() == null ) { - return new ArrayList().stream(); - } - - return transpiler.transform( statement, TransformerContext.NONE, ReturnValueContext.EMPTY ).stream(); - } ) + if ( ! ( statement instanceof BoxFunctionDeclaration ) + && ! ( statement instanceof BoxImport ) + && ! ( statement instanceof BoxStaticInitializer ) ) { + throw new ExpressionException( + "Statement type not supported in an interface: " + statement.getClass().getSimpleName(), + statement + ); + } + + if ( statement instanceof BoxFunctionDeclaration bfd && bfd.getBody() == null ) { + return new ArrayList().stream(); + } + + return transpiler.transform( statement, TransformerContext.NONE, ReturnValueContext.EMPTY ).stream(); + } ) .toList(); + nodes.addAll( ( ( AsmTranspiler ) transpiler ).getUDFDeclarations() ); + nodes.addAll( body ); + return nodes; } ); @@ -421,7 +414,7 @@ public static ClassNode transpile( Transpiler transpiler, BoxInterface boxInterf List annotations = transpiler.transformAnnotations( boxInterface.getAllAnnotations() ); List documenation = transpiler.transformDocumentation( boxInterface.getDocumentation() ); - List name = transpiler.createKey( boxInterfacename ); + List name = transpiler.createKey( boxInterfaceName ); List abstractMethods = AsmHelper.generateMapOfAbstractMethodNames( transpiler, boxInterface ); diff --git a/src/test/java/ortus/boxlang/compiler/FileTesterTest.java b/src/test/java/ortus/boxlang/compiler/FileTesterTest.java index 1b0bdc92f..b19e47cf7 100644 --- a/src/test/java/ortus/boxlang/compiler/FileTesterTest.java +++ b/src/test/java/ortus/boxlang/compiler/FileTesterTest.java @@ -147,4 +147,19 @@ public void testBDDTest() { """, context ); } + + @DisplayName( "Test hoisting" ) + @Test + public void testHoisting() { + instance.executeSource( + """ + result = foo(); + function foo() { + return "bar"; + } + """, + context ); + + } + } From 366bd61cde2aa63c8c1815acce1af6dc91319240 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 4 Dec 2024 15:20:47 -0600 Subject: [PATCH 044/193] BL-784 more performance tuning on regex --- .../ortus/boxlang/compiler/ast/BoxNode.java | 23 ++++++++----- .../compiler/javaboxpiler/JavaBoxpiler.java | 6 ++-- .../compiler/javaboxpiler/Transpiler.java | 17 +++++----- .../compiler/parser/ErrorListener.java | 3 +- .../toolchain/BoxExpressionVisitor.java | 33 ++++++++++--------- .../toolchain/CFExpressionVisitor.java | 33 ++++++++++--------- .../boxlang/runtime/util/RegexBuilder.java | 17 +++++++++- 7 files changed, 79 insertions(+), 53 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/ast/BoxNode.java b/src/main/java/ortus/boxlang/compiler/ast/BoxNode.java index 0ccd6bb28..cccadf1aa 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/BoxNode.java +++ b/src/main/java/ortus/boxlang/compiler/ast/BoxNode.java @@ -14,22 +14,24 @@ */ package ortus.boxlang.compiler.ast; +import java.io.IOException; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + import com.fasterxml.jackson.jr.ob.JSON; import com.fasterxml.jackson.jr.ob.JSON.Feature; import com.fasterxml.jackson.jr.ob.JSONObjectException; + import ortus.boxlang.compiler.ast.comment.BoxComment; import ortus.boxlang.compiler.ast.comment.BoxDocComment; import ortus.boxlang.compiler.ast.statement.BoxAnnotation; import ortus.boxlang.compiler.ast.statement.BoxImport; import ortus.boxlang.compiler.ast.visitor.BoxVisitable; import ortus.boxlang.compiler.ast.visitor.PrettyPrintBoxVisitor; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Predicate; +import ortus.boxlang.runtime.util.RegexBuilder; /** * Base class for the BoxLang AST Nodes @@ -662,9 +664,12 @@ public String getDescription() { if ( className.startsWith( "Box" ) ) { className = className.substring( 3 ); } - var name = className.replaceAll( "([A-Z])", " $1" ).toLowerCase().trim(); + var name = RegexBuilder.of( className, RegexBuilder.UPPERCASE_GROUP ) + .replaceAllAndGet( " $1" ) + .toLowerCase() + .trim(); - if ( name.matches( "^[aeiou].*" ) ) { + if ( RegexBuilder.of( name, RegexBuilder.VOWELS ).matches() ) { return "an " + name; } else { return "a " + name; diff --git a/src/main/java/ortus/boxlang/compiler/javaboxpiler/JavaBoxpiler.java b/src/main/java/ortus/boxlang/compiler/javaboxpiler/JavaBoxpiler.java index d7be6d9b4..d1ffba881 100644 --- a/src/main/java/ortus/boxlang/compiler/javaboxpiler/JavaBoxpiler.java +++ b/src/main/java/ortus/boxlang/compiler/javaboxpiler/JavaBoxpiler.java @@ -58,6 +58,7 @@ import ortus.boxlang.runtime.interop.DynamicObject; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.types.exceptions.ExpressionException; +import ortus.boxlang.runtime.util.RegexBuilder; import ortus.boxlang.runtime.util.ResolvedFilePath; /** @@ -216,7 +217,7 @@ private void compileSource( String javaSource, String fqn, String classPoolName StandardJavaFileManager fileManager = compiler.getStandardFileManager( diagnostics, null, null ); // Set the location where .class files should be written - String classPoolDiskPrefix = classPoolName.replaceAll( "[^a-zA-Z0-9]", "_" ); + String classPoolDiskPrefix = RegexBuilder.of( classPoolName, RegexBuilder.NON_ALPHANUMERIC ).replaceAllAndGet( "_" ); fileManager.setLocation( StandardLocation.CLASS_OUTPUT, Arrays.asList( classGenerationDirectory.resolve( classPoolDiskPrefix ).toFile() ) ); @@ -229,7 +230,8 @@ private void compileSource( String javaSource, String fqn, String classPoolName boolean compilerResult = task.call(); if ( !compilerResult ) { - String errors = diagnostics.getDiagnostics().stream().map( d -> d.toString() ) + String errors = diagnostics.getDiagnostics().stream() + .map( Object::toString ) .collect( Collectors.joining( "\n" ) ); throw new BoxRuntimeException( errors + "\n" + javaSource ); } diff --git a/src/main/java/ortus/boxlang/compiler/javaboxpiler/Transpiler.java b/src/main/java/ortus/boxlang/compiler/javaboxpiler/Transpiler.java index 2cfa12bbc..8258cb01a 100644 --- a/src/main/java/ortus/boxlang/compiler/javaboxpiler/Transpiler.java +++ b/src/main/java/ortus/boxlang/compiler/javaboxpiler/Transpiler.java @@ -46,6 +46,7 @@ import ortus.boxlang.runtime.loader.ImportDefinition; import ortus.boxlang.runtime.runnables.BoxScript; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; +import ortus.boxlang.runtime.util.RegexBuilder; /** * Transpiler Base class @@ -157,7 +158,7 @@ public void run( String fqn, List classPath ) throws Throwable { /** * Increment and return the try catch counter - * + * * @return the incremented value */ public int incrementAndGetTryCatchCounter() { @@ -166,7 +167,7 @@ public int incrementAndGetTryCatchCounter() { /** * Increment and return the switch counter - * + * * @return the incremented value */ public int incrementAndGetSwitchCounter() { @@ -175,7 +176,7 @@ public int incrementAndGetSwitchCounter() { /** * Increment and return the for in counter - * + * * @return the incremented value */ public int incrementAndGetForInCounter() { @@ -184,7 +185,7 @@ public int incrementAndGetForInCounter() { /** * Increment and return the lambda counter - * + * * @return the incremented value */ public void pushContextName( String name ) { @@ -196,7 +197,7 @@ public void pushContextName( String name ) { /** * Increment and return the lambda counter - * + * * @return the incremented value */ public String popContextName() { @@ -205,7 +206,7 @@ public String popContextName() { /** * Increment and return the lambda counter - * + * * @return the incremented value */ public String peekContextName() { @@ -214,7 +215,7 @@ public String peekContextName() { /** * Increment and return the lambda counter - * + * * @param importString the import string to add */ public void addImport( String importString ) { @@ -364,6 +365,6 @@ public String getResolvedFilePath( String mappingName, String mappingPath, Strin } public String escapeJavaString( String str ) { - return str.replaceAll( "\\\\", "\\\\\\\\" ); + return RegexBuilder.of( str, RegexBuilder.BACKSLASH ).replaceAllAndGet( "\\\\\\\\" ); } } diff --git a/src/main/java/ortus/boxlang/compiler/parser/ErrorListener.java b/src/main/java/ortus/boxlang/compiler/parser/ErrorListener.java index 6d1a8212d..cb7f43499 100644 --- a/src/main/java/ortus/boxlang/compiler/parser/ErrorListener.java +++ b/src/main/java/ortus/boxlang/compiler/parser/ErrorListener.java @@ -12,6 +12,7 @@ import ortus.boxlang.compiler.ast.Point; import ortus.boxlang.compiler.ast.Position; import ortus.boxlang.compiler.ast.Source; +import ortus.boxlang.runtime.util.RegexBuilder; public class ErrorListener extends BaseErrorListener { @@ -64,7 +65,7 @@ public void setSource( Source source ) { */ private String[] getSourceLines() { if ( this.sourceLines == null ) { - this.sourceLines = this.sourceToParse.getCode().replaceAll( "\\r", "" ).split( "\n" ); + this.sourceLines = RegexBuilder.stripCarriageReturns( this.sourceToParse.getCode() ).split( "\n" ); } return this.sourceLines; } diff --git a/src/main/java/ortus/boxlang/compiler/toolchain/BoxExpressionVisitor.java b/src/main/java/ortus/boxlang/compiler/toolchain/BoxExpressionVisitor.java index 3eaaaa54a..87136f130 100644 --- a/src/main/java/ortus/boxlang/compiler/toolchain/BoxExpressionVisitor.java +++ b/src/main/java/ortus/boxlang/compiler/toolchain/BoxExpressionVisitor.java @@ -153,6 +153,7 @@ import ortus.boxlang.parser.antlr.BoxScriptGrammar.TestExpressionContext; import ortus.boxlang.parser.antlr.BoxScriptGrammarBaseVisitor; import ortus.boxlang.runtime.types.exceptions.ExpressionException; +import ortus.boxlang.runtime.util.RegexBuilder; /** * This class is responsible for visiting the parse tree and generating the AST for BoxScript expressions. @@ -174,7 +175,7 @@ public BoxExpressionVisitor( BoxScriptParser tools, BoxVisitor statementVisitor * This is here simply to allow tests to resolve a single expression without having to walk exprStaments * * @param ctx the parse tree - * + * * @return the expression */ public BoxExpression visitTestExpression( TestExpressionContext ctx ) { @@ -200,7 +201,7 @@ public BoxExpression visitExprStatInvocable( ExprStatInvocableContext ctx ) { *

* * @param ctx the parse tree - * + * * @return The AST for the parenthesised expression */ @Override @@ -306,7 +307,7 @@ public BoxExpression visitExprIllegalIdentifier( ExprIllegalIdentifierContext ct *

* * @param ctx the parse tree - * + * * @return the AST for a particular accessor operation */ @Override @@ -679,9 +680,9 @@ public BoxExpression visitExprOr( ExprOrContext ctx ) { * Generate the ELVIS AST node. * * @param bermudaTriangle the parse tree - * + * * @return The binary operation representing Elvis - * + * * @apiNote Elvis needs boats */ @Override @@ -997,7 +998,7 @@ public BoxExpression visitExprLiterals( ExprLiteralsContext ctx ) { *

* * @param ctx the parse tree - * + * * @return Either a BoxIdentifier or BoxScope AST node */ @Override @@ -1150,14 +1151,14 @@ private void processIfNotNull( List list, Consumer consumer ) { *

* * @param ctx the parse tree - * + * * @return the operation AST node */ public BoxComparisonOperator buildRelOp( RelOpsContext ctx ) { // Convert the context to a string without whitespace. Then we can just have a string // switch - var op = ctx.getText().replaceAll( "\\s+", "" ).toUpperCase(); + var op = RegexBuilder.stripWhitespace( ctx.getText() ).toUpperCase(); return switch ( op ) { case "GT", ">", "GREATERTHAN" -> BoxComparisonOperator.GreaterThan; @@ -1180,14 +1181,14 @@ public BoxComparisonOperator buildRelOp( RelOpsContext ctx ) { *

* * @param ctx the parse tree - * + * * @return the operation AST node */ public BoxBinaryOperator buildBinOp( BinOpsContext ctx ) { // Convert the context to a string without whitespace. Then we can just have a string // switch - var op = ctx.getText().replaceAll( "\\s+", "" ).toUpperCase(); + var op = RegexBuilder.stripWhitespace( ctx.getText() ).toUpperCase(); return switch ( op ) { case "EQV" -> BoxBinaryOperator.Equivalence; @@ -1202,7 +1203,7 @@ public BoxBinaryOperator buildBinOp( BinOpsContext ctx ) { * Build the assignment operator from the token * * @param token The token to build the operator from - * + * * @return The BoxAssignmentOperator AST */ private BoxAssignmentOperator buildAssignOp( Token token ) { @@ -1228,11 +1229,11 @@ private BoxAssignmentOperator buildAssignOp( Token token ) { *

* * @param prefix The possibly COLON-suffixed string for scope generation. - * + * * @return The BoxScope AST */ private BoxExpression buildScope( Token prefix ) { - var scope = prefix.getText().replaceAll( "[:]+$", "" ).toUpperCase(); + var scope = RegexBuilder.of( prefix.getText(), RegexBuilder.END_OF_LINE_COLONS ).replaceAllAndGet( "" ).toUpperCase(); return new BoxScope( scope, tools.getPosition( prefix ), prefix.getText() ); } @@ -1241,7 +1242,7 @@ private BoxExpression buildScope( Token prefix ) { * when not in a dot accessor. * * @param expr The expression to convert - * + * * @return The converted expression */ private BoxExpression convertDotElement( BoxExpression expr, boolean withScope ) { @@ -1255,7 +1256,7 @@ private BoxExpression convertDotElement( BoxExpression expr, boolean withScope ) * Builds the correct type for a value key or value in a struct literal. * * @param ctx the ParserContext to accept and convert - * + * * @return the correct BoxType */ private BoxExpression buildKey( ExpressionContext ctx ) { @@ -1275,7 +1276,7 @@ public BoxAssignmentModifier buildAssignmentModifier( AssignmentModifierContext * a visitor to handle such nodes so that we can call the visitor even when the parser rejected the input * * @param node the error node - * + * * @return a New error node so that AST building can work */ @Override diff --git a/src/main/java/ortus/boxlang/compiler/toolchain/CFExpressionVisitor.java b/src/main/java/ortus/boxlang/compiler/toolchain/CFExpressionVisitor.java index d7b772f79..44488bd9a 100644 --- a/src/main/java/ortus/boxlang/compiler/toolchain/CFExpressionVisitor.java +++ b/src/main/java/ortus/boxlang/compiler/toolchain/CFExpressionVisitor.java @@ -142,6 +142,7 @@ import ortus.boxlang.parser.antlr.CFGrammar.TestExpressionContext; import ortus.boxlang.parser.antlr.CFGrammarBaseVisitor; import ortus.boxlang.runtime.types.exceptions.ExpressionException; +import ortus.boxlang.runtime.util.RegexBuilder; /** * This class is responsible for visiting the parse tree and generating the AST for CFScript expressions. @@ -170,7 +171,7 @@ public CFVisitor getStatementVisitor() { * This is here simply to allow tests to resolve a single expression without having to walk exprStaments * * @param ctx the parse tree - * + * * @return the expression */ public BoxExpression visitTestExpression( TestExpressionContext ctx ) { @@ -196,7 +197,7 @@ public BoxExpression visitExprStatInvocable( ExprStatInvocableContext ctx ) { *

* * @param ctx the parse tree - * + * * @return The AST for the parenthesised expression */ @Override @@ -302,7 +303,7 @@ public BoxExpression visitExprIllegalIdentifier( ExprIllegalIdentifierContext ct *

* * @param ctx the parse tree - * + * * @return the AST for a particular accessor operation */ @Override @@ -626,9 +627,9 @@ public BoxExpression visitExprOr( ExprOrContext ctx ) { * Generate the ELVIS AST node. * * @param bermudaTriangle the parse tree - * + * * @return The binary operation representing Elvis - * + * * @apiNote Elvis needs boats */ @Override @@ -926,7 +927,7 @@ public BoxExpression visitExprLiterals( ExprLiteralsContext ctx ) { *

* * @param ctx the parse tree - * + * * @return Either a BoxIdentifier or BoxScope AST node */ @Override @@ -1088,14 +1089,14 @@ private void processIfNotNull( List list, Consumer consumer ) { *

* * @param ctx the parse tree - * + * * @return the operation AST node */ public BoxComparisonOperator buildRelOp( RelOpsContext ctx ) { // Convert the context to a string without whitespace. Then we can just have a string // switch - var op = ctx.getText().replaceAll( "\\s+", "" ).toUpperCase(); + var op = RegexBuilder.stripWhitespace( ctx.getText() ).toUpperCase(); return switch ( op ) { case "GT", ">", "GREATERTHAN" -> BoxComparisonOperator.GreaterThan; @@ -1118,14 +1119,14 @@ public BoxComparisonOperator buildRelOp( RelOpsContext ctx ) { *

* * @param ctx the parse tree - * + * * @return the operation AST node */ public BoxBinaryOperator buildBinOp( BinOpsContext ctx ) { // Convert the context to a string without whitespace. Then we can just have a string // switch - var op = ctx.getText().replaceAll( "\\s+", "" ).toUpperCase(); + var op = RegexBuilder.stripWhitespace( ctx.getText() ).toUpperCase(); return switch ( op ) { case "EQV" -> BoxBinaryOperator.Equivalence; @@ -1140,7 +1141,7 @@ public BoxBinaryOperator buildBinOp( BinOpsContext ctx ) { * Build the assignment operator from the token * * @param token The token to build the operator from - * + * * @return The BoxAssignmentOperator AST */ private BoxAssignmentOperator buildAssignOp( Token token ) { @@ -1166,11 +1167,11 @@ private BoxAssignmentOperator buildAssignOp( Token token ) { *

* * @param prefix The possibly COLON-suffixed string for scope generation. - * + * * @return The BoxScope AST */ private BoxExpression buildScope( Token prefix ) { - var scope = prefix.getText().replaceAll( "[:]+$", "" ).toUpperCase(); + var scope = RegexBuilder.of( prefix.getText(), RegexBuilder.END_OF_LINE_COLONS ).replaceAllAndGet( "" ).toUpperCase(); return new BoxScope( scope, tools.getPosition( prefix ), prefix.getText() ); } @@ -1179,7 +1180,7 @@ private BoxExpression buildScope( Token prefix ) { * when not in a dot accessor. * * @param expr The expression to convert - * + * * @return The converted expression */ private BoxExpression convertDotElement( BoxExpression expr, boolean withScope ) { @@ -1193,7 +1194,7 @@ private BoxExpression convertDotElement( BoxExpression expr, boolean withScope ) * Builds the correct type for a value key or value in a struct literal. * * @param ctx the ParserContext to accept and convert - * + * * @return the correct BoxType */ private BoxExpression buildKey( ExpressionContext ctx ) { @@ -1213,7 +1214,7 @@ public BoxAssignmentModifier buildAssignmentModifier( AssignmentModifierContext * a visitor to handle such nodes so that we can call the visitor even when the parser rejected the input * * @param node the error node - * + * * @return a New error node so that AST building can work */ @Override diff --git a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java index 9977a1d06..cd4764c92 100644 --- a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java +++ b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java @@ -35,9 +35,11 @@ public class RegexBuilder { * Add as many patterns as needed, but make sure they are in all caps and alphabetically ordered. */ public static final Pattern BACKSLASH = Pattern.compile( "\\\\" ); - public static final Pattern COLON = Pattern.compile( ":" ); + public static final Pattern CARRIAGE_RETURN = Pattern.compile( "\\r" ); public static final Pattern CF_SQL = Pattern.compile( "(?i)CF_SQL_" ); + public static final Pattern COLON = Pattern.compile( ":" ); public static final Pattern CREDIT_CARD_NUMBERS = Pattern.compile( "[0-9 ,_-]+" ); + public static final Pattern END_OF_LINE_COLONS = Pattern.compile( ":+$" ); public static final Pattern LINE_ENDINGS = Pattern.compile( "\\r?\\n" ); public static final Pattern MULTILINE_START_OF_LINE = Pattern.compile( "(?m)^" ); public static final Pattern MULTIPLE_SPACES = Pattern.compile( "\\s+" ); @@ -55,7 +57,9 @@ public class RegexBuilder { public static final Pattern SQL_PARENTHESIS_START = Pattern.compile( "\\((\\w|\\'|\"|\\`)" ); public static final Pattern STARTS_WITH_DIGIT = Pattern.compile( "^\\d.*" ); public static final Pattern TWO_DOTS = Pattern.compile( "\\.{2}" ); + public static final Pattern UPPERCASE_GROUP = Pattern.compile( "([A-Z])" ); public static final Pattern WHITESPACE = Pattern.compile( "\\s" ); + public static final Pattern VOWELS = Pattern.compile( "^[aeiou].*" ); /** * Build a matcher for the given pattern lookup @@ -92,6 +96,17 @@ public static RegexMatcher of( String input, Pattern pattern ) { return new RegexMatcher( input ).match( pattern ); } + /** + * Conmmonly used utility to strip whitespace from a string + */ + public static String stripWhitespace( String input ) { + return of( input, WHITESPACE ).replaceAllAndGet( "" ); + } + + public static String stripCarriageReturns( String input ) { + return of( input, CARRIAGE_RETURN ).replaceAllAndGet( "" ); + } + /** * The RegexMatcher class is used to match a pattern against an input string * by either using a pre-complied pattern or a string pattern that will be compiled on the fly. From c1b8a3535c6aaf1f73d39d7b37081673e920b046 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Wed, 4 Dec 2024 15:24:23 -0600 Subject: [PATCH 045/193] Fix for potentially null expand var --- src/main/resources/dump/html/Array.bxm | 2 +- src/main/resources/dump/html/Struct.bxm | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/dump/html/Array.bxm b/src/main/resources/dump/html/Array.bxm index 16ba18747..1d0055226 100644 --- a/src/main/resources/dump/html/Array.bxm +++ b/src/main/resources/dump/html/Array.bxm @@ -50,7 +50,7 @@ diff --git a/src/main/resources/dump/html/Struct.bxm b/src/main/resources/dump/html/Struct.bxm index c37566130..aaf59d8f2 100644 --- a/src/main/resources/dump/html/Struct.bxm +++ b/src/main/resources/dump/html/Struct.bxm @@ -86,10 +86,10 @@ tabindex="0" > #encodeForHTML( key )# - +
- +
From 54cc6f31c73c85176b11f9e1b91ba3dba93471d1 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Wed, 4 Dec 2024 16:06:40 -0600 Subject: [PATCH 046/193] Fix for an array of args being passed to invoke and then onMissingMethod --- .../ortus/boxlang/runtime/util/ArgumentUtil.java | 6 ++++++ src/test/java/TestCases/phase3/ClassTest.java | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/main/java/ortus/boxlang/runtime/util/ArgumentUtil.java b/src/main/java/ortus/boxlang/runtime/util/ArgumentUtil.java index d911f8e54..5b885d1e9 100644 --- a/src/main/java/ortus/boxlang/runtime/util/ArgumentUtil.java +++ b/src/main/java/ortus/boxlang/runtime/util/ArgumentUtil.java @@ -29,6 +29,7 @@ import ortus.boxlang.runtime.scopes.IntKey; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.Argument; +import ortus.boxlang.runtime.types.Array; import ortus.boxlang.runtime.types.Function; import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.NullValue; @@ -211,6 +212,11 @@ public static ArgumentsScope createArgumentsScope( IBoxContext context, Map Date: Wed, 4 Dec 2024 16:08:42 -0600 Subject: [PATCH 047/193] Handle arguments coming in as an array --- .../boxlang/runtime/interop/DynamicInteropService.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/ortus/boxlang/runtime/interop/DynamicInteropService.java b/src/main/java/ortus/boxlang/runtime/interop/DynamicInteropService.java index 76baed54f..2a8805169 100644 --- a/src/main/java/ortus/boxlang/runtime/interop/DynamicInteropService.java +++ b/src/main/java/ortus/boxlang/runtime/interop/DynamicInteropService.java @@ -508,6 +508,13 @@ private static T bootstrapBLClass( IBoxContext context, IClassRunnable boxCl } } namedArgs.remove( Key.argumentCollection ); + } else if ( namedArgs.containsKey( Key.argumentCollection ) && namedArgs.get( Key.argumentCollection ) instanceof Array argArray ) { + // Create copy of named args, merge in argCollection without overwriting, and delete arg collection key from copy of namedargs + namedArgs = new HashMap<>( namedArgs ); + for ( int i = 0; i < argArray.size(); i++ ) { + namedArgs.put( Key.of( i + 1 ), argArray.get( i ) ); + } + namedArgs.remove( Key.argumentCollection ); } // loop over args and invoke setter methods for each for ( Map.Entry entry : namedArgs.entrySet() ) { From f3bf112a550e2b5dfc15105e052b344a237afe7d Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Wed, 4 Dec 2024 17:25:33 -0600 Subject: [PATCH 048/193] BL-806 ASM fixes --- .../expression/BoxBreakTransformer.java | 6 --- .../expression/BoxSwitchTransformer.java | 35 ++++++++++---- .../statement/BoxWhileTransformer.java | 18 ++++--- .../ortus/boxlang/compiler/CompileIssue1.cfc | 18 +++++++ .../boxlang/compiler/FileTesterTest.java | 47 +++++++++++++++++++ 5 files changed, 104 insertions(+), 20 deletions(-) create mode 100644 src/test/java/ortus/boxlang/compiler/CompileIssue1.cfc diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxBreakTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxBreakTransformer.java index f2c790965..4f7ed2870 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxBreakTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxBreakTransformer.java @@ -63,12 +63,6 @@ public List transform( BoxNode node, TransformerContext contex labelTarget = getTargetAncestor( breakNode ); } - int intermediateCount = countIntermediateLoops( labelTarget, breakNode ); - - for ( int i = 0; i < intermediateCount; i++ ) { - nodes.add( new InsnNode( Opcodes.POP ) ); - } - if ( returnContext.nullable || exitsAllowed.equals( ExitsAllowed.FUNCTION ) ) { nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxSwitchTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxSwitchTransformer.java index e8e86c337..d08ed7b52 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxSwitchTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxSwitchTransformer.java @@ -25,8 +25,10 @@ import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.VarInsnNode; import ortus.boxlang.compiler.asmboxpiler.AsmHelper; +import ortus.boxlang.compiler.asmboxpiler.MethodContextTracker; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; @@ -50,14 +52,19 @@ public List transform( BoxNode node, TransformerContext contex List condition = transpiler.transform( boxSwitch.getCondition(), TransformerContext.NONE, ReturnValueContext.VALUE ); List nodes = new ArrayList<>(); + MethodContextTracker tracker = transpiler.getCurrentMethodContextTracker().get(); AsmHelper.addDebugLabel( nodes, "BoxSwitch" ); nodes.addAll( condition ); + var switchConditionVarStore = tracker.storeNewVariable( Opcodes.ASTORE ); + nodes.addAll( switchConditionVarStore.nodes() ); nodes.add( new LdcInsnNode( 0 ) ); + var switchVarStore = tracker.storeNewVariable( Opcodes.ISTORE ); + nodes.addAll( switchVarStore.nodes() ); LabelNode endLabel = new LabelNode(); LabelNode breakTarget = new LabelNode(); - transpiler.getCurrentMethodContextTracker().ifPresent( t -> t.setBreak( boxSwitch, endLabel ) ); + tracker.setBreak( boxSwitch, breakTarget ); boxSwitch.getCases().forEach( c -> { if ( c.getCondition() == null ) { @@ -66,9 +73,11 @@ public List transform( BoxNode node, TransformerContext contex AsmHelper.addDebugLabel( nodes, "BoxSwitch - case start" ); LabelNode startOfCase = new LabelNode(), endOfCase = new LabelNode(), endOfAll = new LabelNode(); + nodes.add( new VarInsnNode( Opcodes.ILOAD, switchVarStore.index() ) ); nodes.add( new JumpInsnNode( Opcodes.IFNE, startOfCase ) ); // this dupes the condition - nodes.add( new InsnNode( Opcodes.DUP ) ); + // nodes.add( new InsnNode( Opcodes.DUP ) ); + nodes.add( new VarInsnNode( Opcodes.ALOAD, switchConditionVarStore.index() ) ); if ( c.getDelimiter() == null ) { nodes.addAll( transpiler.transform( c.getCondition(), TransformerContext.NONE, ReturnValueContext.VALUE ) ); @@ -110,12 +119,18 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new JumpInsnNode( Opcodes.IFEQ, endOfCase ) ); nodes.add( startOfCase ); - c.getBody().forEach( stmt -> nodes.addAll( transpiler.transform( stmt, TransformerContext.NONE ) ) ); + c.getBody().forEach( stmt -> nodes.addAll( transpiler.transform( stmt, TransformerContext.NONE, ReturnValueContext.EMPTY_UNLESS_JUMPING ) ) ); nodes.add( new LdcInsnNode( 1 ) ); + nodes.addAll( switchVarStore.nodes() ); + AsmHelper.addDebugLabel( nodes, "BoxSwitch - goto endOfAll" ); nodes.add( new JumpInsnNode( Opcodes.GOTO, endOfAll ) ); + AsmHelper.addDebugLabel( nodes, "BoxSwitch - endOfCase" ); nodes.add( endOfCase ); nodes.add( new LdcInsnNode( 0 ) ); + nodes.addAll( switchVarStore.nodes() ); + + AsmHelper.addDebugLabel( nodes, "BoxSwitch - endOfAll" ); nodes.add( endOfAll ); AsmHelper.addDebugLabel( nodes, "BoxSwitch - case end" ); } ); @@ -131,23 +146,27 @@ public List transform( BoxNode node, TransformerContext contex hasDefault = true; // pop the initial 0 constant in case we didn't match any cases - nodes.add( new InsnNode( Opcodes.POP ) ); + // nodes.add( new InsnNode( Opcodes.POP ) ); // pop the condition off the stack // nodes.add( new InsnNode( Opcodes.POP ) ); - c.getBody().forEach( stmt -> nodes.addAll( transpiler.transform( stmt, TransformerContext.NONE ) ) ); + c.getBody().forEach( stmt -> nodes.addAll( transpiler.transform( stmt, TransformerContext.NONE, ReturnValueContext.EMPTY_UNLESS_JUMPING ) ) ); + AsmHelper.addDebugLabel( nodes, "BoxSwitch - goto Endlabel" ); nodes.add( new JumpInsnNode( Opcodes.GOTO, endLabel ) ); } } - + nodes.add( new JumpInsnNode( Opcodes.GOTO, endLabel ) ); // pop the initial 0 constant in case we didn't match any cases + AsmHelper.addDebugLabel( nodes, "BoxSwitch - breakTarget" ); + nodes.add( breakTarget ); nodes.add( new InsnNode( Opcodes.POP ) ); + + AsmHelper.addDebugLabel( nodes, "BoxSwitch - endLabel" ); nodes.add( endLabel ); // pop the condition off the stack - nodes.add( new InsnNode( Opcodes.POP ) ); - // nodes.add( breakTarget ); + // nodes.add( new InsnNode( Opcodes.POP ) ); if ( !returnContext.empty ) { nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxWhileTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxWhileTransformer.java index 3ab60d6bf..fab598025 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxWhileTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxWhileTransformer.java @@ -24,6 +24,7 @@ import org.objectweb.asm.tree.JumpInsnNode; import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.VarInsnNode; import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.MethodContextTracker; @@ -62,15 +63,18 @@ public List transform( BoxNode node, TransformerContext contex // this is to allow the statement to return an expression in the case of a BoxScript execution // if ( returnContext == ReturnValueContext.VALUE_OR_NULL ) { nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); - nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); + // nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); + var varStore = tracker.storeNewVariable( Opcodes.ASTORE ); + // } + AsmHelper.addDebugLabel( nodes, "BoxWhile - start label" ); nodes.add( start ); - + nodes.addAll( varStore.nodes() ); // every iteration we will swap the values and pop in order to remove the older value // if ( returnContext == ReturnValueContext.VALUE_OR_NULL ) { - nodes.add( new InsnNode( Opcodes.SWAP ) ); - nodes.add( new InsnNode( Opcodes.POP ) ); + // nodes.add( new InsnNode( Opcodes.SWAP ) ); + // nodes.add( new InsnNode( Opcodes.POP ) ); // } nodes.addAll( transpiler.transform( boxWhile.getCondition(), TransformerContext.RIGHT, ReturnValueContext.VALUE ) ); @@ -89,18 +93,20 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new JumpInsnNode( Opcodes.IFEQ, end ) ); nodes.addAll( transpiler.transform( boxWhile.getBody(), TransformerContext.NONE, ReturnValueContext.VALUE_OR_NULL ) ); + AsmHelper.addDebugLabel( nodes, "BoxWhile - jump start" ); nodes.add( new JumpInsnNode( Opcodes.GOTO, start ) ); AsmHelper.addDebugLabel( nodes, "BoxWhile - breakTarget" ); nodes.add( breakTarget ); - nodes.add( new InsnNode( Opcodes.SWAP ) ); - nodes.add( new InsnNode( Opcodes.POP ) ); + nodes.addAll( varStore.nodes() ); AsmHelper.addDebugLabel( nodes, "BoxWhile - end label" ); nodes.add( end ); + nodes.add( new VarInsnNode( Opcodes.ALOAD, varStore.index() ) ); + // every iteration we will swap the values and pop in order to remove the older value if ( returnContext == ReturnValueContext.EMPTY || returnContext == ReturnValueContext.EMPTY_UNLESS_JUMPING ) { nodes.add( new InsnNode( Opcodes.POP ) ); diff --git a/src/test/java/ortus/boxlang/compiler/CompileIssue1.cfc b/src/test/java/ortus/boxlang/compiler/CompileIssue1.cfc new file mode 100644 index 000000000..905c4149f --- /dev/null +++ b/src/test/java/ortus/boxlang/compiler/CompileIssue1.cfc @@ -0,0 +1,18 @@ + + component { + + + + function serializeReferenceEntity(){ + switch( "test" ){ + case "x": + try{ + + } catch( any e ){ + + } + } + + } + +} diff --git a/src/test/java/ortus/boxlang/compiler/FileTesterTest.java b/src/test/java/ortus/boxlang/compiler/FileTesterTest.java index b19e47cf7..6cf129c1a 100644 --- a/src/test/java/ortus/boxlang/compiler/FileTesterTest.java +++ b/src/test/java/ortus/boxlang/compiler/FileTesterTest.java @@ -160,6 +160,53 @@ function foo() { """, context ); + @DisplayName( "Test valueList() to queryColumnData().toList()" ) + @Test + public void testCompileIssue1() { + // instance.useJavaBoxpiler(); + instance.executeSource( + """ + a = new src.test.java.ortus.boxlang.compiler.CompileIssue1(); + """, + context ); + } + + @DisplayName( "Test valueList() to queryColumnData().toList()" ) + @Test + public void zzz() { + // instance.useJavaBoxpiler(); + instance.executeSource( + """ + a = 0 + while( a < 5 ){ + try{ + + + } + catch( any e ){ + + } + a++; + } + """, + context ); + } + + @DisplayName( "Test valueList() to queryColumnData().toList()" ) + @Test + public void switchBreak() { + // instance.useJavaBoxpiler(); + // @formatter:off + instance.executeSource(""" + a = 1; + while(a < 10) { + if(true) { + break; + } + } + """, + context ); + // @formatter:on } } From 732c48c3b3bd00187a4a9f8f5fa495fa62ef4619 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Wed, 4 Dec 2024 17:49:44 -0600 Subject: [PATCH 049/193] Missing curly brace --- src/test/java/ortus/boxlang/compiler/FileTesterTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/ortus/boxlang/compiler/FileTesterTest.java b/src/test/java/ortus/boxlang/compiler/FileTesterTest.java index 6cf129c1a..885408a26 100644 --- a/src/test/java/ortus/boxlang/compiler/FileTesterTest.java +++ b/src/test/java/ortus/boxlang/compiler/FileTesterTest.java @@ -159,6 +159,7 @@ function foo() { } """, context ); + } @DisplayName( "Test valueList() to queryColumnData().toList()" ) @Test From 5d55a35cdfbc4db22f164d31e4bb24f9f75b76b2 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 4 Dec 2024 18:01:05 -0600 Subject: [PATCH 050/193] BL-813 --- .../runtime/context/ThreadBoxContext.java | 5 +++ .../runtime/components/async/ThreadTest.java | 38 +++++++++++++++---- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/context/ThreadBoxContext.java b/src/main/java/ortus/boxlang/runtime/context/ThreadBoxContext.java index ce80ba91d..99c976cc2 100644 --- a/src/main/java/ortus/boxlang/runtime/context/ThreadBoxContext.java +++ b/src/main/java/ortus/boxlang/runtime/context/ThreadBoxContext.java @@ -157,6 +157,11 @@ public IStruct getVisibleScopes( IStruct scopes, boolean nearby, boolean shallow @Override public ScopeSearchResult scopeFindNearby( Key key, IScope defaultScope, boolean shallow ) { + // Look in the local scope first + if ( key.equals( localScope.getName() ) ) { + return new ScopeSearchResult( localScope, localScope, key, true ); + } + Object result = localScope.getRaw( key ); // Null means not found if ( isDefined( result ) ) { diff --git a/src/test/java/ortus/boxlang/runtime/components/async/ThreadTest.java b/src/test/java/ortus/boxlang/runtime/components/async/ThreadTest.java index 807fef5db..5f50d3cab 100644 --- a/src/test/java/ortus/boxlang/runtime/components/async/ThreadTest.java +++ b/src/test/java/ortus/boxlang/runtime/components/async/ThreadTest.java @@ -178,18 +178,40 @@ public void testHasTheadScope() { public void testCanJoinThreadNoTimeout() { // @formatter:off instance.executeSource( - """ - thread name="myThread" { - sleep( 2000 ) - } - thread name="myThread" action="join"; - result = myThread; - """, - context, BoxSourceType.CFSCRIPT ); + """ + thread name="myThread" { + sleep( 2000 ) + } + thread name="myThread" action="join"; + result = myThread; + """, + context, BoxSourceType.CFSCRIPT ); // @formatter:on assertThat( variables.getAsStruct( result ).get( Key.status ) ).isEqualTo( "COMPLETED" ); } + @DisplayName( "It can use local scope" ) + @Test + public void testCanUseLocalScope() { + // @formatter:off + instance.executeSource( + """ + thread name="myThread" { + local.foo = "bar"; + variables.result = local.foo; + variables.threadContext = getBoxContext(); + } + thread name="myThread" action="join"; + + """, + context, BoxSourceType.CFSCRIPT ); + // @formatter:on + assertThat( variables.get( Key.of( "result" ) ) ).isEqualTo( "bar" ); + // ensure variables scope doesn't have local key + assertThat( variables ).doesNotContainKey( Key.of( "local" ) ); + assertThat( ( ( IBoxContext ) variables.get( Key.of( "threadContext" ) ) ).getScopeNearby( Key.of( "local" ) ) ).doesNotContainKey( Key.of( "local" ) ); + } + @DisplayName( "It can join thread zero timeout" ) @Test public void testCanJoinThreadZeroTimeout() { From 534e2d922614d532a137ebb1890f6068649da10d Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 4 Dec 2024 16:10:34 -0600 Subject: [PATCH 051/193] BL-784 #resolve migration to smart regex builders --- .../boxlang/debugger/BoxLangDebugger.java | 4 +- .../runtime/bifs/global/list/ListQualify.java | 19 +++--- .../runtime/bifs/global/string/ReEscape.java | 2 - .../runtime/bifs/global/string/ReFind.java | 4 +- .../runtime/bifs/global/string/ReMatch.java | 17 ++--- .../runtime/bifs/global/string/ReReplace.java | 8 +-- .../dynamic/ExpressionInterpreter.java | 3 +- .../dynamic/casters/DateTimeCaster.java | 3 +- .../loader/resolvers/JavaResolver.java | 10 ++- .../boxlang/runtime/modules/ModuleRecord.java | 5 +- .../runtime/types/util/DateTimeHelper.java | 5 +- .../boxlang/runtime/util/RegexBuilder.java | 49 ++++++++++++- .../boxlang/runtime/util/ValidationUtil.java | 68 +++---------------- .../bifs/global/string/ReMatchNoCaseTest.java | 7 -- 14 files changed, 97 insertions(+), 107 deletions(-) diff --git a/src/main/java/ortus/boxlang/debugger/BoxLangDebugger.java b/src/main/java/ortus/boxlang/debugger/BoxLangDebugger.java index c4341f801..11de5ea42 100644 --- a/src/main/java/ortus/boxlang/debugger/BoxLangDebugger.java +++ b/src/main/java/ortus/boxlang/debugger/BoxLangDebugger.java @@ -34,6 +34,8 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; + import com.sun.jdi.AbsentInformationException; import com.sun.jdi.ClassNotLoadedException; import com.sun.jdi.ClassType; @@ -589,7 +591,7 @@ private void handleThreadDeathEvent( EventSet eventSet, ThreadDeathEvent event ) } private boolean isBoxlangThread( ThreadReference threadRef ) { - return threadRef.name().matches( "BL-Thread" ); + return StringUtils.containsIgnoreCase( threadRef.name(), "BL-Thread" ); } private void handleExceptionEvent( EventSet eventSet, ExceptionEvent exceptionEvent ) { diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/list/ListQualify.java b/src/main/java/ortus/boxlang/runtime/bifs/global/list/ListQualify.java index a90ff8773..adb13edd8 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/list/ListQualify.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/list/ListQualify.java @@ -1,4 +1,3 @@ - /** * [BoxLang] * @@ -16,7 +15,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package ortus.boxlang.runtime.bifs.global.list; import ortus.boxlang.runtime.bifs.BIF; @@ -30,13 +28,14 @@ import ortus.boxlang.runtime.types.Argument; import ortus.boxlang.runtime.types.BoxLangType; import ortus.boxlang.runtime.types.util.ListUtil; +import ortus.boxlang.runtime.util.RegexBuilder; @BoxBIF @BoxMember( type = BoxLangType.STRING, name = "ListQualify" ) public class ListQualify extends BIF { - private final String elementsChar = "char"; + private static final String ELEMENTS_CHAR = "char"; /** * Constructor @@ -59,17 +58,16 @@ public ListQualify() { * @param arguments Argument scope for the BIF. * * @argument.list The list to qualify. - * + * * @argument.qualifier The string to insert at the beginning and end of each element. - * + * * @argument.delimiter The delimiter used in the list. - * + * * @argument.elements The elements to qualify. If set to "char", only elements that are all alphabetic characters will be qualified. - * + * * @argument.includeEmptyFields If true, empty fields will be qualified. */ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { - String elements = arguments.getAsString( Key.elements ); String qualifier = arguments.getAsString( Key.qualifier ); @@ -81,8 +79,9 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { arguments.getAsBoolean( Key.includeEmptyFields ), true ).stream().map( item -> { - if ( elements.equals( elementsChar ) ? StringCaster.cast( item ).matches( "^[a-zA-Z]*$" ) : true ) { - return qualifier + item + qualifier; + + if ( elements.equals( ELEMENTS_CHAR ) ? RegexBuilder.of( StringCaster.cast( item ), RegexBuilder.ALPHA ).matches() : true ) { + return new StringBuilder( qualifier ).append( item ).append( qualifier ).toString(); } else { return item; } diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/string/ReEscape.java b/src/main/java/ortus/boxlang/runtime/bifs/global/string/ReEscape.java index f34df7965..e930dc2a0 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/string/ReEscape.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/string/ReEscape.java @@ -35,12 +35,10 @@ public ReEscape() { } /** - * * Escapes regular expression control characters within a string. * If a string is "foo.bar" and you want to escape it for use in a regular expression, you would use this BIF. * Escaped Pattern will be "foo\\.bar" * - * * @param context The context in which the BIF is being invoked. * @param arguments Argument scope for the BIF. * diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/string/ReFind.java b/src/main/java/ortus/boxlang/runtime/bifs/global/string/ReFind.java index cdce31df5..f7aef11cc 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/string/ReFind.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/string/ReFind.java @@ -16,7 +16,6 @@ import java.util.Set; import java.util.regex.Matcher; -import java.util.regex.Pattern; import ortus.boxlang.runtime.bifs.BIF; import ortus.boxlang.runtime.bifs.BoxBIF; @@ -29,6 +28,7 @@ import ortus.boxlang.runtime.types.BoxLangType; import ortus.boxlang.runtime.types.Struct; import ortus.boxlang.runtime.types.util.RegexUtil; +import ortus.boxlang.runtime.util.RegexBuilder; import ortus.boxlang.runtime.validation.Validator; @BoxBIF @@ -93,7 +93,7 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { start = 1; } // Find the first occurrence of the substring from the specified start position - Matcher matcher = java.util.regex.Pattern.compile( reg_expression, noCase ? Pattern.CASE_INSENSITIVE : 0 ).matcher( string ); + Matcher matcher = RegexBuilder.of( string, reg_expression, noCase ).matcher(); if ( start > 1 ) { matcher.region( start - 1, string.length() ); } diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/string/ReMatch.java b/src/main/java/ortus/boxlang/runtime/bifs/global/string/ReMatch.java index c3d4f95d4..b639cbbf3 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/string/ReMatch.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/string/ReMatch.java @@ -26,6 +26,7 @@ import ortus.boxlang.runtime.types.Array; import ortus.boxlang.runtime.types.BoxLangType; import ortus.boxlang.runtime.types.util.RegexUtil; +import ortus.boxlang.runtime.util.RegexBuilder; @BoxBIF @BoxBIF( alias = "reMatchNoCase" ) @@ -47,16 +48,16 @@ public ReMatch() { } /** - * + * * Uses a regular expression (RE) to search a string for a pattern, starting from a specified position. - * + * * @param context The context in which the BIF is being invoked. * @param arguments Argument scope for the BIF. - * + * * @argument.reg_expression The regular expression to search for - * + * * @argument.string The string to serach in - * + * */ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { String reg_expression = arguments.getAsString( Key.reg_expression ); @@ -67,16 +68,12 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { return new Array(); } - if ( noCase ) { - reg_expression = "(?i)" + reg_expression; - } - // Posix replacement for character classes reg_expression = RegexUtil.posixReplace( reg_expression, noCase ); // Ignore non-quantifier curly braces like PERL reg_expression = RegexUtil.replaceNonQuantiferCurlyBraces( reg_expression ); - Matcher matcher = java.util.regex.Pattern.compile( reg_expression ).matcher( string ); + Matcher matcher = RegexBuilder.of( string, reg_expression, noCase ).matcher(); Array result = new Array(); while ( matcher.find() ) { diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/string/ReReplace.java b/src/main/java/ortus/boxlang/runtime/bifs/global/string/ReReplace.java index b596b3ec4..97847c88e 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/string/ReReplace.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/string/ReReplace.java @@ -16,7 +16,6 @@ import java.util.Set; import java.util.regex.Matcher; -import java.util.regex.Pattern; import ortus.boxlang.runtime.bifs.BIF; import ortus.boxlang.runtime.bifs.BoxBIF; @@ -27,6 +26,7 @@ import ortus.boxlang.runtime.types.Argument; import ortus.boxlang.runtime.types.BoxLangType; import ortus.boxlang.runtime.types.util.RegexUtil; +import ortus.boxlang.runtime.util.RegexBuilder; import ortus.boxlang.runtime.validation.Validator; @BoxBIF @@ -73,10 +73,6 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { String scope = arguments.getAsString( Key.scope ).toLowerCase(); boolean noCase = arguments.get( BIF.__functionName ).equals( reFindNoCase ); - if ( noCase ) { - regex = "(?i)" + regex; - } - // Default string if null if ( string == null ) { string = ""; @@ -94,7 +90,7 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { regex = RegexUtil.replaceNonQuantiferCurlyBraces( regex ); StringBuffer result = new StringBuffer(); - Matcher matcher = Pattern.compile( regex ).matcher( string ); + Matcher matcher = RegexBuilder.of( string, regex, noCase ).matcher(); boolean upperCase = false; boolean lowerCase = false; diff --git a/src/main/java/ortus/boxlang/runtime/dynamic/ExpressionInterpreter.java b/src/main/java/ortus/boxlang/runtime/dynamic/ExpressionInterpreter.java index 07adade4c..4cd20680d 100644 --- a/src/main/java/ortus/boxlang/runtime/dynamic/ExpressionInterpreter.java +++ b/src/main/java/ortus/boxlang/runtime/dynamic/ExpressionInterpreter.java @@ -33,6 +33,7 @@ import ortus.boxlang.runtime.types.exceptions.ExpressionException; import ortus.boxlang.runtime.types.exceptions.KeyNotFoundException; import ortus.boxlang.runtime.types.exceptions.ScopeNotFoundException; +import ortus.boxlang.runtime.util.RegexBuilder; /** * I handle interpreting expressions @@ -68,7 +69,7 @@ public static Object getVariable( IBoxContext context, String expression, boolea return expression.substring( 1, expression.length() - 1 ).replace( "''", "'" ); } // If expression is a number, return it directly - if ( expression.matches( "^-?\\d+(\\.\\d+)?$" ) ) { + if ( RegexBuilder.of( expression, RegexBuilder.NUMBERS ).matches() ) { return NumberCaster.cast( expression ); } // Check for true/false diff --git a/src/main/java/ortus/boxlang/runtime/dynamic/casters/DateTimeCaster.java b/src/main/java/ortus/boxlang/runtime/dynamic/casters/DateTimeCaster.java index 2239d8d95..6a465042f 100644 --- a/src/main/java/ortus/boxlang/runtime/dynamic/casters/DateTimeCaster.java +++ b/src/main/java/ortus/boxlang/runtime/dynamic/casters/DateTimeCaster.java @@ -27,6 +27,7 @@ import ortus.boxlang.runtime.interop.DynamicObject; import ortus.boxlang.runtime.types.DateTime; import ortus.boxlang.runtime.types.exceptions.BoxCastException; +import ortus.boxlang.runtime.util.RegexBuilder; /** * I cast to DateTime objects @@ -220,7 +221,7 @@ public static DateTime cast( Object object, Boolean fail, ZoneId timezone, Boole try { // Timestamp string "^\{ts ([^\}])*\}" - {ts 2023-01-01 12:00:00} - if ( targetString.matches( "^\\{ts ([^\\}]*)\\}" ) ) { + if ( RegexBuilder.of( targetString, RegexBuilder.TIMESTAMP ).matches() ) { return new DateTime( LocalDateTime.parse( targetString.trim(), diff --git a/src/main/java/ortus/boxlang/runtime/loader/resolvers/JavaResolver.java b/src/main/java/ortus/boxlang/runtime/loader/resolvers/JavaResolver.java index ff3c346e9..4b9165fb1 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/resolvers/JavaResolver.java +++ b/src/main/java/ortus/boxlang/runtime/loader/resolvers/JavaResolver.java @@ -36,6 +36,7 @@ import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.services.ModuleService; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; +import ortus.boxlang.runtime.util.RegexBuilder; /** * This resolver deals with Java classes only. @@ -295,9 +296,10 @@ protected boolean importHas( IBoxContext context, ImportDefinition thisImport, S @Override protected boolean importHasMulti( IBoxContext context, ImportDefinition thisImport, String className ) { // We can't interrogate the JDK due to limitations in the JDK itself - if ( thisImport.className().matches( "(?i)(java|javax)\\..*" ) ) { + if ( RegexBuilder.of( thisImport.className(), RegexBuilder.JAVA_PACKAGE ).matches() ) { - logger.debug( "Checking if [{}] is a JDK class", thisImport.getFullyQualifiedClass( className ) ); + if ( logger.isDebugEnabled() ) + logger.debug( "Checking if [{}] is a JDK class", thisImport.getFullyQualifiedClass( className ) ); // Do we have it in the cache? if ( jdkClassImportCache.contains( thisImport.getFullyQualifiedClass( className ) ) ) { @@ -307,7 +309,9 @@ protected boolean importHasMulti( IBoxContext context, ImportDefinition thisImpo try { Class.forName( thisImport.getFullyQualifiedClass( className ), false, getSystemClassLoader() ); jdkClassImportCache.add( thisImport.getFullyQualifiedClass( className ) ); - logger.debug( "Found JDK Class [{}] and added to jdk import cache", thisImport.getFullyQualifiedClass( className ) ); + + if ( logger.isDebugEnabled() ) + logger.debug( "Found JDK Class [{}] and added to jdk import cache", thisImport.getFullyQualifiedClass( className ) ); return true; } catch ( ClassNotFoundException e ) { diff --git a/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java b/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java index af8d0fbc6..a9a687b0f 100644 --- a/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java +++ b/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java @@ -76,6 +76,7 @@ import ortus.boxlang.runtime.types.util.StructUtil; import ortus.boxlang.runtime.util.DataNavigator; import ortus.boxlang.runtime.util.EncryptionUtil; +import ortus.boxlang.runtime.util.RegexBuilder; import ortus.boxlang.runtime.util.ResolvedFilePath; /** @@ -765,7 +766,7 @@ private ModuleRecord registerComponent( File targetFile, IBoxContext context ) { // Skip directories and non CFC/BX files // We are not doing recursive registration for the moment. - if ( targetFile.isDirectory() || !targetFile.getName().matches( "^.*\\.(cfc|bx)$" ) ) { + if ( targetFile.isDirectory() || !RegexBuilder.of( targetFile.getName(), RegexBuilder.CFC_OR_BX_FILE ).matches() ) { return this; } @@ -860,7 +861,7 @@ private ModuleRecord registerBIF( File targetFile, IBoxContext context ) { // Skip directories and non CFC/BX files // We are not doing recursive registration for the moment. - if ( targetFile.isDirectory() || !targetFile.getName().matches( "^.*\\.(cfc|bx)$" ) ) { + if ( targetFile.isDirectory() || !RegexBuilder.of( targetFile.getName(), RegexBuilder.CFC_OR_BX_FILE ).matches() ) { return this; } diff --git a/src/main/java/ortus/boxlang/runtime/types/util/DateTimeHelper.java b/src/main/java/ortus/boxlang/runtime/types/util/DateTimeHelper.java index 0204831f8..5ddbdf5b8 100644 --- a/src/main/java/ortus/boxlang/runtime/types/util/DateTimeHelper.java +++ b/src/main/java/ortus/boxlang/runtime/types/util/DateTimeHelper.java @@ -32,6 +32,8 @@ import javax.management.InvalidAttributeValueException; +import ortus.boxlang.runtime.util.RegexBuilder; + /** * We represent a static date/time helper class that assists with time units on date/time conversions * It doesn't hold any date/time information. @@ -416,7 +418,8 @@ public static LocalDateTime getLastBusinessDayOfTheMonth() { * @throws InvalidAttributeValueException */ public static String validateTime( String time ) throws InvalidAttributeValueException { - if ( !time.matches( "^([0-1][0-9]|[2][0-3]):[0-5][0-9]$" ) ) { + + if ( !RegexBuilder.of( time, RegexBuilder.TWENTY_FOUR_HOUR_TIME ).matches() ) { // Do we have only hours? if ( time.contains( ":" ) ) { diff --git a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java index cd4764c92..564da1236 100644 --- a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java +++ b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java @@ -34,17 +34,23 @@ public class RegexBuilder { * Pattern Dictionary * Add as many patterns as needed, but make sure they are in all caps and alphabetically ordered. */ + public static final Pattern ALPHA = Pattern.compile( "^[a-zA-Z]*$" ); + public static final Pattern CFC_OR_BX_FILE = Pattern.compile( ".*\\.(cfc|bx)$" ); public static final Pattern BACKSLASH = Pattern.compile( "\\\\" ); public static final Pattern CARRIAGE_RETURN = Pattern.compile( "\\r" ); public static final Pattern CF_SQL = Pattern.compile( "(?i)CF_SQL_" ); public static final Pattern COLON = Pattern.compile( ":" ); public static final Pattern CREDIT_CARD_NUMBERS = Pattern.compile( "[0-9 ,_-]+" ); + public static final Pattern EMAIL = Pattern.compile( "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" ); public static final Pattern END_OF_LINE_COLONS = Pattern.compile( ":+$" ); + public static final Pattern JAVA_PACKAGE = Pattern.compile( "(?i)(java|javax)\\\\..*" ); public static final Pattern LINE_ENDINGS = Pattern.compile( "\\r?\\n" ); public static final Pattern MULTILINE_START_OF_LINE = Pattern.compile( "(?m)^" ); public static final Pattern MULTIPLE_SPACES = Pattern.compile( "\\s+" ); public static final Pattern NO_DIGITS = Pattern.compile( "\\D" ); + public static final Pattern NON_ALPHA = Pattern.compile( "[^a-zA-Z]" ); public static final Pattern NON_ALPHANUMERIC = Pattern.compile( "[^a-zA-Z0-9]" ); + public static final Pattern NUMBERS = Pattern.compile( "^-?\\d+(\\.\\d+)?$" ); public static final Pattern PACKAGE_NAMES = Pattern.compile( "[^a-zA-Z0-9$\\.]" ); public static final Pattern PERIOD = Pattern.compile( "\\." ); public static final Pattern REGEX_META = Pattern.compile( "([\\\\$])" ); @@ -56,10 +62,22 @@ public class RegexBuilder { public static final Pattern SQL_PARENTHESIS_END = Pattern.compile( "(\\w|\\'|\"|\\`)\\)" ); public static final Pattern SQL_PARENTHESIS_START = Pattern.compile( "\\((\\w|\\'|\"|\\`)" ); public static final Pattern STARTS_WITH_DIGIT = Pattern.compile( "^\\d.*" ); + public static final Pattern SSN = Pattern.compile( "^(?!219099999|078051120)(?!666|000|9\\d{2})\\d{3}(?!00)\\d{2}(?!0{4})\\d{4}$" ); + public static final Pattern TIMESTAMP = Pattern.compile( "^\\{ts ([^\\}]*)\\}" ); + public static final Pattern TELEPHONE = Pattern.compile( + "^(?:(?:\\+?1\\s*(?:[.-]\\s*)?)?(?:\\(\\s*([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\\s*\\)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\\s*(?:[.-]\\s*)?)?([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\\s*(?:[.-]\\s*)?([0-9]{4})(?:\\s*(?:#|x\\.?|ext\\.?|extension)\\s*(\\d+))?$" ); public static final Pattern TWO_DOTS = Pattern.compile( "\\.{2}" ); + public static final Pattern TWENTY_FOUR_HOUR_TIME = Pattern.compile( "^([0-1][\\d]|2[0-3]):[0-5][\\d]$" ); + public static final Pattern URL = Pattern.compile( "^(https?|ftp|file)://([A-Za-z0-90.]*)/?([-a-zA-Z0-9.+&@#/]+)?(\\??[^\\s]*)$" ); public static final Pattern UPPERCASE_GROUP = Pattern.compile( "([A-Z])" ); - public static final Pattern WHITESPACE = Pattern.compile( "\\s" ); + public static final Pattern UUID_V4 = Pattern + .compile( "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[89abAB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}" ); + public static final Pattern UUID_PATTERN = Pattern + .compile( "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[89abAB][a-fA-F0-9]{3}[a-fA-F0-9]{12}" ); public static final Pattern VOWELS = Pattern.compile( "^[aeiou].*" ); + public static final Pattern VALID_VARIABLENAME = Pattern.compile( "^[a-zA-Z_][a-zA-Z0-9_]*$" ); + public static final Pattern WHITESPACE = Pattern.compile( "\\s" ); + public static final Pattern ZIPCODE = Pattern.compile( "\\d{5}([ -]?\\d{4})?" ); /** * Build a matcher for the given pattern lookup @@ -72,6 +90,19 @@ public static RegexMatcher of( String input ) { return new RegexMatcher( input ); } + /** + * Build a matcher for the given input and string pattern + * + * @param input The input string to match against + * @param pattern The pattern to match against + * @param noCase Whether the pattern should be case insensitive or not + * + * @return A new matcher instance + */ + public static RegexMatcher of( String input, String pattern, Boolean noCase ) { + return new RegexMatcher( input ).match( pattern, noCase ); + } + /** * Build a matcher for the given input and string pattern * @@ -140,13 +171,25 @@ public RegexMatcher match( Pattern pattern ) { } /** - * Compile the pattern from the given string + * Compile the pattern from the given string with case sensitivity * * @param pattern The pattern to compile * * @return The matcher instance */ public RegexMatcher match( String pattern ) { + return this.match( pattern, false ); + } + + /** + * Compile the pattern from the given string + * + * @param pattern The pattern to compile + * @param noCase Whether the pattern should be case insensitive or not + * + * @return The matcher instance + */ + public RegexMatcher match( String pattern, Boolean noCase ) { Objects.requireNonNull( pattern, "Pattern cannot be null" ); if ( pattern.isEmpty() ) { throw new IllegalArgumentException( "Pattern cannot be empty" ); @@ -157,7 +200,7 @@ public RegexMatcher match( String pattern ) { this.pattern = ( Pattern ) BoxRuntime.getInstance() .getCacheService() .getCache( Key.bxRegex ) - .getOrSet( cacheKey, () -> Pattern.compile( pattern ) ); + .getOrSet( cacheKey, () -> Pattern.compile( pattern, noCase ? Pattern.CASE_INSENSITIVE : 0 ) ); return this; } diff --git a/src/main/java/ortus/boxlang/runtime/util/ValidationUtil.java b/src/main/java/ortus/boxlang/runtime/util/ValidationUtil.java index 0e8f453fa..07543e1b7 100644 --- a/src/main/java/ortus/boxlang/runtime/util/ValidationUtil.java +++ b/src/main/java/ortus/boxlang/runtime/util/ValidationUtil.java @@ -14,8 +14,6 @@ */ package ortus.boxlang.runtime.util; -import java.util.regex.Pattern; - import org.apache.commons.lang3.math.NumberUtils; import ortus.boxlang.runtime.dynamic.casters.CastAttempt; @@ -36,50 +34,6 @@ */ public class ValidationUtil { - /** - * Regular expression Pattern to match a URL with a `http`, `https`, `ftp`, or `file` scheme. - * - * @see https://regex101.com/r/kWhB1u/1 - */ - public static final Pattern URL = Pattern.compile( "^(https?|ftp|file)://([A-Za-z0-90.]*)/?([-a-zA-Z0-9.+&@#/]+)?(\\??[^\\s]*)$" ); - - /** - * Regular expression Pattern to match a North American Numbering Plan (NANP) telephone number. This does not support international numbers. - */ - public static final Pattern TELEPHONE = Pattern.compile( - "^(?:(?:\\+?1\\s*(?:[.-]\\s*)?)?(?:\\(\\s*([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\\s*\\)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\\s*(?:[.-]\\s*)?)?([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\\s*(?:[.-]\\s*)?([0-9]{4})(?:\\s*(?:#|x\\.?|ext\\.?|extension)\\s*(\\d+))?$" ); - - /** - * Regular expression Pattern to match a United States Postal Service (USPS) ZIP Code. - */ - public static final Pattern ZIPCODE = Pattern.compile( "\\d{5}([ -]?\\d{4})?" ); - - /** - * Regular expression Pattern to match a Social Security Number (SSN). - */ - public static final Pattern SSN = Pattern.compile( "^(?!219099999|078051120)(?!666|000|9\\d{2})\\d{3}(?!00)\\d{2}(?!0{4})\\d{4}$" ); - - /** - * Regular expression to match a Version 4 Universally Unique Identifier (UUID), in a - * case-insensitive fashion. - * - * @see https://gitlab.com/jamietanna/uuid/-/blob/v0.2.0/uuid-core/src/main/java/me/jvt/uuid/Patterns.java - */ - public static final Pattern UUID_V4 = Pattern - .compile( "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[89abAB][a-fA-F0-9]{3}-[a-fA-F0-9]{12}" ); - - /** - * Regular expression to match a Version 4 Universally Unique Identifier (UUID), in a - * case-insensitive fashion. - */ - public static final Pattern UUID_PATTERN = Pattern - .compile( "[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-4[a-fA-F0-9]{3}-[89abAB][a-fA-F0-9]{3}[a-fA-F0-9]{12}" ); - - /** - * Regular expression to match a valid variable name. - */ - public static final String VALID_VARIABLE_REGEX = "^[a-zA-Z_][a-zA-Z0-9_]*$"; - /** * Perform the Lunh algorithm to validate a credit card number. *

@@ -159,7 +113,7 @@ public static boolean isValidNumeric( Object value ) { * @return Boolean indicating whether the given string is a valid compatible UUID. */ public static boolean isValidGUID( String uuid ) { - return UUID_V4.matcher( uuid ).matches(); + return RegexBuilder.of( uuid, RegexBuilder.UUID_V4 ).matches(); } /** @@ -172,7 +126,7 @@ public static boolean isValidGUID( String uuid ) { * @return Boolean indicating whether the given string is a valid compatible UUID. */ public static boolean isValidUUID( String uuid ) { - return UUID_PATTERN.matcher( uuid ).matches(); + return RegexBuilder.of( uuid, RegexBuilder.UUID_PATTERN ).matches(); } /** @@ -186,9 +140,7 @@ public static boolean isValidUUID( String uuid ) { * @return Boolean indicating whether the given string is a valid SSN. */ public static boolean isValidSSN( String ssn ) { - return SSN.matcher( - ssn.replace( "-", "" ).replace( " ", "" ) - ).matches(); + return RegexBuilder.of( ssn.replace( "-", "" ).replace( " ", "" ), RegexBuilder.SSN ).matches(); } /** @@ -199,7 +151,7 @@ public static boolean isValidSSN( String ssn ) { * @return Boolean indicating whether the given string is a valid US or North American telephone number. */ public static boolean isValidTelephone( String phone ) { - return TELEPHONE.matcher( phone ).matches(); + return RegexBuilder.of( phone, RegexBuilder.TELEPHONE ).matches(); } /** @@ -210,7 +162,7 @@ public static boolean isValidTelephone( String phone ) { * @return Boolean indicating whether the given string is a valid URL. */ public static boolean isValidURL( String url ) { - return URL.matcher( url ).matches(); + return RegexBuilder.of( url, RegexBuilder.URL ).matches(); } /** @@ -226,7 +178,7 @@ public static boolean isValidURL( String url ) { * @return Boolean indicating whether the given string is a valid zip code. */ public static boolean isValidZipCode( String zipCode ) { - return ZIPCODE.matcher( zipCode ).matches(); + return RegexBuilder.of( zipCode, RegexBuilder.ZIPCODE ).matches(); } /** @@ -239,7 +191,7 @@ public static boolean isValidZipCode( String zipCode ) { * @return Boolean indicating whether the given string is a valid variable name. */ public static boolean isValidVariableName( String variableName ) { - return variableName.matches( VALID_VARIABLE_REGEX ); + return RegexBuilder.of( variableName, RegexBuilder.VALID_VARIABLENAME ).matches(); } /** @@ -352,7 +304,7 @@ public static boolean isValidRange( Object value, Number min, Number max ) { * @return Boolean indicating if the value matches the regex */ public static boolean isValidMatch( String value, String regex ) { - return value.matches( regex ); + return RegexBuilder.of( value, regex ).matches(); } /** @@ -374,7 +326,7 @@ public static boolean isValidMatchNoCase( String value, String regex ) { * @param email The email address to validate */ public static boolean isValidEmail( String email ) { - return email.matches( "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" ); + return RegexBuilder.of( email, RegexBuilder.EMAIL ).matches(); } /** @@ -385,7 +337,7 @@ public static boolean isValidEmail( String email ) { * @param pattern The regex pattern to match */ public static boolean isValidPattern( String value, String pattern ) { - return value.matches( pattern ); + return RegexBuilder.of( value, pattern ).matches(); } } diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/string/ReMatchNoCaseTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/string/ReMatchNoCaseTest.java index 5c0f50655..737ff3784 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/string/ReMatchNoCaseTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/string/ReMatchNoCaseTest.java @@ -15,12 +15,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package ortus.boxlang.runtime.bifs.global.string; import static com.google.common.truth.Truth.assertThat; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -45,11 +43,6 @@ public static void setUp() { instance = BoxRuntime.getInstance( true ); } - @AfterAll - public static void teardown() { - - } - @BeforeEach public void setupEach() { context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); From b6998c7503d02713e86400d61c69b981632f3709 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 4 Dec 2024 18:02:03 -0600 Subject: [PATCH 052/193] BL-784 #resolved --- .../ortus/boxlang/runtime/bifs/global/string/ReReplace.java | 1 - src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java | 4 ++-- .../boxlang/runtime/bifs/global/string/ReMatchTest.java | 6 ------ 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/string/ReReplace.java b/src/main/java/ortus/boxlang/runtime/bifs/global/string/ReReplace.java index 97847c88e..0f65e991f 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/string/ReReplace.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/string/ReReplace.java @@ -91,7 +91,6 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { StringBuffer result = new StringBuffer(); Matcher matcher = RegexBuilder.of( string, regex, noCase ).matcher(); - boolean upperCase = false; boolean lowerCase = false; diff --git a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java index 564da1236..ba5e57b7e 100644 --- a/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java +++ b/src/main/java/ortus/boxlang/runtime/util/RegexBuilder.java @@ -43,7 +43,7 @@ public class RegexBuilder { public static final Pattern CREDIT_CARD_NUMBERS = Pattern.compile( "[0-9 ,_-]+" ); public static final Pattern EMAIL = Pattern.compile( "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$" ); public static final Pattern END_OF_LINE_COLONS = Pattern.compile( ":+$" ); - public static final Pattern JAVA_PACKAGE = Pattern.compile( "(?i)(java|javax)\\\\..*" ); + public static final Pattern JAVA_PACKAGE = Pattern.compile( "(?i)(java|javax)\\..*" ); public static final Pattern LINE_ENDINGS = Pattern.compile( "\\r?\\n" ); public static final Pattern MULTILINE_START_OF_LINE = Pattern.compile( "(?m)^" ); public static final Pattern MULTIPLE_SPACES = Pattern.compile( "\\s+" ); @@ -196,7 +196,7 @@ public RegexMatcher match( String pattern, Boolean noCase ) { } // Lookup or compile the pattern into the regex cache - String cacheKey = EncryptionUtil.hash( pattern ); + String cacheKey = EncryptionUtil.hash( pattern + noCase ); this.pattern = ( Pattern ) BoxRuntime.getInstance() .getCacheService() .getCache( Key.bxRegex ) diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/string/ReMatchTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/string/ReMatchTest.java index eb2caf22d..5fe61dba0 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/string/ReMatchTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/string/ReMatchTest.java @@ -20,7 +20,6 @@ import static com.google.common.truth.Truth.assertThat; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -45,11 +44,6 @@ public static void setUp() { instance = BoxRuntime.getInstance( true ); } - @AfterAll - public static void teardown() { - - } - @BeforeEach public void setupEach() { context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); From bbcc0a160e113d6509c08b532a7308840b328f82 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 4 Dec 2024 18:22:56 -0600 Subject: [PATCH 053/193] BL-791 #resolve Support `enabled` in the boxlang.json for modules so they can be enabled or disabled and remove `disabled` double negative --- .../runtime/config/segments/ModuleConfig.java | 16 ++++++---------- .../boxlang/runtime/modules/ModuleRecord.java | 18 +++++++++--------- .../runtime/services/ModuleService.java | 4 ++-- src/main/resources/config/boxlang.json | 4 ++-- src/modules/test/src/main/bx/ModuleConfig.bx | 2 +- .../runtime/modules/ModuleRecordTest.java | 7 +++---- 6 files changed, 23 insertions(+), 28 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/config/segments/ModuleConfig.java b/src/main/java/ortus/boxlang/runtime/config/segments/ModuleConfig.java index 4532f7549..e747bc5dc 100644 --- a/src/main/java/ortus/boxlang/runtime/config/segments/ModuleConfig.java +++ b/src/main/java/ortus/boxlang/runtime/config/segments/ModuleConfig.java @@ -18,6 +18,7 @@ package ortus.boxlang.runtime.config.segments; import ortus.boxlang.runtime.config.util.PlaceholderHelper; +import ortus.boxlang.runtime.config.util.PropertyHelper; import ortus.boxlang.runtime.dynamic.casters.BooleanCaster; import ortus.boxlang.runtime.dynamic.casters.StructCaster; import ortus.boxlang.runtime.scopes.Key; @@ -40,9 +41,9 @@ public class ModuleConfig implements IConfigSegment { public String name; /** - * Whether the module is disabled or not + * Whether the module is enabled or not */ - public Boolean disabled = false; + public Boolean enabled = true; /** * The settings for the module as a struct @@ -70,13 +71,8 @@ public ModuleConfig( String name ) { * @return Return itself for chaining */ public ModuleConfig process( IStruct config ) { - // Check if the module is enabled - if ( config.containsKey( "disabled" ) ) { - this.disabled = BooleanCaster.cast( PlaceholderHelper.resolve( config.getOrDefault( "disabled", false ) ) ); - } - - // Store the settings - this.settings = StructCaster.cast( config.getOrDefault( Key.settings, new Struct() ) ); + this.enabled = BooleanCaster.cast( PropertyHelper.processString( config, Key.enabled, "true" ) ); + this.settings = StructCaster.cast( config.getOrDefault( Key.settings, new Struct() ) ); // Process placeholders this.settings.forEach( ( key, value ) -> { if ( value instanceof String ) { @@ -97,7 +93,7 @@ public ModuleConfig process( IStruct config ) { public IStruct asStruct() { return Struct.of( Key._NAME, this.name, - Key.disabled, this.disabled, + Key.enabled, this.enabled, Key.settings, new Struct( this.settings ) ); } diff --git a/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java b/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java index a9a687b0f..eac37380e 100644 --- a/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java +++ b/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java @@ -122,9 +122,9 @@ public class ModuleRecord { public String mapping; /** - * If the module is disabled for activation, defaults to false + * If the module is enabled for activation, defaults to false */ - public boolean disabled = false; + public boolean enabled = true; /** * Flag to indicate if the module has been activated or not yet @@ -322,12 +322,12 @@ public ModuleRecord loadDescriptor( IBoxContext context ) { this.author = ( String ) thisScope.getOrDefault( Key.author, "" ); this.description = ( String ) thisScope.getOrDefault( Key.description, "" ); this.webURL = ( String ) thisScope.getOrDefault( Key.webURL, "" ); - this.disabled = ( Boolean ) thisScope.getOrDefault( Key.disabled, false ); + this.enabled = ( Boolean ) thisScope.getOrDefault( Key.enabled, true ); // Verify if we disabled the loading of the module in the runtime config if ( runtime.getConfiguration().modules.containsKey( this.name ) ) { ModuleConfig config = ( ModuleConfig ) runtime.getConfiguration().modules.get( this.name ); - this.disabled = config.disabled; + this.enabled = config.enabled; } // Do we have a custom mapping to override? @@ -695,12 +695,12 @@ public void execute( IBoxContext context, String[] args ) { */ /** - * If the module is disabled for activation + * If the module is enabled for activation * - * @return {@code true} if the module is disabled for activation, {@code false} otherwise + * @return {@code true} if the module is enabled for activation, {@code false} otherwise */ - public boolean isDisabled() { - return disabled; + public boolean isEnabled() { + return enabled; } /** @@ -732,7 +732,7 @@ public IStruct asStruct() { "author", author, "customInterceptionPoints", Array.copyOf( customInterceptionPoints ), "description", description, - "disabled", disabled, + "enabled", enabled, "Id", id, "interceptors", Array.copyOf( interceptors ), "invocationPath", invocationPath, diff --git a/src/main/java/ortus/boxlang/runtime/services/ModuleService.java b/src/main/java/ortus/boxlang/runtime/services/ModuleService.java index 5458c3271..7a2481c48 100644 --- a/src/main/java/ortus/boxlang/runtime/services/ModuleService.java +++ b/src/main/java/ortus/boxlang/runtime/services/ModuleService.java @@ -250,7 +250,7 @@ void register( Key name ) { moduleRecord.loadDescriptor( runtimeContext ); // Check if the module is disabled, if so, skip it - if ( moduleRecord.isDisabled() ) { + if ( moduleRecord.isEnabled() ) { logger.warn( "+ Module Service: Module [{}] is disabled, skipping registration", moduleRecord.name @@ -342,7 +342,7 @@ void activate( Key name ) { } // Check if the module is disabled - if ( this.registry.get( name ).isDisabled() ) { + if ( this.registry.get( name ).isEnabled() ) { logger.debug( "+ Module Service: Module [{}] is disabled, skipping activation", name diff --git a/src/main/resources/config/boxlang.json b/src/main/resources/config/boxlang.json index c219a3f04..a772e1ea4 100644 --- a/src/main/resources/config/boxlang.json +++ b/src/main/resources/config/boxlang.json @@ -413,13 +413,13 @@ /** * The BoxLang module settings * The key is the module name and the value is a struct of settings for that specific module - * The `disabled` property is a boolean that determines if the module should be enabled or not + * The `enabled` property is a boolean that determines if the module should be enabled or not. Default is true * The `settings` property is a struct of settings that are specific to the module and will be override the module settings */ "modules": { // The Compat Module // "compat": { - // "disabled": false, + // "enabled": true, // "settings": { // "isLucee": true, // "isAdobe": true diff --git a/src/modules/test/src/main/bx/ModuleConfig.bx b/src/modules/test/src/main/bx/ModuleConfig.bx index f81d0e6c8..bd19531ac 100644 --- a/src/modules/test/src/main/bx/ModuleConfig.bx +++ b/src/modules/test/src/main/bx/ModuleConfig.bx @@ -56,7 +56,7 @@ class{ /** * This boolean flag tells the module service to skip the module registration/activation process. */ - this.disabled = false; + this.enabled = true; /** * -------------------------------------------------------------------------- diff --git a/src/test/java/ortus/boxlang/runtime/modules/ModuleRecordTest.java b/src/test/java/ortus/boxlang/runtime/modules/ModuleRecordTest.java index ccd94ea74..8f6a4a5a5 100644 --- a/src/test/java/ortus/boxlang/runtime/modules/ModuleRecordTest.java +++ b/src/test/java/ortus/boxlang/runtime/modules/ModuleRecordTest.java @@ -23,7 +23,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.time.Instant; -import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -113,7 +112,7 @@ void testModuleRecordAsStruct() { assertThat( structRepresentation.get( "activatedOn" ) ).isNull(); assertThat( structRepresentation.get( "author" ) ).isEqualTo( "" ); assertThat( structRepresentation.get( "description" ) ).isEqualTo( "" ); - assertThat( structRepresentation.getAsBoolean( Key.of( "disabled" ) ) ).isFalse(); + assertThat( structRepresentation.getAsBoolean( Key.of( "enabled" ) ) ).isTrue(); assertThat( structRepresentation.get( "id" ) ).isNotNull(); assertThat( structRepresentation.getAsArray( Key.of( "interceptors" ) ).size() ).isEqualTo( 0 ); assertThat( structRepresentation.get( "invocationPath" ) ).isEqualTo( "bxModules.TestModule" ); @@ -138,7 +137,7 @@ void testCanLoadModuleDescriptor() { assertThat( moduleRecord.author ).isEqualTo( "Luis Majano" ); assertThat( moduleRecord.description ).isEqualTo( "This module does amazing things" ); assertThat( moduleRecord.webURL ).isEqualTo( "https://www.ortussolutions.com" ); - assertThat( moduleRecord.disabled ).isEqualTo( false ); + assertThat( moduleRecord.enabled ).isEqualTo( false ); assertThat( moduleRecord.mapping ).isEqualTo( ModuleService.MODULE_MAPPING_PREFIX + "test" ); assertThat( moduleRecord.invocationPath ).isEqualTo( ModuleService.MODULE_MAPPING_INVOCATION_PREFIX + moduleRecord.name.getName() ); } @@ -179,7 +178,7 @@ void testCanConfigureModuleDescriptor() { assertThat( moduleRecord.author ).isEqualTo( "Luis Majano" ); assertThat( moduleRecord.description ).isEqualTo( "This module does amazing things" ); assertThat( moduleRecord.webURL ).isEqualTo( "https://www.ortussolutions.com" ); - assertThat( moduleRecord.disabled ).isEqualTo( false ); + assertThat( moduleRecord.enabled ).isEqualTo( true ); assertThat( moduleRecord.mapping ).isEqualTo( ModuleService.MODULE_MAPPING_PREFIX + "test" ); assertThat( moduleRecord.invocationPath ).isEqualTo( ModuleService.MODULE_MAPPING_INVOCATION_PREFIX + moduleRecord.name.getName() ); assertThat( moduleRecord.settings.getAsStruct( Key.of( "nested" ) ).get( Key.of( "SLA" ) ) ).isEqualTo( "24 hours" ); From 4685a406dff98faf39a83ed56ea42a08e5b22e5e Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 4 Dec 2024 18:35:04 -0600 Subject: [PATCH 054/193] fixed tests --- .../java/ortus/boxlang/runtime/modules/ModuleRecordTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/ortus/boxlang/runtime/modules/ModuleRecordTest.java b/src/test/java/ortus/boxlang/runtime/modules/ModuleRecordTest.java index 8f6a4a5a5..d200fa627 100644 --- a/src/test/java/ortus/boxlang/runtime/modules/ModuleRecordTest.java +++ b/src/test/java/ortus/boxlang/runtime/modules/ModuleRecordTest.java @@ -137,7 +137,7 @@ void testCanLoadModuleDescriptor() { assertThat( moduleRecord.author ).isEqualTo( "Luis Majano" ); assertThat( moduleRecord.description ).isEqualTo( "This module does amazing things" ); assertThat( moduleRecord.webURL ).isEqualTo( "https://www.ortussolutions.com" ); - assertThat( moduleRecord.enabled ).isEqualTo( false ); + assertThat( moduleRecord.enabled ).isEqualTo( true ); assertThat( moduleRecord.mapping ).isEqualTo( ModuleService.MODULE_MAPPING_PREFIX + "test" ); assertThat( moduleRecord.invocationPath ).isEqualTo( ModuleService.MODULE_MAPPING_INVOCATION_PREFIX + moduleRecord.name.getName() ); } From f1c920ba7588f2f5ff849b7e62d591cebd67e8cd Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 4 Dec 2024 18:47:12 -0600 Subject: [PATCH 055/193] more refactoring --- modules/bx-derby/ModuleConfig.bx | 2 +- src/test/java/TestCases/phase3/ClassTest.java | 2 +- src/test/resources/modules/anotherModule/ModuleConfig.bx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/bx-derby/ModuleConfig.bx b/modules/bx-derby/ModuleConfig.bx index 1b986b205..db1a1d64e 100644 --- a/modules/bx-derby/ModuleConfig.bx +++ b/modules/bx-derby/ModuleConfig.bx @@ -51,7 +51,7 @@ class{ /** * This boolean flag tells the module service to skip the module registration/activation process. */ - this.disabled = false; + this.enabled = true; /** * -------------------------------------------------------------------------- diff --git a/src/test/java/TestCases/phase3/ClassTest.java b/src/test/java/TestCases/phase3/ClassTest.java index b7d38f9a6..d24377f4a 100644 --- a/src/test/java/TestCases/phase3/ClassTest.java +++ b/src/test/java/TestCases/phase3/ClassTest.java @@ -129,7 +129,7 @@ void testVanillaModuleConfig() { /** * This boolean flag tells the module service to skip the module registration/activation process. */ - this.disabled = false; + this.enabled = true; /** * -------------------------------------------------------------------------- diff --git a/src/test/resources/modules/anotherModule/ModuleConfig.bx b/src/test/resources/modules/anotherModule/ModuleConfig.bx index 003269615..b01ecd361 100644 --- a/src/test/resources/modules/anotherModule/ModuleConfig.bx +++ b/src/test/resources/modules/anotherModule/ModuleConfig.bx @@ -51,7 +51,7 @@ class { /** * This boolean flag tells the module service to skip the module registration/activation process. */ - this.disabled = false; + this.enabled = true; /** * -------------------------------------------------------------------------- From f1f246e8e8123c0c1de8973a49d81a11079ba7af Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 4 Dec 2024 18:49:09 -0600 Subject: [PATCH 056/193] return the task record as a struct --- .../runtime/async/tasks/TaskRecord.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/main/java/ortus/boxlang/runtime/async/tasks/TaskRecord.java b/src/main/java/ortus/boxlang/runtime/async/tasks/TaskRecord.java index c34d5c6dc..b101bbf55 100644 --- a/src/main/java/ortus/boxlang/runtime/async/tasks/TaskRecord.java +++ b/src/main/java/ortus/boxlang/runtime/async/tasks/TaskRecord.java @@ -20,6 +20,9 @@ import java.time.LocalDateTime; import java.util.concurrent.ScheduledFuture; +import ortus.boxlang.runtime.types.IStruct; +import ortus.boxlang.runtime.types.Struct; + /** * The task record holds all the information of a living task in the scheduler. */ @@ -87,4 +90,24 @@ public TaskRecord( String name, String group, ScheduledTask task ) { this.task = task; this.registeredAt = LocalDateTime.now( task.getTimezone() ); } + + /** + * Return the record as a struct + */ + public IStruct asStruct() { + return Struct.of( + "name", name, + "group", group, + "task", task, + "future", future, + "scheduledAt", scheduledAt, + "registeredAt", registeredAt, + "disabled", disabled, + "error", error, + "errorMessage", errorMessage, + "stacktrace", stacktrace, + "inetHost", inetHost, + "localIp", localIp + ); + } } From 05b21f4a5107e93920968168e94edf2f034638b3 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 4 Dec 2024 18:57:52 -0600 Subject: [PATCH 057/193] BL-699 Schedulers now have a startupTask( task ) to dynamically startup a task by name or task object --- .../runtime/async/tasks/BaseScheduler.java | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/async/tasks/BaseScheduler.java b/src/main/java/ortus/boxlang/runtime/async/tasks/BaseScheduler.java index f21196bc5..42c6c0c8e 100644 --- a/src/main/java/ortus/boxlang/runtime/async/tasks/BaseScheduler.java +++ b/src/main/java/ortus/boxlang/runtime/async/tasks/BaseScheduler.java @@ -236,7 +236,7 @@ public synchronized BaseScheduler startup() { // Iterate over tasks and send them off for scheduling this.tasks.entrySet() .parallelStream() - .forEachOrdered( entry -> startupTask( entry.getKey(), entry.getValue() ) ); + .forEachOrdered( entry -> startupTask( entry.getKey() ) ); // Mark scheduler as started this.started = true; @@ -284,13 +284,27 @@ public synchronized BaseScheduler clearTasks() { return this; } + /** + * Startup manullay the passed in task + * + * @param task The task to startup + * + * @return The task record + */ + public TaskRecord startupTask( ScheduledTask task ) { + return startupTask( task.getName() ); + } + /** * Startup a specific task by name * - * @param taskName The name of the task - * @param taskRecord The task record object + * @param taskName The name of the task + * + * @return The task record */ - private void startupTask( String taskName, TaskRecord taskRecord ) { + public TaskRecord startupTask( String taskName ) { + // Get the task record + var taskRecord = getTaskRecord( taskName ); // Verify we can start it up the task or not if ( taskRecord.task.isDisabled() ) { taskRecord.disabled = true; @@ -299,8 +313,7 @@ private void startupTask( String taskName, TaskRecord taskRecord ) { this.name, taskName ); - // Continue iteration - return; + return taskRecord; } else { // Log scheduling startup logger.info( @@ -310,6 +323,16 @@ private void startupTask( String taskName, TaskRecord taskRecord ) { ); } + // Verify that the task record: scheduledAt is null + if ( taskRecord.scheduledAt != null ) { + logger.warn( + "- Scheduler ({}) skipping task ({}) as it has already been scheduled.", + this.name, + taskName + ); + return taskRecord; + } + // Send it off for scheduling try { taskRecord.future = taskRecord.task.start(); @@ -330,6 +353,8 @@ private void startupTask( String taskName, TaskRecord taskRecord ) { taskRecord.errorMessage = e.getMessage(); taskRecord.stacktrace = Arrays.toString( e.getStackTrace() ); } + + return taskRecord; } /** From 65499fa73628a5161fa43a49d6d438a5dc4c0bd7 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 4 Dec 2024 19:02:49 -0600 Subject: [PATCH 058/193] regression modules loading --- .../java/ortus/boxlang/runtime/services/ModuleService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/services/ModuleService.java b/src/main/java/ortus/boxlang/runtime/services/ModuleService.java index 7a2481c48..d2cd46391 100644 --- a/src/main/java/ortus/boxlang/runtime/services/ModuleService.java +++ b/src/main/java/ortus/boxlang/runtime/services/ModuleService.java @@ -250,7 +250,7 @@ void register( Key name ) { moduleRecord.loadDescriptor( runtimeContext ); // Check if the module is disabled, if so, skip it - if ( moduleRecord.isEnabled() ) { + if ( !moduleRecord.isEnabled() ) { logger.warn( "+ Module Service: Module [{}] is disabled, skipping registration", moduleRecord.name @@ -342,7 +342,7 @@ void activate( Key name ) { } // Check if the module is disabled - if ( this.registry.get( name ).isEnabled() ) { + if ( !this.registry.get( name ).isEnabled() ) { logger.debug( "+ Module Service: Module [{}] is disabled, skipping activation", name From c65b6b7c36c44a1da2addc2c489e9b64723ccaba Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 4 Dec 2024 21:31:29 -0600 Subject: [PATCH 059/193] leverage the new getLogger( name) method --- .../runtime/logging/LoggingService.java | 68 ++++++------------- 1 file changed, 20 insertions(+), 48 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index 0eabfb666..38fb4bd6f 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -409,30 +409,9 @@ public LoggingService logMessage( if ( logger.isEmpty() ) { logger = DEFAULT_LOG_FILE; } - // Verify the log file ends in `.log` and if not, append it - if ( !logger.toLowerCase().endsWith( ".log" ) ) { - logger += ".log"; - } - // If the file is an absolute path, use it, otherwise use the logs directory as the base - // All logger names should be lowercase - String loggerFilePath = Path.of( logger ).isAbsolute() - ? Path.of( logger ).normalize().toString() - : Paths.get( getLogsDirectory(), "/", logger.toLowerCase() ).normalize().toString(); - String loggerName = FilenameUtils.getBaseName( loggerFilePath.toLowerCase() ); - - // Get the logger and set the level - LoggerContext targetContext = getLoggerContext(); - Logger oLogger = targetContext.getLogger( loggerName ); - oLogger.setLevel( Level.TRACE ); - FileAppender appender = getOrBuildAppender( loggerFilePath, targetContext ); - - // Create or compute the file appender requested - // This provides locking also and caching so we don't have to keep creating them - // Shutdown will stop the appenders - if ( !oLogger.isAttached( appender ) ) { - oLogger.addAppender( appender ); - } + // Compute and get the logger + Logger oLogger = getLogger( logger ); // Log according to the level switch ( targetLogLevel.getNameNoCase() ) { @@ -454,40 +433,33 @@ public LoggingService logMessage( * If the logger doesn't exist, it will auto-register it and load it * using the name as the file name in the logs directory. * - * @param loggerName The name of the logger + * @param logger The name of the logger to retrieve. * * @return The logger requested */ - public Logger getLogger( Key loggerName ) { - // Is it regsitered already? - if ( this.loggersMap.containsKey( loggerName ) ) { - return ( Logger ) this.loggersMap.get( loggerName ); + public Logger getLogger( String logger ) { + // The incoming logger can be: + // 1. A named logger: "scheduler", "application", "orm", etc + // 2. A relative path: "scheduler.log", "application.log", "orm.log" + // 3. An absolute path: "/var/log/boxlang/scheduler.log" + + // Make sure it ends in .log + if ( !logger.endsWith( ".log" ) ) { + logger = logger + ".log"; } - // Compute it - return getLogger( - loggerName, - Paths.get( getLogsDirectory(), "/", loggerName.getNameNoCase() ).normalize().toString() - ); - } + // If the file is an absolute path, use it, otherwise use the logs directory as the base + String loggerFilePath = Path.of( logger ).normalize().isAbsolute() + ? Path.of( logger ).normalize().toString() + : Paths.get( getLogsDirectory(), logger.toLowerCase() ).normalize().toString(); + Key loggerKey = Key.of( FilenameUtils.getBaseName( loggerFilePath.toLowerCase() ) ); - /** - * Get a logger by registered name. - * If the logger doesn't exist, it will auto-register it and load it - * using the name as the file name in the logs directory. - * - * @param loggerName The name of the logger - * @param filePath The file path to the logger. If any, this can be null - * - * @return The logger requested - */ - public Logger getLogger( Key loggerName, String filePath ) { - // Comput if absent and return the logger - return ( Logger ) this.loggersMap.computeIfAbsent( loggerName, key -> { + // Compute it or return it + return ( Logger ) this.loggersMap.computeIfAbsent( loggerKey, key -> { LoggerContext targetContext = getLoggerContext(); Logger oLogger = targetContext.getLogger( key.getNameNoCase() ); oLogger.setLevel( Level.TRACE ); - oLogger.addAppender( getOrBuildAppender( filePath, targetContext ) ); + oLogger.addAppender( getOrBuildAppender( loggerFilePath, targetContext ) ); oLogger.setAdditive( true ); return oLogger; } ); From d2cbe603693067fefef4ef1497d5c8c684f0c439 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 4 Dec 2024 21:34:34 -0600 Subject: [PATCH 060/193] more refactoring to use getLogger() --- .../runtime/logging/LoggingService.java | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index 38fb4bd6f..bdabfe750 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -455,14 +455,24 @@ public Logger getLogger( String logger ) { Key loggerKey = Key.of( FilenameUtils.getBaseName( loggerFilePath.toLowerCase() ) ); // Compute it or return it - return ( Logger ) this.loggersMap.computeIfAbsent( loggerKey, key -> { - LoggerContext targetContext = getLoggerContext(); - Logger oLogger = targetContext.getLogger( key.getNameNoCase() ); - oLogger.setLevel( Level.TRACE ); - oLogger.addAppender( getOrBuildAppender( loggerFilePath, targetContext ) ); - oLogger.setAdditive( true ); - return oLogger; - } ); + return ( Logger ) this.loggersMap.computeIfAbsent( loggerKey, key -> createLogger( loggerKey, loggerFilePath ) ); + } + + /** + * Build a logger with the specified name and file path + * + * @param loggerKey The key of the logger to build + * @param loggerFilePath The file path to log to + * + * @return The logger requested + */ + private Logger createLogger( Key loggerKey, String loggerFilePath ) { + LoggerContext targetContext = getLoggerContext(); + Logger oLogger = targetContext.getLogger( loggerKey.getNameNoCase() ); + oLogger.setLevel( Level.TRACE ); + oLogger.setAdditive( true ); + oLogger.addAppender( getOrBuildAppender( loggerFilePath, targetContext ) ); + return oLogger; } /** From e3050c646810bcc0f51a844d600bf1d42e3a8554 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 4 Dec 2024 21:55:37 -0600 Subject: [PATCH 061/193] dynamic loggers now created --- .../runtime/config/segments/LoggerConfig.java | 12 +++++- .../runtime/logging/LoggingService.java | 43 +++++++++++-------- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/config/segments/LoggerConfig.java b/src/main/java/ortus/boxlang/runtime/config/segments/LoggerConfig.java index a85188357..1949ce72f 100644 --- a/src/main/java/ortus/boxlang/runtime/config/segments/LoggerConfig.java +++ b/src/main/java/ortus/boxlang/runtime/config/segments/LoggerConfig.java @@ -81,7 +81,17 @@ public class LoggerConfig implements IConfigSegment { * @param loggingConfig The logging configuration */ public LoggerConfig( String name, LoggingConfig loggingConfig ) { - this.name = new Key( name ); + this( new Key( name ), loggingConfig ); + } + + /** + * Constructor + * + * @param name The name of the logger + * @param loggingConfig The logging configuration + */ + public LoggerConfig( Key name, LoggingConfig loggingConfig ) { + this.name = name; this.loggingConfig = loggingConfig; } diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index bdabfe750..c0bb4e04c 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -44,6 +44,7 @@ import ch.qos.logback.core.util.FileSize; import ch.qos.logback.core.util.StatusPrinter; import ortus.boxlang.runtime.BoxRuntime; +import ortus.boxlang.runtime.config.segments.LoggerConfig; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.Struct; @@ -458,23 +459,6 @@ public Logger getLogger( String logger ) { return ( Logger ) this.loggersMap.computeIfAbsent( loggerKey, key -> createLogger( loggerKey, loggerFilePath ) ); } - /** - * Build a logger with the specified name and file path - * - * @param loggerKey The key of the logger to build - * @param loggerFilePath The file path to log to - * - * @return The logger requested - */ - private Logger createLogger( Key loggerKey, String loggerFilePath ) { - LoggerContext targetContext = getLoggerContext(); - Logger oLogger = targetContext.getLogger( loggerKey.getNameNoCase() ); - oLogger.setLevel( Level.TRACE ); - oLogger.setAdditive( true ); - oLogger.addAppender( getOrBuildAppender( loggerFilePath, targetContext ) ); - return oLogger; - } - /** * Verify if a logger with the specified name exists * @@ -653,4 +637,29 @@ private JsonEncoder buildJsonEncoder() { return targetEncoder; } + /** + * Build a logger with the specified name and file path + * + * @param loggerKey The key of the logger to build + * @param loggerFilePath The file path to log to + * + * @return The logger requested + */ + private Logger createLogger( Key loggerKey, String loggerFilePath ) { + LoggerContext targetContext = getLoggerContext(); + Logger oLogger = targetContext.getLogger( loggerKey.getNameNoCase() ); + + // Check if we have the logger configuration or else build a vanilla one + LoggerConfig loggerConfig = ( LoggerConfig ) this.runtime + .getConfiguration().logging.loggers + .computeIfAbsent( loggerKey, key -> new LoggerConfig( key.getNameNoCase(), this.runtime.getConfiguration().logging ) ); + + Level targetLevel = Level.toLevel( LogLevel.valueOf( loggerConfig.level.getName(), false ).getName() ); + + oLogger.setLevel( targetLevel ); + oLogger.setAdditive( loggerConfig.additive ); + oLogger.addAppender( getOrBuildAppender( loggerFilePath, targetContext ) ); + return oLogger; + } + } From 7fafa028faa0b126af487d572ce1489db31ffce0 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 4 Dec 2024 21:56:52 -0600 Subject: [PATCH 062/193] more wip on config based appenders --- .../ortus/boxlang/runtime/logging/LoggingService.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index c0bb4e04c..61b29a853 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -524,13 +524,13 @@ public boolean removeLogger( Key loggerName ) { /** * Get the requested file appender according to log location * - * @param filePath The file path to get the appender for - * @param logContext The logger context requested for the appender - * @param logger The logger to add the appender to + * @param filePath The file path to get the appender for + * @param logContext The logger context requested for the appender + * @param loggerConfig The logger configuration * * @return The file appender, computed or from cache */ - public FileAppender getOrBuildAppender( String filePath, LoggerContext logContext ) { + public FileAppender getOrBuildAppender( String filePath, LoggerContext logContext, LoggerConfig loggerConfig ) { return ( FileAppender ) this.appendersMap.computeIfAbsent( filePath.toLowerCase(), key -> { var appender = new RollingFileAppender(); String fileName = FilenameUtils.getBaseName( filePath ); @@ -658,7 +658,7 @@ private Logger createLogger( Key loggerKey, String loggerFilePath ) { oLogger.setLevel( targetLevel ); oLogger.setAdditive( loggerConfig.additive ); - oLogger.addAppender( getOrBuildAppender( loggerFilePath, targetContext ) ); + oLogger.addAppender( getOrBuildAppender( loggerFilePath, targetContext, loggerConfig ) ); return oLogger; } From 5d1a1b6994316d5a552e734eae68cbf614971b4a Mon Sep 17 00:00:00 2001 From: lmajano Date: Thu, 5 Dec 2024 03:57:50 +0000 Subject: [PATCH 063/193] Apply cfformat changes --- src/main/java/ortus/boxlang/runtime/logging/LoggingService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index 61b29a853..54d5a2d2a 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -652,7 +652,7 @@ private Logger createLogger( Key loggerKey, String loggerFilePath ) { // Check if we have the logger configuration or else build a vanilla one LoggerConfig loggerConfig = ( LoggerConfig ) this.runtime .getConfiguration().logging.loggers - .computeIfAbsent( loggerKey, key -> new LoggerConfig( key.getNameNoCase(), this.runtime.getConfiguration().logging ) ); + .computeIfAbsent( loggerKey, key -> new LoggerConfig( key.getNameNoCase(), this.runtime.getConfiguration().logging ) ); Level targetLevel = Level.toLevel( LogLevel.valueOf( loggerConfig.level.getName(), false ).getName() ); From 4f4f1443c5394242e6fc149de9faddeab7c0c6a3 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Wed, 4 Dec 2024 22:02:49 -0600 Subject: [PATCH 064/193] Add two more keys for client scope --- src/main/java/ortus/boxlang/runtime/scopes/Key.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/ortus/boxlang/runtime/scopes/Key.java b/src/main/java/ortus/boxlang/runtime/scopes/Key.java index ba62aa89f..732d6d0e5 100644 --- a/src/main/java/ortus/boxlang/runtime/scopes/Key.java +++ b/src/main/java/ortus/boxlang/runtime/scopes/Key.java @@ -429,6 +429,7 @@ public class Key implements Comparable, Serializable { public static final Key limit = Key.of( "limit" ); public static final Key line = Key.of( "line" ); public static final Key list = Key.of( "list" ); + public static final Key listener = Key.of( "listener" ); public static final Key listInfo = Key.of( "listInfo" ); public static final Key listToJSON = Key.of( "listToJSON" ); public static final Key lJustify = Key.of( "lJustify" ); @@ -806,6 +807,7 @@ public class Key implements Comparable, Serializable { public static final Key cacheKey = Key.of( "cacheKey" ); public static final Key cacheRegion = Key.of( "cacheRegion" ); public static final Key clientInfo = Key.of( "clientInfo" ); + public static final Key clientManagement = Key.of( "clientManagement" ); public static final Key connectionString = Key.of( "connectionString" ); public static final Key database = Key.of( "database" ); public static final Key dbtype = Key.of( "dbtype" ); From d59742ae41f10747f323b25d8ae2fdda5fe63a62 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Wed, 4 Dec 2024 23:21:18 -0600 Subject: [PATCH 065/193] Only set values with param when needed --- .../runtime/components/system/Param.java | 5 ++-- .../runtime/components/system/ParamTest.java | 24 +++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/components/system/Param.java b/src/main/java/ortus/boxlang/runtime/components/system/Param.java index 70f10a4aa..ea3001747 100644 --- a/src/main/java/ortus/boxlang/runtime/components/system/Param.java +++ b/src/main/java/ortus/boxlang/runtime/components/system/Param.java @@ -22,11 +22,11 @@ import ortus.boxlang.runtime.components.Attribute; import ortus.boxlang.runtime.components.BoxComponent; import ortus.boxlang.runtime.components.Component; -import ortus.boxlang.runtime.validation.Validator; import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.dynamic.ExpressionInterpreter; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.IStruct; +import ortus.boxlang.runtime.validation.Validator; @BoxComponent public class Param extends Component { @@ -75,14 +75,13 @@ public BodyResult _invoke( IBoxContext context, IStruct attributes, ComponentBod Object defaultValue = attributes.get( Key._DEFAULT ); Object existingValue = ExpressionInterpreter.getVariable( context, varName, defaultValue != null ); if ( existingValue == null && defaultValue != null ) { - existingValue = defaultValue; + ExpressionInterpreter.setVariable( context, varName, defaultValue ); } // TODO: Enforce validation here // BL types can be passed to GenericCaster // Other type delegate to isValid() - ExpressionInterpreter.setVariable( context, varName, existingValue ); return DEFAULT_RETURN; } } diff --git a/src/test/java/ortus/boxlang/runtime/components/system/ParamTest.java b/src/test/java/ortus/boxlang/runtime/components/system/ParamTest.java index 69bc052c4..b81b2b6c5 100644 --- a/src/test/java/ortus/boxlang/runtime/components/system/ParamTest.java +++ b/src/test/java/ortus/boxlang/runtime/components/system/ParamTest.java @@ -33,6 +33,7 @@ import ortus.boxlang.runtime.scopes.IScope; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.scopes.VariablesScope; +import ortus.boxlang.runtime.types.Struct; public class ParamTest { @@ -179,4 +180,27 @@ public void testCanParamScriptShortcutWithTypeOnly() { assertThat( variables.getAsString( result ) ).isEqualTo( "foo" ); } + @DisplayName( "It can param a struct to a default value" ) + @Test + public void testParamStructOnMissing() { + instance.executeSource( + """ + param variables.result = {}; + """, + context ); + assertThat( variables.getAsStruct( result ) ).isEqualTo( Struct.of() ); + } + + @DisplayName( "It skips paraming a struct with values" ) + @Test + public void testParamStructWithValues() { + instance.executeSource( + """ + variables.result = { "foo": "bar" }; + param variables.result = {}; + """, + context ); + assertThat( variables.getAsStruct( result ) ).isEqualTo( Struct.of( "foo", "bar" ) ); + } + } From e6af6881cbcad790eb2b41f64d60f486cc420417 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Wed, 4 Dec 2024 23:21:35 -0600 Subject: [PATCH 066/193] Shortcut setting a scope equal to itself --- src/main/java/ortus/boxlang/runtime/dynamic/Referencer.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/ortus/boxlang/runtime/dynamic/Referencer.java b/src/main/java/ortus/boxlang/runtime/dynamic/Referencer.java index 4bdc3e1b3..ba4684e40 100644 --- a/src/main/java/ortus/boxlang/runtime/dynamic/Referencer.java +++ b/src/main/java/ortus/boxlang/runtime/dynamic/Referencer.java @@ -206,6 +206,11 @@ public static Object setDeep( IBoxContext context, boolean isFinal, Key mustBeSc // Catch the case where we're assigning an actual scope // arguments = someStruct if ( object instanceof IScope os && keys.length == 0 ) { + // if we are setting a scope equal to itself, just return it. + if ( os == value ) { + return value; + } + CastAttempt castedStruct = StructCaster.attempt( value ); if ( castedStruct.wasSuccessful() ) { os.clear(); From e7cc6b9795f454f389234593144d870011f8e1b0 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 5 Dec 2024 01:09:19 -0600 Subject: [PATCH 067/193] BL-239 WIP --- src/main/antlr/SQLGrammar.g4 | 28 +- src/main/antlr/SQLLexer.g4 | 3 +- .../compiler/ast/sql/SQLStatement.java | 2 +- .../compiler/ast/sql/select/SQLJoin.java | 18 +- .../ast/sql/select/SQLResultColumn.java | 72 +- .../compiler/ast/sql/select/SQLSelect.java | 106 ++- .../ast/sql/select/SQLSelectStatement.java | 52 +- .../compiler/ast/sql/select/SQLTable.java | 48 +- .../ast/sql/select/expression/SQLColumn.java | 40 +- .../select/expression/SQLCountFunction.java | 23 +- .../sql/select/expression/SQLExpression.java | 22 +- .../sql/select/expression/SQLFunction.java | 31 +- .../ast/sql/select/expression/SQLOrderBy.java | 16 +- .../sql/select/expression/SQLParenthesis.java | 29 +- .../select/expression/SQLStarExpression.java | 25 +- .../expression/literal/SQLBooleanLiteral.java | 21 +- .../expression/literal/SQLNullLiteral.java | 20 +- .../expression/literal/SQLNumberLiteral.java | 29 +- .../expression/literal/SQLStringLiteral.java | 29 +- .../operation/SQLBetweenOperation.java | 24 +- .../operation/SQLBinaryOperation.java | 50 +- .../expression/operation/SQLInOperation.java | 23 +- .../operation/SQLUnaryOperation.java | 37 +- .../compiler/parser/AbstractParser.java | 9 +- .../boxlang/compiler/parser/SQLParser.java | 30 +- .../compiler/toolchain/SQLVisitor.java | 287 ++++++- .../bifs/global/jdbc/QueryExecute.java | 35 +- .../runtime/components/jdbc/Query.java | 21 +- .../boxlang/runtime/jdbc/ExecutedQuery.java | 13 +- .../boxlang/runtime/jdbc/PendingQuery.java | 1 - .../boxlang/runtime/jdbc/QueryOptions.java | 27 +- .../runtime/jdbc/qoq/QoQConnection.java | 262 ++++++ .../runtime/jdbc/qoq/QoQResultSet.java | 800 ++++++++++++++++++ .../boxlang/runtime/jdbc/qoq/QoQService.java | 166 ++++ .../runtime/jdbc/qoq/QoQStatement.java | 217 +++++ .../runtime/types/QueryColumnType.java | 11 + .../ortus/boxlang/compiler/QoQParseTest.java | 96 +++ 37 files changed, 2610 insertions(+), 113 deletions(-) create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQConnection.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQResultSet.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQService.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQStatement.java create mode 100644 src/test/java/ortus/boxlang/compiler/QoQParseTest.java diff --git a/src/main/antlr/SQLGrammar.g4 b/src/main/antlr/SQLGrammar.g4 index 632486f81..fef3f3d91 100644 --- a/src/main/antlr/SQLGrammar.g4 +++ b/src/main/antlr/SQLGrammar.g4 @@ -340,25 +340,33 @@ reindex_stmt: //select_stmt: common_table_stmt? select_core (compound_operator select_core)* order_by_stmt? limit_stmt?; select_stmt: - select_core (UNION_ ALL_? select_core)* order_by_stmt? limit_stmt? + select_core (union)* order_by_stmt? limit_stmt? +; + +union: + UNION_ ALL_? select_core ; join_clause: table (join_operator table join_constraint?)* ; -select_core: ( - SELECT_ (DISTINCT_ /*| ALL_*/)? result_column (COMMA result_column)* ( - FROM_ (table (COMMA table)* | join_clause) - )? (WHERE_ whereExpr = expr)? ( - GROUP_ BY_ groupByExpr += expr (COMMA groupByExpr += expr)* ( - HAVING_ havingExpr = expr - )? - )? //(WINDOW_ window_name AS_ window_defn ( COMMA window_name AS_ window_defn)*)? - ) +select_core: + SELECT_ top? (DISTINCT_ /*| ALL_*/)? result_column (COMMA result_column)* ( + FROM_ (table (COMMA table)* | join_clause) + )? (WHERE_ whereExpr = expr)? ( + GROUP_ BY_ groupByExpr += expr (COMMA groupByExpr += expr)* ( + HAVING_ havingExpr = expr + )? + )? limit_stmt? + //(WINDOW_ window_name AS_ window_defn ( COMMA window_name AS_ window_defn)*)? // | values_clause ; +top: + TOP NUMERIC_LITERAL +; + factored_select_stmt: select_stmt ; diff --git a/src/main/antlr/SQLLexer.g4 b/src/main/antlr/SQLLexer.g4 index f58aa3c77..74ef24c58 100644 --- a/src/main/antlr/SQLLexer.g4 +++ b/src/main/antlr/SQLLexer.g4 @@ -149,6 +149,7 @@ TEMP_: 'TEMP'; TEMPORARY_: 'TEMPORARY'; THEN_: 'THEN'; TO_: 'TO'; +TOP: 'TOP'; TRANSACTION_: 'TRANSACTION'; TRIGGER_: 'TRIGGER'; UNION_: 'UNION'; @@ -207,7 +208,7 @@ IDENTIFIER: NUMERIC_LITERAL: ((DIGIT+ ('.' DIGIT*)?) | ('.' DIGIT+)) ('E' [-+]? DIGIT+)? | '0x' HEX_DIGIT+; -BIND_PARAMETER: '?' DIGIT* | [:@$] IDENTIFIER; +BIND_PARAMETER: '?' | ':' IDENTIFIER; STRING_LITERAL: '\'' ( ~'\'' | '\'\'')* '\''; diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/SQLStatement.java b/src/main/java/ortus/boxlang/compiler/ast/sql/SQLStatement.java index 873d75a0e..7ea0fb740 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/SQLStatement.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/SQLStatement.java @@ -28,7 +28,7 @@ public abstract class SQLStatement extends SQLNode { * @param position position of the statement in the source code * @param sourceText source code of the statement */ - protected SQLStatement( Position position, String sourceText ) { + public SQLStatement( Position position, String sourceText ) { super( position, sourceText ); } } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLJoin.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLJoin.java index b89c85107..8eed69023 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLJoin.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLJoin.java @@ -14,6 +14,8 @@ */ package ortus.boxlang.compiler.ast.sql.select; +import java.util.Map; + import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; import ortus.boxlang.compiler.ast.sql.SQLNode; @@ -39,7 +41,7 @@ public class SQLJoin extends SQLNode { * @param position position of the statement in the source code * @param sourceText source code of the statement */ - protected SQLJoin( SQLJoinType type, SQLTable table, SQLExpression on, Position position, String sourceText ) { + public SQLJoin( SQLJoinType type, SQLTable table, SQLExpression on, Position position, String sourceText ) { super( position, sourceText ); } @@ -103,4 +105,18 @@ public BoxNode accept( ReplacingBoxVisitor v ) { // TODO Auto-generated method stub throw new UnsupportedOperationException( "Unimplemented method 'accept'" ); } + + @Override + public Map toMap() { + Map map = super.toMap(); + + map.put( "type", enumToMap( type ) ); + map.put( "table", table.toMap() ); + if ( on != null ) { + map.put( "on", on.toMap() ); + } else { + map.put( "on", null ); + } + return map; + } } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLResultColumn.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLResultColumn.java index f2f986ca5..4e5e4c1b6 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLResultColumn.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLResultColumn.java @@ -14,20 +14,28 @@ */ package ortus.boxlang.compiler.ast.sql.select; +import java.util.Map; + import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; import ortus.boxlang.compiler.ast.sql.SQLNode; +import ortus.boxlang.compiler.ast.sql.select.expression.SQLColumn; +import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; +import ortus.boxlang.compiler.ast.sql.select.expression.SQLStarExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.scopes.Key; /** * Abstract Node class representing SQL result column declaration */ public class SQLResultColumn extends SQLNode { - private SQLNode expression; + private SQLExpression expression; + + private Key alias; - private String alias; + private int ordinalPosition; /** * Constructor @@ -35,23 +43,24 @@ public class SQLResultColumn extends SQLNode { * @param position position of the statement in the source code * @param sourceText source code of the statement */ - protected SQLResultColumn( SQLNode expression, String alias, Position position, String sourceText ) { + public SQLResultColumn( SQLExpression expression, String alias, int ordinalPosition, Position position, String sourceText ) { super( position, sourceText ); setExpression( expression ); setAlias( alias ); + setOrdinalPosition( ordinalPosition ); } /** * Get the expression */ - public SQLNode getExpression() { + public SQLExpression getExpression() { return expression; } /** * Set the expression */ - public void setExpression( SQLNode expression ) { + public void setExpression( SQLExpression expression ) { replaceChildren( this.expression, expression ); this.expression = expression; this.expression.setParent( this ); @@ -60,7 +69,7 @@ public void setExpression( SQLNode expression ) { /** * Get the table alias */ - public String getAlias() { + public Key getAlias() { return alias; } @@ -68,7 +77,42 @@ public String getAlias() { * Set the table alias */ public void setAlias( String alias ) { - this.alias = alias; + this.alias = ( alias == null ) ? null : Key.of( alias ); + } + + /** + * Get the ordinal position + */ + public int getOrdinalPosition() { + return ordinalPosition; + } + + /** + * Set the ordinal position + */ + public void setOrdinalPosition( int ordinalPosition ) { + this.ordinalPosition = ordinalPosition; + } + + /** + * The name this result column will have in the final result set. This is either the alias or the column name. + * If it's any other expression, we name it column_0, column_1, column_2, etc based on the ordinal position in the overall result set. + */ + public Key getResultColumnName() { + if ( alias != null ) { + return alias; + } else if ( expression instanceof SQLColumn c ) { + return c.getName(); + } else { + return Key.of( "column_" + ( ordinalPosition - 1 ) ); + } + } + + /** + * Is this column a star column? + */ + public boolean isStarExpression() { + return expression instanceof SQLStarExpression; } @Override @@ -82,4 +126,18 @@ public BoxNode accept( ReplacingBoxVisitor v ) { // TODO Auto-generated method stub throw new UnsupportedOperationException( "Unimplemented method 'accept'" ); } + + @Override + public Map toMap() { + Map map = super.toMap(); + + map.put( "expression", expression.toMap() ); + if ( alias != null ) { + map.put( "alias", alias ); + } else { + map.put( "alias", null ); + } + return map; + } + } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLSelect.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLSelect.java index 82fe54f13..89c12fa38 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLSelect.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLSelect.java @@ -15,11 +15,13 @@ package ortus.boxlang.compiler.ast.sql.select; import java.util.List; +import java.util.Map; import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; import ortus.boxlang.compiler.ast.sql.SQLNode; import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; +import ortus.boxlang.compiler.ast.sql.select.expression.literal.SQLNumberLiteral; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; @@ -43,15 +45,16 @@ public class SQLSelect extends SQLNode { private SQLExpression having; + private SQLNumberLiteral limit; + /** * Constructor * * @param position position of the statement in the source code * @param sourceText source code of the statement */ - protected SQLSelect( boolean distinct, List resultColumns, SQLTable table, List joins, SQLExpression where, - List groupBys, - SQLExpression having, Position position, String sourceText ) { + public SQLSelect( boolean distinct, List resultColumns, SQLTable table, List joins, SQLExpression where, + List groupBys, SQLExpression having, SQLNumberLiteral limit, Position position, String sourceText ) { super( position, sourceText ); setDistinct( distinct ); setResultColumns( resultColumns ); @@ -60,6 +63,7 @@ protected SQLSelect( boolean distinct, List resultColumns, SQLT setWhere( where ); setGroupBys( groupBys ); setHaving( having ); + setLimit( limit ); } /** @@ -75,7 +79,13 @@ public void setDistinct( boolean distinct ) { public void setResultColumns( List resultColumns ) { replaceChildren( this.resultColumns, resultColumns ); this.resultColumns = resultColumns; - resultColumns.forEach( c -> c.setParent( this ) ); + if ( resultColumns != null ) { + for ( int i = 0; i < resultColumns.size(); i++ ) { + SQLResultColumn column = resultColumns.get( i ); + column.setParent( this ); + column.setOrdinalPosition( i + 1 ); + } + } } /** @@ -84,7 +94,9 @@ public void setResultColumns( List resultColumns ) { public void setTable( SQLTable table ) { replaceChildren( this.table, table ); this.table = table; - table.setParent( this ); + if ( table != null ) { + table.setParent( this ); + } } /** @@ -93,19 +105,23 @@ public void setTable( SQLTable table ) { public void setJoins( List joins ) { replaceChildren( this.joins, joins ); this.joins = joins; - joins.forEach( j -> j.setParent( this ) ); + if ( joins != null ) { + joins.forEach( j -> j.setParent( this ) ); + } } /** * Set the WHERE node */ public void setWhere( SQLExpression where ) { - if ( !where.isBoolean() ) { + if ( where != null && !where.isBoolean() ) { throw new BoxRuntimeException( "WHERE clause must be a boolean expression" ); } replaceChildren( this.where, where ); this.where = where; - where.setParent( this ); + if ( where != null ) { + where.setParent( this ); + } } /** @@ -114,19 +130,23 @@ public void setWhere( SQLExpression where ) { public void setGroupBys( List groupBys ) { replaceChildren( this.groupBys, groupBys ); this.groupBys = groupBys; - groupBys.forEach( g -> g.setParent( this ) ); + if ( groupBys != null ) { + groupBys.forEach( g -> g.setParent( this ) ); + } } /** * Set the HAVING node */ public void setHaving( SQLExpression having ) { - if ( !having.isBoolean() ) { + if ( having != null && !having.isBoolean() ) { throw new BoxRuntimeException( "HAVING clause must be a boolean expression" ); } replaceChildren( this.having, having ); this.having = having; - having.setParent( this ); + if ( having != null ) { + having.setParent( this ); + } } /** @@ -178,6 +198,24 @@ public SQLExpression getHaving() { return having; } + /** + * Set the LIMIT node + */ + public void setLimit( SQLNumberLiteral limit ) { + replaceChildren( this.limit, limit ); + this.limit = limit; + if ( limit != null ) { + limit.setParent( this ); + } + } + + /** + * Get the LIMIT node + */ + public SQLNumberLiteral getLimit() { + return limit; + } + @Override public void accept( VoidBoxVisitor v ) { // TODO Auto-generated method stub @@ -189,4 +227,50 @@ public BoxNode accept( ReplacingBoxVisitor v ) { // TODO Auto-generated method stub throw new UnsupportedOperationException( "Unimplemented method 'accept'" ); } + + @Override + public Map toMap() { + Map map = super.toMap(); + + if ( distinct ) { + map.put( "distinct", distinct ); + } else { + map.put( "distinct", null ); + } + + map.put( "resultColumns", resultColumns.stream().map( SQLResultColumn::toMap ).toList() ); + if ( table != null ) { + map.put( "table", table.toMap() ); + } else { + map.put( "table", null ); + } + if ( joins != null ) { + map.put( "joins", joins.stream().map( SQLJoin::toMap ).toList() ); + } else { + map.put( "joins", null ); + } + if ( where != null ) { + map.put( "where", where.toMap() ); + } else { + map.put( "where", null ); + } + if ( groupBys != null ) { + map.put( "groupBys", groupBys.stream().map( SQLExpression::toMap ).toList() ); + } else { + map.put( "groupBys", null ); + } + if ( having != null ) { + map.put( "having", having.toMap() ); + } else { + map.put( "having", null ); + } + if ( limit != null ) { + map.put( "limit", limit.toMap() ); + } else { + map.put( "limit", null ); + } + + return map; + } + } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLSelectStatement.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLSelectStatement.java index 89735a1cc..684172035 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLSelectStatement.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLSelectStatement.java @@ -15,6 +15,7 @@ package ortus.boxlang.compiler.ast.sql.select; import java.util.List; +import java.util.Map; import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; @@ -41,7 +42,7 @@ public class SQLSelectStatement extends SQLStatement { * @param position position of the statement in the source code * @param sourceText source code of the statement */ - protected SQLSelectStatement( SQLSelect select, List unions, List orderBys, SQLNumberLiteral limit, Position position, + public SQLSelectStatement( SQLSelect select, List unions, List orderBys, SQLNumberLiteral limit, Position position, String sourceText ) { super( position, sourceText ); setSelect( select ); @@ -56,7 +57,9 @@ protected SQLSelectStatement( SQLSelect select, List unions, List unions ) { replaceChildren( this.unions, unions ); this.unions = unions; - unions.forEach( u -> u.setParent( this ) ); + if ( unions != null ) { + unions.forEach( u -> u.setParent( this ) ); + } } /** @@ -88,14 +93,9 @@ public List getUnions() { public void setOrderBys( List orderBys ) { replaceChildren( this.orderBys, orderBys ); this.orderBys = orderBys; - orderBys.forEach( o -> o.setParent( this ) ); - } - - /** - * Get the ORDER BY nodes - */ - public List getOrderBys() { - return orderBys; + if ( orderBys != null ) { + orderBys.forEach( o -> o.setParent( this ) ); + } } /** @@ -104,7 +104,9 @@ public List getOrderBys() { public void setLimit( SQLNumberLiteral limit ) { replaceChildren( this.limit, limit ); this.limit = limit; - limit.setParent( this ); + if ( limit != null ) { + limit.setParent( this ); + } } /** @@ -114,6 +116,13 @@ public SQLNumberLiteral getLimit() { return limit; } + /** + * Get the ORDER BY nodes + */ + public List getOrderBys() { + return orderBys; + } + @Override public void accept( VoidBoxVisitor v ) { // TODO Auto-generated method stub @@ -125,4 +134,23 @@ public BoxNode accept( ReplacingBoxVisitor v ) { // TODO Auto-generated method stub throw new UnsupportedOperationException( "Unimplemented method 'accept'" ); } + + @Override + public Map toMap() { + Map map = super.toMap(); + + map.put( "select", select.toMap() ); + if ( unions != null ) { + map.put( "unions", unions.stream().map( SQLSelect::toMap ).toList() ); + } else { + map.put( "unions", null ); + } + if ( orderBys != null ) { + map.put( "orderBys", orderBys.stream().map( SQLOrderBy::toMap ).toList() ); + } else { + map.put( "orderBys", null ); + } + return map; + } + } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLTable.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLTable.java index bbd4af586..1fa3f0acd 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLTable.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLTable.java @@ -14,11 +14,14 @@ */ package ortus.boxlang.compiler.ast.sql.select; +import java.util.Map; + import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; import ortus.boxlang.compiler.ast.sql.SQLNode; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.scopes.Key; /** * Abstract Node class representing SQL table declaration @@ -27,9 +30,9 @@ public class SQLTable extends SQLNode { private String schema; - private String name; + private Key name; - private String alias; + private Key alias; /** * Constructor @@ -37,7 +40,7 @@ public class SQLTable extends SQLNode { * @param position position of the statement in the source code * @param sourceText source code of the statement */ - protected SQLTable( String schema, String name, String alias, Position position, String sourceText ) { + public SQLTable( String schema, String name, String alias, Position position, String sourceText ) { super( position, sourceText ); setSchema( schema ); setName( name ); @@ -61,7 +64,7 @@ public void setSchema( String schema ) { /** * Get the table name */ - public String getName() { + public Key getName() { return name; } @@ -69,13 +72,13 @@ public String getName() { * Set the table name */ public void setName( String name ) { - this.name = name; + this.name = Key.of( name ); } /** * Get the table alias */ - public String getAlias() { + public Key getAlias() { return alias; } @@ -83,7 +86,19 @@ public String getAlias() { * Set the table alias */ public void setAlias( String alias ) { - this.alias = alias; + this.alias = ( alias == null ) ? null : Key.of( alias ); + } + + public boolean isCalled( Key name ) { + return this.name.equals( name ) || ( alias != null && alias.equals( name ) ); + } + + public String getVariableName() { + if ( schema != null ) { + return schema + "." + name.getName(); + } else { + return name.getName(); + } } @Override @@ -97,4 +112,23 @@ public BoxNode accept( ReplacingBoxVisitor v ) { // TODO Auto-generated method stub throw new UnsupportedOperationException( "Unimplemented method 'accept'" ); } + + @Override + public Map toMap() { + Map map = super.toMap(); + + if ( schema != null ) { + map.put( "schema", schema ); + } else { + map.put( "schema", null ); + } + map.put( "name", name.getName() ); + if ( alias != null ) { + map.put( "alias", alias.getName() ); + } else { + map.put( "alias", null ); + } + return map; + } + } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLColumn.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLColumn.java index a7f46a34f..d1645d412 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLColumn.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLColumn.java @@ -14,11 +14,16 @@ */ package ortus.boxlang.compiler.ast.sql.select.expression; +import java.util.Map; + import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.Query; +import ortus.boxlang.runtime.types.QueryColumnType; /** * Abstract Node class representing SQL column reference @@ -27,7 +32,7 @@ public class SQLColumn extends SQLExpression { private SQLTable table; - private String name; + private Key name; /** * Constructor @@ -35,7 +40,7 @@ public class SQLColumn extends SQLExpression { * @param position position of the statement in the source code * @param sourceText source code of the statement */ - protected SQLColumn( SQLTable table, String name, Position position, String sourceText ) { + public SQLColumn( SQLTable table, String name, Position position, String sourceText ) { super( position, sourceText ); setName( name ); setTable( table ); @@ -46,7 +51,7 @@ protected SQLColumn( SQLTable table, String name, Position position, String sour * * @return the name of the function */ - public String getName() { + public Key getName() { return name; } @@ -56,7 +61,7 @@ public String getName() { * @param name the name of the function */ public void setName( String name ) { - this.name = name; + this.name = Key.of( name ); } /** @@ -74,6 +79,20 @@ public void setTable( SQLTable table ) { this.table = table; } + /** + * What type does this expression evaluate to + */ + public QueryColumnType getType( Map tableLookup ) { + return tableLookup.get( table ).getColumns().get( name ).getType(); + } + + /** + * Evaluate the expression + */ + public Object evaluate( Map tableLookup, int i ) { + return tableLookup.get( table ).getCell( name, i - 1 ); + } + @Override public void accept( VoidBoxVisitor v ) { // TODO Auto-generated method stub @@ -86,4 +105,17 @@ public BoxNode accept( ReplacingBoxVisitor v ) { throw new UnsupportedOperationException( "Unimplemented method 'accept'" ); } + @Override + public Map toMap() { + Map map = super.toMap(); + + map.put( "name", name.getName() ); + if ( table != null ) { + map.put( "table", table.toMap() ); + } else { + map.put( "table", null ); + } + return map; + } + } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLCountFunction.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLCountFunction.java index c29f40ebc..cd5e7f7db 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLCountFunction.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLCountFunction.java @@ -15,11 +15,15 @@ package ortus.boxlang.compiler.ast.sql.select.expression; import java.util.List; +import java.util.Map; import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; +import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.types.Query; +import ortus.boxlang.runtime.types.QueryColumnType; /** * Abstract Node class representing SQL count() function call @@ -34,7 +38,7 @@ public class SQLCountFunction extends SQLFunction { * @param position position of the statement in the source code * @param sourceText source code of the statement */ - protected SQLCountFunction( String name, List arguments, boolean distinct, Position position, String sourceText ) { + public SQLCountFunction( String name, List arguments, boolean distinct, Position position, String sourceText ) { super( name, arguments, position, sourceText ); setDistinct( distinct ); } @@ -53,6 +57,13 @@ public boolean isDistinct() { return distinct; } + /** + * What type does this expression evaluate to + */ + public QueryColumnType getType( Map tableLookup ) { + return QueryColumnType.INTEGER; + } + @Override public void accept( VoidBoxVisitor v ) { // TODO Auto-generated method stub @@ -65,4 +76,14 @@ public BoxNode accept( ReplacingBoxVisitor v ) { throw new UnsupportedOperationException( "Unimplemented method 'accept'" ); } + @Override + public Map toMap() { + Map map = super.toMap(); + + map.put( "distinct", isDistinct() ); + map.put( "name", getName() ); + map.put( "arguments", getArguments().stream().map( BoxNode::toMap ).toList() ); + return map; + } + } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLExpression.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLExpression.java index 3937b25e2..d4ad6647d 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLExpression.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLExpression.java @@ -14,8 +14,13 @@ */ package ortus.boxlang.compiler.ast.sql.select.expression; +import java.util.Map; + import ortus.boxlang.compiler.ast.Position; import ortus.boxlang.compiler.ast.sql.SQLNode; +import ortus.boxlang.compiler.ast.sql.select.SQLTable; +import ortus.boxlang.runtime.types.Query; +import ortus.boxlang.runtime.types.QueryColumnType; /** * Abstract Node class representing SQL expression @@ -28,7 +33,7 @@ public abstract class SQLExpression extends SQLNode { * @param position position of the statement in the source code * @param sourceText source code of the statement */ - protected SQLExpression( Position position, String sourceText ) { + public SQLExpression( Position position, String sourceText ) { super( position, sourceText ); } @@ -46,4 +51,19 @@ public boolean isBoolean() { return false; } + /** + * What type does this expression evaluate to + */ + public QueryColumnType getType( Map tableLookup ) { + if ( isBoolean() ) { + return QueryColumnType.BIT; + } + return QueryColumnType.OBJECT; + } + + /** + * Evaluate the expression + */ + public abstract Object evaluate( Map tableLookup, int i ); + } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLFunction.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLFunction.java index cc79d29d5..f06e6e1ef 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLFunction.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLFunction.java @@ -15,11 +15,16 @@ package ortus.boxlang.compiler.ast.sql.select.expression; import java.util.List; +import java.util.Map; import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; +import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.types.Query; +import ortus.boxlang.runtime.types.QueryColumnType; +import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; /** * Abstract Node class representing SQL function call @@ -36,7 +41,7 @@ public class SQLFunction extends SQLExpression { * @param position position of the statement in the source code * @param sourceText source code of the statement */ - protected SQLFunction( String name, List arguments, Position position, String sourceText ) { + public SQLFunction( String name, List arguments, Position position, String sourceText ) { super( position, sourceText ); setName( name ); setArguments( arguments ); @@ -88,6 +93,21 @@ public boolean isBoolean() { return false; } + /** + * What type does this expression evaluate to + */ + public QueryColumnType getType( Map tableLookup ) { + // TODO: actually return proper type based on the function in question + return QueryColumnType.OBJECT; + } + + /** + * Evaluate the expression + */ + public Object evaluate( Map tableLookup, int i ) { + throw new BoxRuntimeException( "not implemented" ); + } + @Override public void accept( VoidBoxVisitor v ) { // TODO Auto-generated method stub @@ -100,4 +120,13 @@ public BoxNode accept( ReplacingBoxVisitor v ) { throw new UnsupportedOperationException( "Unimplemented method 'accept'" ); } + @Override + public Map toMap() { + Map map = super.toMap(); + + map.put( "name", getName() ); + map.put( "arguments", getArguments().stream().map( BoxNode::toMap ).toList() ); + return map; + } + } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLOrderBy.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLOrderBy.java index 6db36e988..1248af906 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLOrderBy.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLOrderBy.java @@ -14,15 +14,18 @@ */ package ortus.boxlang.compiler.ast.sql.select.expression; +import java.util.Map; + import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; +import ortus.boxlang.compiler.ast.sql.SQLNode; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; /** * Abstract Node class representing SQL Order By item */ -public class SQLOrderBy extends SQLExpression { +public class SQLOrderBy extends SQLNode { private SQLExpression expression; @@ -34,7 +37,7 @@ public class SQLOrderBy extends SQLExpression { * @param position position of the statement in the source code * @param sourceText source code of the statement */ - protected SQLOrderBy( SQLExpression expression, boolean ascending, Position position, String sourceText ) { + public SQLOrderBy( SQLExpression expression, boolean ascending, Position position, String sourceText ) { super( position, sourceText ); setExpression( expression ); setAscending( ascending ); @@ -82,4 +85,13 @@ public BoxNode accept( ReplacingBoxVisitor v ) { throw new UnsupportedOperationException( "Unimplemented method 'accept'" ); } + @Override + public Map toMap() { + Map map = super.toMap(); + + map.put( "expression", expression.toMap() ); + map.put( "ascending", ascending ); + return map; + } + } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLParenthesis.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLParenthesis.java index 17ef09d12..72c46cad9 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLParenthesis.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLParenthesis.java @@ -14,10 +14,15 @@ */ package ortus.boxlang.compiler.ast.sql.select.expression; +import java.util.Map; + import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; +import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.types.Query; +import ortus.boxlang.runtime.types.QueryColumnType; /** * Abstract Node class representing SQL parenthetical expression @@ -32,7 +37,7 @@ public class SQLParenthesis extends SQLExpression { * @param position position of the statement in the source code * @param sourceText source code of the statement */ - protected SQLParenthesis( SQLExpression expression, Position position, String sourceText ) { + public SQLParenthesis( SQLExpression expression, Position position, String sourceText ) { super( position, sourceText ); setExpression( expression ); } @@ -60,6 +65,20 @@ public boolean isBoolean() { return expression.isBoolean(); } + /** + * What type does this expression evaluate to + */ + public QueryColumnType getType( Map tableLookup ) { + return expression.getType( tableLookup ); + } + + /** + * Evaluate the expression + */ + public Object evaluate( Map tableLookup, int i ) { + return expression.evaluate( tableLookup, i ); + } + @Override public void accept( VoidBoxVisitor v ) { // TODO Auto-generated method stub @@ -72,4 +91,12 @@ public BoxNode accept( ReplacingBoxVisitor v ) { throw new UnsupportedOperationException( "Unimplemented method 'accept'" ); } + @Override + public Map toMap() { + Map map = super.toMap(); + + map.put( "expression", expression.toMap() ); + return map; + } + } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLStarExpression.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLStarExpression.java index a6ec7aa10..cde8d7f4c 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLStarExpression.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLStarExpression.java @@ -14,11 +14,15 @@ */ package ortus.boxlang.compiler.ast.sql.select.expression; +import java.util.Map; + import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.types.Query; +import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; /** * Abstract Node class representing SQL * expression @@ -33,7 +37,7 @@ public class SQLStarExpression extends SQLExpression { * @param position position of the statement in the source code * @param sourceText source code of the statement */ - protected SQLStarExpression( SQLTable table, Position position, String sourceText ) { + public SQLStarExpression( SQLTable table, Position position, String sourceText ) { super( position, sourceText ); setTable( table ); } @@ -53,6 +57,13 @@ public void setTable( SQLTable table ) { this.table = table; } + /** + * Evaluate the expression + */ + public Object evaluate( Map tableLookup, int i ) { + throw new BoxRuntimeException( "Cannot evaluate a * expression" ); + } + @Override public void accept( VoidBoxVisitor v ) { // TODO Auto-generated method stub @@ -65,4 +76,16 @@ public BoxNode accept( ReplacingBoxVisitor v ) { throw new UnsupportedOperationException( "Unimplemented method 'accept'" ); } + @Override + public Map toMap() { + Map map = super.toMap(); + + if ( table != null ) { + map.put( "table", table.toMap() ); + } else { + map.put( "table", null ); + } + return map; + } + } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLBooleanLiteral.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLBooleanLiteral.java index 5372cfe50..4d2c870bc 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLBooleanLiteral.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLBooleanLiteral.java @@ -14,11 +14,15 @@ */ package ortus.boxlang.compiler.ast.sql.select.expression.literal; +import java.util.Map; + import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; +import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.types.Query; /** * Abstract Node class representing SQL boolean @@ -33,7 +37,7 @@ public class SQLBooleanLiteral extends SQLExpression { * @param position position of the statement in the source code * @param sourceText source code of the statement */ - protected SQLBooleanLiteral( boolean value, Position position, String sourceText ) { + public SQLBooleanLiteral( boolean value, Position position, String sourceText ) { super( position, sourceText ); setValue( value ); } @@ -65,6 +69,13 @@ public boolean isBoolean() { return true; } + /** + * Evaluate the expression + */ + public Object evaluate( Map tableLookup, int i ) { + return value; + } + @Override public void accept( VoidBoxVisitor v ) { // TODO Auto-generated method stub @@ -77,4 +88,12 @@ public BoxNode accept( ReplacingBoxVisitor v ) { throw new UnsupportedOperationException( "Unimplemented method 'accept'" ); } + @Override + public Map toMap() { + Map map = super.toMap(); + + map.put( "value", value ); + return map; + } + } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNullLiteral.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNullLiteral.java index 45514b3fd..51f8f09e6 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNullLiteral.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNullLiteral.java @@ -14,11 +14,15 @@ */ package ortus.boxlang.compiler.ast.sql.select.expression.literal; +import java.util.Map; + import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; +import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.types.Query; /** * Abstract Node class representing SQL null @@ -31,7 +35,7 @@ public class SQLNullLiteral extends SQLExpression { * @param position position of the statement in the source code * @param sourceText source code of the statement */ - protected SQLNullLiteral( Position position, String sourceText ) { + public SQLNullLiteral( Position position, String sourceText ) { super( position, sourceText ); } @@ -42,6 +46,13 @@ public boolean isLiteral() { return true; } + /** + * Evaluate the expression + */ + public Object evaluate( Map tableLookup, int i ) { + return null; + } + @Override public void accept( VoidBoxVisitor v ) { // TODO Auto-generated method stub @@ -54,4 +65,11 @@ public BoxNode accept( ReplacingBoxVisitor v ) { throw new UnsupportedOperationException( "Unimplemented method 'accept'" ); } + @Override + public Map toMap() { + Map map = super.toMap(); + + return map; + } + } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNumberLiteral.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNumberLiteral.java index 9e318f7b2..69a90e524 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNumberLiteral.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNumberLiteral.java @@ -14,11 +14,16 @@ */ package ortus.boxlang.compiler.ast.sql.select.expression.literal; +import java.util.Map; + import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; +import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.types.Query; +import ortus.boxlang.runtime.types.QueryColumnType; /** * Abstract Node class representing SQL number literal @@ -33,7 +38,7 @@ public class SQLNumberLiteral extends SQLExpression { * @param position position of the statement in the source code * @param sourceText source code of the statement */ - protected SQLNumberLiteral( Number value, Position position, String sourceText ) { + public SQLNumberLiteral( Number value, Position position, String sourceText ) { super( position, sourceText ); setValue( value ); } @@ -58,6 +63,20 @@ public boolean isLiteral() { return true; } + /** + * What type does this expression evaluate to + */ + public QueryColumnType getType( Map tableLookup ) { + return QueryColumnType.DOUBLE; + } + + /** + * Evaluate the expression + */ + public Object evaluate( Map tableLookup, int i ) { + return value; + } + @Override public void accept( VoidBoxVisitor v ) { // TODO Auto-generated method stub @@ -70,4 +89,12 @@ public BoxNode accept( ReplacingBoxVisitor v ) { throw new UnsupportedOperationException( "Unimplemented method 'accept'" ); } + @Override + public Map toMap() { + Map map = super.toMap(); + + map.put( "value", value ); + return map; + } + } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLStringLiteral.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLStringLiteral.java index 6d9d91f0c..0a5773a14 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLStringLiteral.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLStringLiteral.java @@ -14,11 +14,16 @@ */ package ortus.boxlang.compiler.ast.sql.select.expression.literal; +import java.util.Map; + import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; +import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.types.Query; +import ortus.boxlang.runtime.types.QueryColumnType; /** * Abstract Node class representing SQL string literal @@ -33,7 +38,7 @@ public class SQLStringLiteral extends SQLExpression { * @param position position of the statement in the source code * @param sourceText source code of the statement */ - protected SQLStringLiteral( String value, Position position, String sourceText ) { + public SQLStringLiteral( String value, Position position, String sourceText ) { super( position, sourceText ); setValue( value ); } @@ -58,6 +63,20 @@ public boolean isLiteral() { return true; } + /** + * What type does this expression evaluate to + */ + public QueryColumnType getType( Map tableLookup ) { + return QueryColumnType.VARCHAR; + } + + /** + * Evaluate the expression + */ + public Object evaluate( Map tableLookup, int i ) { + return value; + } + @Override public void accept( VoidBoxVisitor v ) { // TODO Auto-generated method stub @@ -70,4 +89,12 @@ public BoxNode accept( ReplacingBoxVisitor v ) { throw new UnsupportedOperationException( "Unimplemented method 'accept'" ); } + @Override + public Map toMap() { + Map map = super.toMap(); + + map.put( "value", value ); + return map; + } + } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBetweenOperation.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBetweenOperation.java index 8fec19759..a330fb630 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBetweenOperation.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBetweenOperation.java @@ -14,11 +14,16 @@ */ package ortus.boxlang.compiler.ast.sql.select.expression.operation; +import java.util.Map; + import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; +import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.types.Query; +import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; /** * Abstract Node class representing SQL BETWEEN operation @@ -37,7 +42,7 @@ public class SQLBetweenOperation extends SQLExpression { * @param position position of the statement in the source code * @param sourceText source code of the statement */ - protected SQLBetweenOperation( SQLExpression expression, SQLExpression left, SQLExpression right, Position position, String sourceText ) { + public SQLBetweenOperation( SQLExpression expression, SQLExpression left, SQLExpression right, Position position, String sourceText ) { super( position, sourceText ); setExpression( expression ); setLeft( left ); @@ -99,6 +104,13 @@ public boolean isBoolean() { return true; } + /** + * Evaluate the expression + */ + public Object evaluate( Map tableLookup, int i ) { + throw new BoxRuntimeException( "not implemented" ); + } + @Override public void accept( VoidBoxVisitor v ) { // TODO Auto-generated method stub @@ -111,4 +123,14 @@ public BoxNode accept( ReplacingBoxVisitor v ) { throw new UnsupportedOperationException( "Unimplemented method 'accept'" ); } + @Override + public Map toMap() { + Map map = super.toMap(); + + map.put( "expression", expression.toMap() ); + map.put( "left", left.toMap() ); + map.put( "right", right.toMap() ); + return map; + } + } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBinaryOperation.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBinaryOperation.java index 1ca4d6656..1390b7cd7 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBinaryOperation.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBinaryOperation.java @@ -14,13 +14,18 @@ */ package ortus.boxlang.compiler.ast.sql.select.expression.operation; +import java.util.Map; import java.util.Set; import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; +import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.types.Query; +import ortus.boxlang.runtime.types.QueryColumnType; +import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; /** * Abstract Node class representing SQL binary operation @@ -31,6 +36,9 @@ public class SQLBinaryOperation extends SQLExpression { private static final Set booleanOperators = Set.of( SQLBinaryOperator.EQUAL, SQLBinaryOperator.NOTEQUAL, SQLBinaryOperator.LESSTHAN, SQLBinaryOperator.LESSTHANOREQUAL, SQLBinaryOperator.GREATERTHAN, SQLBinaryOperator.GREATERTHANOREQUAL, SQLBinaryOperator.AND, SQLBinaryOperator.OR ); + private static Set mathOperators = Set.of( SQLBinaryOperator.MINUS, SQLBinaryOperator.MULTIPLY, SQLBinaryOperator.DIVIDE, + SQLBinaryOperator.MODULO ); + private SQLExpression left; private SQLExpression right; @@ -43,7 +51,7 @@ public class SQLBinaryOperation extends SQLExpression { * @param position position of the statement in the source code * @param sourceText source code of the statement */ - protected SQLBinaryOperation( SQLExpression left, SQLExpression right, SQLBinaryOperator operator, Position position, String sourceText ) { + public SQLBinaryOperation( SQLExpression left, SQLExpression right, SQLBinaryOperator operator, Position position, String sourceText ) { super( position, sourceText ); setLeft( left ); setRight( right ); @@ -103,6 +111,34 @@ public boolean isBoolean() { return booleanOperators.contains( operator ); } + /** + * What type does this expression evaluate to + */ + public QueryColumnType getType( Map tableLookup ) { + // If this is a boolean operation, then we're a bit + if ( isBoolean() ) { + return QueryColumnType.BIT; + } + // All math operators but + return a number + if ( mathOperators.contains( operator ) ) { + return QueryColumnType.DOUBLE; + } + // Plus returns a string if the left and right operand were a string, otherwise it's a math operation. + if ( operator == SQLBinaryOperator.PLUS ) { + return QueryColumnType.isStringType( left.getType( tableLookup ) ) && QueryColumnType.isStringType( right.getType( tableLookup ) ) + ? QueryColumnType.VARCHAR + : QueryColumnType.DOUBLE; + } + return QueryColumnType.OBJECT; + } + + /** + * Evaluate the expression + */ + public Object evaluate( Map tableLookup, int i ) { + throw new BoxRuntimeException( "not implemented" ); + } + @Override public void accept( VoidBoxVisitor v ) { // TODO Auto-generated method stub @@ -115,4 +151,14 @@ public BoxNode accept( ReplacingBoxVisitor v ) { throw new UnsupportedOperationException( "Unimplemented method 'accept'" ); } -} + @Override + public Map toMap() { + Map map = super.toMap(); + + map.put( "left", left.toMap() ); + map.put( "right", right.toMap() ); + map.put( "operator", enumToMap( operator ) ); + return map; + } + +} \ No newline at end of file diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLInOperation.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLInOperation.java index 7f2a2c234..306a5822f 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLInOperation.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLInOperation.java @@ -15,12 +15,16 @@ package ortus.boxlang.compiler.ast.sql.select.expression.operation; import java.util.List; +import java.util.Map; import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; +import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.types.Query; +import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; /** * Abstract Node class representing SQL IN operation @@ -39,7 +43,7 @@ public class SQLInOperation extends SQLExpression { * @param position position of the statement in the source code * @param sourceText source code of the statement */ - protected SQLInOperation( boolean not, SQLExpression expression, List values, Position position, String sourceText ) { + public SQLInOperation( boolean not, SQLExpression expression, List values, Position position, String sourceText ) { super( position, sourceText ); setExpression( expression ); setValues( values ); @@ -99,6 +103,13 @@ public boolean isBoolean() { return true; } + /** + * Evaluate the expression + */ + public Object evaluate( Map tableLookup, int i ) { + throw new BoxRuntimeException( "not implemented" ); + } + @Override public void accept( VoidBoxVisitor v ) { // TODO Auto-generated method stub @@ -111,4 +122,14 @@ public BoxNode accept( ReplacingBoxVisitor v ) { throw new UnsupportedOperationException( "Unimplemented method 'accept'" ); } + @Override + public Map toMap() { + Map map = super.toMap(); + + map.put( "not", not ); + map.put( "expression", expression.toMap() ); + map.put( "values", values.stream().map( SQLExpression::toMap ).toList() ); + return map; + } + } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLUnaryOperation.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLUnaryOperation.java index 2b4a1c48d..ddf94d696 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLUnaryOperation.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLUnaryOperation.java @@ -14,13 +14,18 @@ */ package ortus.boxlang.compiler.ast.sql.select.expression.operation; +import java.util.Map; import java.util.Set; import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; +import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.types.Query; +import ortus.boxlang.runtime.types.QueryColumnType; +import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; /** * Abstract Node class representing SQL unary operation @@ -40,7 +45,7 @@ public class SQLUnaryOperation extends SQLExpression { * @param position position of the statement in the source code * @param sourceText source code of the statement */ - protected SQLUnaryOperation( SQLExpression expression, SQLUnaryOperator operator, Position position, String sourceText ) { + public SQLUnaryOperation( SQLExpression expression, SQLUnaryOperator operator, Position position, String sourceText ) { super( position, sourceText ); setExpression( expression ); setOperator( operator ); @@ -83,6 +88,27 @@ public boolean isBoolean() { return booleanOperators.contains( operator ); } + /** + * What type does this expression evaluate to + */ + public QueryColumnType getType( Map tableLookup ) { + // If this is a boolean operation, then we're a bit + if ( isBoolean() ) { + return QueryColumnType.BIT; + } + if ( operator == SQLUnaryOperator.PLUS || operator == SQLUnaryOperator.MINUS ) { + return QueryColumnType.DOUBLE; + } + return QueryColumnType.OBJECT; + } + + /** + * Evaluate the expression + */ + public Object evaluate( Map tableLookup, int i ) { + throw new BoxRuntimeException( "not implemented" ); + } + @Override public void accept( VoidBoxVisitor v ) { // TODO Auto-generated method stub @@ -95,4 +121,13 @@ public BoxNode accept( ReplacingBoxVisitor v ) { throw new UnsupportedOperationException( "Unimplemented method 'accept'" ); } + @Override + public Map toMap() { + Map map = super.toMap(); + + map.put( "expression", expression.toMap() ); + map.put( "operator", enumToMap( operator ) ); + return map; + } + } diff --git a/src/main/java/ortus/boxlang/compiler/parser/AbstractParser.java b/src/main/java/ortus/boxlang/compiler/parser/AbstractParser.java index 14a5d3198..49350f679 100644 --- a/src/main/java/ortus/boxlang/compiler/parser/AbstractParser.java +++ b/src/main/java/ortus/boxlang/compiler/parser/AbstractParser.java @@ -39,6 +39,7 @@ import ortus.boxlang.compiler.ast.Position; import ortus.boxlang.compiler.ast.Source; import ortus.boxlang.compiler.ast.comment.BoxComment; +import ortus.boxlang.runtime.types.exceptions.BoxIOException; /** * Parser abstract class @@ -91,8 +92,12 @@ public AbstractParser( int startLine, int startColumn ) { * * @throws IOException */ - protected BOMInputStream getInputStream( File file ) throws IOException { - return BOMInputStream.builder().setFile( file ).setByteOrderMarks( ByteOrderMark.UTF_8 ).setInclude( false ).get(); + protected BOMInputStream getInputStream( File file ) { + try { + return BOMInputStream.builder().setFile( file ).setByteOrderMarks( ByteOrderMark.UTF_8 ).setInclude( false ).get(); + } catch ( IOException e ) { + throw new BoxIOException( e ); + } } /** diff --git a/src/main/java/ortus/boxlang/compiler/parser/SQLParser.java b/src/main/java/ortus/boxlang/compiler/parser/SQLParser.java index 9fb4d6c86..62aadb961 100644 --- a/src/main/java/ortus/boxlang/compiler/parser/SQLParser.java +++ b/src/main/java/ortus/boxlang/compiler/parser/SQLParser.java @@ -37,6 +37,7 @@ import ortus.boxlang.compiler.toolchain.SQLVisitor; import ortus.boxlang.parser.antlr.SQLGrammar; import ortus.boxlang.parser.antlr.SQLLexer; +import ortus.boxlang.runtime.types.exceptions.BoxIOException; /** * Parser for QoQ scripts @@ -63,11 +64,11 @@ public SQLParser( int startLine, int startColumn ) { * * @return a ParsingResult containing the AST with a BoxScript as root and the list of errors (if any) * - * @throws IOException if the input stream is in error + * @ if the input stream is in error * * @see ParsingResult */ - public ParsingResult parse( File file, boolean isScript ) throws IOException { + public ParsingResult parse( File file, boolean isScript ) { this.file = file; setSource( new SourceFile( file ) ); BOMInputStream inputStream = getInputStream( file ); @@ -84,16 +85,16 @@ public ParsingResult parse( File file, boolean isScript ) throws IOException { * * @return a ParsingResult containing the AST with a BoxScript as root and the list of errors (if any) * - * @throws IOException if the input stream is in error + * @ if the input stream is in error * * @see BoxScript * @see ParsingResult */ - public ParsingResult parse( String code, boolean isScript ) throws IOException { + public ParsingResult parse( String code, boolean isScript ) { return parse( code, false, isScript ); } - public ParsingResult parse( String code ) throws IOException { + public ParsingResult parse( String code ) { return parse( code, false, true ); } @@ -104,13 +105,13 @@ public ParsingResult parse( String code ) throws IOException { * * @return a ParsingResult containing the AST with a BoxScript as root and the list of errors (if any) * - * @throws IOException if the input stream is in error + * @ if the input stream is in error * * @see BoxScript * @see ParsingResult */ @Override - public ParsingResult parse( String code, boolean classOrInterface, boolean isScript ) throws IOException { + public ParsingResult parse( String code, boolean classOrInterface, boolean isScript ) { this.sourceCode = code; setSource( new SourceCode( code ) ); InputStream inputStream = IOUtils.toInputStream( code, StandardCharsets.UTF_8 ); @@ -126,13 +127,18 @@ public ParsingResult parse( String code, boolean classOrInterface, boolean isScr * * @return the ANTLR ParserRule representing the parse tree of the code * - * @throws IOException io error + * @ io error */ @Override - protected BoxNode parserFirstStage( InputStream stream, boolean classOrInterface, boolean isScript ) throws IOException { + protected BoxNode parserFirstStage( InputStream stream, boolean classOrInterface, boolean isScript ) { - SQLLexerCustom lexer = new SQLLexerCustom( CharStreams.fromStream( stream, StandardCharsets.UTF_8 ), errorListener, this ); - SQLGrammar parser = new SQLGrammar( new CommonTokenStream( lexer ) ); + SQLLexerCustom lexer; + try { + lexer = new SQLLexerCustom( CharStreams.fromStream( stream, StandardCharsets.UTF_8 ), errorListener, this ); + } catch ( IOException e ) { + throw new BoxIOException( e ); + } + SQLGrammar parser = new SQLGrammar( new CommonTokenStream( lexer ) ); // DEBUG: Will print a trace of all parser rules visited: // boxParser.setTrace( true ); @@ -205,7 +211,7 @@ private void validateParse( SQLLexerCustom lexer ) { } } - private void extractComments( SQLLexerCustom lexer ) throws IOException { + private void extractComments( SQLLexerCustom lexer ) { lexer.reset(); Token token = lexer.nextToken(); while ( token.getType() != Token.EOF ) { diff --git a/src/main/java/ortus/boxlang/compiler/toolchain/SQLVisitor.java b/src/main/java/ortus/boxlang/compiler/toolchain/SQLVisitor.java index cd4ef6993..0f9c60c41 100644 --- a/src/main/java/ortus/boxlang/compiler/toolchain/SQLVisitor.java +++ b/src/main/java/ortus/boxlang/compiler/toolchain/SQLVisitor.java @@ -2,13 +2,35 @@ import java.util.List; +import org.antlr.v4.runtime.tree.TerminalNode; + import ortus.boxlang.compiler.ast.BoxNode; +import ortus.boxlang.compiler.ast.sql.select.SQLJoin; +import ortus.boxlang.compiler.ast.sql.select.SQLResultColumn; +import ortus.boxlang.compiler.ast.sql.select.SQLSelect; +import ortus.boxlang.compiler.ast.sql.select.SQLSelectStatement; +import ortus.boxlang.compiler.ast.sql.select.SQLTable; +import ortus.boxlang.compiler.ast.sql.select.expression.SQLColumn; +import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; +import ortus.boxlang.compiler.ast.sql.select.expression.SQLOrderBy; +import ortus.boxlang.compiler.ast.sql.select.expression.SQLStarExpression; +import ortus.boxlang.compiler.ast.sql.select.expression.literal.SQLBooleanLiteral; +import ortus.boxlang.compiler.ast.sql.select.expression.literal.SQLNullLiteral; +import ortus.boxlang.compiler.ast.sql.select.expression.literal.SQLNumberLiteral; +import ortus.boxlang.compiler.ast.sql.select.expression.literal.SQLStringLiteral; import ortus.boxlang.compiler.parser.SQLParser; +import ortus.boxlang.parser.antlr.SQLGrammar.ExprContext; +import ortus.boxlang.parser.antlr.SQLGrammar.Literal_valueContext; +import ortus.boxlang.parser.antlr.SQLGrammar.Ordering_termContext; import ortus.boxlang.parser.antlr.SQLGrammar.ParseContext; +import ortus.boxlang.parser.antlr.SQLGrammar.Result_columnContext; +import ortus.boxlang.parser.antlr.SQLGrammar.Select_coreContext; import ortus.boxlang.parser.antlr.SQLGrammar.Select_stmtContext; import ortus.boxlang.parser.antlr.SQLGrammar.Sql_stmtContext; import ortus.boxlang.parser.antlr.SQLGrammar.Sql_stmt_listContext; +import ortus.boxlang.parser.antlr.SQLGrammar.TableContext; import ortus.boxlang.parser.antlr.SQLGrammarBaseVisitor; +import ortus.boxlang.runtime.scopes.Key; /** * This class is responsible for creating the SQL AST from the ANTLR generated parse tree. @@ -82,8 +104,271 @@ public BoxNode visitSql_stmt_list( Sql_stmt_listContext ctx ) { */ @Override public BoxNode visitSelect_stmt( Select_stmtContext ctx ) { - var pos = tools.getPosition( ctx ); + var pos = tools.getPosition( ctx ); + var src = tools.getSourceText( ctx ); + + SQLSelect select = null; + List unions = null; + List orderBys = null; + SQLNumberLiteral limit = null; + + select = ( SQLSelect ) visit( ctx.select_core() ); + final SQLSelect finalSelect = select; + + if ( ctx.order_by_stmt() != null ) { + orderBys = ctx.order_by_stmt().ordering_term().stream().map( term -> visitOrdering_term( term, finalSelect.getTable(), finalSelect.getJoins() ) ) + .toList(); + } + + // Limit after the order by, applies to all unions + if ( ctx.limit_stmt() != null ) { + limit = NUMERIC_LITERAL( ctx.limit_stmt().NUMERIC_LITERAL() ); + } + + // TODO: handle unions + + return new SQLSelectStatement( select, unions, orderBys, limit, pos, src ); + } + + /** + * Visit the class or interface context to generate the AST node for the + * top level node + * + * @param ctx the parse tree + * + * @return the AST node representing the class or interface + */ + @Override + public SQLSelect visitSelect_core( Select_coreContext ctx ) { + var pos = tools.getPosition( ctx ); + var src = tools.getSourceText( ctx ); + + boolean distinct = ctx.DISTINCT_() != null; + SQLNumberLiteral limit = null; + List resultColumns = null; + SQLTable table = null; + List joins = null; + SQLExpression where = null; + List groupBys = null; + SQLExpression having = null; + + // limit before order by, can have one per unioned table + if ( ctx.limit_stmt() != null ) { + limit = NUMERIC_LITERAL( ctx.limit_stmt().NUMERIC_LITERAL() ); + } + // each + if ( ctx.top() != null ) { + limit = NUMERIC_LITERAL( ctx.top().NUMERIC_LITERAL() ); + } + + if ( !ctx.table().isEmpty() ) { + var firstTable = ctx.table().get( 0 ); + table = ( SQLTable ) visit( firstTable ); + } + + if ( ctx.whereExpr != null ) { + where = visitExpr( ctx.whereExpr, table, joins ); + } + + // TODO: group by + + // TODO: having + // TODO: handle additional tables as joins + + // TODO: handle joins + + // Do this after all joins above so we know the tables available to us + final SQLTable finalTable = table; + final List finalJoins = joins; + resultColumns = ctx.result_column().stream().map( col -> visitResult_column( col, finalTable, finalJoins ) ).toList(); + + var result = new SQLSelect( distinct, resultColumns, table, joins, where, groupBys, having, limit, pos, src ); + var cols = result.getDescendantsOfType( SQLColumn.class, c -> c.getTable() == null ); + if ( cols.size() > 0 ) { + if ( joins != null && !joins.isEmpty() ) { + tools.reportError( "Column reference must have table prefix to disambiguate.", pos ); + } else { + if ( table == null ) { + tools.reportError( "This QoQ has column references, but there is no table!", pos ); + } else { + cols.forEach( c -> c.setTable( finalTable ) ); + } + } + } + return result; + } + + /** + * Visit the class or interface context to generate the AST node for the + * top level node + * + * @param ctx the parse tree + * + * @return the AST node representing the class or interface + */ + @Override + public SQLTable visitTable( TableContext ctx ) { + var pos = tools.getPosition( ctx ); + var src = tools.getSourceText( ctx ); + String schema = null; + String name = ctx.table_name().getText(); + String alias = null; + + if ( ctx.schema_name() != null ) { + schema = ctx.schema_name().getText(); + } + + if ( ctx.table_alias() != null ) { + alias = ctx.table_alias().getText(); + } + + return new SQLTable( schema, name, alias, pos, src ); + } + + /** + * Visit the class or interface context to generate the AST node for the + * top level node + * + * @param ctx the parse tree + * + * @return the AST node representing the class or interface + */ + public SQLResultColumn visitResult_column( Result_columnContext ctx, SQLTable table, List joins ) { + var pos = tools.getPosition( ctx ); + var src = tools.getSourceText( ctx ); + String alias = null; + SQLExpression expression; + + if ( ctx.column_alias() != null ) { + alias = ctx.column_alias().getText(); + } + + if ( ctx.STAR() != null ) { + SQLTable tableRef = null; + // if we have tableName.* or tAlias.* then we need to find the table reference + if ( ctx.table_name() != null ) { + String tableName = ctx.table_name().getText(); + tableRef = findTableRef( table, joins, tableName ); + // If we didn't find the table reference then error + if ( tableRef == null ) { + tools.reportError( "Table reference not found for " + src, pos ); + } + } + expression = new SQLStarExpression( tableRef, pos, src ); + } else { + expression = visitExpr( ctx.expr(), table, joins ); + } + + return new SQLResultColumn( expression, alias, 0, pos, src ); + } + + /** + * Visit the class or interface context to generate the AST node for the + * top level node + * + * @param ctx the parse tree + * + * @return the AST node representing the class or interface + */ + public SQLOrderBy visitOrdering_term( Ordering_termContext ctx, SQLTable table, List joins ) { + var pos = tools.getPosition( ctx ); + var src = tools.getSourceText( ctx ); + boolean ascending = true; + + if ( ctx.asc_desc() != null ) { + ascending = ctx.asc_desc().DESC_() != null; + } + + return new SQLOrderBy( visitExpr( ctx.expr(), table, joins ), ascending, pos, src ); + } + + /** + * Visit the class or interface context to generate the AST node for the + * top level node + * + * @param ctx the parse tree + * + * @return the AST node representing the class or interface + */ + public SQLExpression visitExpr( ExprContext ctx, SQLTable table, List joins ) { + var pos = tools.getPosition( ctx ); + var src = tools.getSourceText( ctx ); + + if ( ctx.column_name() != null ) { + SQLTable tableRef = null; + // if we have tableName.* or tAlias.* then we need to find the table reference + if ( ctx.table_name() != null ) { + String tableName = ctx.table_name().getText(); + tableRef = findTableRef( table, joins, tableName ); + // If we didn't find the table reference then error + if ( tableRef == null ) { + tools.reportError( "Table reference not found for " + src, pos ); + } + } + return new SQLColumn( tableRef, ctx.column_name().getText(), pos, src ); + } else if ( ctx.literal_value() != null ) { + return ( SQLExpression ) visit( ctx.literal_value() ); + } else { + throw new UnsupportedOperationException( "Unimplemented expression: " + src ); + } + } + + /** + * Visit the class or interface context to generate the AST node for the + * top level node + * + * @param ctx the parse tree + * + * @return the AST node representing the class or interface + */ + @Override + public SQLExpression visitLiteral_value( Literal_valueContext ctx ) { + var pos = tools.getPosition( ctx ); + var src = tools.getSourceText( ctx ); + + if ( ctx.NULL_() != null ) { + return new SQLNullLiteral( pos, src ); + } else if ( ctx.NUMERIC_LITERAL() != null ) { + return NUMERIC_LITERAL( ctx.NUMERIC_LITERAL() ); + } else if ( ctx.TRUE_() != null ) { + return new SQLBooleanLiteral( true, pos, src ); + } else if ( ctx.FALSE_() != null ) { + return new SQLBooleanLiteral( false, pos, src ); + } else if ( ctx.STRING_LITERAL() != null ) { + return new SQLStringLiteral( ctx.STRING_LITERAL().getText(), pos, src ); + } else { + throw new UnsupportedOperationException( "Unimplemented literal expression: " + src ); + } + } + + /** + * Visit the class or interface context to generate the AST node for the + * top level node + * + * @param ctx the parse tree + * + * @return the AST node representing the class or interface + */ + public SQLNumberLiteral NUMERIC_LITERAL( TerminalNode ctx ) { + var pos = tools.getPosition( ctx ); + var src = ctx.getText(); + + // TODO: handle different types (int, long, double, bigdecimal) + return new SQLNumberLiteral( Double.parseDouble( src ), pos, src ); + } + + private SQLTable findTableRef( SQLTable table, List joins, String tableName ) { + Key tableNameKey = Key.of( tableName ); + if ( table.isCalled( tableNameKey ) ) { + return table; + } else if ( joins != null ) { + for ( SQLJoin join : joins ) { + if ( join.getTable().isCalled( tableNameKey ) ) { + return join.getTable(); + } + } + } return null; } diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/jdbc/QueryExecute.java b/src/main/java/ortus/boxlang/runtime/bifs/global/jdbc/QueryExecute.java index 2bd9be6f5..fb2644e09 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/jdbc/QueryExecute.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/jdbc/QueryExecute.java @@ -14,6 +14,7 @@ */ package ortus.boxlang.runtime.bifs.global.jdbc; +import java.sql.Connection; import java.util.Set; import ortus.boxlang.runtime.bifs.BIF; @@ -21,12 +22,11 @@ import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.context.IJDBCCapableContext; import ortus.boxlang.runtime.dynamic.ExpressionInterpreter; -import ortus.boxlang.runtime.dynamic.casters.CastAttempt; -import ortus.boxlang.runtime.dynamic.casters.StructCaster; import ortus.boxlang.runtime.jdbc.ConnectionManager; import ortus.boxlang.runtime.jdbc.ExecutedQuery; import ortus.boxlang.runtime.jdbc.PendingQuery; import ortus.boxlang.runtime.jdbc.QueryOptions; +import ortus.boxlang.runtime.jdbc.qoq.QoQConnection; import ortus.boxlang.runtime.scopes.ArgumentsScope; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.Argument; @@ -57,21 +57,34 @@ public QueryExecute() { * @param arguments Argument scope for the BIF. * * @argument.sql The SQL to execute - * + * * @argument.params An array of binding parameters or a struct of named binding parameters * * @argument.options A struct of query options * */ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { - IJDBCCapableContext jdbcContext = context.getParentOfType( IJDBCCapableContext.class ); - ConnectionManager connectionManager = jdbcContext.getConnectionManager(); - CastAttempt optionsAsStruct = StructCaster.attempt( arguments.get( Key.options ) ); - QueryOptions options = new QueryOptions( optionsAsStruct.getOrDefault( new Struct() ) ); - String sql = arguments.getAsString( Key.sql ); - Object bindings = arguments.get( Key.params ); - PendingQuery pendingQuery = new PendingQuery( sql, bindings, options ); - ExecutedQuery executedQuery = pendingQuery.execute( connectionManager ); + IStruct optionsAsStruct = arguments.getAsStruct( Key.options ); + if ( optionsAsStruct == null ) { + optionsAsStruct = new Struct(); + } + String sql = arguments.getAsString( Key.sql ); + Object bindings = arguments.get( Key.params ); + + QueryOptions options = new QueryOptions( optionsAsStruct ); + PendingQuery pendingQuery = new PendingQuery( sql, bindings, options ); + + ExecutedQuery executedQuery; + // QoQ uses a special QoQ connection + if ( options.isQoQ() ) { + Connection connection = new QoQConnection( context ); + executedQuery = pendingQuery.execute( connection ); + } else { + // whereas normal queries use the JDBC connection manager + IJDBCCapableContext jdbcContext = context.getParentOfType( IJDBCCapableContext.class ); + ConnectionManager connectionManager = jdbcContext.getConnectionManager(); + executedQuery = pendingQuery.execute( connectionManager ); + } if ( options.wantsResultStruct() ) { assert options.resultVariableName != null; diff --git a/src/main/java/ortus/boxlang/runtime/components/jdbc/Query.java b/src/main/java/ortus/boxlang/runtime/components/jdbc/Query.java index 3dd8cefd1..7a8c12afc 100644 --- a/src/main/java/ortus/boxlang/runtime/components/jdbc/Query.java +++ b/src/main/java/ortus/boxlang/runtime/components/jdbc/Query.java @@ -17,6 +17,7 @@ */ package ortus.boxlang.runtime.components.jdbc; +import java.sql.Connection; import java.util.Set; import ortus.boxlang.runtime.components.Attribute; @@ -30,6 +31,7 @@ import ortus.boxlang.runtime.jdbc.ExecutedQuery; import ortus.boxlang.runtime.jdbc.PendingQuery; import ortus.boxlang.runtime.jdbc.QueryOptions; +import ortus.boxlang.runtime.jdbc.qoq.QoQConnection; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.Array; import ortus.boxlang.runtime.types.IStruct; @@ -70,7 +72,7 @@ public Query() { Validator.NOT_IMPLEMENTED ) ), new Attribute( Key.dbtype, "string", Set.of( - Validator.NOT_IMPLEMENTED + Validator.NON_EMPTY, Validator.valueOneOf( "query", "hql" ) ) ), new Attribute( Key.username, "string", Set.of( Validator.NOT_IMPLEMENTED @@ -102,9 +104,7 @@ public Query() { } public BodyResult _invoke( IBoxContext context, IStruct attributes, ComponentBody body, IStruct executionState ) { - IJDBCCapableContext jdbcContext = context.getParentOfType( IJDBCCapableContext.class ); - ConnectionManager connectionManager = jdbcContext.getConnectionManager(); - QueryOptions options = new QueryOptions( attributes ); + QueryOptions options = new QueryOptions( attributes ); executionState.put( Key.queryParams, new Array() ); @@ -129,7 +129,18 @@ public BodyResult _invoke( IBoxContext context, IStruct attributes, ComponentBod String sql = buffer.toString(); Array bindings = executionState.getAsArray( Key.queryParams ); PendingQuery pendingQuery = new PendingQuery( sql, bindings, options ); - ExecutedQuery executedQuery = pendingQuery.execute( connectionManager ); + + ExecutedQuery executedQuery; + // QoQ uses a special QoQ connection + if ( options.isQoQ() ) { + Connection connection = new QoQConnection( context ); + executedQuery = pendingQuery.execute( connection ); + } else { + // whereas normal queries use the JDBC connection manager + IJDBCCapableContext jdbcContext = context.getParentOfType( IJDBCCapableContext.class ); + ConnectionManager connectionManager = jdbcContext.getConnectionManager(); + executedQuery = pendingQuery.execute( connectionManager ); + } if ( options.wantsResultStruct() ) { assert options.resultVariableName != null; diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/ExecutedQuery.java b/src/main/java/ortus/boxlang/runtime/jdbc/ExecutedQuery.java index 081726d1a..5048d9d6d 100644 --- a/src/main/java/ortus/boxlang/runtime/jdbc/ExecutedQuery.java +++ b/src/main/java/ortus/boxlang/runtime/jdbc/ExecutedQuery.java @@ -32,6 +32,7 @@ import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.events.BoxEvent; +import ortus.boxlang.runtime.jdbc.qoq.QoQStatement; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.services.InterceptorService; import ortus.boxlang.runtime.types.Array; @@ -93,10 +94,14 @@ public static ExecutedQuery fromPendingQuery( @Nonnull PendingQuery pendingQuery Object generatedKey = null; Query results = null; - try ( ResultSet rs = statement.getResultSet() ) { - results = Query.fromResultSet( rs ); - } catch ( SQLException e ) { - throw new DatabaseException( e.getMessage(), e ); + if ( statement instanceof QoQStatement qs ) { + results = qs.getQueryResult(); + } else { + try ( ResultSet rs = statement.getResultSet() ) { + results = Query.fromResultSet( rs ); + } catch ( SQLException e ) { + throw new DatabaseException( e.getMessage(), e ); + } } // Capture generated keys, if any. diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/PendingQuery.java b/src/main/java/ortus/boxlang/runtime/jdbc/PendingQuery.java index c2f787148..ccbacb5b8 100644 --- a/src/main/java/ortus/boxlang/runtime/jdbc/PendingQuery.java +++ b/src/main/java/ortus/boxlang/runtime/jdbc/PendingQuery.java @@ -474,7 +474,6 @@ private void applyStatementOptions( Statement statement ) throws SQLException { /** * TODO: Implement the following options: * ormoptions - * dbtype : query of queries (In progress) * username and password : To evaluate later due to security concerns of overriding datasources, not going to implement unless requested * clientInfo : Part of the connection: get/setClientInfo() */ diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/QueryOptions.java b/src/main/java/ortus/boxlang/runtime/jdbc/QueryOptions.java index eace219fc..a93c24deb 100644 --- a/src/main/java/ortus/boxlang/runtime/jdbc/QueryOptions.java +++ b/src/main/java/ortus/boxlang/runtime/jdbc/QueryOptions.java @@ -43,6 +43,7 @@ *

  • password - The password to use when connecting to the datasource. *
  • timeout - The number of seconds to wait for the query to execute before timing out. *
  • maxRows - The maximum number of rows to return from the query. + *
  • dbtype - The type of query. "query" or "hql" * */ public class QueryOptions { @@ -98,6 +99,11 @@ public class QueryOptions { */ public final Long maxRows; + /** + * dbtype of the query. "query" or "hql" + */ + public final String dbtype; + /** * The fetch size for the query. Should be preferred over `maxRows` for large result sets, as `maxrows` will only truncate further rows from the * result, whereas `fetchsize` will prevent the retrieval of those rows in the first place. @@ -180,7 +186,9 @@ public QueryOptions( IStruct options ) { this.cacheProvider = ( String ) options.getOrDefault( Key.cacheProvider, cacheService.getDefaultCache().getName().toString() ); Integer intMaxRows = options.getAsInteger( Key.maxRows ); - this.maxRows = Long.valueOf( intMaxRows != null ? intMaxRows : -1 ); + this.maxRows = Long.valueOf( intMaxRows != null ? intMaxRows : -1 ); + + this.dbtype = options.getAsString( Key.dbtype ); determineReturnType(); } @@ -223,12 +231,6 @@ public Object castAsReturnType( ExecutedQuery query ) { }; } - /** - * -------------------------------------------------------------------------- - * Private Helpers - * -------------------------------------------------------------------------- - */ - /** * If the query options contain a `username` field, then the query should use the provided username and password to connect to the datasource. * @@ -238,6 +240,16 @@ public boolean wantsUsernameAndPassword() { return this.username != null; } + public boolean isQoQ() { + return this.dbtype != null && this.dbtype.equalsIgnoreCase( "query" ); + } + + /** + * -------------------------------------------------------------------------- + * Private Helpers + * -------------------------------------------------------------------------- + */ + /** * Parse the `returnType` query option and set the `returnType` and `columnKey` fields. * @@ -270,6 +282,7 @@ public IStruct toStruct() { result.put( "fetchSize", this.fetchSize ); result.put( "setQueryTimeout", this.queryTimeout ); result.put( "setMaxRows", this.maxRows ); + result.put( "dbtype", this.dbtype ); return result; } diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQConnection.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQConnection.java new file mode 100644 index 000000000..c5da85a95 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQConnection.java @@ -0,0 +1,262 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq; + +import java.sql.Array; +import java.sql.Blob; +import java.sql.CallableStatement; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.NClob; +import java.sql.PreparedStatement; +import java.sql.SQLClientInfoException; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Savepoint; +import java.sql.Statement; +import java.sql.Struct; +import java.util.Properties; +import java.util.concurrent.Executor; + +import ortus.boxlang.runtime.context.IBoxContext; + +/** + * This class is a dummy implementation of the {@link Connection} interface. It exists to support Query of Queries + * so they can use the same pending query plumbing without needing new code paths and duplicate logic. The only methods + * implement are for creating statements. + */ +public class QoQConnection implements Connection { + + private boolean closed = false; + private IBoxContext context; + + public QoQConnection( IBoxContext context ) { + this.context = context; + } + + public Statement createStatement() throws SQLException { + return new QoQStatement( context, this ); + } + + public PreparedStatement prepareStatement( String sql ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public CallableStatement prepareCall( String sql ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public String nativeSQL( String sql ) throws SQLException { + return sql; + } + + public void setAutoCommit( boolean autoCommit ) throws SQLException { + } + + public boolean getAutoCommit() throws SQLException { + return true; + } + + public void commit() throws SQLException { + } + + public void rollback() throws SQLException { + } + + public void close() throws SQLException { + closed = true; + } + + public boolean isClosed() throws SQLException { + return closed; + } + + public DatabaseMetaData getMetaData() throws SQLException { + throw new UnsupportedOperationException(); + } + + public void setReadOnly( boolean readOnly ) throws SQLException { + } + + public boolean isReadOnly() throws SQLException { + return false; + } + + public void setCatalog( String catalog ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public String getCatalog() throws SQLException { + throw new UnsupportedOperationException(); + } + + public void setTransactionIsolation( int level ) throws SQLException { + } + + public int getTransactionIsolation() throws SQLException { + return Connection.TRANSACTION_READ_UNCOMMITTED; + } + + public SQLWarning getWarnings() throws SQLException { + throw new UnsupportedOperationException(); + } + + public void clearWarnings() throws SQLException { + throw new UnsupportedOperationException(); + } + + public Statement createStatement( int resultSetType, int resultSetConcurrency ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public PreparedStatement prepareStatement( String sql, int resultSetType, int resultSetConcurrency ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public CallableStatement prepareCall( String sql, int resultSetType, int resultSetConcurrency ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.util.Map> getTypeMap() throws SQLException { + throw new UnsupportedOperationException(); + } + + public void setTypeMap( java.util.Map> map ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void setHoldability( int holdability ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public int getHoldability() throws SQLException { + throw new UnsupportedOperationException(); + } + + public Savepoint setSavepoint() throws SQLException { + throw new UnsupportedOperationException(); + } + + public Savepoint setSavepoint( String name ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void rollback( Savepoint savepoint ) throws SQLException { + } + + public void releaseSavepoint( Savepoint savepoint ) throws SQLException { + } + + public Statement createStatement( int resultSetType, int resultSetConcurrency, int resultSetHoldability ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public PreparedStatement prepareStatement( String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public CallableStatement prepareCall( String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public PreparedStatement prepareStatement( String sql, int autoGeneratedKeys ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public PreparedStatement prepareStatement( String sql, int columnIndexes[] ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public PreparedStatement prepareStatement( String sql, String columnNames[] ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public Clob createClob() throws SQLException { + throw new UnsupportedOperationException(); + } + + public Blob createBlob() throws SQLException { + throw new UnsupportedOperationException(); + } + + public NClob createNClob() throws SQLException { + throw new UnsupportedOperationException(); + } + + public SQLXML createSQLXML() throws SQLException { + throw new UnsupportedOperationException(); + } + + public boolean isValid( int timeout ) throws SQLException { + return true; + } + + public void setClientInfo( String name, String value ) throws SQLClientInfoException { + throw new UnsupportedOperationException(); + } + + public void setClientInfo( Properties properties ) throws SQLClientInfoException { + throw new UnsupportedOperationException(); + } + + public String getClientInfo( String name ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public Properties getClientInfo() throws SQLException { + throw new UnsupportedOperationException(); + } + + public Array createArrayOf( String typeName, Object[] elements ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public Struct createStruct( String typeName, Object[] attributes ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void setSchema( String schema ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public String getSchema() throws SQLException { + throw new UnsupportedOperationException(); + } + + public void abort( Executor executor ) throws SQLException { + + } + + public void setNetworkTimeout( Executor executor, int milliseconds ) throws SQLException { + + } + + public int getNetworkTimeout() throws SQLException { + return 0; + } + + @Override + public T unwrap( Class iface ) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isWrapperFor( Class iface ) throws SQLException { + return false; + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQResultSet.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQResultSet.java new file mode 100644 index 000000000..91a05017d --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQResultSet.java @@ -0,0 +1,800 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.NClob; +import java.sql.Ref; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLWarning; +import java.sql.SQLXML; +import java.sql.Statement; +import java.util.Calendar; + +public class QoQResultSet implements java.sql.ResultSet { + + public boolean next() throws SQLException { + return false; + } + + public void close() throws SQLException { + } + + public boolean wasNull() throws SQLException { + throw new UnsupportedOperationException(); + } + + public String getString( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public boolean getBoolean( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public byte getByte( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public short getShort( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public int getInt( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public long getLong( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public float getFloat( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public double getDouble( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public BigDecimal getBigDecimal( int columnIndex, int scale ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public byte[] getBytes( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.sql.Date getDate( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.sql.Time getTime( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.sql.Timestamp getTimestamp( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.io.InputStream getAsciiStream( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.io.InputStream getUnicodeStream( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.io.InputStream getBinaryStream( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public String getString( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public boolean getBoolean( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public byte getByte( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public short getShort( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public int getInt( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public long getLong( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public float getFloat( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public double getDouble( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public BigDecimal getBigDecimal( String columnLabel, int scale ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public byte[] getBytes( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.sql.Date getDate( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.sql.Time getTime( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.sql.Timestamp getTimestamp( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.io.InputStream getAsciiStream( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.io.InputStream getUnicodeStream( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.io.InputStream getBinaryStream( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public SQLWarning getWarnings() throws SQLException { + throw new UnsupportedOperationException(); + } + + public void clearWarnings() throws SQLException { + throw new UnsupportedOperationException(); + } + + public String getCursorName() throws SQLException { + throw new UnsupportedOperationException(); + } + + public ResultSetMetaData getMetaData() throws SQLException { + throw new UnsupportedOperationException(); + } + + public Object getObject( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public Object getObject( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public int findColumn( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.io.Reader getCharacterStream( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.io.Reader getCharacterStream( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public BigDecimal getBigDecimal( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public BigDecimal getBigDecimal( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public boolean isBeforeFirst() throws SQLException { + throw new UnsupportedOperationException(); + } + + public boolean isAfterLast() throws SQLException { + throw new UnsupportedOperationException(); + } + + public boolean isFirst() throws SQLException { + throw new UnsupportedOperationException(); + } + + public boolean isLast() throws SQLException { + throw new UnsupportedOperationException(); + } + + public void beforeFirst() throws SQLException { + throw new UnsupportedOperationException(); + } + + public void afterLast() throws SQLException { + throw new UnsupportedOperationException(); + } + + public boolean first() throws SQLException { + throw new UnsupportedOperationException(); + } + + public boolean last() throws SQLException { + throw new UnsupportedOperationException(); + } + + public int getRow() throws SQLException { + throw new UnsupportedOperationException(); + } + + public boolean absolute( int row ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public boolean relative( int rows ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public boolean previous() throws SQLException { + throw new UnsupportedOperationException(); + } + + public void setFetchDirection( int direction ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public int getFetchDirection() throws SQLException { + throw new UnsupportedOperationException(); + } + + public void setFetchSize( int rows ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public int getFetchSize() throws SQLException { + throw new UnsupportedOperationException(); + } + + public int getType() throws SQLException { + throw new UnsupportedOperationException(); + } + + public int getConcurrency() throws SQLException { + throw new UnsupportedOperationException(); + } + + public boolean rowUpdated() throws SQLException { + throw new UnsupportedOperationException(); + } + + public boolean rowInserted() throws SQLException { + throw new UnsupportedOperationException(); + } + + public boolean rowDeleted() throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateNull( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateBoolean( int columnIndex, boolean x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateByte( int columnIndex, byte x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateShort( int columnIndex, short x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateInt( int columnIndex, int x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateLong( int columnIndex, long x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateFloat( int columnIndex, float x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateDouble( int columnIndex, double x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateBigDecimal( int columnIndex, BigDecimal x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateString( int columnIndex, String x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateBytes( int columnIndex, byte x[] ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateDate( int columnIndex, java.sql.Date x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateTime( int columnIndex, java.sql.Time x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateTimestamp( int columnIndex, java.sql.Timestamp x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateAsciiStream( int columnIndex, java.io.InputStream x, int length ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateBinaryStream( int columnIndex, java.io.InputStream x, int length ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateCharacterStream( int columnIndex, java.io.Reader x, int length ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateObject( int columnIndex, Object x, int scaleOrLength ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateObject( int columnIndex, Object x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateNull( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateBoolean( String columnLabel, boolean x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateByte( String columnLabel, byte x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateShort( String columnLabel, short x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateInt( String columnLabel, int x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateLong( String columnLabel, long x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateFloat( String columnLabel, float x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateDouble( String columnLabel, double x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateBigDecimal( String columnLabel, BigDecimal x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateString( String columnLabel, String x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateBytes( String columnLabel, byte x[] ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateDate( String columnLabel, java.sql.Date x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateTime( String columnLabel, java.sql.Time x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateTimestamp( String columnLabel, java.sql.Timestamp x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateAsciiStream( String columnLabel, java.io.InputStream x, int length ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateBinaryStream( String columnLabel, java.io.InputStream x, int length ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateCharacterStream( String columnLabel, java.io.Reader reader, int length ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateObject( String columnLabel, Object x, int scaleOrLength ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateObject( String columnLabel, Object x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void insertRow() throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateRow() throws SQLException { + throw new UnsupportedOperationException(); + } + + public void deleteRow() throws SQLException { + throw new UnsupportedOperationException(); + } + + public void refreshRow() throws SQLException { + throw new UnsupportedOperationException(); + } + + public void cancelRowUpdates() throws SQLException { + throw new UnsupportedOperationException(); + } + + public void moveToInsertRow() throws SQLException { + throw new UnsupportedOperationException(); + } + + public void moveToCurrentRow() throws SQLException { + throw new UnsupportedOperationException(); + } + + public Statement getStatement() throws SQLException { + throw new UnsupportedOperationException(); + } + + public Object getObject( int columnIndex, java.util.Map> map ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public Ref getRef( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public Blob getBlob( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public Clob getClob( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public Array getArray( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public Object getObject( String columnLabel, java.util.Map> map ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public Ref getRef( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public Blob getBlob( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public Clob getClob( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public Array getArray( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.sql.Date getDate( int columnIndex, Calendar cal ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.sql.Date getDate( String columnLabel, Calendar cal ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.sql.Time getTime( int columnIndex, Calendar cal ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.sql.Time getTime( String columnLabel, Calendar cal ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.sql.Timestamp getTimestamp( int columnIndex, Calendar cal ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.sql.Timestamp getTimestamp( String columnLabel, Calendar cal ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.net.URL getURL( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.net.URL getURL( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateRef( int columnIndex, java.sql.Ref x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateRef( String columnLabel, java.sql.Ref x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateBlob( int columnIndex, java.sql.Blob x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateBlob( String columnLabel, java.sql.Blob x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateClob( int columnIndex, java.sql.Clob x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateClob( String columnLabel, java.sql.Clob x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateArray( int columnIndex, java.sql.Array x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateArray( String columnLabel, java.sql.Array x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public RowId getRowId( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public RowId getRowId( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateRowId( int columnIndex, RowId x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateRowId( String columnLabel, RowId x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public int getHoldability() throws SQLException { + throw new UnsupportedOperationException(); + } + + public boolean isClosed() throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateNString( int columnIndex, String nString ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateNString( String columnLabel, String nString ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateNClob( int columnIndex, NClob nClob ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateNClob( String columnLabel, NClob nClob ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public NClob getNClob( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public NClob getNClob( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public SQLXML getSQLXML( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public SQLXML getSQLXML( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateSQLXML( int columnIndex, SQLXML xmlObject ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateSQLXML( String columnLabel, SQLXML xmlObject ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public String getNString( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public String getNString( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.io.Reader getNCharacterStream( int columnIndex ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public java.io.Reader getNCharacterStream( String columnLabel ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateNCharacterStream( int columnIndex, java.io.Reader x, long length ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateNCharacterStream( String columnLabel, java.io.Reader reader, long length ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateAsciiStream( int columnIndex, java.io.InputStream x, long length ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateBinaryStream( int columnIndex, java.io.InputStream x, long length ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateCharacterStream( int columnIndex, java.io.Reader x, long length ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateAsciiStream( String columnLabel, java.io.InputStream x, long length ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateBinaryStream( String columnLabel, java.io.InputStream x, long length ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateCharacterStream( String columnLabel, java.io.Reader reader, long length ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateBlob( int columnIndex, InputStream inputStream, long length ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateBlob( String columnLabel, InputStream inputStream, long length ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateClob( int columnIndex, Reader reader, long length ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateClob( String columnLabel, Reader reader, long length ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateNClob( int columnIndex, Reader reader, long length ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateNClob( String columnLabel, Reader reader, long length ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateNCharacterStream( int columnIndex, java.io.Reader x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateNCharacterStream( String columnLabel, java.io.Reader reader ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateAsciiStream( int columnIndex, java.io.InputStream x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateBinaryStream( int columnIndex, java.io.InputStream x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateCharacterStream( int columnIndex, java.io.Reader x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateAsciiStream( String columnLabel, java.io.InputStream x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateBinaryStream( String columnLabel, java.io.InputStream x ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateCharacterStream( String columnLabel, java.io.Reader reader ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateBlob( int columnIndex, InputStream inputStream ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateBlob( String columnLabel, InputStream inputStream ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateClob( int columnIndex, Reader reader ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateClob( String columnLabel, Reader reader ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateNClob( int columnIndex, Reader reader ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void updateNClob( String columnLabel, Reader reader ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public T getObject( int columnIndex, Class type ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public T getObject( String columnLabel, Class type ) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public T unwrap( Class iface ) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isWrapperFor( Class iface ) throws SQLException { + throw new UnsupportedOperationException(); + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQService.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQService.java new file mode 100644 index 000000000..124f366e3 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQService.java @@ -0,0 +1,166 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq; + +import java.util.LinkedHashMap; +import java.util.Map; + +import ortus.boxlang.compiler.ast.sql.SQLNode; +import ortus.boxlang.compiler.ast.sql.select.SQLResultColumn; +import ortus.boxlang.compiler.ast.sql.select.SQLSelectStatement; +import ortus.boxlang.compiler.ast.sql.select.SQLTable; +import ortus.boxlang.compiler.ast.sql.select.expression.SQLColumn; +import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; +import ortus.boxlang.compiler.parser.ParsingResult; +import ortus.boxlang.compiler.parser.SQLParser; +import ortus.boxlang.runtime.BoxRuntime; +import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.dynamic.ExpressionInterpreter; +import ortus.boxlang.runtime.interop.DynamicObject; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.IStruct; +import ortus.boxlang.runtime.types.Query; +import ortus.boxlang.runtime.types.QueryColumnType; +import ortus.boxlang.runtime.types.Struct; +import ortus.boxlang.runtime.types.exceptions.DatabaseException; +import ortus.boxlang.runtime.types.exceptions.ParseException; +import ortus.boxlang.runtime.util.FRTransService; + +/** + * I handle executing query of queries + */ +public class QoQService { + + /** + * The transaction service used to track subtransactions + */ + private static final FRTransService frTransService = FRTransService.getInstance( true ); + + public static SQLNode parseSQL( String sql ) { + DynamicObject trans = frTransService.startTransaction( "BL QoQ Parse", "" ); + SQLParser parser = new SQLParser(); + ParsingResult result; + + try { + result = parser.parse( sql ); + } finally { + frTransService.endTransaction( trans ); + } + + if ( !result.isCorrect() ) { + throw new ParseException( result.getIssues(), sql ); + } + IStruct data = Struct.of( + "file", null, + "result", result + ); + BoxRuntime.getInstance().announce( "onParse", data ); + + return ( SQLNode ) result.getRoot(); + } + + public static Query executeSelect( IBoxContext context, SQLSelectStatement select ) { + Map tableLookup = new LinkedHashMap(); + + // TODO: Process all joins + String tableVarName = select.getSelect().getTable().getVariableName(); + Query source = getSourceQuery( context, tableVarName ); + tableLookup.put( select.getSelect().getTable(), source ); + + Map resultColumns = calculateResultColumns( select, tableLookup ); + + Query target = buildTargetQuery( resultColumns ); + + for ( int i = 1; i <= source.size(); i++ ) { + SQLExpression where = select.getSelect().getWhere(); + // Evaluate the where expression + if ( where == null || ( Boolean ) where.evaluate( tableLookup, i ) ) { + Object[] values = new Object[ resultColumns.size() ]; + for ( Key key : resultColumns.keySet() ) { + TypedResultColumn typedResultColumn = resultColumns.get( key ); + QueryColumnType type = typedResultColumn.type; + SQLResultColumn resultColumn = typedResultColumn.resultColumn; + Object value = resultColumn.getExpression().evaluate( tableLookup, i ); + values[ resultColumn.getOrdinalPosition() - 1 ] = value; + } + target.addRow( values ); + } + } + + return target; + } + + /** + * Build the target query based on the result columns + * + * @param resultColumns the result columns + * + * @return the target query + */ + private static Query buildTargetQuery( Map resultColumns ) { + Query target = new Query(); + for ( Key key : resultColumns.keySet() ) { + target.addColumn( key, resultColumns.get( key ).type ); + } + return target; + } + + private static Map calculateResultColumns( SQLSelectStatement select, Map tableLookup ) { + Map resultColumns = new LinkedHashMap(); + for ( SQLResultColumn resultColumn : select.getSelect().getResultColumns() ) { + // For *, expand all columns in the query + if ( resultColumn.isStarExpression() ) { + // TODO: when we add joins, handle looking up the correct table reference based on resultColumn.getTable() + var thisTable = tableLookup.get( select.getSelect().getTable() ); + for ( Key key : thisTable.getColumns().keySet() ) { + resultColumns.put( key, + TypedResultColumn.of( + thisTable.getColumns().get( key ).getType(), + new SQLResultColumn( + new SQLColumn( select.getSelect().getTable(), key.getName(), null, null ), + null, + resultColumns.size() + 1, + null, + null + ) + ) + ); + } + // Non-star columns are named after the column, or given a column_0, column_1, etc name + } else { + resultColumns.put( resultColumn.getResultColumnName(), + TypedResultColumn.of( resultColumn.getExpression().getType( tableLookup ), resultColumn ) ); + } + } + return resultColumns; + } + + private static Query getSourceQuery( IBoxContext context, String tableVarName ) { + Object oSource = ExpressionInterpreter.getVariable( context, tableVarName, false ); + if ( oSource instanceof Query qSource ) { + return qSource; + } else { + throw new DatabaseException( "The QoQ table name [" + tableVarName + "] cannot be found as a variable." ); + } + } + + public record TypedResultColumn( QueryColumnType type, SQLResultColumn resultColumn ) { + + public static TypedResultColumn of( QueryColumnType type, SQLResultColumn resultColumn ) { + return new TypedResultColumn( type, resultColumn ); + } + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQStatement.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQStatement.java new file mode 100644 index 000000000..336ac3aeb --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQStatement.java @@ -0,0 +1,217 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLWarning; + +import ortus.boxlang.compiler.ast.sql.select.SQLSelectStatement; +import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.types.Query; + +public class QoQStatement implements java.sql.Statement { + + private IBoxContext context; + private int maxRows = 0; + private Connection connection; + private Query result; + + public QoQStatement( IBoxContext context, Connection connection ) { + this.context = context; + this.connection = connection; + } + + public ResultSet executeQuery( String sql ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public int executeUpdate( String sql ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void close() throws SQLException { + } + + public int getMaxFieldSize() throws SQLException { + return Integer.MAX_VALUE; + } + + public void setMaxFieldSize( int max ) throws SQLException { + } + + public int getMaxRows() throws SQLException { + return maxRows; + } + + public void setMaxRows( int max ) throws SQLException { + maxRows = max; + } + + public void setEscapeProcessing( boolean enable ) throws SQLException { + } + + public int getQueryTimeout() throws SQLException { + return 0; + } + + public void setQueryTimeout( int seconds ) throws SQLException { + } + + public void cancel() throws SQLException { + } + + public SQLWarning getWarnings() throws SQLException { + throw new UnsupportedOperationException(); + } + + public void clearWarnings() throws SQLException { + } + + public void setCursorName( String name ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public boolean execute( String sql ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public ResultSet getResultSet() throws SQLException { + throw new UnsupportedOperationException( "Do not call this method. Since this is a Query of Query statement, use getQueryResult() instead." ); + } + + public Query getQueryResult() { + return result; + } + + public int getUpdateCount() throws SQLException { + throw new UnsupportedOperationException(); + } + + public boolean getMoreResults() throws SQLException { + throw new UnsupportedOperationException(); + } + + public void setFetchDirection( int direction ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public int getFetchDirection() throws SQLException { + throw new UnsupportedOperationException(); + } + + public void setFetchSize( int rows ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public int getFetchSize() throws SQLException { + throw new UnsupportedOperationException(); + } + + public int getResultSetConcurrency() throws SQLException { + throw new UnsupportedOperationException(); + } + + public int getResultSetType() throws SQLException { + throw new UnsupportedOperationException(); + } + + public void addBatch( String sql ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public void clearBatch() throws SQLException { + throw new UnsupportedOperationException(); + } + + public int[] executeBatch() throws SQLException { + throw new UnsupportedOperationException(); + } + + public Connection getConnection() throws SQLException { + return connection; + } + + public boolean getMoreResults( int current ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public ResultSet getGeneratedKeys() throws SQLException { + return new QoQResultSet(); + } + + public int executeUpdate( String sql, int autoGeneratedKeys ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public int executeUpdate( String sql, int columnIndexes[] ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public int executeUpdate( String sql, String columnNames[] ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public boolean execute( String sql, int autoGeneratedKeys ) throws SQLException { + // parse the SQL string into an AST object + SQLSelectStatement select = ( SQLSelectStatement ) QoQService.parseSQL( sql ); + + // execute the query + result = QoQService.executeSelect( context, select ); + return true; + } + + public boolean execute( String sql, int columnIndexes[] ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public boolean execute( String sql, String columnNames[] ) throws SQLException { + throw new UnsupportedOperationException(); + } + + public int getResultSetHoldability() throws SQLException { + throw new UnsupportedOperationException(); + } + + public boolean isClosed() throws SQLException { + return false; + } + + public void setPoolable( boolean poolable ) throws SQLException { + } + + public boolean isPoolable() throws SQLException { + return false; + } + + public void closeOnCompletion() throws SQLException { + } + + public boolean isCloseOnCompletion() throws SQLException { + return false; + } + + @Override + public T unwrap( Class iface ) throws SQLException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isWrapperFor( Class iface ) throws SQLException { + throw new UnsupportedOperationException(); + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/types/QueryColumnType.java b/src/main/java/ortus/boxlang/runtime/types/QueryColumnType.java index d011e9ef6..6a36702a3 100644 --- a/src/main/java/ortus/boxlang/runtime/types/QueryColumnType.java +++ b/src/main/java/ortus/boxlang/runtime/types/QueryColumnType.java @@ -242,4 +242,15 @@ public static QueryColumnType fromSQLType( int type ) { return OTHER; } } + + /** + * Does this represent a type that can be treated as a string? + * + * @param type The type to check. + * + * @return true if the type can be treated as a string. + */ + public static boolean isStringType( QueryColumnType type ) { + return type == VARCHAR || type == CHAR || type == TIME; + } } diff --git a/src/test/java/ortus/boxlang/compiler/QoQParseTest.java b/src/test/java/ortus/boxlang/compiler/QoQParseTest.java new file mode 100644 index 000000000..b2050f264 --- /dev/null +++ b/src/test/java/ortus/boxlang/compiler/QoQParseTest.java @@ -0,0 +1,96 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.compiler; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import ortus.boxlang.compiler.parser.ParsingResult; +import ortus.boxlang.compiler.parser.SQLParser; +import ortus.boxlang.runtime.BoxRuntime; +import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; +import ortus.boxlang.runtime.scopes.IScope; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.scopes.VariablesScope; +import ortus.boxlang.runtime.types.IStruct; +import ortus.boxlang.runtime.types.Struct; + +public class QoQParseTest { + + static BoxRuntime instance; + IBoxContext context; + IScope variables; + static Key result = new Key( "result" ); + + @BeforeAll + public static void setUp() { + instance = BoxRuntime.getInstance( true ); + } + + @AfterAll + public static void teardown() { + + } + + @BeforeEach + public void setupEach() { + context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); + variables = context.getScopeNearby( VariablesScope.name ); + } + + @Test + public void testMetadataVisitor() { + ParsingResult result = new SQLParser().parse( + """ + select foo, bar b, bum as b2, * + from mytable t + where true + order by t.baz + limit 5 + """ + ); + IStruct data = Struct.of( + "file", null, + "result", result + ); + BoxRuntime.getInstance().announce( "onParse", data ); + + assertThat( result ).isNotNull(); + System.out.println( result.getIssues() ); + assertThat( result.isCorrect() ).isTrue(); + System.out.println( result.getRoot().toJSON() ); + } + + @Test + public void testRunQoQ() { + instance.executeSource( + """ + myQry = queryNew( "col,col2", "varchar,integer", [["foo",42],["bar",9001]] ) + q = queryExecute( + "select col, 5 as brad, col2 luis from myQry where true", + [], + { dbType : "query" } + ); + println( q ) + """, + context ); + } + +} From a68886e38b566c2c4d4b184a0e38642f75e62a46 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 5 Dec 2024 08:53:53 -0600 Subject: [PATCH 068/193] fix bug on concurrency for loggers --- .../boxlang/runtime/logging/LoggingService.java | 13 +++++++------ .../interceptors/LoggingInterceptorTest.java | 11 +++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index 54d5a2d2a..b92082746 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -456,7 +456,7 @@ public Logger getLogger( String logger ) { Key loggerKey = Key.of( FilenameUtils.getBaseName( loggerFilePath.toLowerCase() ) ); // Compute it or return it - return ( Logger ) this.loggersMap.computeIfAbsent( loggerKey, key -> createLogger( loggerKey, loggerFilePath ) ); + return ( Logger ) this.loggersMap.computeIfAbsent( Key.of( loggerFilePath ), key -> createLogger( loggerKey, loggerFilePath ) ); } /** @@ -638,7 +638,8 @@ private JsonEncoder buildJsonEncoder() { } /** - * Build a logger with the specified name and file path + * Build a logger with the specified name and file path. + * This will also look into the configuration file for the logger level and additivity. * * @param loggerKey The key of the logger to build * @param loggerFilePath The file path to log to @@ -652,11 +653,11 @@ private Logger createLogger( Key loggerKey, String loggerFilePath ) { // Check if we have the logger configuration or else build a vanilla one LoggerConfig loggerConfig = ( LoggerConfig ) this.runtime .getConfiguration().logging.loggers - .computeIfAbsent( loggerKey, key -> new LoggerConfig( key.getNameNoCase(), this.runtime.getConfiguration().logging ) ); + .computeIfAbsent( loggerKey, key -> new LoggerConfig( key.getNameNoCase(), this.runtime.getConfiguration().logging ) ); + Level configLevel = Level.toLevel( LogLevel.valueOf( loggerConfig.level.getName(), false ).getName() ); - Level targetLevel = Level.toLevel( LogLevel.valueOf( loggerConfig.level.getName(), false ).getName() ); - - oLogger.setLevel( targetLevel ); + // Seed the properties + oLogger.setLevel( configLevel ); oLogger.setAdditive( loggerConfig.additive ); oLogger.addAppender( getOrBuildAppender( loggerFilePath, targetContext, loggerConfig ) ); return oLogger; diff --git a/src/test/java/ortus/boxlang/runtime/interceptors/LoggingInterceptorTest.java b/src/test/java/ortus/boxlang/runtime/interceptors/LoggingInterceptorTest.java index 18c79eecd..9b371a9d5 100644 --- a/src/test/java/ortus/boxlang/runtime/interceptors/LoggingInterceptorTest.java +++ b/src/test/java/ortus/boxlang/runtime/interceptors/LoggingInterceptorTest.java @@ -29,7 +29,6 @@ import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.dynamic.casters.StringCaster; -import ortus.boxlang.runtime.logging.LoggingService; import ortus.boxlang.runtime.scopes.IScope; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.Struct; @@ -53,25 +52,25 @@ public static void setUp() { instance = BoxRuntime.getInstance( true ); loggingInterceptor = new Logging( instance ); logDirectory = instance.getConfiguration().logging.logsDirectory; - logFilePath = Paths.get( logDirectory, "/", testLogFile.toLowerCase() ).toString(); + logFilePath = Paths.get( logDirectory, testLogFile.toLowerCase() ).toString(); absoluteLogeFilePath = Paths.get( tmpDirectory, testLogFile.toLowerCase() ).toAbsolutePath().toString(); } @AfterAll public static void teardown() { - LoggingService.getInstance().shutdownAppenders(); + // LoggingService.getInstance().shutdownAppenders(); if ( FileSystemUtil.exists( logFilePath ) ) { - FileSystemUtil.deleteFile( logFilePath ); + // FileSystemUtil.deleteFile( logFilePath ); } if ( FileSystemUtil.exists( absoluteLogeFilePath ) ) { - FileSystemUtil.deleteFile( absoluteLogeFilePath ); + // FileSystemUtil.deleteFile( absoluteLogeFilePath ); } } @DisplayName( "It can log a message" ) @Test void testLogMessage() { - System.out.println( logFilePath ); + // System.out.println( testLogFile ); loggingInterceptor.logMessage( Struct.of( Key.text, "Hello, World!", Key.type, "INFO", From dfddd68b55972cdcff82076f2b7645ad9f1ae581 Mon Sep 17 00:00:00 2001 From: lmajano Date: Thu, 5 Dec 2024 15:00:53 +0000 Subject: [PATCH 069/193] Apply cfformat changes --- src/main/java/ortus/boxlang/runtime/logging/LoggingService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index b92082746..5bf6f5c7b 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -653,7 +653,7 @@ private Logger createLogger( Key loggerKey, String loggerFilePath ) { // Check if we have the logger configuration or else build a vanilla one LoggerConfig loggerConfig = ( LoggerConfig ) this.runtime .getConfiguration().logging.loggers - .computeIfAbsent( loggerKey, key -> new LoggerConfig( key.getNameNoCase(), this.runtime.getConfiguration().logging ) ); + .computeIfAbsent( loggerKey, key -> new LoggerConfig( key.getNameNoCase(), this.runtime.getConfiguration().logging ) ); Level configLevel = Level.toLevel( LogLevel.valueOf( loggerConfig.level.getName(), false ).getName() ); // Seed the properties From 4dd7803f3088669313c89d1dc4162f8acf49652d Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 5 Dec 2024 09:12:02 -0600 Subject: [PATCH 070/193] update --- src/main/java/ortus/boxlang/runtime/interceptors/Logging.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java b/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java index d13fce7cd..87f020927 100644 --- a/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java +++ b/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java @@ -94,7 +94,6 @@ public void logMessage( IStruct data ) { ( applicationName instanceof Key ) ? ( ( Key ) applicationName ).getName() : "", logger ); - } } From 3d70be9088b4b6faee58e40bfd0aa598ea94b2ea Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Wed, 4 Dec 2024 23:48:31 -0600 Subject: [PATCH 071/193] BL-806 ASM fixes --- .../compiler/asmboxpiler/AsmHelper.java | 91 ++++++++++++++++++- .../compiler/asmboxpiler/AsmTranspiler.java | 4 + .../compiler/asmboxpiler/DividerNode.java | 26 ++++++ .../expression/BoxIdentifierTransformer.java | 4 + .../statement/BoxComponentTransformer.java | 17 +++- .../statement/BoxForIndexTransformer.java | 18 ++-- 6 files changed, 145 insertions(+), 15 deletions(-) create mode 100644 src/main/java/ortus/boxlang/compiler/asmboxpiler/DividerNode.java diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java index 8909fb5c5..22808879f 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java @@ -60,6 +60,8 @@ public class AsmHelper { + private static final int METHOD_SIZE_LIMIT = 25000; + public record LineNumberIns( List start, List end ) { } @@ -879,7 +881,9 @@ public static void methodWithContextAndClassLocator( ClassNode classNode, false ); tracker.storeNewVariable( Opcodes.ASTORE ).nodes().forEach( ( node ) -> node.accept( methodVisitor ) ); - supplier.get().forEach( node -> node.accept( methodVisitor ) ); + var nodes = supplier.get(); + + nodes.forEach( node -> node.accept( methodVisitor ) ); if ( implicityReturnNull && !returnType.equals( Type.VOID_TYPE ) ) { // push a null onto the stack so that we can return it if there isn't an explicity return @@ -1120,4 +1124,89 @@ public static List loadClass( Transpiler transpiler, BoxIdenti false ) ); return nodes; } + + public static List methodLengthGuard( + Type mainType, + List nodes, + ClassNode classNode, + String name, + Type parameterType, + Type returnType, + Transpiler transpiler ) { + + if ( nodes.size() < METHOD_SIZE_LIMIT ) { + return nodes; + } + + List> subNodes = splitifyInstructions( nodes ); + + List toReturn = subNodes.stream().map( nodeList -> { + String subName = "_sub_" + name + nodeList.hashCode(); + methodWithContextAndClassLocator( + classNode, + subName, + parameterType, + returnType, + false, + transpiler, + true, + () -> nodeList + ); + + List subMethodCallNodes = new ArrayList(); + + subMethodCallNodes.add( + new VarInsnNode( Opcodes.ALOAD, 0 ) + ); + + subMethodCallNodes.add( + new VarInsnNode( Opcodes.ALOAD, 1 ) + ); + + subMethodCallNodes.add( + new MethodInsnNode( + Opcodes.INVOKEVIRTUAL, + mainType.getInternalName(), + subName, + Type.getMethodDescriptor( returnType, parameterType ), + false + ) + ); + + subMethodCallNodes.add( new InsnNode( Opcodes.POP ) ); + + return subMethodCallNodes; + } ) + .flatMap( s -> s.stream() ) + .collect( Collectors.toList() ); + + toReturn.removeLast(); + + return toReturn; + } + + private static List> splitifyInstructions( List nodes ) { + List> subNodes = new ArrayList<>(); + int min = 0; + + while ( min < nodes.size() ) { + + for ( var i = min + Math.min( METHOD_SIZE_LIMIT, nodes.size() - min ); i > min; i-- ) { + if ( ! ( nodes.get( i ) instanceof DividerNode ) ) { + continue; + } + + subNodes.add( nodes.subList( min, i ) ); + min = i; + break; + } + + if ( nodes.size() - min <= METHOD_SIZE_LIMIT ) { + subNodes.add( nodes.subList( min, nodes.size() ) ); + break; + } + } + + return subNodes; + } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index 2037467e3..f64953453 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -583,6 +583,10 @@ public List transform( BoxNode node, TransformerContext contex try { List nodes = transformer.transform( node, context, returnValueContext ); + if ( returnValueContext == ReturnValueContext.EMPTY ) { + nodes.add( 0, new DividerNode() ); + } + return nodes; } catch ( Exception e ) { this.logger.error( "Error transforming:" + node.getClass().toString() ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/DividerNode.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/DividerNode.java new file mode 100644 index 000000000..e0f175e6b --- /dev/null +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/DividerNode.java @@ -0,0 +1,26 @@ +package ortus.boxlang.compiler.asmboxpiler; + +import org.objectweb.asm.tree.LabelNode; + +public class DividerNode extends LabelNode { + + // public DividerNode() { + // super( -1 ); + // } + + // @Override + // public int getType() { + // return -1; + // } + + // @Override + // public void accept( MethodVisitor methodVisitor ) { + // return; + // } + + // @Override + // public AbstractInsnNode clone( Map clonedLabels ) { + // return new DividerNode(); + // } + +} diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxIdentifierTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxIdentifierTransformer.java index 7fbfc9204..ee6b56ba6 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxIdentifierTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxIdentifierTransformer.java @@ -73,6 +73,10 @@ public List transform( BoxNode node, TransformerContext contex Type.getMethodDescriptor( Type.getType( Object.class ) ), false ) ); } + + if ( returnContext.empty ) { + nodes.add( new InsnNode( Opcodes.POP ) ); + } return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java index 6d4b992b0..5c42d3a90 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java @@ -212,13 +212,20 @@ private Type defineBodyLambdaClass( List body ) { AsmHelper.methodWithContextAndClassLocator( classNode, "process", Type.getType( IBoxContext.class ), Type.getType( Component.BodyResult.class ), false, transpiler, false, () -> { - List nodes = new ArrayList<>(); + List nodes = new ArrayList<>(); + List bodyNodes = body.stream() + .flatMap( statement -> transpiler.transform( statement, TransformerContext.NONE ).stream() ) + .toList(); + + // nodes.addAll( + // body.stream() + // .flatMap( statement -> transpiler.transform( statement, TransformerContext.NONE ).stream() ) + // .toList() + // ); nodes.addAll( - body.stream() - .flatMap( statement -> transpiler.transform( statement, TransformerContext.NONE ).stream() ) - .toList() - ); + AsmHelper.methodLengthGuard( + type, bodyNodes, classNode, "process", Type.getType( IBoxContext.class ), Type.getType( Component.BodyResult.class ), transpiler ) ); nodes.add( new FieldInsnNode( Opcodes.GETSTATIC, diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForIndexTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForIndexTransformer.java index 3c05b4f32..0f86460b3 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForIndexTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForIndexTransformer.java @@ -26,6 +26,7 @@ import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.VarInsnNode; import javassist.bytecode.Opcode; import ortus.boxlang.compiler.asmboxpiler.AsmHelper; @@ -73,13 +74,10 @@ public List transform( BoxNode node, TransformerContext contex nodes.addAll( transpiler.transform( forIn.getInitializer(), context, ReturnValueContext.EMPTY ) ); } - // push two nulls onto the stack in order to initialize our strategy for keeping - // the stack height consistent - // this is to allow the statement to return an expression in the case of a - // BoxScript execution - nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); + var varStore = tracker.storeNewVariable( Opcodes.ASTORE ); nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); + AsmHelper.addDebugLabel( nodes, "BoxForIndex - goto firstLoop" ); nodes.add( new JumpInsnNode( Opcode.GOTO, firstLoop ) ); AsmHelper.addDebugLabel( nodes, "BoxForIndex - loopStart" ); @@ -93,8 +91,7 @@ public List transform( BoxNode node, TransformerContext contex AsmHelper.addDebugLabel( nodes, "BoxForIndex - firstLoop" ); nodes.add( firstLoop ); - nodes.add( new InsnNode( Opcodes.SWAP ) ); - nodes.add( new InsnNode( Opcodes.POP ) ); + nodes.addAll( varStore.nodes() ); AsmHelper.addDebugLabel( nodes, "BoxForIndex - condition" ); if ( forIn.getCondition() != null ) { @@ -114,22 +111,25 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new LdcInsnNode( 1 ) ); } + AsmHelper.addDebugLabel( nodes, "BoxForIndex - goto loopend" ); nodes.add( new JumpInsnNode( Opcodes.IFEQ, loopEnd ) ); AsmHelper.addDebugLabel( nodes, "BoxForIndex - body" ); nodes.addAll( transpiler.transform( forIn.getBody(), context, ReturnValueContext.VALUE_OR_NULL ) ); + AsmHelper.addDebugLabel( nodes, "BoxForIndex - goto loopStart" ); nodes.add( new JumpInsnNode( Opcode.GOTO, loopStart ) ); AsmHelper.addDebugLabel( nodes, "BoxForIndex - breakTarget" ); nodes.add( breakTarget ); - nodes.add( new InsnNode( Opcodes.SWAP ) ); - nodes.add( new InsnNode( Opcodes.POP ) ); + nodes.addAll( varStore.nodes() ); AsmHelper.addDebugLabel( nodes, "BoxForIndex - loopEnd" ); nodes.add( loopEnd ); + nodes.add( new VarInsnNode( Opcodes.ALOAD, varStore.index() ) ); + if ( returnValueContext.empty ) { nodes.add( new InsnNode( Opcodes.POP ) ); } From 38b108f5014aba9a784e798f7b84443bf3d20d64 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Thu, 5 Dec 2024 09:43:03 -0600 Subject: [PATCH 072/193] BL-806 Add support for long methods --- .../compiler/asmboxpiler/AsmHelper.java | 6 +---- .../compiler/asmboxpiler/AsmTranspiler.java | 4 +-- .../compiler/asmboxpiler/DividerNode.java | 26 +++++-------------- 3 files changed, 9 insertions(+), 27 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java index 22808879f..82cfaee7d 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java @@ -881,9 +881,7 @@ public static void methodWithContextAndClassLocator( ClassNode classNode, false ); tracker.storeNewVariable( Opcodes.ASTORE ).nodes().forEach( ( node ) -> node.accept( methodVisitor ) ); - var nodes = supplier.get(); - - nodes.forEach( node -> node.accept( methodVisitor ) ); + supplier.get().forEach( node -> node.accept( methodVisitor ) ); if ( implicityReturnNull && !returnType.equals( Type.VOID_TYPE ) ) { // push a null onto the stack so that we can return it if there isn't an explicity return @@ -1019,8 +1017,6 @@ public static MethodNode dereferenceAndInvoke( String name, Type descriptor, Typ node.visitLdcInsn( index ); node.visitVarInsn( descriptor.getArgumentTypes()[ index ].getOpcode( Opcodes.ILOAD ), offset ); // TODO: boxing of primitives - node.visitLdcInsn( "DEBUG - ASMHelper 451" ); - node.visitInsn( Opcodes.POP ); node.visitInsn( Opcodes.AASTORE ); offset += descriptor.getArgumentTypes()[ index ].getSize(); } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index f64953453..8dfa54879 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -581,9 +581,9 @@ public List transform( BoxNode node, TransformerContext contex Transformer transformer = registry.get( node.getClass() ); if ( transformer != null ) { try { - List nodes = transformer.transform( node, context, returnValueContext ); + List nodes = new ArrayList( transformer.transform( node, context, returnValueContext ) ); - if ( returnValueContext == ReturnValueContext.EMPTY ) { + if ( returnValueContext == ReturnValueContext.EMPTY && nodes.size() > 0 ) { nodes.add( 0, new DividerNode() ); } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/DividerNode.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/DividerNode.java index e0f175e6b..e97654634 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/DividerNode.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/DividerNode.java @@ -1,26 +1,12 @@ package ortus.boxlang.compiler.asmboxpiler; -import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.InsnNode; -public class DividerNode extends LabelNode { +public class DividerNode extends InsnNode { - // public DividerNode() { - // super( -1 ); - // } - - // @Override - // public int getType() { - // return -1; - // } - - // @Override - // public void accept( MethodVisitor methodVisitor ) { - // return; - // } - - // @Override - // public AbstractInsnNode clone( Map clonedLabels ) { - // return new DividerNode(); - // } + public DividerNode() { + super( Opcodes.NOP ); + } } From 3af5f7f968cda02519680a8ca7cabe6722abbd27 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 5 Dec 2024 10:26:05 -0600 Subject: [PATCH 073/193] Fix where we trimmed all strings when converting a list to an array instead of just the query ones we wanted. --- .../runtime/bifs/global/query/QueryNew.java | 13 +++++++++- .../boxlang/runtime/types/util/ListUtil.java | 1 - .../bifs/global/array/ArrayToListTest.java | 2 +- .../bifs/global/list/ListToArrayTest.java | 25 +++++++++++++++++++ 4 files changed, 38 insertions(+), 3 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/query/QueryNew.java b/src/main/java/ortus/boxlang/runtime/bifs/global/query/QueryNew.java index 176a311c8..b4e5e019a 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/query/QueryNew.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/query/QueryNew.java @@ -24,7 +24,9 @@ import ortus.boxlang.runtime.types.Argument; import ortus.boxlang.runtime.types.Array; import ortus.boxlang.runtime.types.Query; +import ortus.boxlang.runtime.types.QueryColumnType; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; +import ortus.boxlang.runtime.types.util.BLCollector; import ortus.boxlang.runtime.types.util.ListUtil; @BoxBIF @@ -86,6 +88,10 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { if ( columnList instanceof String cl ) { columnNames = ArrayCaster.cast( ListUtil.asList( cl, "," ) + .stream() + .map( String::valueOf ) + .map( String::trim ) + .collect( BLCollector.toArray() ) ); } // If it's an array, then it's data @@ -100,7 +106,12 @@ else if ( columnList instanceof Array castedRowData ) { } // Verify Column Types - Array columnTypes = ListUtil.asList( arguments.getAsString( Key.columnTypeList ), "," ); + Array columnTypes = ListUtil + .asList( arguments.getAsString( Key.columnTypeList ), "," ) + .stream() + .map( String::valueOf ) + .map( String::trim ) + .collect( BLCollector.toArray() ); if ( columnTypes.isEmpty() ) { // add "object" as default type for ( int i = 0; i < columnNames.size(); i++ ) { diff --git a/src/main/java/ortus/boxlang/runtime/types/util/ListUtil.java b/src/main/java/ortus/boxlang/runtime/types/util/ListUtil.java index baf03dddf..d09cc8731 100644 --- a/src/main/java/ortus/boxlang/runtime/types/util/ListUtil.java +++ b/src/main/java/ortus/boxlang/runtime/types/util/ListUtil.java @@ -142,7 +142,6 @@ public static Array asList( } return Arrays.stream( result ) - .map( String::trim ) .collect( BLCollector.toArray() ); } diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/array/ArrayToListTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/array/ArrayToListTest.java index bdfd1de8f..a77b3a7cf 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/array/ArrayToListTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/array/ArrayToListTest.java @@ -126,7 +126,7 @@ public void testCastNullToString() { assertThat( joined ).isEqualTo( ",,,3" ); } - @DisplayName( "It should preseve empty delimiters" ) + @DisplayName( "It should preserve empty delimiters" ) @Test public void testPreserveEmpty() { instance.executeSource( diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/list/ListToArrayTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/list/ListToArrayTest.java index 7f4f24374..8a5f0e0ad 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/list/ListToArrayTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/list/ListToArrayTest.java @@ -121,4 +121,29 @@ public void testsMultiCharDelim() { assertEquals( result.get( 2 ), "baz" ); } + @DisplayName( "It preserves spaces in strings when splitting on empty strings" ) + @Test + public void testPreservesSpaces() { + Array result = ( Array ) instance.executeStatement( "listToArray( list='first second third', delimiter='' )" ); + assertEquals( result.size(), 18 ); + assertEquals( result.get( 0 ), "f" ); + assertEquals( result.get( 1 ), "i" ); + assertEquals( result.get( 2 ), "r" ); + assertEquals( result.get( 3 ), "s" ); + assertEquals( result.get( 4 ), "t" ); + assertEquals( result.get( 5 ), " " ); + assertEquals( result.get( 6 ), "s" ); + assertEquals( result.get( 7 ), "e" ); + assertEquals( result.get( 8 ), "c" ); + assertEquals( result.get( 9 ), "o" ); + assertEquals( result.get( 10 ), "n" ); + assertEquals( result.get( 11 ), "d" ); + assertEquals( result.get( 12 ), " " ); + assertEquals( result.get( 13 ), "t" ); + assertEquals( result.get( 14 ), "h" ); + assertEquals( result.get( 15 ), "i" ); + assertEquals( result.get( 16 ), "r" ); + assertEquals( result.get( 17 ), "d" ); + } + } From 7763ef1c2650bc61ff925b39ee098ed276b45ec2 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 5 Dec 2024 10:30:04 -0600 Subject: [PATCH 074/193] BL-815 #resolve this.javaSettings is not accounting for mappings --- .../runtime/application/Application.java | 13 +++++------ .../application/BaseApplicationListener.java | 10 ++++---- .../TestCases/phase3/ApplicationTest.java | 23 +++++++++++++++++++ 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/application/Application.java b/src/main/java/ortus/boxlang/runtime/application/Application.java index c77da54a7..0e6cf5c0a 100644 --- a/src/main/java/ortus/boxlang/runtime/application/Application.java +++ b/src/main/java/ortus/boxlang/runtime/application/Application.java @@ -207,10 +207,10 @@ public long getClassLoaderCount() { /** * Startup the class loader paths from the this.javaSettings.loadPaths * - * @param appContext The application context + * @param requestContext The request context */ - public void startupClassLoaderPaths( ApplicationBoxContext appContext ) { - URL[] loadPathsUrls = this.startingListener.getJavaSettingsLoadPaths( appContext ); + public void startupClassLoaderPaths( RequestBoxContext requestContext ) { + URL[] loadPathsUrls = this.startingListener.getJavaSettingsLoadPaths( requestContext ); // if we don't have any return out if ( loadPathsUrls.length == 0 ) { @@ -271,12 +271,11 @@ public Application start( IBoxContext context ) { this.started = true; // Get the app listener (Application.bx) - this.startingListener = context.getParentOfType( RequestBoxContext.class ).getApplicationListener(); - ApplicationBoxContext appContext = context.getParentOfType( ApplicationBoxContext.class ); + this.startingListener = context.getRequestContext().getApplicationListener(); // Startup the class loader - startupClassLoaderPaths( appContext ); + startupClassLoaderPaths( context.getRequestContext() ); // Startup session storages - startupSessionStorage( appContext ); + startupSessionStorage( context.getApplicationContext() ); // Announce it globally BoxRuntime.getInstance().getInterceptorService().announce( Key.onApplicationStart, Struct.of( diff --git a/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java b/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java index 363e55168..c9d2feb65 100644 --- a/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java +++ b/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java @@ -277,7 +277,7 @@ public DynamicClassLoader getRequestClassLoader( RequestBoxContext context ) { } // We are in app mode - URL[] loadPathsUrls = getJavaSettingsLoadPaths( context.getParentOfType( ApplicationBoxContext.class ) ); + URL[] loadPathsUrls = getJavaSettingsLoadPaths( context ); String loaderCacheKey = EncryptionUtil.hash( Arrays.toString( loadPathsUrls ) ); DynamicClassLoader target = this.application.getClassLoader( loaderCacheKey ); if ( target == null ) { @@ -291,11 +291,11 @@ public DynamicClassLoader getRequestClassLoader( RequestBoxContext context ) { * This reads the javaSettings.loadPaths, expands them, and returns them as URLs of * jars and classes * - * @param appContext The application context + * @param requestContext The request context which can contain all the necessary information to expand the paths * * @return The expanded load paths as URLs */ - public URL[] getJavaSettingsLoadPaths( ApplicationBoxContext appContext ) { + public URL[] getJavaSettingsLoadPaths( RequestBoxContext requestContext ) { // Get the source location to resolve pathing String source = StringCaster.cast( this.settings.get( Key.source ) ); ResolvedFilePath listenerResolvedPath = ResolvedFilePath.of( source ); @@ -306,7 +306,7 @@ public URL[] getJavaSettingsLoadPaths( ApplicationBoxContext appContext ) { IStruct javaSettings = this.settings.getAsStruct( Key.javaSettings ); Array loadPaths = ArrayCaster.cast( javaSettings.getOrDefault( Key.loadPaths, new Array() ) ) .stream() - .map( item -> FileSystemUtil.expandPath( appContext, ( String ) item, listenerResolvedPath ).absolutePath().toString() ) + .map( item -> FileSystemUtil.expandPath( requestContext, ( String ) item, listenerResolvedPath ).absolutePath().toString() ) .collect( BLCollector.toArray() ); // Inflate them to what we need now @@ -318,7 +318,7 @@ public URL[] getJavaSettingsLoadPaths( ApplicationBoxContext appContext ) { * discovered and passed app context */ private void createOrUpdateClassLoaderPaths() { - this.application.startupClassLoaderPaths( this.context.getParentOfType( ApplicationBoxContext.class ) ); + this.application.startupClassLoaderPaths( this.context ); } /** diff --git a/src/test/java/TestCases/phase3/ApplicationTest.java b/src/test/java/TestCases/phase3/ApplicationTest.java index 59fcf3275..cb4495d0e 100644 --- a/src/test/java/TestCases/phase3/ApplicationTest.java +++ b/src/test/java/TestCases/phase3/ApplicationTest.java @@ -231,4 +231,27 @@ public void testJavaSettingsRelativePaths() { assertThat( app.getClassLoaderCount() ).isEqualTo( 1 ); } + @DisplayName( "Can resolve mappings with java settings" ) + @Test + public void testJavaSettingsMappings() { + // @formatter:off + instance.executeSource( + """ + application + name="myJavaApp" + mappings = { "/javalib": "/src/test/resources/libs/" } + javaSettings={ + loadPaths = [ "/javalib/helloworld.jar" ], + reloadOnChange = true + }; + + hello = createObject( "java", "HelloWorld" ); + """, context ); + // @formatter:on + + ApplicationBoxContext appContext = context.getParentOfType( ApplicationBoxContext.class ); + Application app = appContext.getApplication(); + assertThat( app.getClassLoaderCount() ).isEqualTo( 1 ); + } + } From eb3e3bc418c92fd295b36f6fdb554faa2f3b4906 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 5 Dec 2024 10:41:25 -0600 Subject: [PATCH 075/193] concurreny regresion --- src/test/java/TestCases/phase3/ApplicationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/TestCases/phase3/ApplicationTest.java b/src/test/java/TestCases/phase3/ApplicationTest.java index cb4495d0e..b38638a00 100644 --- a/src/test/java/TestCases/phase3/ApplicationTest.java +++ b/src/test/java/TestCases/phase3/ApplicationTest.java @@ -238,7 +238,7 @@ public void testJavaSettingsMappings() { instance.executeSource( """ application - name="myJavaApp" + name="myJavaAppWithMappings" mappings = { "/javalib": "/src/test/resources/libs/" } javaSettings={ loadPaths = [ "/javalib/helloworld.jar" ], From 7cc02841651610d3b091cb0da6deb58c01982bcf Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 5 Dec 2024 11:25:50 -0600 Subject: [PATCH 076/193] BL-817 #resolve Missing visibility on module service functions --- .../boxlang/runtime/services/ModuleService.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/services/ModuleService.java b/src/main/java/ortus/boxlang/runtime/services/ModuleService.java index d2cd46391..43a845f7a 100644 --- a/src/main/java/ortus/boxlang/runtime/services/ModuleService.java +++ b/src/main/java/ortus/boxlang/runtime/services/ModuleService.java @@ -186,7 +186,7 @@ public void onShutdown( Boolean force ) { * Scans all possible module locations and registers all modules found * This method doesn't activate the modules, it just registers them */ - void registerAll() { + public void registerAll() { var timerLabel = "moduleservice-registerallmodules"; BoxRuntime.timerUtil.start( timerLabel ); @@ -223,7 +223,7 @@ void registerAll() { * * @throws BoxRuntimeException If the module is not in the module registry */ - void register( Key name ) { + public void register( Key name ) { var timerLabel = "moduleservice-register-" + name.getName(); BoxRuntime.timerUtil.start( timerLabel ); @@ -289,7 +289,7 @@ void register( Key name ) { /** * Activate all modules that are not disabled */ - void activateAll() { + public void activateAll() { var timerLabel = "moduleservice-activateallmodules"; BoxRuntime.timerUtil.start( timerLabel ); @@ -320,7 +320,7 @@ void activateAll() { * * @throws BoxRuntimeException If the module is not in the module registry */ - void activate( Key name ) { + public void activate( Key name ) { var timerLabel = "moduleservice-activate-" + name.getName(); BoxRuntime.timerUtil.start( timerLabel ); @@ -391,7 +391,7 @@ void activate( Key name ) { /** * Unload all modules */ - void unloadAll() { + public void unloadAll() { this.registry .keySet() .stream() @@ -403,7 +403,7 @@ void unloadAll() { * * @param name The name of the module to unload */ - void unload( Key name ) { + public void unload( Key name ) { // Check if the module is in the registry or it's already deactivated if ( !this.registry.containsKey( name ) || !this.registry.get( name ).isActivated() ) { return; From db7d17f0c5bb6f8e03dcc02b7a9cae679ebb293c Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 5 Dec 2024 11:26:06 -0600 Subject: [PATCH 077/193] more tests --- src/test/java/TestCases/phase3/ApplicationTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/test/java/TestCases/phase3/ApplicationTest.java b/src/test/java/TestCases/phase3/ApplicationTest.java index b38638a00..c5c046389 100644 --- a/src/test/java/TestCases/phase3/ApplicationTest.java +++ b/src/test/java/TestCases/phase3/ApplicationTest.java @@ -244,8 +244,6 @@ public void testJavaSettingsMappings() { loadPaths = [ "/javalib/helloworld.jar" ], reloadOnChange = true }; - - hello = createObject( "java", "HelloWorld" ); """, context ); // @formatter:on From 8457317d50198d05e26d9c7c47b7cc7f2e29b22d Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 5 Dec 2024 12:10:30 -0600 Subject: [PATCH 078/193] Add getColumnsName function for Query --- src/main/java/ortus/boxlang/runtime/types/Query.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/types/Query.java b/src/main/java/ortus/boxlang/runtime/types/Query.java index c7238506f..c381c333b 100644 --- a/src/main/java/ortus/boxlang/runtime/types/Query.java +++ b/src/main/java/ortus/boxlang/runtime/types/Query.java @@ -21,6 +21,7 @@ import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; +import java.time.Duration; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -33,7 +34,6 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; -import java.time.Duration; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.bifs.MemberDescriptor; @@ -976,4 +976,8 @@ public String toString() { return asString(); } + public Array getColumnNames() { + return getColumnArray(); + } + } From 991028cf860aba439b45bf4deba2961b091ca186 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 5 Dec 2024 12:10:49 -0600 Subject: [PATCH 079/193] Handle null savepoint for rollback in Transaction --- .../boxlang/runtime/components/jdbc/Transaction.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/components/jdbc/Transaction.java b/src/main/java/ortus/boxlang/runtime/components/jdbc/Transaction.java index 1d1d4b574..7c51106d3 100644 --- a/src/main/java/ortus/boxlang/runtime/components/jdbc/Transaction.java +++ b/src/main/java/ortus/boxlang/runtime/components/jdbc/Transaction.java @@ -30,8 +30,8 @@ import ortus.boxlang.runtime.context.IJDBCCapableContext; import ortus.boxlang.runtime.jdbc.ConnectionManager; import ortus.boxlang.runtime.jdbc.DataSource; -import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.jdbc.ITransaction; +import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.types.exceptions.DatabaseException; @@ -121,7 +121,12 @@ public BodyResult _invoke( IBoxContext context, IStruct attributes, ComponentBod transaction.commit(); break; case "rollback" : - transaction.rollback( Key.of( attributes.getAsString( Key.savepoint ) ) ); + String savepoint = attributes.getAsString( Key.savepoint ); + if ( savepoint == null ) { + transaction.rollback(); + } else { + transaction.rollback( Key.of( savepoint ) ); + } break; case "setsavepoint" : transaction.setSavepoint( Key.of( attributes.getAsString( Key.savepoint ) ) ); From 74c88fe4a0834182956f26e8742928cdefb3d8a2 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 5 Dec 2024 12:11:14 -0600 Subject: [PATCH 080/193] Null handling in transactions --- .../ortus/boxlang/runtime/jdbc/Transaction.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/Transaction.java b/src/main/java/ortus/boxlang/runtime/jdbc/Transaction.java index 307932ba3..5d5c52e0c 100644 --- a/src/main/java/ortus/boxlang/runtime/jdbc/Transaction.java +++ b/src/main/java/ortus/boxlang/runtime/jdbc/Transaction.java @@ -14,22 +14,23 @@ */ package ortus.boxlang.runtime.jdbc; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.sql.Connection; import java.sql.SQLException; import java.sql.Savepoint; import java.util.HashMap; import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.context.RequestBoxContext; -import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.events.BoxEvent; +import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.Struct; -import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.types.exceptions.DatabaseException; /** @@ -123,6 +124,9 @@ public int getIsolationLevel() { public Connection getConnection() { if ( this.connection == null ) { this.connection = this.datasource.getConnection(); + if ( this.connection == null ) { + throw new BoxRuntimeException( "Failed to acquire connection from datasource" ); + } try { this.connection.setAutoCommit( false ); From 1ad56c3776a2ab1b4e0676ffed3ea4eeb60c9752 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 5 Dec 2024 12:49:04 -0600 Subject: [PATCH 081/193] Fix for nested try statements not throwing on uncaught exception types --- .../statement/BoxTryTransformer.java | 3 ++- .../java/TestCases/phase1/CoreLangTest.java | 26 ++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTryTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTryTransformer.java index 343b36137..f591a978c 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTryTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTryTransformer.java @@ -113,7 +113,8 @@ public List transform( BoxNode node, TransformerContext contex } } nodes.addAll( AsmHelper.transformBodyExpressions( transpiler, boxTry.getFinallyBody(), context, returnValueContext ) ); - nodes.add( new JumpInsnNode( Opcodes.GOTO, finallyEndLabel ) ); + nodes.add( new VarInsnNode( Opcodes.ALOAD, eVar.index() ) ); + nodes.add( new InsnNode( Opcodes.ATHROW ) ); } TryCatchBlockNode catchHandler = new TryCatchBlockNode( tryStartLabel, tryEndLabel, finallyStartLabel, diff --git a/src/test/java/TestCases/phase1/CoreLangTest.java b/src/test/java/TestCases/phase1/CoreLangTest.java index 9fae37cfa..d393c629c 100644 --- a/src/test/java/TestCases/phase1/CoreLangTest.java +++ b/src/test/java/TestCases/phase1/CoreLangTest.java @@ -234,7 +234,6 @@ public void testTryCatch() { assertThat( variables.get( result ) ).isEqualTo( "in catch also finally" ); assertThat( variables.get( Key.of( "message" ) ) ).isEqualTo( "You cannot divide by zero." ); assertThat( variables.get( Key.of( "message2" ) ) ).isEqualTo( "You cannot divide by zero." ); - } @DisplayName( "try catch with var in CF" ) @@ -3774,7 +3773,7 @@ public void testAssignPublicJavaPropertiesDirectly() { BaseBoxContext.nullIsUndefined = true; result = BaseBoxContext.nullIsUndefined; result2 = BaseBoxContext.nullIsUndefined.len(); - + CoreLangTest.num += 5; result3 = CoreLangTest.num; """, @@ -3849,7 +3848,7 @@ function getSystem() { public void testCastStringToKey() { // @formatter:off - instance.executeSource( """ + instance.executeSource( """ getBoxContext().getRuntime().getDatasourceService().get( "myDataSourceNameFromTheArray" ) """ , context ); @@ -4106,4 +4105,25 @@ public void testBadWhitespaceAfterWordOperator() { assertThat( variables.get( result ) ).isEqualTo( 2 ); } + @DisplayName( "nested try catch with specific catch" ) + @Test + public void testNestedTryCatchWithSpecificCatch() { + // @formatter:off + instance.executeSource( + """ + result = "default"; + try { + try { + throw( type = "Different", message = "boom" ); + } catch ( Specific e ) { + result = "specific"; + } + } catch ( any e ) { + result = "general"; + } + """, + context ); + assertThat( variables.get( result ) ).isEqualTo( "general" ); + } + } From eab8edd4caabdc49a6a49c64fb9a2aba3c048a27 Mon Sep 17 00:00:00 2001 From: Jon Clausen Date: Thu, 5 Dec 2024 13:19:22 -0600 Subject: [PATCH 082/193] add tests to replicate BL-818 --- src/test/java/TestCases/phase3/Child.cfc | 7 +++++-- src/test/java/TestCases/phase3/ClassTest.java | 19 +++++++++++++++++++ src/test/java/TestCases/phase3/MyClass.bx | 4 ++-- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/src/test/java/TestCases/phase3/Child.cfc b/src/test/java/TestCases/phase3/Child.cfc index ffc8ed46e..402ad8ee5 100644 --- a/src/test/java/TestCases/phase3/Child.cfc +++ b/src/test/java/TestCases/phase3/Child.cfc @@ -1,6 +1,9 @@ -component extends="Parent" { +component extends="Parent" accessors="true" { - function init() { + property name="properties"; + + function init( properties = {} ) { + variables.properties = arguments.properties; return super.init(); } diff --git a/src/test/java/TestCases/phase3/ClassTest.java b/src/test/java/TestCases/phase3/ClassTest.java index d24377f4a..1e0b833f7 100644 --- a/src/test/java/TestCases/phase3/ClassTest.java +++ b/src/test/java/TestCases/phase3/ClassTest.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -392,6 +393,24 @@ public void testBasicClassFile() { // @formatter:on } + @DisplayName( "parent init will re-call the child init" ) + @Test + @Disabled + public void superInitTest() { + // @formatter:off + instance.executeSource( + """ + settings = { + "foo" : "bar" + }; + cfc = new src.test.java.TestCases.phase3.Child( properties=settings ); + + assert cfc.getSettings() == settings; + + """, context ); + // @formatter:on + } + @DisplayName( "basic class file via component path" ) @Test public void testBasicClassFileViaComponentPath() { diff --git a/src/test/java/TestCases/phase3/MyClass.bx b/src/test/java/TestCases/phase3/MyClass.bx index bb1cf1a4b..4db6f6432 100644 --- a/src/test/java/TestCases/phase3/MyClass.bx +++ b/src/test/java/TestCases/phase3/MyClass.bx @@ -6,9 +6,9 @@ import java.lang.System; /** * This is my class description * continued on this line - * + * * and this one - * as well. + * as well. * * @brad wood * @luis From d42269b52252da32ed8691b08e2f7f8cff4a9769 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 5 Dec 2024 13:29:53 -0600 Subject: [PATCH 083/193] Add database information to Throwable template --- src/main/resources/dump/html/Throwable.bxm | 86 ++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/src/main/resources/dump/html/Throwable.bxm b/src/main/resources/dump/html/Throwable.bxm index 3bc05eacc..0a0b4ee0b 100644 --- a/src/main/resources/dump/html/Throwable.bxm +++ b/src/main/resources/dump/html/Throwable.bxm @@ -89,6 +89,92 @@ + + + + SQL + + +
    + +
    + + + + + + Query Error + + +
    + +
    + + + + + + Where + + +
    + +
    + + + + + + SQL State + + +
    + +
    + + + + + + Native Error Code + + +
    + +
    + + +
    From 8323b551e39604eeb26f035b2a778fe9126430b6 Mon Sep 17 00:00:00 2001 From: Jon Clausen Date: Thu, 5 Dec 2024 13:33:18 -0600 Subject: [PATCH 084/193] change remove trailing whitespace directive for class tests --- .editorconfig | 2 +- src/test/java/TestCases/phase3/MyClass.bx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.editorconfig b/.editorconfig index 7cc780c00..00e6ed1ea 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,7 +5,7 @@ root = true [*] end_of_line = lf charset = utf-8 -trim_trailing_whitespace = true +trim_trailing_whitespace = false insert_final_newline = false indent_style = tab indent_size = 4 diff --git a/src/test/java/TestCases/phase3/MyClass.bx b/src/test/java/TestCases/phase3/MyClass.bx index 4db6f6432..bb1cf1a4b 100644 --- a/src/test/java/TestCases/phase3/MyClass.bx +++ b/src/test/java/TestCases/phase3/MyClass.bx @@ -6,9 +6,9 @@ import java.lang.System; /** * This is my class description * continued on this line - * + * * and this one - * as well. + * as well. * * @brad wood * @luis From 765ee9a09c0a78b9e1056cc0db41c30c7a77b8f0 Mon Sep 17 00:00:00 2001 From: Jon Clausen Date: Thu, 5 Dec 2024 13:52:30 -0600 Subject: [PATCH 085/193] update test --- src/test/java/TestCases/phase3/Child.cfc | 2 +- src/test/java/TestCases/phase3/ClassTest.java | 7 +++---- src/test/java/TestCases/phase3/Parent.cfc | 5 ++++- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/test/java/TestCases/phase3/Child.cfc b/src/test/java/TestCases/phase3/Child.cfc index 402ad8ee5..a7f87fad7 100644 --- a/src/test/java/TestCases/phase3/Child.cfc +++ b/src/test/java/TestCases/phase3/Child.cfc @@ -1,4 +1,4 @@ -component extends="Parent" accessors="true" { +component extends="Parent" { property name="properties"; diff --git a/src/test/java/TestCases/phase3/ClassTest.java b/src/test/java/TestCases/phase3/ClassTest.java index 1e0b833f7..d3473ef33 100644 --- a/src/test/java/TestCases/phase3/ClassTest.java +++ b/src/test/java/TestCases/phase3/ClassTest.java @@ -23,7 +23,6 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -393,19 +392,19 @@ public void testBasicClassFile() { // @formatter:on } - @DisplayName( "parent init will re-call the child init" ) + @DisplayName( "parent super.init will preserve child variables scope" ) @Test - @Disabled public void superInitTest() { // @formatter:off instance.executeSource( """ + request.calls = []; settings = { "foo" : "bar" }; cfc = new src.test.java.TestCases.phase3.Child( properties=settings ); - assert cfc.getSettings() == settings; + assert cfc.getProperties() == settings; """, context ); // @formatter:on diff --git a/src/test/java/TestCases/phase3/Parent.cfc b/src/test/java/TestCases/phase3/Parent.cfc index 0e435dab7..31958a073 100644 --- a/src/test/java/TestCases/phase3/Parent.cfc +++ b/src/test/java/TestCases/phase3/Parent.cfc @@ -1,6 +1,9 @@ -component { +component accessors="true"{ + + property name="foo"; function init() { + variables.foo = "bar"; setupFrameworkDefaults(); return this; } From cbdf7591e8672e268899d2444cd82eb289e1a051 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 5 Dec 2024 13:58:06 -0600 Subject: [PATCH 086/193] Add failing test showing that we need to support argument collections using structs that look like arrays --- .../boxlang/runtime/types/FunctionTest.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/test/java/ortus/boxlang/runtime/types/FunctionTest.java b/src/test/java/ortus/boxlang/runtime/types/FunctionTest.java index cdd025161..f07a5b56c 100644 --- a/src/test/java/ortus/boxlang/runtime/types/FunctionTest.java +++ b/src/test/java/ortus/boxlang/runtime/types/FunctionTest.java @@ -212,6 +212,30 @@ void testCanProcessArgumentCollectionArray() { assertThat( argscope.get( extraExtra ) ).isEqualTo( "Jorge" ); } + @DisplayName( "can process argumentCollection structs that look like arrays" ) + @Test + void testCanProcessArrayLikeStructArgumentCollection() { + Key firstName = Key.of( "firstName" ); + Key lastName = Key.of( "lastName" ); + Key key1 = Key.of( "1" ); + Key key2 = Key.of( "2" ); + Key key3 = Key.of( "3" ); + Argument[] args = new Argument[] { + new Argument( true, "String", firstName, "brad" ), + new Argument( true, "String", lastName, "wood" ) + }; + UDF udf = new SampleUDF( UDF.Access.PUBLIC, Key.of( "foo" ), "any", args, null ); + IScope argscope = udf + .createArgumentsScope( context, new HashMap( Map.of( + Function.ARGUMENT_COLLECTION, Map.of( key1, "Luis", key3, "Gavin", key2, "Majano" ) + ) ) ); + + assertThat( argscope.get( firstName ) ).isEqualTo( "Luis" ); + assertThat( argscope.get( lastName ) ).isEqualTo( "Majano" ); + assertThat( argscope.size() ).isEqualTo( 3 ); + assertThat( argscope.get( key3 ) ).isEqualTo( "Gavin" ); + } + @DisplayName( "can process argumentCollection Array override" ) @Test void testCanProcessArgumentCollectionArrayOverride() { From 8687298995695d5052f031d07dabdad6da19221a Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 5 Dec 2024 15:11:59 -0600 Subject: [PATCH 087/193] Add `function` to `callStackGet` output --- .../types/exceptions/ExceptionUtil.java | 9 ++-- .../java/TestCases/components/CallStack.bx | 11 ++++ .../bifs/global/system/CallStackGetTest.java | 54 +++++++++++++++++++ 3 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 src/test/java/TestCases/components/CallStack.bx create mode 100644 src/test/java/ortus/boxlang/runtime/bifs/global/system/CallStackGetTest.java diff --git a/src/main/java/ortus/boxlang/runtime/types/exceptions/ExceptionUtil.java b/src/main/java/ortus/boxlang/runtime/types/exceptions/ExceptionUtil.java index 9f095511f..e4aea84ee 100644 --- a/src/main/java/ortus/boxlang/runtime/types/exceptions/ExceptionUtil.java +++ b/src/main/java/ortus/boxlang/runtime/types/exceptions/ExceptionUtil.java @@ -193,16 +193,19 @@ public static Array buildTagContext( Throwable e, int depth ) { lineNo = sourceMap.convertJavaLineToSourceLine( element.getLineNumber() ); BLFileName = sourceMap.getSource(); } - String id = ""; - Matcher m = Pattern.compile( ".*\\$Func_(.*)$" ).matcher( element.getClassName() ); + String functionName = ""; + String id = ""; + Matcher m = Pattern.compile( ".*\\$Func_(.*)$" ).matcher( element.getClassName() ); if ( m.find() ) { - id = m.group( 1 ) + "()"; + functionName = m.group( 1 ); + id = id + "()"; } thisTagContext.add( Struct.of( Key.codePrintHTML, getSurroudingLinesOfCode( BLFileName, lineNo, true ), Key.codePrintPlain, getSurroudingLinesOfCode( BLFileName, lineNo, false ), Key.column, -1, Key.id, id, + Key.function, functionName, Key.line, lineNo, Key.Raw_Trace, element.toString(), Key.template, BLFileName, diff --git a/src/test/java/TestCases/components/CallStack.bx b/src/test/java/TestCases/components/CallStack.bx new file mode 100644 index 000000000..eab6c5491 --- /dev/null +++ b/src/test/java/TestCases/components/CallStack.bx @@ -0,0 +1,11 @@ +class { + + function run() { + return nested(); + } + + function nested() { + return callStackGet(); + } + +} \ No newline at end of file diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/system/CallStackGetTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/system/CallStackGetTest.java new file mode 100644 index 000000000..dc4d8e0f4 --- /dev/null +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/system/CallStackGetTest.java @@ -0,0 +1,54 @@ +package ortus.boxlang.runtime.bifs.global.system; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import ortus.boxlang.runtime.BoxRuntime; +import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; +import ortus.boxlang.runtime.scopes.IScope; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.scopes.VariablesScope; +import ortus.boxlang.runtime.types.Array; +import ortus.boxlang.runtime.types.IStruct; + +public class CallStackGetTest { + + static BoxRuntime instance; + IBoxContext context; + IScope variables; + static Key result = new Key( "result" ); + + @BeforeAll + public static void setUp() { + instance = BoxRuntime.getInstance( true ); + } + + @BeforeEach + void setupEach() { + context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); + variables = context.getScopeNearby( VariablesScope.name ); + } + + @DisplayName( "It has the correct keys when calling callStackGet" ) + @Test + void testCallStackGetKeys() { + // @formatter:off + instance.executeSource( + """ + cs = new src.test.java.TestCases.components.CallStack() + result = cs.run(); + """, + context ); + Array stack = variables.getAsArray( result ); + assertThat( stack.size() ).isEqualTo( 2 ); + IStruct frame = (IStruct) stack.get(0); + assertThat( frame.containsKey( "Function" ) ).isEqualTo( true ); + assertThat( frame.get( "Function" ) ).isEqualTo( "nested" ); + } + +} From f71e3971081b8a76e88c377038e07726883c5107 Mon Sep 17 00:00:00 2001 From: Jon Clausen Date: Thu, 5 Dec 2024 15:14:12 -0600 Subject: [PATCH 088/193] BL-818 - fail --- src/test/java/TestCases/phase3/Child.cfc | 13 ++++++++----- src/test/java/TestCases/phase3/ClassTest.java | 3 +-- src/test/java/TestCases/phase3/GrandParent.cfc | 10 ++++++++++ src/test/java/TestCases/phase3/Parent.cfc | 9 +++++---- 4 files changed, 24 insertions(+), 11 deletions(-) create mode 100644 src/test/java/TestCases/phase3/GrandParent.cfc diff --git a/src/test/java/TestCases/phase3/Child.cfc b/src/test/java/TestCases/phase3/Child.cfc index a7f87fad7..c2e40a51d 100644 --- a/src/test/java/TestCases/phase3/Child.cfc +++ b/src/test/java/TestCases/phase3/Child.cfc @@ -1,11 +1,14 @@ component extends="Parent" { - property name="properties"; + // If you un-comment this out, then the ClassTest.superInitTest will pass + // function init( struct properties = {} ) { + // super.init( argumentCollection = arguments ); + // return this; + // } - function init( properties = {} ) { - variables.properties = arguments.properties; - return super.init(); - } + function configure(){ + return variables.properties; + } private void function setupFrameworkDefaults() { request.calls.append( "running child setupFrameworkDefaults()" ); diff --git a/src/test/java/TestCases/phase3/ClassTest.java b/src/test/java/TestCases/phase3/ClassTest.java index d3473ef33..d1594d1be 100644 --- a/src/test/java/TestCases/phase3/ClassTest.java +++ b/src/test/java/TestCases/phase3/ClassTest.java @@ -403,8 +403,7 @@ public void superInitTest() { "foo" : "bar" }; cfc = new src.test.java.TestCases.phase3.Child( properties=settings ); - - assert cfc.getProperties() == settings; + assert cfc.configure() == settings; """, context ); // @formatter:on diff --git a/src/test/java/TestCases/phase3/GrandParent.cfc b/src/test/java/TestCases/phase3/GrandParent.cfc new file mode 100644 index 000000000..3b79af611 --- /dev/null +++ b/src/test/java/TestCases/phase3/GrandParent.cfc @@ -0,0 +1,10 @@ +component accessors="true"{ + + property name="grandpa"; + + function init() { + variables.grandpa = "me"; + return this; + } + +} \ No newline at end of file diff --git a/src/test/java/TestCases/phase3/Parent.cfc b/src/test/java/TestCases/phase3/Parent.cfc index 31958a073..9afdaaf25 100644 --- a/src/test/java/TestCases/phase3/Parent.cfc +++ b/src/test/java/TestCases/phase3/Parent.cfc @@ -1,9 +1,10 @@ -component accessors="true"{ +component accessors="true" extends="GrandParent" { - property name="foo"; + property name="properties" type="struct"; - function init() { - variables.foo = "bar"; + function init( struct properties = {} ) { + variables.properties = arguments.properties; + super.init(); setupFrameworkDefaults(); return this; } From fee82362d82235627bdf29cb910df530d94247f9 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Thu, 5 Dec 2024 15:52:27 -0600 Subject: [PATCH 089/193] Add component attribute test --- .../ortus/boxlang/compiler/CFTranspilerTest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/test/java/ortus/boxlang/compiler/CFTranspilerTest.java b/src/test/java/ortus/boxlang/compiler/CFTranspilerTest.java index 34e44637d..3bba2bb63 100644 --- a/src/test/java/ortus/boxlang/compiler/CFTranspilerTest.java +++ b/src/test/java/ortus/boxlang/compiler/CFTranspilerTest.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -306,4 +307,19 @@ public void testBIFReturnValueCFArrayDeleteNamed() { } + @Disabled + @Test + public void testUnquotedAttribute() { + instance.executeSource( + """ + + select messagedate + from messages + where topicid = #topicid# order by messagedate ASC + + """, + context, BoxSourceType.CFTEMPLATE ); + + } + } From 1f184d26fc73e1bdcd952edfa5d9a89fd349b23e Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Thu, 5 Dec 2024 16:13:14 -0600 Subject: [PATCH 090/193] Add another component attribute test --- src/test/java/TestCases/phase1/CoreLangTest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/test/java/TestCases/phase1/CoreLangTest.java b/src/test/java/TestCases/phase1/CoreLangTest.java index d393c629c..ef9421b59 100644 --- a/src/test/java/TestCases/phase1/CoreLangTest.java +++ b/src/test/java/TestCases/phase1/CoreLangTest.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; @@ -4126,4 +4127,15 @@ public void testNestedTryCatchWithSpecificCatch() { assertThat( variables.get( result ) ).isEqualTo( "general" ); } + @Disabled + @Test + public void testComponentAttributeName() { + assertThrows( KeyNotFoundException.class, () -> { + instance.executeSource( + """ + ftp server="xxxx"; + """, + context, BoxSourceType.CFSCRIPT ); + } ); + } } From b7a2bf2a65b5451e93ff6cc5f073d5080b03b8cc Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 5 Dec 2024 16:41:40 -0600 Subject: [PATCH 091/193] BL-239 WIP --- .../compiler/ast/sql/select/SQLSelect.java | 10 +++++ .../ast/sql/select/SQLSelectStatement.java | 10 +++++ .../operation/SQLBinaryOperation.java | 36 ++++++++++++++- .../compiler/toolchain/SQLVisitor.java | 12 ++++- .../boxlang/runtime/jdbc/qoq/QoQService.java | 45 +++++++++++++++---- .../runtime/jdbc/qoq/QoQStatement.java | 14 ++++-- .../ortus/boxlang/runtime/types/Query.java | 13 ++++++ .../ortus/boxlang/compiler/QoQParseTest.java | 19 ++++---- 8 files changed, 137 insertions(+), 22 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLSelect.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLSelect.java index 89c12fa38..d6728f4a6 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLSelect.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLSelect.java @@ -216,6 +216,16 @@ public SQLNumberLiteral getLimit() { return limit; } + /** + * Get the value of the limit node, defaulting to -1 if not set. -1 means no limit. + */ + public Long getLimitValue() { + if ( getLimit() == null ) { + return -1L; + } + return getLimit().getValue().longValue(); + } + @Override public void accept( VoidBoxVisitor v ) { // TODO Auto-generated method stub diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLSelectStatement.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLSelectStatement.java index 684172035..ea3520091 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLSelectStatement.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLSelectStatement.java @@ -116,6 +116,16 @@ public SQLNumberLiteral getLimit() { return limit; } + /** + * Get the value of the limit node, defaulting to -1 if not set. -1 means no limit. + */ + public Long getLimitValue() { + if ( getLimit() == null ) { + return -1L; + } + return getLimit().getValue().longValue(); + } + /** * Get the ORDER BY nodes */ diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBinaryOperation.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBinaryOperation.java index 1390b7cd7..c01ed27e5 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBinaryOperation.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBinaryOperation.java @@ -23,6 +23,7 @@ import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.operators.EqualsEquals; import ortus.boxlang.runtime.types.Query; import ortus.boxlang.runtime.types.QueryColumnType; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; @@ -136,7 +137,40 @@ public QueryColumnType getType( Map tableLookup ) { * Evaluate the expression */ public Object evaluate( Map tableLookup, int i ) { - throw new BoxRuntimeException( "not implemented" ); + // Implement each binary operator + switch ( operator ) { + case AND : + break; + case DIVIDE : + break; + case EQUAL : + Object leftValue = left.evaluate( tableLookup, i ); + Object rightValue = right.evaluate( tableLookup, i ); + return EqualsEquals.invoke( leftValue, rightValue, true ); + case GREATERTHAN : + break; + case GREATERTHANOREQUAL : + break; + case LESSTHAN : + break; + case LESSTHANOREQUAL : + break; + case MINUS : + break; + case MODULO : + break; + case MULTIPLY : + break; + case NOTEQUAL : + break; + case OR : + break; + case PLUS : + break; + default : + throw new BoxRuntimeException( "Unknown binary operator: " + operator ); + } + throw new UnsupportedOperationException( "Unimplemented binary operator: " + operator ); } @Override diff --git a/src/main/java/ortus/boxlang/compiler/toolchain/SQLVisitor.java b/src/main/java/ortus/boxlang/compiler/toolchain/SQLVisitor.java index 0f9c60c41..85a6f8a36 100644 --- a/src/main/java/ortus/boxlang/compiler/toolchain/SQLVisitor.java +++ b/src/main/java/ortus/boxlang/compiler/toolchain/SQLVisitor.java @@ -18,6 +18,8 @@ import ortus.boxlang.compiler.ast.sql.select.expression.literal.SQLNullLiteral; import ortus.boxlang.compiler.ast.sql.select.expression.literal.SQLNumberLiteral; import ortus.boxlang.compiler.ast.sql.select.expression.literal.SQLStringLiteral; +import ortus.boxlang.compiler.ast.sql.select.expression.operation.SQLBinaryOperation; +import ortus.boxlang.compiler.ast.sql.select.expression.operation.SQLBinaryOperator; import ortus.boxlang.compiler.parser.SQLParser; import ortus.boxlang.parser.antlr.SQLGrammar.ExprContext; import ortus.boxlang.parser.antlr.SQLGrammar.Literal_valueContext; @@ -309,6 +311,9 @@ public SQLExpression visitExpr( ExprContext ctx, SQLTable table, List j return new SQLColumn( tableRef, ctx.column_name().getText(), pos, src ); } else if ( ctx.literal_value() != null ) { return ( SQLExpression ) visit( ctx.literal_value() ); + } else if ( ctx.EQ() != null || ctx.ASSIGN() != null ) { + return new SQLBinaryOperation( visitExpr( ctx.expr( 0 ), table, joins ), visitExpr( ctx.expr( 1 ), table, joins ), SQLBinaryOperator.EQUAL, pos, + src ); } else { throw new UnsupportedOperationException( "Unimplemented expression: " + src ); } @@ -336,7 +341,12 @@ public SQLExpression visitLiteral_value( Literal_valueContext ctx ) { } else if ( ctx.FALSE_() != null ) { return new SQLBooleanLiteral( false, pos, src ); } else if ( ctx.STRING_LITERAL() != null ) { - return new SQLStringLiteral( ctx.STRING_LITERAL().getText(), pos, src ); + String str = ctx.STRING_LITERAL().getText(); + // strip quote chars + str = str.substring( 1, str.length() - 1 ); + // unescape `''` inside string + str = str.replace( "''", "'" ); + return new SQLStringLiteral( str, pos, src ); } else { throw new UnsupportedOperationException( "Unimplemented literal expression: " + src ); } diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQService.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQService.java index 124f366e3..ee6eb1918 100644 --- a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQService.java +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQService.java @@ -14,11 +14,13 @@ */ package ortus.boxlang.runtime.jdbc.qoq; +import java.sql.SQLException; import java.util.LinkedHashMap; import java.util.Map; import ortus.boxlang.compiler.ast.sql.SQLNode; import ortus.boxlang.compiler.ast.sql.select.SQLResultColumn; +import ortus.boxlang.compiler.ast.sql.select.SQLSelect; import ortus.boxlang.compiler.ast.sql.select.SQLSelectStatement; import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.sql.select.expression.SQLColumn; @@ -71,34 +73,59 @@ public static SQLNode parseSQL( String sql ) { return ( SQLNode ) result.getRoot(); } - public static Query executeSelect( IBoxContext context, SQLSelectStatement select ) { + public static Query executeSelect( IBoxContext context, SQLSelectStatement selectStatement, QoQStatement statement ) throws SQLException { Map tableLookup = new LinkedHashMap(); // TODO: Process all joins - String tableVarName = select.getSelect().getTable().getVariableName(); + String tableVarName = selectStatement.getSelect().getTable().getVariableName(); Query source = getSourceQuery( context, tableVarName ); - tableLookup.put( select.getSelect().getTable(), source ); + tableLookup.put( selectStatement.getSelect().getTable(), source ); - Map resultColumns = calculateResultColumns( select, tableLookup ); + Map resultColumns = calculateResultColumns( selectStatement, tableLookup ); Query target = buildTargetQuery( resultColumns ); + // Process one select + // TODO: refactor this out + SQLSelect select = selectStatement.getSelect(); + + Long thisSelectLimit = select.getLimitValue(); + // This boolean expression will be used to filter the records we keep + SQLExpression where = select.getWhere(); + boolean canEarlyLimit = selectStatement.getOrderBys() == null; + + // 1-based index! for ( int i = 1; i <= source.size(); i++ ) { - SQLExpression where = select.getSelect().getWhere(); + // enforce top/limit for this select. This would be a "top N" clause in the select or a "limit N" clause BEFORE the order by, which + // could exist or all selects in a union. + if ( canEarlyLimit && thisSelectLimit > -1 && i > thisSelectLimit ) { + break; + } // Evaluate the where expression if ( where == null || ( Boolean ) where.evaluate( tableLookup, i ) ) { Object[] values = new Object[ resultColumns.size() ]; for ( Key key : resultColumns.keySet() ) { - TypedResultColumn typedResultColumn = resultColumns.get( key ); - QueryColumnType type = typedResultColumn.type; - SQLResultColumn resultColumn = typedResultColumn.resultColumn; - Object value = resultColumn.getExpression().evaluate( tableLookup, i ); + SQLResultColumn resultColumn = resultColumns.get( key ).resultColumn; + Object value = resultColumn.getExpression().evaluate( tableLookup, i ); values[ resultColumn.getOrdinalPosition() - 1 ] = value; } target.addRow( values ); } } + // TODO: Sort the query + + // This is the maxRows in the query options. It takes priority. + Long overallSelectLimit = statement.getLargeMaxRows(); + // If that wasn't set, use the limit clause AFTER the order by (which could apply at the end of a union) + if ( overallSelectLimit == -1 ) { + overallSelectLimit = selectStatement.getLimitValue(); + } + + // If we have a limit for the final select, apply it here. + if ( overallSelectLimit > -1 ) { + target.truncate( overallSelectLimit ); + } return target; } diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQStatement.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQStatement.java index 336ac3aeb..15cf203fa 100644 --- a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQStatement.java +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQStatement.java @@ -26,7 +26,7 @@ public class QoQStatement implements java.sql.Statement { private IBoxContext context; - private int maxRows = 0; + private long maxRows = -1; private Connection connection; private Query result; @@ -53,10 +53,18 @@ public int getMaxFieldSize() throws SQLException { public void setMaxFieldSize( int max ) throws SQLException { } - public int getMaxRows() throws SQLException { + public void setLargeMaxRows( long max ) throws SQLException { + maxRows = max; + } + + public long getLargeMaxRows() throws SQLException { return maxRows; } + public int getMaxRows() throws SQLException { + return ( int ) maxRows; + } + public void setMaxRows( int max ) throws SQLException { maxRows = max; } @@ -170,7 +178,7 @@ public boolean execute( String sql, int autoGeneratedKeys ) throws SQLException SQLSelectStatement select = ( SQLSelectStatement ) QoQService.parseSQL( sql ); // execute the query - result = QoQService.executeSelect( context, select ); + result = QoQService.executeSelect( context, select, this ); return true; } diff --git a/src/main/java/ortus/boxlang/runtime/types/Query.java b/src/main/java/ortus/boxlang/runtime/types/Query.java index c381c333b..e368b6f14 100644 --- a/src/main/java/ortus/boxlang/runtime/types/Query.java +++ b/src/main/java/ortus/boxlang/runtime/types/Query.java @@ -673,6 +673,19 @@ public void sort( Comparator compareFunc ) { .collect( Collectors.toList() ); } + /** + * Truncate the query to a specific number of rows + * This method does not lock the query and would allow other modifications or access while trimming the rows, whcih is not an atomic operation. + */ + public Query truncate( long rows ) { + rows = Math.max( 0, rows ); + // loop and remove all rows over the count + while ( data.size() > rows ) { + data.remove( data.size() - 1 ); + } + return this; + } + /*************************** * Collection implementation ****************************/ diff --git a/src/test/java/ortus/boxlang/compiler/QoQParseTest.java b/src/test/java/ortus/boxlang/compiler/QoQParseTest.java index b2050f264..1184bcd2f 100644 --- a/src/test/java/ortus/boxlang/compiler/QoQParseTest.java +++ b/src/test/java/ortus/boxlang/compiler/QoQParseTest.java @@ -82,14 +82,17 @@ public void testMetadataVisitor() { public void testRunQoQ() { instance.executeSource( """ - myQry = queryNew( "col,col2", "varchar,integer", [["foo",42],["bar",9001]] ) - q = queryExecute( - "select col, 5 as brad, col2 luis from myQry where true", - [], - { dbType : "query" } - ); - println( q ) - """, + myQry = queryNew( "col,col2", "varchar,integer", [["foo",42],["bar",9001]] ) + q = queryExecute( " + select col, 5 as brad, col2 luis + from myQry + where col = 'foo' + ", + [], + { dbType : "query", maxRows = 1 } + ); + println( q ) + """, context ); } From 6fa71eab97523f2e30c6906f8b70b51a4485c1a3 Mon Sep 17 00:00:00 2001 From: Jon Clausen Date: Thu, 5 Dec 2024 16:42:07 -0600 Subject: [PATCH 092/193] BL-818 Resolve - add handling for Long form date with no comma --- .../runtime/util/LocalizationUtil.java | 7 +++++-- .../global/temporal/ParseDateTimeTest.java | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/util/LocalizationUtil.java b/src/main/java/ortus/boxlang/runtime/util/LocalizationUtil.java index e7c94f4a9..e70498d05 100644 --- a/src/main/java/ortus/boxlang/runtime/util/LocalizationUtil.java +++ b/src/main/java/ortus/boxlang/runtime/util/LocalizationUtil.java @@ -218,7 +218,7 @@ public final class LocalizationUtil { // Matches long form date strings like "Jan 1, 2023" or "January 1, 2023" public static final Pattern REGEX_LONGFORM_PATTERN = Pattern.compile( - "(Jan(uary)?|Feb(ruary)?|Mar(ch)?|Apr(il)?|May|Jun(e)?|Jul(y)?|Aug(ust)?|Sep(tember)?|Oct(ober)?|Nov(ember)?|Dec(ember)?)\\s+\\d{1,2},\\s+\\d{4}" + "(Jan(uary)?|Feb(ruary)?|Mar(ch)?|Apr(il)?|May|Jun(e)?|Jul(y)?|Aug(ust)?|Sep(tember)?|Oct(ober)?|Nov(ember)?|Dec(ember)?)\\s+\\d{1,2},?\\s+\\d{4}" ); // Matches timezone offset like +01:00, -08:00, or Z public static final Pattern REGEX_TZ_OFFSET_PATTERN = Pattern.compile( ".*[+-][0-9]{2}:?[0-9]{2}|Z$" ); @@ -581,7 +581,7 @@ public static DecimalFormatSymbols localizedDecimalSymbols( Locale locale ) { public static ZonedDateTime parseFromString( String dateTime, Locale locale, ZoneId timezone ) { Boolean likelyHasDate = dateTime.contains( "/" ) || dateTime.contains( "-" ); - Boolean likelyIsLongFormDate = !likelyHasDate && REGEX_LONGFORM_PATTERN.matcher( dateTime ).matches(); + Boolean likelyIsLongFormDate = !likelyHasDate && REGEX_LONGFORM_PATTERN.matcher( dateTime ).find(); Boolean likelyHasTime = dateTime.contains( ":" ); Boolean likelyHasDateTime = ( likelyIsLongFormDate || likelyHasDate ) && likelyHasTime; Boolean likelyIsTimeOnly = !likelyHasDate && !likelyIsLongFormDate && likelyHasTime; @@ -742,6 +742,9 @@ public static DateTimeFormatterBuilder appendLocaleDateTimeParsers( DateTimeForm .appendPattern( "yyyy-MM-dd hh:mm:ss a" ) .toFormatter( locale ) ) + .appendOptional( + DateTimeFormatter.ofPattern( "MMMM d yyyy HH:mm" ) + ) .appendOptional( DateTimeFormatter.ofPattern( DateTime.ISO_DATE_TIME_MILIS_FORMAT_MASK ) ) .appendOptional( DateTimeFormatter.ofPattern( DateTime.ISO_DATE_TIME_MILIS_NO_T_FORMAT_MASK ) ) .appendOptional( DateTimeFormatter.ofPattern( DateTime.ISO_DATE_TIME_VARIATION_FORMAT_MASK ) ) diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/temporal/ParseDateTimeTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/temporal/ParseDateTimeTest.java index 07f840ced..cf4a3ad5f 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/temporal/ParseDateTimeTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/temporal/ParseDateTimeTest.java @@ -365,4 +365,24 @@ public void testParseLuisFormat() { } + @DisplayName( "It tests the BIF ParseDateTime with a common epoch pattern" ) + @Test + public void testParseLongAndShortDateTime() { + instance.executeSource( + """ + result = ParseDateTime( "January 1 1970 00:00" ); + """, + context ); + DateTime result = ( DateTime ) variables.get( Key.of( "result" ) ); + assertThat( result ).isInstanceOf( DateTime.class ); + assertThat( result.toString() ).isInstanceOf( String.class ); + assertThat( IntegerCaster.cast( result.format( "yyyy" ) ) ).isEqualTo( 1970 ); + assertThat( IntegerCaster.cast( result.format( "M" ) ) ).isEqualTo( 1 ); + assertThat( IntegerCaster.cast( result.format( "d" ) ) ).isEqualTo( 1 ); + assertThat( IntegerCaster.cast( result.format( "H" ) ) ).isEqualTo( 0 ); + assertThat( IntegerCaster.cast( result.format( "m" ) ) ).isEqualTo( 0 ); + assertThat( IntegerCaster.cast( result.format( "s" ) ) ).isEqualTo( 0 ); + + } + } From f45539ff5532f5f5b119b91606d479ba62b1f50c Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 5 Dec 2024 16:50:30 -0600 Subject: [PATCH 093/193] class test updates --- src/test/java/TestCases/phase3/Child.cfc | 6 +++++- src/test/java/TestCases/phase3/ClassTest.java | 2 ++ src/test/java/TestCases/phase3/GrandParent.cfc | 6 +++++- src/test/java/TestCases/phase3/Parent.cfc | 6 +++++- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/test/java/TestCases/phase3/Child.cfc b/src/test/java/TestCases/phase3/Child.cfc index c2e40a51d..d6654e499 100644 --- a/src/test/java/TestCases/phase3/Child.cfc +++ b/src/test/java/TestCases/phase3/Child.cfc @@ -15,4 +15,8 @@ component extends="Parent" { super.setupFrameworkDefaults(); } -} \ No newline at end of file + function childFunction(){ + return "childFunction"; + } + +} diff --git a/src/test/java/TestCases/phase3/ClassTest.java b/src/test/java/TestCases/phase3/ClassTest.java index d1594d1be..59a0442f6 100644 --- a/src/test/java/TestCases/phase3/ClassTest.java +++ b/src/test/java/TestCases/phase3/ClassTest.java @@ -23,6 +23,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -394,6 +395,7 @@ public void testBasicClassFile() { @DisplayName( "parent super.init will preserve child variables scope" ) @Test + @Disabled( "For Brad's super duper ref map fix" ) public void superInitTest() { // @formatter:off instance.executeSource( diff --git a/src/test/java/TestCases/phase3/GrandParent.cfc b/src/test/java/TestCases/phase3/GrandParent.cfc index 3b79af611..b4998ddff 100644 --- a/src/test/java/TestCases/phase3/GrandParent.cfc +++ b/src/test/java/TestCases/phase3/GrandParent.cfc @@ -7,4 +7,8 @@ component accessors="true"{ return this; } -} \ No newline at end of file + function grandParentFunction(){ + return "grandParentFunction"; + } + +} diff --git a/src/test/java/TestCases/phase3/Parent.cfc b/src/test/java/TestCases/phase3/Parent.cfc index 9afdaaf25..d4549c8da 100644 --- a/src/test/java/TestCases/phase3/Parent.cfc +++ b/src/test/java/TestCases/phase3/Parent.cfc @@ -13,4 +13,8 @@ component accessors="true" extends="GrandParent" { request.calls.append( "running parent setupFrameworkDefaults()" ); } -} \ No newline at end of file + function parentFunction(){ + return "parentFunction"; + } + +} From 570a8056bdfed1da2b566c9504baf7e74206e912 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 5 Dec 2024 16:57:21 -0600 Subject: [PATCH 094/193] BL-812 --- .../expression/BoxAssignmentTransformer.java | 13 ++++++++++++- src/test/java/TestCases/phase1/CoreLangTest.java | 5 ++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxAssignmentTransformer.java b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxAssignmentTransformer.java index 992b85d70..8b9c3662d 100644 --- a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxAssignmentTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxAssignmentTransformer.java @@ -242,7 +242,7 @@ public Node transformEquals( BoxExpression left, Expression jRight, BoxAssignmen ) """; } else { - if ( accessKeys.size() == 0 ) { + if ( accessKeys.size() == 0 && ! ( left instanceof BoxScope ) ) { throw new ExpressionException( "You cannot assign a value to " + left.getClass().getSimpleName(), left.getPosition(), left.getSourceText() ); } values.put( "furthestLeft", transpiler.transform( furthestLeft, TransformerContext.NONE ).toString() ); @@ -260,6 +260,17 @@ public Node transformEquals( BoxExpression left, Expression jRight, BoxAssignmen ${accessKeys} ) """; + if ( accessKeys.size() == 0 ) { + template = """ + Referencer.setDeep( + ${contextName}, + ${hasFinal}, + ${mustBeScopeName}, + ${furthestLeft}, + ${right} + ) + """; + } } Node javaExpr = parseExpression( template, values ); diff --git a/src/test/java/TestCases/phase1/CoreLangTest.java b/src/test/java/TestCases/phase1/CoreLangTest.java index ef9421b59..2e985947f 100644 --- a/src/test/java/TestCases/phase1/CoreLangTest.java +++ b/src/test/java/TestCases/phase1/CoreLangTest.java @@ -30,7 +30,6 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Timeout; @@ -4127,14 +4126,14 @@ public void testNestedTryCatchWithSpecificCatch() { assertThat( variables.get( result ) ).isEqualTo( "general" ); } - @Disabled +// @Disabled @Test public void testComponentAttributeName() { assertThrows( KeyNotFoundException.class, () -> { instance.executeSource( """ ftp server="xxxx"; - """, + """, context, BoxSourceType.CFSCRIPT ); } ); } From a028d7cfeb717a211c20051eb0ae023fd563c5dc Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 5 Dec 2024 16:57:37 -0600 Subject: [PATCH 095/193] BL-812 --- src/test/java/TestCases/phase1/CoreLangTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/TestCases/phase1/CoreLangTest.java b/src/test/java/TestCases/phase1/CoreLangTest.java index 2e985947f..1b8ef7aaa 100644 --- a/src/test/java/TestCases/phase1/CoreLangTest.java +++ b/src/test/java/TestCases/phase1/CoreLangTest.java @@ -4126,7 +4126,6 @@ public void testNestedTryCatchWithSpecificCatch() { assertThat( variables.get( result ) ).isEqualTo( "general" ); } -// @Disabled @Test public void testComponentAttributeName() { assertThrows( KeyNotFoundException.class, () -> { From f5d86108b388c0e39ebb815679115339523b16e5 Mon Sep 17 00:00:00 2001 From: Jon Clausen Date: Thu, 5 Dec 2024 17:27:47 -0600 Subject: [PATCH 096/193] BL-820 resolve - Workaround for incorrectly padded JWT by jwtCFML --- .../bifs/global/binary/BinaryDecode.java | 21 ++++++++++++++++++- .../bifs/global/binary/BinaryDecodeTest.java | 17 +++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/binary/BinaryDecode.java b/src/main/java/ortus/boxlang/runtime/bifs/global/binary/BinaryDecode.java index ed2f4aaa6..9eb6ec883 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/binary/BinaryDecode.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/binary/BinaryDecode.java @@ -68,7 +68,26 @@ else if ( encodingKey.equals( Key.encodingUU ) ) { } // Base64 encoding else if ( encodingKey.equals( Key.encodingBase64 ) ) { - return Base64.getDecoder().decode( ref ); + try { + return Base64.getDecoder().decode( ref ); + } catch ( java.lang.IllegalArgumentException e ) { + String initialRef = ref; + try { + // Try decoding via UU if artificially/incorrectly padded + // TODO: This is a code smell, but it is a workaround for BL-820 caused by jwtCFML + while ( ref.substring( ref.length() - 1 ).equals( "=" ) ) { + ref = ref.substring( 0, ref.length() - 1 ); + } + return Base64.getMimeDecoder().decode( ref ); + } catch ( java.lang.IllegalArgumentException e2 ) { + throw new BoxRuntimeException( + String.format( + "The string argument [%s] is not a valid Base64 encoded string for the function BinaryDecode", + initialRef + ) + ); + } + } } // Base64 URL encoding else if ( encodingKey.equals( Key.encodingBase64Url ) ) { diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/binary/BinaryDecodeTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/binary/BinaryDecodeTest.java index 1d5a325cb..bcd54d204 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/binary/BinaryDecodeTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/binary/BinaryDecodeTest.java @@ -19,6 +19,7 @@ package ortus.boxlang.runtime.bifs.global.binary; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import java.util.Base64; @@ -119,4 +120,20 @@ public void testMemberFunction() { assertTrue( variables.get( result ) instanceof byte[] ); } + @DisplayName( "It tests the member function for BinaryDecode with invalid padding" ) + @Test + public void testBinaryDecodeJWT() { + String jwt = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9===="; + + variables.put( Key.of( "base64String" ), jwt ); + instance.executeSource( + """ + result = BinaryDecode( base64String, "base64" ); + """, + context ); + + assertTrue( variables.get( result ) instanceof byte[] ); + assertEquals( "{\"typ\":\"JWT\",\"alg\":\"HS512\"}", new String( ( byte[] ) variables.get( result ) ) ); + } + } From a385205c68a867f5b81970dcf3d3d49058b92e54 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 5 Dec 2024 17:37:27 -0600 Subject: [PATCH 097/193] Don't force flush the buffer on exceptions --- src/main/java/ortus/boxlang/runtime/types/Function.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/types/Function.java b/src/main/java/ortus/boxlang/runtime/types/Function.java index cb0536dde..44507b32f 100644 --- a/src/main/java/ortus/boxlang/runtime/types/Function.java +++ b/src/main/java/ortus/boxlang/runtime/types/Function.java @@ -203,7 +203,6 @@ public Object invoke( FunctionBoxContext context ) { } throw e; } catch ( Throwable e ) { - context.flushBuffer( true ); throw e; } finally { context.popTemplate(); From 7316d010d5f685ef38307b12b8718ec1c353de9c Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 5 Dec 2024 17:37:38 -0600 Subject: [PATCH 098/193] Add the generated key to the query meta, if available --- src/main/java/ortus/boxlang/runtime/jdbc/ExecutedQuery.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/ExecutedQuery.java b/src/main/java/ortus/boxlang/runtime/jdbc/ExecutedQuery.java index 5048d9d6d..26e33337f 100644 --- a/src/main/java/ortus/boxlang/runtime/jdbc/ExecutedQuery.java +++ b/src/main/java/ortus/boxlang/runtime/jdbc/ExecutedQuery.java @@ -141,6 +141,10 @@ public static ExecutedQuery fromPendingQuery( @Nonnull PendingQuery pendingQuery "executionTime", executionTime ); + if ( generatedKey != null ) { + queryMeta.put( "generatedKey", generatedKey ); + } + // important that we set the metadata on the Query object for later getBoxMeta(), i.e. $bx.meta calls. results.setMetadata( queryMeta ); ExecutedQuery executedQuery = new ExecutedQuery( results, generatedKey ); From a2490a7dc72f51af69cff3c392b183072d9b052b Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 5 Dec 2024 18:12:16 -0600 Subject: [PATCH 099/193] BL-822 #resolve prefix IClassRunnable methods to avoid collisions --- .../transformer/BoxClassTransformer.java | 2 +- .../bifs/global/system/DebugBoxContexts.java | 2 +- .../interop/DynamicInteropService.java | 4 ++-- .../boxlang/runtime/modules/ModuleRecord.java | 4 ++-- .../boxlang/runtime/operators/InstanceOf.java | 24 +++++++++---------- .../runtime/runnables/BoxClassSupport.java | 10 ++++---- .../runtime/runnables/IClassRunnable.java | 4 ++-- .../boxlang/runtime/types/meta/ClassMeta.java | 6 ++--- .../util/conversion/BoxClassState.java | 2 +- .../boxlang/compiler/CFTranspilerTest.java | 2 +- 10 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/BoxClassTransformer.java b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/BoxClassTransformer.java index bb1f99047..9e3614949 100644 --- a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/BoxClassTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/BoxClassTransformer.java @@ -272,7 +272,7 @@ public IStruct getDocumentation() { return documentation; } - public Key getName() { + public Key bxGetName() { return this.name; } diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/system/DebugBoxContexts.java b/src/main/java/ortus/boxlang/runtime/bifs/global/system/DebugBoxContexts.java index 95684453f..f8960e98f 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/system/DebugBoxContexts.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/system/DebugBoxContexts.java @@ -62,7 +62,7 @@ private Object generateContextData( IBoxContext c ) { ( c instanceof FunctionBoxContext fbc ? fbc.getFunction().getName() + "() - " + fbc.getFunction().getClass().getSuperclass().getSimpleName() : "N/A" ), "isInClass", - ( c instanceof FunctionBoxContext fbc ? fbc.isInClass() + ( fbc.isInClass() ? " (" + fbc.getThisClass().getName() + ")" : "" ) : "N/A" ), + ( c instanceof FunctionBoxContext fbc ? fbc.isInClass() + ( fbc.isInClass() ? " (" + fbc.getThisClass().bxGetName() + ")" : "" ) : "N/A" ), "declaringContext", ( c instanceof ClosureBoxContext cbc ? generateContextData( cbc.getFunction().getDeclaringContext() ) : "N/A" ) ); } diff --git a/src/main/java/ortus/boxlang/runtime/interop/DynamicInteropService.java b/src/main/java/ortus/boxlang/runtime/interop/DynamicInteropService.java index 2a8805169..c4ea734c1 100644 --- a/src/main/java/ortus/boxlang/runtime/interop/DynamicInteropService.java +++ b/src/main/java/ortus/boxlang/runtime/interop/DynamicInteropService.java @@ -431,7 +431,7 @@ private static T bootstrapBLClass( IBoxContext context, IClassRunnable boxCl // Check for final annotation and throw if we're trying to extend a final class if ( _super.getAnnotations().get( Key._final ) != null ) { - throw new BoxRuntimeException( "Cannot extend final class: " + _super.getName() ); + throw new BoxRuntimeException( "Cannot extend final class: " + _super.bxGetName() ); } // Set in our super class boxClass.setSuper( _super ); @@ -462,7 +462,7 @@ private static T bootstrapBLClass( IBoxContext context, IClassRunnable boxCl if ( !noInit ) { if ( boxClass.getAnnotations().get( Key._ABSTRACT ) != null ) { - throw new AbstractClassException( "Cannot instantiate an abstract class: " + boxClass.getName() ); + throw new AbstractClassException( "Cannot instantiate an abstract class: " + boxClass.bxGetName() ); } if ( boxClass.getSuper() != null ) { BoxClassSupport.validateAbstractMethods( boxClass, boxClass.getSuper().getAllAbstractMethods() ); diff --git a/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java b/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java index eac37380e..d64b25924 100644 --- a/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java +++ b/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java @@ -812,7 +812,7 @@ private ModuleRecord registerComponent( File targetFile, IBoxContext context ) { // Get Name Delegate oComponent.getVariablesScope().put( Key.getName, new DynamicFunction( Key.getName, - ( context1, fnc ) -> oComponent.getName() + ( context1, fnc ) -> oComponent.bxGetName() ) ); /** @@ -823,7 +823,7 @@ private ModuleRecord registerComponent( File targetFile, IBoxContext context ) { IStruct annotations = oComponent.getBoxMeta().getMeta().getAsStruct( Key.annotations ); ComponentDescriptor descriptor = new ComponentDescriptor( - oComponent.getName(), + oComponent.bxGetName(), oComponent.getClass(), this.name.getName(), null, diff --git a/src/main/java/ortus/boxlang/runtime/operators/InstanceOf.java b/src/main/java/ortus/boxlang/runtime/operators/InstanceOf.java index de40981fb..df84e002c 100644 --- a/src/main/java/ortus/boxlang/runtime/operators/InstanceOf.java +++ b/src/main/java/ortus/boxlang/runtime/operators/InstanceOf.java @@ -60,7 +60,7 @@ public static Boolean invoke( IBoxContext context, Object left, Object right ) { // First perform exact boxClass check if ( left instanceof IClassRunnable boxClass2 ) { boxClass = boxClass2; - String boxClassName = boxClass.getName().getName(); + String boxClassName = boxClass.bxGetName().getName(); if ( looseClassCheck( boxClassName, type ) ) { return true; } @@ -76,7 +76,7 @@ public static Boolean invoke( IBoxContext context, Object left, Object right ) { IClassRunnable _super = boxClass; while ( ( _super = _super.getSuper() ) != null ) { // For each super class, check if it's the same as the type - String boxClassName = _super.getName().getName(); + String boxClassName = _super.bxGetName().getName(); if ( looseClassCheck( boxClassName, type ) ) { return true; } @@ -102,10 +102,10 @@ public static Boolean invoke( IBoxContext context, Object left, Object right ) { /** * I check a list of interfaces for a specific type - * + * * @param interfaces The interfaces to check * @param type The type to check against - * + * * @return true if the interface is assignable from the type */ private static Boolean checkInterfaces( List interfaces, String type ) { @@ -119,10 +119,10 @@ private static Boolean checkInterfaces( List interfaces, String ty /** * I check a single interface for a specific type and recurse into its super interfaces - * + * * @param type The type to check against * @param boxInterface The interface to check - * + * * @return true if the interface is assignable from the type */ private static Boolean checkInterface( String type, BoxInterface boxInterface ) { @@ -142,10 +142,10 @@ private static Boolean checkInterface( String type, BoxInterface boxInterface ) /** * Check Java inheritance - * + * * @param targetTypeName The string type to check against * @param leftClass The class to check - * + * * @return true if the class is assignable from the type */ private static boolean isAssignableFromIgnoreCase( String targetTypeName, Class leftClass ) { @@ -164,10 +164,10 @@ private static boolean isAssignableFromIgnoreCase( String targetTypeName, Class< /** * Check Java interfaces including super interfaces - * + * * @param interfaces The interfaces to check * @param targetTypeName The type to check against - * + * * @return true if the interface is assignable from the type */ private static boolean checkJavaInterfaces( Class[] interfaces, String targetTypeName ) { @@ -185,10 +185,10 @@ private static boolean checkJavaInterfaces( Class[] interfaces, String target /** * Will match java.lang.String or just String, or even java.lang.string or string. - * + * * @param actual The actual class name * @param expected The expected class name - * + * * @return true if the class names match */ private static boolean looseClassCheck( String actual, String expected ) { diff --git a/src/main/java/ortus/boxlang/runtime/runnables/BoxClassSupport.java b/src/main/java/ortus/boxlang/runtime/runnables/BoxClassSupport.java index 93acedd8e..f12795337 100644 --- a/src/main/java/ortus/boxlang/runtime/runnables/BoxClassSupport.java +++ b/src/main/java/ortus/boxlang/runtime/runnables/BoxClassSupport.java @@ -117,7 +117,7 @@ public static BoxMeta getBoxMeta( IClassRunnable thisClass ) { * @return The string representation */ public static String asString( IClassRunnable thisClass ) { - return "Class: " + thisClass.getName().getName(); + return "Class: " + thisClass.bxGetName().getName(); } /** @@ -471,7 +471,7 @@ public static IStruct getMetaData( IClassRunnable thisClass ) { functions.add( fun.getMetaData() ); } } - meta.put( "name", thisClass.getName().getName() ); + meta.put( "name", thisClass.bxGetName().getName() ); meta.put( "accessors", hasAccessors( thisClass ) ); meta.put( "functions", Array.fromList( functions ) ); @@ -502,8 +502,8 @@ public static IStruct getMetaData( IClassRunnable thisClass ) { } meta.put( "properties", properties ); meta.put( "type", "Component" ); - meta.put( "name", thisClass.getName().getName() ); - meta.put( "fullname", thisClass.getName().getName() ); + meta.put( "name", thisClass.bxGetName().getName() ); + meta.put( "fullname", thisClass.bxGetName().getName() ); meta.put( "path", thisClass.getRunnablePath().absolutePath().toString() ); meta.put( "persisent", false ); @@ -737,7 +737,7 @@ public static DynamicObject ensureClass( IBoxContext context, Object obj, List abstractMethods ) { - String className = thisClass.getName().getName(); + String className = thisClass.bxGetName().getName(); // Having an onMissingMethod() UDF is the golden ticket to implementing any interface if ( thisClass.getThisScope().get( Key.onMissingMethod ) instanceof Function ) { diff --git a/src/main/java/ortus/boxlang/runtime/runnables/IClassRunnable.java b/src/main/java/ortus/boxlang/runtime/runnables/IClassRunnable.java index 8f910c33d..0bf66b601 100644 --- a/src/main/java/ortus/boxlang/runtime/runnables/IClassRunnable.java +++ b/src/main/java/ortus/boxlang/runtime/runnables/IClassRunnable.java @@ -51,7 +51,7 @@ public interface IClassRunnable extends ITemplateRunnable, IStruct { /** * Get the name */ - public Key getName(); + public Key bxGetName(); /** * Get the variables scope @@ -112,7 +112,7 @@ public interface IClassRunnable extends ITemplateRunnable, IStruct { /** * A helper to look at the "output" annotation, caching the result - * + * * @return Whether the function can output */ public Boolean canOutput(); diff --git a/src/main/java/ortus/boxlang/runtime/types/meta/ClassMeta.java b/src/main/java/ortus/boxlang/runtime/types/meta/ClassMeta.java index c9096a4ed..23770629f 100644 --- a/src/main/java/ortus/boxlang/runtime/types/meta/ClassMeta.java +++ b/src/main/java/ortus/boxlang/runtime/types/meta/ClassMeta.java @@ -64,8 +64,8 @@ public ClassMeta( IClassRunnable target ) { } ); this.meta = UnmodifiableStruct.of( - Key._NAME, target.getName().getName(), - Key.nameAsKey, target.getName(), + Key._NAME, target.bxGetName().getName(), + Key.nameAsKey, target.bxGetName(), Key.documentation, UnmodifiableStruct.fromStruct( target.getDocumentation() ), Key.annotations, UnmodifiableStruct.fromStruct( target.getAnnotations() ), Key._EXTENDS, target.getSuper() != null ? target.getSuper().getBoxMeta().getMeta() : Struct.EMPTY, @@ -81,7 +81,7 @@ public ClassMeta( IClassRunnable target ) { Key.documentation, UnmodifiableStruct.fromStruct( entry.getValue().documentation() ) ) ).toArray() ), Key.type, "Component", - Key.fullname, target.getName().getName(), + Key.fullname, target.bxGetName().getName(), Key.path, target.getRunnablePath().absolutePath().toString() ); diff --git a/src/main/java/ortus/boxlang/runtime/util/conversion/BoxClassState.java b/src/main/java/ortus/boxlang/runtime/util/conversion/BoxClassState.java index 10cb63083..f2492f7c8 100644 --- a/src/main/java/ortus/boxlang/runtime/util/conversion/BoxClassState.java +++ b/src/main/java/ortus/boxlang/runtime/util/conversion/BoxClassState.java @@ -65,7 +65,7 @@ public class BoxClassState implements Serializable { */ public BoxClassState( IClassRunnable target ) { // Store the class path - this.classPath = target.getName(); + this.classPath = target.bxGetName(); // Get the metadata properties to see which ones // are NOT serializable Array aProperties = target.getBoxMeta().getMeta().getAsArray( Key.properties ); diff --git a/src/test/java/ortus/boxlang/compiler/CFTranspilerTest.java b/src/test/java/ortus/boxlang/compiler/CFTranspilerTest.java index 3bba2bb63..0e75aad8c 100644 --- a/src/test/java/ortus/boxlang/compiler/CFTranspilerTest.java +++ b/src/test/java/ortus/boxlang/compiler/CFTranspilerTest.java @@ -176,7 +176,7 @@ public void testNewComponent() { """, context, BoxSourceType.CFSCRIPT ); assertThat( variables.get( Key.of( "clazz" ) ) ).isInstanceOf( IClassRunnable.class ); - assertThat( ( ( IClassRunnable ) variables.get( Key.of( "clazz" ) ) ).getName().getName() ).isEqualTo( "src.test.java.TestCases.phase3.MyClassCF" ); + assertThat( ( ( IClassRunnable ) variables.get( Key.of( "clazz" ) ) ).bxGetName().getName() ).isEqualTo( "src.test.java.TestCases.phase3.MyClassCF" ); } @DisplayName( "Test BIF return value" ) From 84b8999a26dcae03d5be809a432e770192324db4 Mon Sep 17 00:00:00 2001 From: Jon Clausen Date: Thu, 5 Dec 2024 18:21:56 -0600 Subject: [PATCH 100/193] BL-821 Resolve - Ensure App timezone directive is promoted --- .../runtime/context/RequestBoxContext.java | 6 +++ .../TestCases/phase3/ApplicationTest.java | 43 +++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/main/java/ortus/boxlang/runtime/context/RequestBoxContext.java b/src/main/java/ortus/boxlang/runtime/context/RequestBoxContext.java index df81f5467..c47872694 100644 --- a/src/main/java/ortus/boxlang/runtime/context/RequestBoxContext.java +++ b/src/main/java/ortus/boxlang/runtime/context/RequestBoxContext.java @@ -38,6 +38,7 @@ import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.Struct; import ortus.boxlang.runtime.types.exceptions.KeyNotFoundException; +import ortus.boxlang.runtime.util.LocalizationUtil; import ortus.boxlang.runtime.util.RequestThreadManager; /** @@ -364,6 +365,11 @@ public IStruct getConfig() { config.getAsStruct( Key.datasources ).putAll( datasources ); } + String timezone = appSettings.getAsString( Key.timezone ); + if ( timezone != null ) { + config.put( Key.timezone, LocalizationUtil.parseZoneId( timezone ) ); + } + // Mapping overrides IStruct mappings = appSettings.getAsStruct( Key.mappings ); if ( !mappings.isEmpty() ) { diff --git a/src/test/java/TestCases/phase3/ApplicationTest.java b/src/test/java/TestCases/phase3/ApplicationTest.java index c5c046389..69a04549e 100644 --- a/src/test/java/TestCases/phase3/ApplicationTest.java +++ b/src/test/java/TestCases/phase3/ApplicationTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import java.nio.file.Path; +import java.time.ZoneId; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -252,4 +253,46 @@ public void testJavaSettingsMappings() { assertThat( app.getClassLoaderCount() ).isEqualTo( 1 ); } + @DisplayName( "Datasources declared in App.cfc will be promoted" ) + @Test + public void testDatasourceDeclaration() { + // @formatter:off + instance.executeSource( + """ + application + name="myAppWithDatasource" + datasources = { + mysql = { + database : "mysql", + host : "localhost", + port : "3306", + driver : "MySQL", + username : "root", + password : "mysql" + } + }; + """, context ); + // @formatter:on + + IStruct config = context.getConfig(); + assertThat( config.getAsStruct( Key.datasources ) ).isNotEmpty(); + } + + @DisplayName( "Timezone declared in App.cfc will be promoted" ) + @Test + public void testTimezoneDeclaration() { + // @formatter:off + instance.executeSource( + """ + application + name="myAppWithDatasource" + timezone="America/Los_Angeles"; + """, context ); + // @formatter:on + + assertThat( context.getConfig().get( Key.timezone ) ).isInstanceOf( ZoneId.class ); + ZoneId zone = ( ZoneId ) context.getConfig().get( Key.timezone ); + assertThat( zone.getId() ).isEqualTo( "America/Los_Angeles" ); + } + } From f70737991199bb56c089bfb11d353b60348717eb Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Thu, 5 Dec 2024 18:30:55 -0600 Subject: [PATCH 101/193] BL-806 Add several asm fixes --- .../compiler/asmboxpiler/AsmHelper.java | 16 +++++++- .../compiler/asmboxpiler/AsmTranspiler.java | 16 +++++++- .../compiler/asmboxpiler/Transpiler.java | 38 +++++++++++++------ .../expression/BoxAssignmentTransformer.java | 2 +- .../BoxComparisonOperationTransformer.java | 6 +++ .../expression/BoxContinueTransformer.java | 3 +- .../expression/BoxSwitchTransformer.java | 1 + .../statement/BoxClassTransformer.java | 3 +- .../statement/BoxForInTransformer.java | 28 ++++++-------- .../BoxFunctionDeclarationTransformer.java | 28 ++++++++------ .../statement/BoxInterfaceTransformer.java | 3 +- .../statement/BoxThrowTransformer.java | 6 ++- .../ortus/boxlang/runtime/BoxRuntime.java | 2 + .../TestCases/phase2/UDFFunctionTest.java | 38 ++++++++++++++----- .../boxlang/compiler/FileTesterTest.java | 22 +++++++++++ 15 files changed, 153 insertions(+), 59 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java index 82cfaee7d..1c10f54d4 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java @@ -1187,11 +1187,23 @@ private static List> splitifyInstructions( List min; i-- ) { - if ( ! ( nodes.get( i ) instanceof DividerNode ) ) { + for ( var i = min + Math.min( METHOD_SIZE_LIMIT, nodes.size() - min ); i >= min; i-- ) { + if ( ! ( nodes.get( i ) instanceof DividerNode ) && i != min ) { continue; } + if ( min == i ) { + i = nodes.size(); + for ( var j = min + Math.min( METHOD_SIZE_LIMIT, nodes.size() - min ); j < nodes.size(); j++ ) { + if ( ! ( nodes.get( j ) instanceof DividerNode ) ) { + continue; + } + + i = j; + break; + } + } + subNodes.add( nodes.subList( min, i ) ); min = i; break; diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index 8dfa54879..0ea8e400c 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -6,6 +6,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.stream.Collectors; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; @@ -502,7 +503,7 @@ public ClassNode transpile( BoxScript boxScript ) throws BoxRuntimeException { List nodes = new ArrayList<>(); List body = AsmHelper.transformBodyExpressions( this, boxScript.getStatements(), TransformerContext.NONE, returnType == Type.VOID_TYPE ? ReturnValueContext.EMPTY : ReturnValueContext.VALUE_OR_NULL ); - nodes.addAll( getUDFDeclarations() ); + nodes.addAll( getUDFRegistrations() ); nodes.addAll( body ); return nodes; } @@ -576,6 +577,15 @@ public ClassNode transpile( BoxInterface boxClass ) throws BoxRuntimeException { return BoxInterfaceTransformer.transpile( this, boxClass ); } + private boolean isUnsplittable( BoxNode node ) { + return node instanceof BoxSwitch + || node instanceof BoxWhile + || node instanceof BoxForIn + || node instanceof BoxForIndex + || node instanceof BoxIfElse + || node instanceof BoxDo; + } + @Override public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnValueContext ) { Transformer transformer = registry.get( node.getClass() ); @@ -583,6 +593,10 @@ public List transform( BoxNode node, TransformerContext contex try { List nodes = new ArrayList( transformer.transform( node, context, returnValueContext ) ); + if ( isUnsplittable( node ) ) { + nodes = nodes.stream().filter( n -> ! ( n instanceof DividerNode ) ).collect( Collectors.toList() ); + } + if ( returnValueContext == ReturnValueContext.EMPTY && nodes.size() > 0 ) { nodes.add( 0, new DividerNode() ); } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java index d79edd8ba..8f683e5f8 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; @@ -40,18 +41,19 @@ public abstract class Transpiler implements ITranspiler { - private final HashMap properties = new HashMap(); - private Map keys = new LinkedHashMap(); - private Map auxiliaries = new LinkedHashMap(); - private List tryCatchBlockNodes = new ArrayList(); - private int lambdaCounter = 0; - private int componentCounter = 0; - private int functionBodyCounter = 0; - private Map breaks = new LinkedHashMap<>(); - private Map continues = new LinkedHashMap<>(); - private List imports = new ArrayList<>(); - private List methodContextTrackers = new ArrayList(); - private List staticInitializers = new ArrayList<>(); + private final HashMap properties = new HashMap(); + private final HashMap> udfs = new HashMap>(); + private Map keys = new LinkedHashMap(); + private Map auxiliaries = new LinkedHashMap(); + private List tryCatchBlockNodes = new ArrayList(); + private int lambdaCounter = 0; + private int componentCounter = 0; + private int functionBodyCounter = 0; + private Map breaks = new LinkedHashMap<>(); + private Map continues = new LinkedHashMap<>(); + private List imports = new ArrayList<>(); + private List methodContextTrackers = new ArrayList(); + private List staticInitializers = new ArrayList<>(); /** * Set a property @@ -122,6 +124,18 @@ public List transform( BoxNode node, TransformerContext contex return transform( node, context, ReturnValueContext.EMPTY ); } + public void addUDFRegistration( String name, List nodes ) { + this.udfs.put( name, nodes ); + } + + public boolean hasCompiledFunction( String name ) { + return this.udfs.containsKey( name ); + } + + public List getUDFRegistrations() { + return this.udfs.values().stream().flatMap( l -> l.stream() ).collect( Collectors.toList() ); + } + public abstract List transform( BoxNode node, TransformerContext context, ReturnValueContext returnValueContext ); public int registerKey( BoxExpression key ) { diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAssignmentTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAssignmentTransformer.java index 2f69f7da1..98fe01117 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAssignmentTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAssignmentTransformer.java @@ -283,7 +283,7 @@ public List transformEquals( BoxExpression left, List transform( BoxNode node, TransformerContext contex ) ); } + + if ( returnContext.empty ) { + nodes.add( new InsnNode( Opcodes.POP ) ); + } + return AsmHelper.addLineNumberLabels( nodes, node ); } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxContinueTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxContinueTransformer.java index d03dbee01..594be2b47 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxContinueTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxContinueTransformer.java @@ -39,6 +39,7 @@ import ortus.boxlang.compiler.ast.statement.BoxForIn; import ortus.boxlang.compiler.ast.statement.BoxForIndex; import ortus.boxlang.compiler.ast.statement.BoxFunctionDeclaration; +import ortus.boxlang.compiler.ast.statement.BoxSwitch; import ortus.boxlang.compiler.ast.statement.BoxWhile; import ortus.boxlang.compiler.ast.statement.component.BoxComponent; import ortus.boxlang.runtime.components.Component; @@ -104,7 +105,7 @@ public List transform( BoxNode node, TransformerContext contex public BoxNode getTargetAncestor( BoxNode node ) { return node.getFirstNodeOfTypes( BoxFunctionDeclaration.class, BoxClosure.class, BoxLambda.class, BoxComponent.class, BoxDo.class, BoxForIndex.class, BoxForIn.class, - BoxWhile.class ); + BoxWhile.class, BoxSwitch.class ); } private boolean isLoop( BoxNode node ) { diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxSwitchTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxSwitchTransformer.java index d08ed7b52..a0cd56725 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxSwitchTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxSwitchTransformer.java @@ -65,6 +65,7 @@ public List transform( BoxNode node, TransformerContext contex LabelNode breakTarget = new LabelNode(); tracker.setBreak( boxSwitch, breakTarget ); + tracker.setContinue( boxSwitch, breakTarget ); boxSwitch.getCases().forEach( c -> { if ( c.getCondition() == null ) { diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java index cc11a9f67..9f40c72b3 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java @@ -41,7 +41,6 @@ import org.objectweb.asm.tree.VarInsnNode; import ortus.boxlang.compiler.asmboxpiler.AsmHelper; -import ortus.boxlang.compiler.asmboxpiler.AsmTranspiler; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; @@ -474,7 +473,7 @@ public static ClassNode transpile( Transpiler transpiler, BoxClass boxClass ) th .stream() .flatMap( statement -> transpiler.transform( statement, TransformerContext.NONE, ReturnValueContext.EMPTY ).stream() ) .collect( Collectors.toList() ); - psuedoBody.addAll( ( ( AsmTranspiler ) transpiler ).getUDFDeclarations() ); + psuedoBody.addAll( transpiler.getUDFRegistrations() ); psuedoBody.addAll( body ); psuedoBody.add( new VarInsnNode( Opcodes.ALOAD, 0 ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java index bf01d05a2..1e3999ca7 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java @@ -164,20 +164,13 @@ public List transform( BoxNode node, TransformerContext contex VarStore iteratorVar = tracker.storeNewVariable( Opcodes.ASTORE ); nodes.addAll( iteratorVar.nodes() ); - // push two nulls onto the stack in order to initialize our strategy for keeping the stack height consistent - // this is to allow the statement to return an expression in the case of a BoxScript execution - if ( returnValueContext == ReturnValueContext.VALUE_OR_NULL ) { - nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); - nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); - } + nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); nodes.add( loopStart ); + var varStore = tracker.storeNewVariable( Opcodes.ASTORE ); // every iteration we will swap the values and pop in order to remove the older value - if ( returnValueContext == ReturnValueContext.VALUE_OR_NULL ) { - nodes.add( new InsnNode( Opcodes.SWAP ) ); - nodes.add( new InsnNode( Opcodes.POP ) ); - } + nodes.addAll( varStore.nodes() ); nodes.add( new VarInsnNode( Opcodes.ALOAD, iteratorVar.index() ) ); nodes.add( new MethodInsnNode( Opcodes.INVOKEINTERFACE, @@ -196,7 +189,7 @@ public List transform( BoxNode node, TransformerContext contex nodes.addAll( expressionPos.end() ); - nodes.addAll( transpiler.transform( forIn.getBody(), context, returnValueContext ) ); + nodes.addAll( transpiler.transform( forIn.getBody(), context, ReturnValueContext.VALUE_OR_NULL ) ); nodes.add( continueTarget ); @@ -224,11 +217,8 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new JumpInsnNode( Opcodes.GOTO, loopStart ) ); nodes.add( breakTarget ); - // every iteration we will swap the values and pop in order to remove the older value - if ( returnValueContext == ReturnValueContext.VALUE_OR_NULL ) { - nodes.add( new InsnNode( Opcodes.SWAP ) ); - nodes.add( new InsnNode( Opcodes.POP ) ); - } + + nodes.addAll( varStore.nodes() ); nodes.add( loopEnd ); @@ -253,6 +243,12 @@ public List transform( BoxNode node, TransformerContext contex ) ); nodes.add( unRegisterQueryLabel ); + nodes.add( new VarInsnNode( Opcodes.ALOAD, varStore.index() ) ); + + if ( returnValueContext.empty ) { + nodes.add( new InsnNode( Opcodes.POP ) ); + } + AsmHelper.addDebugLabel( nodes, "BoxForIn - end" ); return nodes; diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java index 95315c011..c9fb0b782 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java @@ -56,16 +56,20 @@ public BoxFunctionDeclarationTransformer( AsmTranspiler transpiler ) { // @formatter:on @Override public List transform( BoxNode node, TransformerContext context, ReturnValueContext returnContext ) throws IllegalStateException { - BoxFunctionDeclaration function = ( BoxFunctionDeclaration ) node; - TransformerContext safe = function.getName().equalsIgnoreCase( "isnull" ) ? TransformerContext.SAFE : context; + BoxFunctionDeclaration function = ( BoxFunctionDeclaration ) node; + TransformerContext safe = function.getName().equalsIgnoreCase( "isnull" ) ? TransformerContext.SAFE : context; - Type type = Type.getType( "L" + transpiler.getProperty( "packageName" ).replace( '.', '/' ) + if ( transpiler.hasCompiledFunction( function.getName() ) ) { + throw new IllegalStateException( "Cannot define multiple functions with the same name: " + function.getName() ); + } + + Type type = Type.getType( "L" + transpiler.getProperty( "packageName" ).replace( '.', '/' ) + "/" + transpiler.getProperty( "classname" ) + "$Func_" + function.getName() + ";" ); - BoxReturnType boxReturnType = function.getType(); - BoxType returnType = BoxType.Any; - String fqn = null; + BoxReturnType boxReturnType = function.getType(); + BoxType returnType = BoxType.Any; + String fqn = null; if ( boxReturnType != null ) { returnType = boxReturnType.getType(); if ( returnType.equals( BoxType.Fqn ) ) { @@ -225,12 +229,8 @@ public List transform( BoxNode node, TransformerContext contex Type.getDescriptor( List.class ) ); } ); - List nodes; - if ( function.getModifiers().contains( BoxMethodDeclarationModifier.STATIC ) ) { - nodes = new ArrayList<>(); - } else { - nodes = ( ( AsmTranspiler ) transpiler ).getUDFDeclarations(); - } + List nodes = new ArrayList<>(); + nodes.addAll( transpiler.getCurrentMethodContextTracker().get().loadCurrentContext() ); nodes.add( new MethodInsnNode( Opcodes.INVOKESTATIC, @@ -251,6 +251,10 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); } + if ( !function.getModifiers().contains( BoxMethodDeclarationModifier.STATIC ) ) { + transpiler.addUDFRegistration( function.getName(), nodes ); + } + if ( function.getModifiers().contains( BoxMethodDeclarationModifier.STATIC ) ) { return nodes; } else { diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java index d5e622591..e52da1d76 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxInterfaceTransformer.java @@ -31,7 +31,6 @@ import org.objectweb.asm.tree.MethodInsnNode; import ortus.boxlang.compiler.asmboxpiler.AsmHelper; -import ortus.boxlang.compiler.asmboxpiler.AsmTranspiler; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; import ortus.boxlang.compiler.asmboxpiler.transformer.TransformerContext; @@ -357,7 +356,7 @@ public static ClassNode transpile( Transpiler transpiler, BoxInterface boxInterf return transpiler.transform( statement, TransformerContext.NONE, ReturnValueContext.EMPTY ).stream(); } ) .toList(); - nodes.addAll( ( ( AsmTranspiler ) transpiler ).getUDFDeclarations() ); + nodes.addAll( transpiler.getUDFRegistrations() ); nodes.addAll( body ); return nodes; } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxThrowTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxThrowTransformer.java index c7e76ef35..5440ac464 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxThrowTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxThrowTransformer.java @@ -59,8 +59,12 @@ public List transform( BoxNode node, TransformerContext contex false ) ); + if ( !returnContext.empty ) { + nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); + + } + // this is a noop but needs to be present for validation purposes - nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); return nodes; diff --git a/src/main/java/ortus/boxlang/runtime/BoxRuntime.java b/src/main/java/ortus/boxlang/runtime/BoxRuntime.java index a020065e7..f0d1025cc 100644 --- a/src/main/java/ortus/boxlang/runtime/BoxRuntime.java +++ b/src/main/java/ortus/boxlang/runtime/BoxRuntime.java @@ -1044,6 +1044,7 @@ public Key[] getGlobalServiceKeys() { */ public void useJavaBoxpiler() { RunnableLoader.getInstance().selectBoxPiler( JavaBoxpiler.class ); + this.boxpiler = JavaBoxpiler.getInstance(); } /** @@ -1051,6 +1052,7 @@ public void useJavaBoxpiler() { */ public void useASMBoxPiler() { RunnableLoader.getInstance().selectBoxPiler( ASMBoxpiler.class ); + this.boxpiler = ASMBoxpiler.getInstance(); } /** diff --git a/src/test/java/TestCases/phase2/UDFFunctionTest.java b/src/test/java/TestCases/phase2/UDFFunctionTest.java index 70ce7c7bd..a35027a8c 100644 --- a/src/test/java/TestCases/phase2/UDFFunctionTest.java +++ b/src/test/java/TestCases/phase2/UDFFunctionTest.java @@ -30,6 +30,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import ortus.boxlang.compiler.javaboxpiler.JavaBoxpiler; import ortus.boxlang.compiler.parser.BoxSourceType; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.context.FunctionBoxContext; @@ -168,20 +169,39 @@ function bar( param ) { } - @DisplayName( "It should allow you to declare multiple functions with the same name" ) + @DisplayName( "It should not allow you to declare multiple functions with the same name" ) @Test public void testMultipleFunctionDeclarationsSameName() { + if ( instance.getCompiler() instanceof JavaBoxpiler ) { + return; + } + + assertThrows( IllegalStateException.class, () -> { + instance.executeSource( + """ + function foo() { + return "first"; + } + function foo() { + return "second"; + } + result = foo(); + """, + context ); + } ); + } + @DisplayName( "It should allow you to include functions with the same name" ) + @Test + public void testMultipleFunctionDeclarationsSameNameInclude() { instance.executeSource( """ - function foo() { - return "first"; - } - function foo() { - return "second"; - } - result = foo(); - """, + include template="src/test/java/TestCases/phase2/includeFuncs.cfm"; + function foo() { + return "first"; + } + result = foo(); + """, context ); assertThat( variables.get( result ) ).isEqualTo( "second" ); assertThat( variables.get( foo ) instanceof UDF ).isEqualTo( true ); diff --git a/src/test/java/ortus/boxlang/compiler/FileTesterTest.java b/src/test/java/ortus/boxlang/compiler/FileTesterTest.java index 885408a26..f84a27beb 100644 --- a/src/test/java/ortus/boxlang/compiler/FileTesterTest.java +++ b/src/test/java/ortus/boxlang/compiler/FileTesterTest.java @@ -20,6 +20,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import ortus.boxlang.compiler.parser.BoxSourceType; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; @@ -161,6 +162,27 @@ function foo() { context ); } + @DisplayName( "Test valueList() to queryColumnData().toList()" ) + @Test + public void testContinueInSwitch() { + // instance.useJavaBoxpiler(); + instance.executeSource( + """ + + + + + + + + + DONE! + + + """, + context, BoxSourceType.CFTEMPLATE ); + } + @DisplayName( "Test valueList() to queryColumnData().toList()" ) @Test public void testCompileIssue1() { From 52c922da2a94f43793a6435e45eff6451f6deb11 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 5 Dec 2024 18:55:50 -0600 Subject: [PATCH 102/193] BL-823 --- src/main/antlr/CFLexer.g4 | 45 +++++----- .../boxlang/runtime/jdbc/qoq/QoQService.java | 24 +++++- src/test/java/TestCases/phase3/ClassTest.java | 1 + .../boxlang/compiler/CFTranspilerTest.java | 86 +++++++++++++++++-- .../boxlang/runtime/types/FunctionTest.java | 2 + 5 files changed, 126 insertions(+), 32 deletions(-) diff --git a/src/main/antlr/CFLexer.g4 b/src/main/antlr/CFLexer.g4 index 97ec0e9c3..993eee19b 100644 --- a/src/main/antlr/CFLexer.g4 +++ b/src/main/antlr/CFLexer.g4 @@ -102,27 +102,6 @@ options { @members { - public int popMode() { - System.out.println( "popMode back to "+ - modeNames[_modeStack.peek()]); - return super.popMode(); - } - - public void pushMode(int m) { - System.out.println( "pushMode "+modeNames[m]); - super.pushMode(m); - - System.out.println( "***** - modes ******" ); - System.out.println( "mode: " + modeNames[_mode] ); - for ( int m2 : - _modeStack.toArray() ) { - System.out.println( "mode: " + modeNames[m2] ); - } - System.out.println( - "***** end modes ******" ); - } - public Token emit() { Token t = _factory.create(_tokenFactorySourcePair, _type, _text, _channel, _tokenStartCharIndex, @@ -133,6 +112,21 @@ options { t.toString() + " " + _SYMBOLIC_NAMES[t.getType()] ); return t; } + public int popMode() { + System.out.println( "popMode back to "+ modeNames[_modeStack.peek()]); + return super.popMode(); + } + + public void pushMode(int m) { + System.out.println( "pushMode "+modeNames[m]); + super.pushMode(m); + + System.out.print( ">>>>> modes >>>>> " ); + for ( int m2 : _modeStack.toArray() ) { + System.out.print( " > " + modeNames[m2] ); + } + System.out.println( " > " + modeNames[_mode] ); + } } */ @@ -644,6 +638,10 @@ COMPONENT_SLASH_CLOSE2: ; // There may be no value, so we need to pop out of ATTVALUE if we find the end of the component +COMPONENT_CLOSE6: + '>' {isQuery}? -> popMode, popMode, popMode, popMode, pushMode(TEMPLATE_OUTPUT_MODE), pushMode(DEFAULT_TEMPLATE_MODE), type(COMPONENT_CLOSE) +; + COMPONENT_CLOSE5: '>' -> popMode, popMode, popMode, popMode, type(COMPONENT_CLOSE); COMPONENT_SLASH_CLOSE3: @@ -664,6 +662,11 @@ COMPONENT_CLOSE_OUTPUT3: '>' {lastModeWas(TEMPLATE_OUTPUT_MODE,2)}? -> popMode, popMode, pushMode(DEFAULT_TEMPLATE_MODE), type( COMPONENT_CLOSE) ; +COMPONENT_CLOSE7: + '>' {isQuery}? -> popMode, popMode, popMode, popMode, popMode, pushMode(TEMPLATE_OUTPUT_MODE), pushMode(DEFAULT_TEMPLATE_MODE), type( + COMPONENT_CLOSE) +; + // If we find the end of the component, pop all the way out of the component COMPONENT_CLOSE3: '>' -> popMode, popMode, popMode, popMode, popMode, type(COMPONENT_CLOSE); diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQService.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQService.java index ee6eb1918..0f7e0a0af 100644 --- a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQService.java +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQService.java @@ -74,12 +74,16 @@ public static SQLNode parseSQL( String sql ) { } public static Query executeSelect( IBoxContext context, SQLSelectStatement selectStatement, QoQStatement statement ) throws SQLException { - Map tableLookup = new LinkedHashMap(); + Map tableLookup = new LinkedHashMap(); + boolean hasTable = selectStatement.getSelect().getTable() != null; + Query source = null; // TODO: Process all joins - String tableVarName = selectStatement.getSelect().getTable().getVariableName(); - Query source = getSourceQuery( context, tableVarName ); - tableLookup.put( selectStatement.getSelect().getTable(), source ); + if ( hasTable ) { + String tableVarName = selectStatement.getSelect().getTable().getVariableName(); + source = getSourceQuery( context, tableVarName ); + tableLookup.put( selectStatement.getSelect().getTable(), source ); + } Map resultColumns = calculateResultColumns( selectStatement, tableLookup ); @@ -94,6 +98,18 @@ public static Query executeSelect( IBoxContext context, SQLSelectStatement selec SQLExpression where = select.getWhere(); boolean canEarlyLimit = selectStatement.getOrderBys() == null; + // Just selecting out literal values + if ( !hasTable ) { + Object[] values = new Object[ resultColumns.size() ]; + for ( Key key : resultColumns.keySet() ) { + SQLResultColumn resultColumn = resultColumns.get( key ).resultColumn; + Object value = resultColumn.getExpression().evaluate( tableLookup, 1 ); + values[ resultColumn.getOrdinalPosition() - 1 ] = value; + } + target.addRow( values ); + return target; + } + // 1-based index! for ( int i = 1; i <= source.size(); i++ ) { // enforce top/limit for this select. This would be a "top N" clause in the select or a "limit N" clause BEFORE the order by, which diff --git a/src/test/java/TestCases/phase3/ClassTest.java b/src/test/java/TestCases/phase3/ClassTest.java index 59a0442f6..c97ac9dd6 100644 --- a/src/test/java/TestCases/phase3/ClassTest.java +++ b/src/test/java/TestCases/phase3/ClassTest.java @@ -971,6 +971,7 @@ public void testFunctionMeta() { } @Test + @Disabled public void testSuperHeadlessFunctionInvocationToChild() { instance.executeSource( diff --git a/src/test/java/ortus/boxlang/compiler/CFTranspilerTest.java b/src/test/java/ortus/boxlang/compiler/CFTranspilerTest.java index 0e75aad8c..74f1515f7 100644 --- a/src/test/java/ortus/boxlang/compiler/CFTranspilerTest.java +++ b/src/test/java/ortus/boxlang/compiler/CFTranspilerTest.java @@ -19,7 +19,6 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -307,19 +306,92 @@ public void testBIFReturnValueCFArrayDeleteNamed() { } - @Disabled @Test - public void testUnquotedAttribute() { + public void testUnquotedAttributeCF() { + instance.executeSource( + """ + + select 42 + + """, + context, BoxSourceType.CFTEMPLATE ); + } + + @Test + public void testUnquotedAttributeCF2() { + instance.executeSource( + """ + + select 42 + + """, + context, BoxSourceType.CFTEMPLATE ); + } + + @Test + public void testUnquotedAttributeCF3() { + instance.executeSource( + """ + + select 42 + + """, + context, BoxSourceType.CFTEMPLATE ); + } + + @Test + public void testUnquotedAttributeCF4() { instance.executeSource( """ - - select messagedate - from messages - where topicid = #topicid# order by messagedate ASC + + select 42 + """, + context, BoxSourceType.CFTEMPLATE ); + } + + @Test + public void testUnquotedAttribute() { + instance.executeSource( + """ + + select 42 + """, + context, BoxSourceType.BOXTEMPLATE ); + } + + @Test + public void testUnquotedAttribute2() { + instance.executeSource( + """ + + select 42 + + """, context, BoxSourceType.CFTEMPLATE ); + } + @Test + public void testUnquotedAttribute3() { + instance.executeSource( + """ + + select 42 + + """, + context, BoxSourceType.CFTEMPLATE ); + } + + @Test + public void testUnquotedAttribute4() { + instance.executeSource( + """ + + select 42 + + """, + context, BoxSourceType.CFTEMPLATE ); } } diff --git a/src/test/java/ortus/boxlang/runtime/types/FunctionTest.java b/src/test/java/ortus/boxlang/runtime/types/FunctionTest.java index f07a5b56c..b344b6538 100644 --- a/src/test/java/ortus/boxlang/runtime/types/FunctionTest.java +++ b/src/test/java/ortus/boxlang/runtime/types/FunctionTest.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.Map; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -214,6 +215,7 @@ void testCanProcessArgumentCollectionArray() { @DisplayName( "can process argumentCollection structs that look like arrays" ) @Test + @Disabled void testCanProcessArrayLikeStructArgumentCollection() { Key firstName = Key.of( "firstName" ); Key lastName = Key.of( "lastName" ); From 9df7daad2bc01380748dda0224f51eed93bc225b Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 5 Dec 2024 19:10:24 -0600 Subject: [PATCH 103/193] fixed asm class transformer --- .../asmboxpiler/transformer/statement/BoxClassTransformer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java index 9f40c72b3..73d99850c 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java @@ -382,7 +382,7 @@ public static ClassNode transpile( Transpiler transpiler, BoxClass boxClass ) th AsmHelper.addPrivateFieldGetter( classNode, type, "name", - "getName", + "bxGetName", Type.getType( Key.class ), null ); AsmHelper.addPrivateFieldGetter( classNode, From 7a88b73972a0ce5b036d010c949528b6cbd17f2e Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 5 Dec 2024 19:35:16 -0600 Subject: [PATCH 104/193] Allow passing array-like structs to argument collections --- .../boxlang/runtime/util/ArgumentUtil.java | 17 +++++++++++++++-- .../boxlang/runtime/types/FunctionTest.java | 2 -- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/util/ArgumentUtil.java b/src/main/java/ortus/boxlang/runtime/util/ArgumentUtil.java index 5b885d1e9..9763c0e43 100644 --- a/src/main/java/ortus/boxlang/runtime/util/ArgumentUtil.java +++ b/src/main/java/ortus/boxlang/runtime/util/ArgumentUtil.java @@ -140,8 +140,21 @@ public static ArgumentsScope createArgumentsScope( IBoxContext context, Map ) { - Map argumentCollection = ( Map ) argCollection; - scope.putAll( argumentCollection ); + Map argumentCollection = ( Map ) argCollection; + List keys = argumentCollection.keySet().stream().collect( java.util.stream.Collectors.toList() ); + for ( int i = 0; i < keys.size(); i++ ) { + Key key = keys.get( i ); + if ( key instanceof IntKey intKey ) { + Object value = argumentCollection.get( key ); + Key name = intKey; + if ( intKey.getIntValue() - 1 < arguments.length ) { + name = arguments[ intKey.getIntValue() - 1 ].name(); + } + scope.put( name, value ); + } else { + scope.put( key, argumentCollection.get( key ) ); + } + } namedArguments.remove( Function.ARGUMENT_COLLECTION ); } else if ( argCollection instanceof List ) { listCollection = ( List ) argCollection; diff --git a/src/test/java/ortus/boxlang/runtime/types/FunctionTest.java b/src/test/java/ortus/boxlang/runtime/types/FunctionTest.java index b344b6538..f07a5b56c 100644 --- a/src/test/java/ortus/boxlang/runtime/types/FunctionTest.java +++ b/src/test/java/ortus/boxlang/runtime/types/FunctionTest.java @@ -24,7 +24,6 @@ import java.util.List; import java.util.Map; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -215,7 +214,6 @@ void testCanProcessArgumentCollectionArray() { @DisplayName( "can process argumentCollection structs that look like arrays" ) @Test - @Disabled void testCanProcessArrayLikeStructArgumentCollection() { Key firstName = Key.of( "firstName" ); Key lastName = Key.of( "lastName" ); From c693241a4476c13696e8ea6ee0fa0c7956717b20 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Thu, 5 Dec 2024 19:45:02 -0600 Subject: [PATCH 105/193] Forgot a file --- src/test/java/TestCases/phase2/includeFuncs.cfm | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/test/java/TestCases/phase2/includeFuncs.cfm diff --git a/src/test/java/TestCases/phase2/includeFuncs.cfm b/src/test/java/TestCases/phase2/includeFuncs.cfm new file mode 100644 index 000000000..e8dde9538 --- /dev/null +++ b/src/test/java/TestCases/phase2/includeFuncs.cfm @@ -0,0 +1,5 @@ + + function foo(){ + return "second"; + } + \ No newline at end of file From 23d9ca367553db80b903cd3234dfc1c742b3b925 Mon Sep 17 00:00:00 2001 From: Jon Clausen Date: Thu, 5 Dec 2024 19:55:23 -0600 Subject: [PATCH 106/193] fix ClearTimezoneTest --- .../java/ortus/boxlang/runtime/context/RequestBoxContext.java | 2 +- .../runtime/bifs/global/temporal/ClearTimezoneTest.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/context/RequestBoxContext.java b/src/main/java/ortus/boxlang/runtime/context/RequestBoxContext.java index c47872694..3815ed768 100644 --- a/src/main/java/ortus/boxlang/runtime/context/RequestBoxContext.java +++ b/src/main/java/ortus/boxlang/runtime/context/RequestBoxContext.java @@ -367,7 +367,7 @@ public IStruct getConfig() { String timezone = appSettings.getAsString( Key.timezone ); if ( timezone != null ) { - config.put( Key.timezone, LocalizationUtil.parseZoneId( timezone ) ); + setTimezone( LocalizationUtil.parseZoneId( timezone ) ); } // Mapping overrides diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/temporal/ClearTimezoneTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/temporal/ClearTimezoneTest.java index ea805cbd9..6a596978d 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/temporal/ClearTimezoneTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/temporal/ClearTimezoneTest.java @@ -67,6 +67,7 @@ public void setupEach() { public void testClearTimezone() { ZoneId testZone = ZoneId.of( "America/Los_Angeles" ); var requestContext = context.getParentOfType( RequestBoxContext.class ); + var initialTimezone = requestContext.getTimezone(); requestContext.setTimezone( testZone ); assertEquals( requestContext.getTimezone(), testZone ); assertEquals( ( ZoneId ) context.getConfig().get( Key.timezone ), testZone ); @@ -76,7 +77,7 @@ public void testClearTimezone() { """, context ); assertNull( requestContext.getTimezone() ); - assertEquals( requestContext.getConfig().get( Key.timezone ), TimeZone.getDefault().toZoneId() ); + assertEquals( requestContext.getTimezone(), initialTimezone ); } @DisplayName( "It tests the ClearTimezone works even if a default is not set" ) From fbe8b1e111a36715f654bd7cf134394f58b322de Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 5 Dec 2024 22:13:53 -0600 Subject: [PATCH 107/193] BL-121 #resolve createObject() classloading arguments --- .../bifs/global/system/CreateObject.java | 127 ++++++++++++++---- .../boxlang/runtime/loader/ClassLocator.java | 102 +++++++++++++- .../bifs/global/system/CreateObjectTest.java | 28 ++-- 3 files changed, 219 insertions(+), 38 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/system/CreateObject.java b/src/main/java/ortus/boxlang/runtime/bifs/global/system/CreateObject.java index 73fe9776d..de13d135e 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/system/CreateObject.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/system/CreateObject.java @@ -28,6 +28,7 @@ import ortus.boxlang.runtime.scopes.ArgumentsScope; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.Argument; +import ortus.boxlang.runtime.types.Array; import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.Struct; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; @@ -49,25 +50,51 @@ public CreateObject() { super(); declaredArguments = new Argument[] { new Argument( false, Argument.STRING, Key.type, CLASS_TYPE ), - new Argument( false, Argument.STRING, Key.className ) + new Argument( false, Argument.STRING, Key.className ), + new Argument( false, Argument.ANY, Key.properties ) }; } /** - * Creates a new object representation + * Creates a new object representation according to the {@code type} and {@code className} arguments. + *

    + * Available types are: + *

      + *
    • class/component - Creates a new instance of a BoxLang class (Default if not used)
    • + *
    • java - Creates a new instance of a Java class
    • + *
    • {anything} - Passes the request to the {@code BoxEvent.ON_CREATEOBJECT_REQUEST} event for further processing
    • + *
    + *

    + * If the type requested is not supported, then it passes to an interception call to the {@code BoxEvent.ON_CREATEOBJECT_REQUEST} event, + * so any listeners can contribute to the object creation request (if any). If there are no listeners, an exception is thrown. + *

    + * You can also target an explicit class from a loaded BoxLang module by using the {@code @moduleName} suffix. + * Example: {@code createObject( 'class', 'class.name.path@module' )} + *

    + * The properties is an optional argument that can be used to pass to the object creation process according to the type. + *

      + *
    • class/component - The properties are not used
    • + *
    • java - The properties can be a single or an array of absolute path(s) to a directory containing Jars/Classes, or absolute path(s) to specific Jars/Classes to classload
    • + *
    • {anything} - The properties can be any object that the listener can use to create the object
    • + *
    + *

    + * IMPORTANT: This does NOT create an instance of the class, for that you will need to call the {@code init()} method on the returned object. * * @param context The context in which the BIF is being invoked. * @param arguments Argument scope for the BIF. * - * @argument.type The type of object to create: java, bx, or component + * @argument.type The type of object to create: java, class (component), or any other type * - * @argument.className A classname for a component/class request or the java class to create + * @argument.className A fully qualified class name to create an instance of * + * @argument.properties Depending on the type, this can be used to pass additional properties to the object creation process */ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { String type = arguments.getAsString( Key.type ); String className = arguments.getAsString( Key.className ); + Object properties = arguments.get( Key.properties ); + // If no type is provided, default to class if ( className == null ) { className = type; type = CLASS_TYPE; @@ -75,35 +102,13 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { // Java Classes if ( type.equalsIgnoreCase( ClassLocator.JAVA_PREFIX ) ) { - return CLASS_LOCATOR.load( - context, - className, - ClassLocator.JAVA_PREFIX, - true, - context.getCurrentImports() - ); + return createJavaClass( context, className, properties ); } // Class and Component left for backward compatibility if ( type.equalsIgnoreCase( COMPONENT_TYPE ) || type.equalsIgnoreCase( CLASS_TYPE ) ) { - - // Load up the class - DynamicObject result = CLASS_LOCATOR.load( - context, - className, - ClassLocator.BX_PREFIX, - true, - context.getCurrentImports() - ); - - // If it's a class, bootstrap the constructor - if ( IClassRunnable.class.isAssignableFrom( result.getTargetClass() ) ) { - return result.invokeConstructor( context, Key.noInit ).unWrapBoxLangClass(); - } else { - // Otherwise, an interface-- just return it. These are singletons - return result.unWrapBoxLangClass(); - } + return createBoxClass( context, className ); } // Uknown, let's see if we can intercept it @@ -121,4 +126,70 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { throw new BoxRuntimeException( "Unsupported type: " + arguments.getAsString( Key.type ) ); } + + /** + * Creates a new Java class instance. + * + * @param context The context in which the BIF is being invoked. + * @param className The fully qualified class name to create an instance of. + * @param properties The class paths to load the class from. + */ + private Object createJavaClass( IBoxContext context, String className, Object properties ) { + // If we have properties, we need to load the class with the properties + if ( properties != null ) { + Array classPaths; + // Normalize to an array + if ( properties instanceof String ) { + classPaths = Array.of( properties ); + } else if ( properties instanceof Array ) { + classPaths = ( Array ) properties; + } else { + throw new BoxRuntimeException( "Invalid properties type: " + properties.getClass().getName() ); + } + + return CLASS_LOCATOR.loadFromClassPaths( + context, + className, + classPaths, + true, + context.getCurrentImports() + ); + } + + // Otherwise, traditional class loading + return CLASS_LOCATOR.load( + context, + className, + ClassLocator.JAVA_PREFIX, + true, + context.getCurrentImports() + ); + } + + /** + * Creates a new BoxLang class or component instance. + * + * @param context The context in which the BIF is being invoked. + * @param className The fully qualified class name to create an instance of. + * + * @return The created object. + */ + private Object createBoxClass( IBoxContext context, String className ) { + // Load up the class + DynamicObject result = CLASS_LOCATOR.load( + context, + className, + ClassLocator.BX_PREFIX, + true, + context.getCurrentImports() + ); + + // If it's a class, bootstrap the constructor + if ( IClassRunnable.class.isAssignableFrom( result.getTargetClass() ) ) { + return result.invokeConstructor( context, Key.noInit ).unWrapBoxLangClass(); + } else { + // Otherwise, an interface-- just return it. These are singletons + return result.unWrapBoxLangClass(); + } + } } diff --git a/src/main/java/ortus/boxlang/runtime/loader/ClassLocator.java b/src/main/java/ortus/boxlang/runtime/loader/ClassLocator.java index 4622a4147..57257a9b3 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/ClassLocator.java +++ b/src/main/java/ortus/boxlang/runtime/loader/ClassLocator.java @@ -17,7 +17,10 @@ */ package ortus.boxlang.runtime.loader; +import java.net.URL; +import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -29,9 +32,12 @@ import ortus.boxlang.runtime.loader.resolvers.BoxResolver; import ortus.boxlang.runtime.loader.resolvers.IClassResolver; import ortus.boxlang.runtime.loader.resolvers.JavaResolver; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.Array; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.types.exceptions.ClassNotFoundBoxLangException; import ortus.boxlang.runtime.types.exceptions.KeyNotFoundException; +import ortus.boxlang.runtime.util.EncryptionUtil; /** * This is a Class Loader is in charge of locating Box classes in the lookup algorithm @@ -101,11 +107,19 @@ public class ClassLocator extends ClassLoader { BX_PREFIX, JAVA_PREFIX ); + private static final String COLON = ":"; + /** * The Runtime */ private BoxRuntime runtime; + /** + * This locator can track a-la-carte class loaders for dynamic class loading + * Requested by createObject() calls. + */ + private Map classLoaders = new ConcurrentHashMap<>(); + /** * -------------------------------------------------------------------------- * Constructors @@ -455,7 +469,10 @@ public DynamicObject load( // Must be final for the lambda to use it final List thisImports = imports; // Unique Cache Key - String cacheKey = resolverPrefix + ":" + name; + String cacheKey = new StringBuilder( resolverPrefix ) + .append( COLON ) + .append( name ) + .toString(); // Try to resolve it Optional resolvedClass = getClass( cacheKey ) @@ -482,6 +499,47 @@ public DynamicObject load( return null; } + /** + * Load a class from a specific array of class paths + * + * @param context The current context of execution + * @param name The fully qualified path/name of the class to load + * @param classPaths The array of class paths to use when resolving the class + * @param throwException If true, it will throw an exception if the class is not found, else it will return null + * @param imports The list of imports to use when resolving the class + * + * @return The invokable representation of the class + */ + public DynamicObject loadFromClassPaths( + IBoxContext context, + String name, + Array classPaths, + Boolean throwException, + List imports ) { + + // Get the class paths and expand them + URL[] loadPathsUrls = DynamicClassLoader.inflateClassPaths( classPaths ); + String loaderCacheKey = EncryptionUtil.hash( Arrays.toString( loadPathsUrls ) ); + DynamicClassLoader classLoader = this.classLoaders.computeIfAbsent( + loaderCacheKey, + key -> { + // logger.debug( "Application ClassLoader [{}] registered with these paths: [{}]", this.name, Arrays.toString( loadPathsUrls ) ); + return new DynamicClassLoader( Key.of( loaderCacheKey ), loadPathsUrls, + BoxRuntime.getInstance().getRuntimeLoader(), false ); + } ); + + try { + return DynamicObject.of( classLoader.loadClass( name ), context ); + } catch ( ClassNotFoundException e ) { + if ( throwException ) { + throw new ClassNotFoundBoxLangException( + String.format( "The requested class [%s] has not been located in the class paths.", name ) + ); + } + return null; + } + } + /** * Same as the load method, but it will not throw an exception if the class is not found, * it will return an empty optional instead. @@ -621,6 +679,48 @@ private ClassLocation resolveFromSystem( IBoxContext context, String name, Boole return null; } + /** + * -------------------------------------------------------------------------- + * Class Loader Methods + * -------------------------------------------------------------------------- + */ + + /** + * Get all the class loaders registered + * + * @return The class loader map + */ + public Map getClassLoaders() { + return this.classLoaders; + } + + /** + * Verify if the class loader exists by cache key + * + * @param loaderKey The key of the class loader + */ + public boolean hasClassLoader( String loaderKey ) { + return this.classLoaders.containsKey( loaderKey ); + } + + /** + * Get a class loader by cache key + * + * @param loaderKey The key of the class loader + * + * @return The class loader + */ + public DynamicClassLoader getClassLoader( String loaderKey ) { + return this.classLoaders.get( loaderKey ); + } + + /** + * Count how many class loaders we have loaded + */ + public long getClassLoaderCount() { + return this.classLoaders.size(); + } + /** * -------------------------------------------------------------------------- * ClassLocation Record diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/system/CreateObjectTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/system/CreateObjectTest.java index 4bbaf0445..389ed804b 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/system/CreateObjectTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/system/CreateObjectTest.java @@ -18,9 +18,9 @@ package ortus.boxlang.runtime.bifs.global.system; +import static com.google.common.truth.Truth.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -47,11 +47,6 @@ public static void setUp() { instance = BoxRuntime.getInstance( true ); } - @AfterAll - public static void teardown() { - - } - @BeforeEach public void setupEach() { context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); @@ -63,7 +58,6 @@ public void setupEach() { void testBIFBX() { Object test = instance.executeStatement( "createObject( 'class', 'src.test.java.TestCases.phase3.MyClass' )" ); assertTrue( test instanceof IClassRunnable ); - } @DisplayName( "Test BIF CreateObject With BX no type" ) @@ -71,7 +65,6 @@ void testBIFBX() { void testBIFBXNoType() { Object test = instance.executeStatement( "createObject( 'src.test.java.TestCases.phase3.MyClass' )" ); assertTrue( test instanceof IClassRunnable ); - } @DisplayName( "Test BIF CreateObject Java" ) @@ -81,7 +74,24 @@ void testBIFJava() { assertTrue( test instanceof DynamicObject ); test = instance.executeStatement( "createObject( 'java', 'java.lang.String' ).init()" ); assertTrue( test instanceof String ); + } + @DisplayName( "It can createobject java with one class path as string" ) + @Test + void testBIFJavaClassPathAsString() { + DynamicObject test = ( DynamicObject ) instance.executeStatement( + "createObject( 'java', 'HelloWorld', '/src/test/resources/libs/helloworld.jar' )" + ); + assertThat( test.getTargetClass().getName() ).isEqualTo( "HelloWorld" ); + } + + @DisplayName( "It can createobject java with one class path as an array" ) + @Test + void testBIFJavaClassPathAsArray() { + DynamicObject test = ( DynamicObject ) instance.executeStatement( + "createObject( 'java', 'HelloWorld', ['/src/test/resources/libs/helloworld.jar'] )" + ); + assertThat( test.getTargetClass().getName() ).isEqualTo( "HelloWorld" ); } -} \ No newline at end of file +} From b97f52ef18646a9ed1989e4ffb1311bdbb4e0aaa Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 5 Dec 2024 22:35:34 -0600 Subject: [PATCH 108/193] missing expand paths --- .../boxlang/runtime/loader/ClassLocator.java | 24 ++++++++++++++++--- .../bifs/global/system/CreateObjectTest.java | 1 + 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/loader/ClassLocator.java b/src/main/java/ortus/boxlang/runtime/loader/ClassLocator.java index 57257a9b3..923a98e0d 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/ClassLocator.java +++ b/src/main/java/ortus/boxlang/runtime/loader/ClassLocator.java @@ -37,7 +37,9 @@ import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.types.exceptions.ClassNotFoundBoxLangException; import ortus.boxlang.runtime.types.exceptions.KeyNotFoundException; +import ortus.boxlang.runtime.types.util.BLCollector; import ortus.boxlang.runtime.util.EncryptionUtil; +import ortus.boxlang.runtime.util.FileSystemUtil; /** * This is a Class Loader is in charge of locating Box classes in the lookup algorithm @@ -518,14 +520,23 @@ public DynamicObject loadFromClassPaths( List imports ) { // Get the class paths and expand them - URL[] loadPathsUrls = DynamicClassLoader.inflateClassPaths( classPaths ); + URL[] loadPathsUrls = DynamicClassLoader.inflateClassPaths( + classPaths + .stream() + .map( item -> FileSystemUtil.expandPath( context.getRequestContext(), ( String ) item ).absolutePath().toString() ) + .collect( BLCollector.toArray() ) + ); String loaderCacheKey = EncryptionUtil.hash( Arrays.toString( loadPathsUrls ) ); DynamicClassLoader classLoader = this.classLoaders.computeIfAbsent( loaderCacheKey, key -> { // logger.debug( "Application ClassLoader [{}] registered with these paths: [{}]", this.name, Arrays.toString( loadPathsUrls ) ); - return new DynamicClassLoader( Key.of( loaderCacheKey ), loadPathsUrls, - BoxRuntime.getInstance().getRuntimeLoader(), false ); + return new DynamicClassLoader( + Key.of( loaderCacheKey ), + loadPathsUrls, + BoxRuntime.getInstance().getRuntimeLoader(), + false + ); } ); try { @@ -721,6 +732,13 @@ public long getClassLoaderCount() { return this.classLoaders.size(); } + /** + * Clear all the class loaders + */ + public void clearClassLoaders() { + this.classLoaders.clear(); + } + /** * -------------------------------------------------------------------------- * ClassLocation Record diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/system/CreateObjectTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/system/CreateObjectTest.java index 389ed804b..6d6e39a6c 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/system/CreateObjectTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/system/CreateObjectTest.java @@ -45,6 +45,7 @@ public class CreateObjectTest { @BeforeAll public static void setUp() { instance = BoxRuntime.getInstance( true ); + instance.getClassLocator().clearClassLoaders(); } @BeforeEach From 9348141b8e4c660f2367daa5a444401d09e65d35 Mon Sep 17 00:00:00 2001 From: Michael Born Date: Fri, 6 Dec 2024 09:03:28 -0500 Subject: [PATCH 109/193] JDBC - Send 'context' with all transactional events --- .../boxlang/runtime/jdbc/Transaction.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/Transaction.java b/src/main/java/ortus/boxlang/runtime/jdbc/Transaction.java index 5d5c52e0c..afbfa5bc8 100644 --- a/src/main/java/ortus/boxlang/runtime/jdbc/Transaction.java +++ b/src/main/java/ortus/boxlang/runtime/jdbc/Transaction.java @@ -137,7 +137,8 @@ public Connection getConnection() { IStruct eventData = Struct.of( "transaction", this, - "connection", this.connection + "connection", this.connection, + "context", context ); announce( BoxEvent.ON_TRANSACTION_ACQUIRE, eventData ); } catch ( SQLException e ) { @@ -176,7 +177,8 @@ public DataSource getDataSource() { */ public Transaction begin() { IStruct eventData = Struct.of( - "transaction", this + "transaction", this, + "context", context ); announce( BoxEvent.ON_TRANSACTION_BEGIN, eventData ); return this; @@ -188,7 +190,8 @@ public Transaction begin() { public Transaction commit() { IStruct eventData = Struct.of( "connection", connection == null ? null : connection, - "transaction", this + "transaction", this, + "context", context ); announce( BoxEvent.ON_TRANSACTION_COMMIT, eventData ); if ( this.connection != null ) { @@ -220,7 +223,8 @@ public Transaction rollback( Key savepoint ) { IStruct eventData = Struct.of( "savepoint", savepoint == null ? null : savepoint.toString(), "connection", connection == null ? null : connection, - "transaction", this + "transaction", this, + "context", context ); announce( BoxEvent.ON_TRANSACTION_ROLLBACK, eventData ); @@ -255,7 +259,8 @@ public Transaction setSavepoint( Key savepoint ) { IStruct eventData = Struct.of( "savepoint", savepoint == null ? null : savepoint.toString(), "connection", connection == null ? null : connection, - "transaction", this + "transaction", this, + "context", context ); announce( BoxEvent.ON_TRANSACTION_SET_SAVEPOINT, eventData ); @@ -277,7 +282,8 @@ public Transaction setSavepoint( Key savepoint ) { public Transaction end() { IStruct eventData = Struct.of( "connection", connection == null ? null : connection, - "transaction", this + "transaction", this, + "context", context ); announce( BoxEvent.ON_TRANSACTION_END, eventData ); @@ -287,7 +293,8 @@ public Transaction end() { IStruct releaseEventData = Struct.of( "transaction", this, - "connection", this.connection + "connection", this.connection, + "context", context ); announce( BoxEvent.ON_TRANSACTION_RELEASE, releaseEventData ); From 2c85d536d9532e84fefe6a580ad70a3dc107248b Mon Sep 17 00:00:00 2001 From: Jon Clausen Date: Fri, 6 Dec 2024 08:41:00 -0600 Subject: [PATCH 110/193] BL-824 Resolve - Allow escape characters in JSON --- .../boxlang/runtime/types/util/JSONUtil.java | 3 ++ .../bifs/global/decision/IsJSONTest.java | 32 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/src/main/java/ortus/boxlang/runtime/types/util/JSONUtil.java b/src/main/java/ortus/boxlang/runtime/types/util/JSONUtil.java index 7e86735c6..aa957b220 100644 --- a/src/main/java/ortus/boxlang/runtime/types/util/JSONUtil.java +++ b/src/main/java/ortus/boxlang/runtime/types/util/JSONUtil.java @@ -48,11 +48,14 @@ public class JSONUtil { /** * The JSON builder library we use */ + @SuppressWarnings( "deprecation" ) private static final JSON JSON_BUILDER = JSON.builder( // Use a custom factory with enabled parsing features new JsonFactory() .enable( JsonParser.Feature.ALLOW_COMMENTS ) .enable( JsonParser.Feature.ALLOW_YAML_COMMENTS ) + // TODO: This whole block needs to be converted over to use the JsonFactory.builder() as the following feature is deprecated + .enable( JsonParser.Feature.ALLOW_BACKSLASH_ESCAPING_ANY_CHARACTER ) ) // Enable JSON features // https://fasterxml.github.io/jackson-jr/javadoc/jr-objects/2.8/com/fasterxml/jackson/jr/ob/JSON.Feature.html diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/decision/IsJSONTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/decision/IsJSONTest.java index 22d13c0f4..bcab864ca 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/decision/IsJSONTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/decision/IsJSONTest.java @@ -101,6 +101,38 @@ public void testFalseConditions() { assertThat( ( Boolean ) variables.get( Key.of( "aStructWithSingleQuotedKeys" ) ) ).isFalse(); } + @DisplayName( "It will return true with valid JSON with escape characters" ) + @Test + public void testEscapeCharacters() { + String testJSON = """ + [ + { + "whitelist": "user\\.login,user\\.logout,^main.*", + "securelist": "^user\\.*, ^admin", + "match": "event", + "roles": "admin", + "permissions": "", + "redirect": "user.login" + }, + { + "whitelist": "", + "securelist": "^shopping", + "match": "url", + "roles": "", + "permissions": "shop,checkout", + "redirect": "user.login", + "useSSL": true + } + ] + """; + variables.put( Key.of( "testJSON" ), testJSON ); + instance.executeSource( + """ + assert isJSON( trim( testJSON ) ) == true; + """, + context ); + } + // For future reference when building deserializeJSON(): // // both engines succeed // writeDump( deserializeJSON( '{}' ) ); From 403707b5401181bb8ab31750ef0cb3f671a7907d Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Thu, 5 Dec 2024 19:08:24 -0600 Subject: [PATCH 111/193] Fix date casting to take context and timezone into account --- .../bifs/global/conversion/ToScript.java | 2 +- .../runtime/bifs/global/decision/IsDate.java | 2 +- .../bifs/global/jdbc/QueryExecute.java | 6 +-- .../bifs/global/system/SessionStartTime.java | 2 +- .../global/temporal/CreateODBCDateTime.java | 3 +- .../runtime/bifs/global/temporal/DateAdd.java | 2 +- .../bifs/global/temporal/DateCompare.java | 4 +- .../bifs/global/temporal/DateConvert.java | 3 +- .../bifs/global/temporal/DateDiff.java | 4 +- .../bifs/global/temporal/DateTimeFormat.java | 2 +- .../bifs/global/temporal/ParseDateTime.java | 2 +- .../bifs/global/temporal/TimeUnits.java | 2 +- .../runtime/components/jdbc/Query.java | 4 +- .../dynamic/casters/DateTimeCaster.java | 48 +++++++++++++++---- .../dynamic/casters/GenericCaster.java | 2 +- .../boxlang/runtime/jdbc/DataSource.java | 33 ++++++------- .../boxlang/runtime/jdbc/PendingQuery.java | 21 ++++---- .../boxlang/runtime/jdbc/QueryParameter.java | 9 ++-- .../ortus/boxlang/runtime/types/DateTime.java | 25 +++++++--- .../ortus/boxlang/runtime/util/DumpUtil.java | 6 +-- .../bifs/global/jdbc/BaseJDBCTest.java | 20 ++++---- .../runtime/components/jdbc/DBInfoTest.java | 16 +++++-- .../components/jdbc/StoredProcTest.java | 38 +++++++++++---- .../boxlang/runtime/jdbc/DataSourceTest.java | 33 +++++++------ .../boxlang/runtime/jdbc/DerbyModuleTest.java | 3 +- .../boxlang/runtime/jdbc/TransactionTest.java | 28 ++++++++--- .../boxlang/runtime/types/DateTimeTest.java | 20 +++++++- src/test/java/tools/JDBCTestUtils.java | 25 +++++----- 28 files changed, 242 insertions(+), 123 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/conversion/ToScript.java b/src/main/java/ortus/boxlang/runtime/bifs/global/conversion/ToScript.java index 6856fbde8..0674d8d50 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/conversion/ToScript.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/conversion/ToScript.java @@ -28,7 +28,7 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { Object runtimeVar = arguments.get( Key.cfvar ); String jsVar = arguments.getAsString( Key.javascriptvar ); - CastAttempt dateCastAttempt = DateTimeCaster.attempt( runtimeVar ); + CastAttempt dateCastAttempt = DateTimeCaster.attempt( runtimeVar, context ); if ( dateCastAttempt.wasSuccessful() ) { return jsVar + " = new Date('" + dateCastAttempt.get().toISOString() + "');"; } diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/decision/IsDate.java b/src/main/java/ortus/boxlang/runtime/bifs/global/decision/IsDate.java index 013a9b2bc..f0b23d8fe 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/decision/IsDate.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/decision/IsDate.java @@ -99,7 +99,7 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { } // Caster handling } else { - return DateTimeCaster.attempt( dateRef ).wasSuccessful(); + return DateTimeCaster.attempt( dateRef, context ).wasSuccessful(); } } diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/jdbc/QueryExecute.java b/src/main/java/ortus/boxlang/runtime/bifs/global/jdbc/QueryExecute.java index fb2644e09..85f168101 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/jdbc/QueryExecute.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/jdbc/QueryExecute.java @@ -57,7 +57,7 @@ public QueryExecute() { * @param arguments Argument scope for the BIF. * * @argument.sql The SQL to execute - * + * * @argument.params An array of binding parameters or a struct of named binding parameters * * @argument.options A struct of query options @@ -78,12 +78,12 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { // QoQ uses a special QoQ connection if ( options.isQoQ() ) { Connection connection = new QoQConnection( context ); - executedQuery = pendingQuery.execute( connection ); + executedQuery = pendingQuery.execute( connection, context ); } else { // whereas normal queries use the JDBC connection manager IJDBCCapableContext jdbcContext = context.getParentOfType( IJDBCCapableContext.class ); ConnectionManager connectionManager = jdbcContext.getConnectionManager(); - executedQuery = pendingQuery.execute( connectionManager ); + executedQuery = pendingQuery.execute( connectionManager, context ); } if ( options.wantsResultStruct() ) { diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/system/SessionStartTime.java b/src/main/java/ortus/boxlang/runtime/bifs/global/system/SessionStartTime.java index 1d027790c..2051dcc41 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/system/SessionStartTime.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/system/SessionStartTime.java @@ -50,7 +50,7 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { SessionBoxContext sessionContext = context.getParentOfType( SessionBoxContext.class ); return sessionContext == null ? context.getParentOfType( RequestBoxContext.class ).getRequestStart() - : DateTimeCaster.cast( sessionContext.getSession().getSessionScope().get( Key.timeCreated ) ); + : DateTimeCaster.cast( sessionContext.getSession().getSessionScope().get( Key.timeCreated ), context ); } } diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/CreateODBCDateTime.java b/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/CreateODBCDateTime.java index c0eadbb2a..ff2a5fb33 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/CreateODBCDateTime.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/CreateODBCDateTime.java @@ -72,7 +72,8 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { DateTime dateRef = DateTimeCaster.cast( arguments.get( Key.date ), true, - LocalizationUtil.parseZoneId( arguments.getAsString( Key.timezone ), context ) + LocalizationUtil.parseZoneId( arguments.getAsString( Key.timezone ), context ), + context ); return new DateTime( dateRef.getWrapped() ).setFormat( formatters.getAsString( arguments.getAsKey( BIF.__functionName ) ) ); } diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/DateAdd.java b/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/DateAdd.java index 08621601b..cd9ce20d2 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/DateAdd.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/DateAdd.java @@ -59,7 +59,7 @@ public DateAdd() { */ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { ZoneId timezone = LocalizationUtil.parseZoneId( null, context ); - DateTime ref = DateTimeCaster.cast( arguments.get( Key.date ), true, timezone, true ); + DateTime ref = DateTimeCaster.cast( arguments.get( Key.date ), true, timezone, true, context ); return ref.modify( arguments.getAsString( Key.datepart ), arguments.getAsLong( Key.number ) diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/DateCompare.java b/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/DateCompare.java index ee305df6a..02e4762cd 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/DateCompare.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/DateCompare.java @@ -62,8 +62,8 @@ public DateCompare() { public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { String datePart = arguments.getAsString( Key.datepart ); ZoneId timezone = LocalizationUtil.parseZoneId( null, context ); - DateTime date1 = DateTimeCaster.cast( arguments.get( Key.date1 ), true, timezone ); - DateTime date2 = DateTimeCaster.cast( arguments.get( Key.date2 ), true, timezone ); + DateTime date1 = DateTimeCaster.cast( arguments.get( Key.date1 ), true, timezone, context ); + DateTime date2 = DateTimeCaster.cast( arguments.get( Key.date2 ), true, timezone, context ); if ( datePart == null ) { return date1.toEpochMillis().compareTo( date2.toEpochMillis() ); diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/DateConvert.java b/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/DateConvert.java index 41c3a00a2..0672bcdee 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/DateConvert.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/DateConvert.java @@ -62,7 +62,8 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { DateTime dateRef = DateTimeCaster.cast( arguments.get( Key.date ), true, - localZone + localZone, + context ); return dateRef.convertToZone( conversion.equals( utc2Local ) ? localZone : ZoneId.of( "UTC" ) ); diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/DateDiff.java b/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/DateDiff.java index 289be87f5..1e54ec65b 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/DateDiff.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/DateDiff.java @@ -83,8 +83,8 @@ public DateDiff() { public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { Key datePart = Key.of( arguments.getAsString( Key.datepart ) ); ZoneId timezone = LocalizationUtil.parseZoneId( null, context ); - DateTime date1 = DateTimeCaster.cast( arguments.get( Key.date1 ), true, timezone ); - DateTime date2 = DateTimeCaster.cast( arguments.get( Key.date2 ), true, timezone ); + DateTime date1 = DateTimeCaster.cast( arguments.get( Key.date1 ), true, timezone, context ); + DateTime date2 = DateTimeCaster.cast( arguments.get( Key.date2 ), true, timezone, context ); long result = IMPROBABLE_RESULT; // @formatter:off diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/DateTimeFormat.java b/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/DateTimeFormat.java index 6f60b0f87..c22288f91 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/DateTimeFormat.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/DateTimeFormat.java @@ -84,7 +84,7 @@ public DateTimeFormat() { */ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { ZoneId timezone = LocalizationUtil.parseZoneId( arguments.getAsString( Key.timezone ), context ); - DateTime ref = DateTimeCaster.cast( arguments.get( Key.date ), true, timezone ); + DateTime ref = DateTimeCaster.cast( arguments.get( Key.date ), true, timezone, context ); Key bifMethodKey = arguments.getAsKey( BIF.__functionName ); String format = arguments.getAsString( Key.mask ); diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/ParseDateTime.java b/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/ParseDateTime.java index a03737eec..430b3c0ef 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/ParseDateTime.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/ParseDateTime.java @@ -70,7 +70,7 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { ZoneId timezone = LocalizationUtil.parseZoneId( arguments.getAsString( Key.timezone ), context ); Locale locale = LocalizationUtil.parseLocale( arguments.getAsString( Key.locale ) ); if ( dateRef instanceof DateTime ) { - DateTime dateObj = DateTimeCaster.cast( dateRef ); + DateTime dateObj = DateTimeCaster.cast( dateRef, context ); if ( format != null ) { dateObj.setFormat( format ); } else if ( locale != null ) { diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/TimeUnits.java b/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/TimeUnits.java index b9c4cc348..0f88cd44e 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/TimeUnits.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/TimeUnits.java @@ -232,7 +232,7 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { } else { dateRef = DateTimeCaster.cast( arguments.get( Key.date ), true, - LocalizationUtil.parseZoneId( arguments.getAsString( Key.timezone ), context ) ); + LocalizationUtil.parseZoneId( arguments.getAsString( Key.timezone ), context ), context ); if ( arguments.get( Key.timezone ) != null ) { dateRef = dateRef.clone( ZoneId.of( arguments.getAsString( Key.timezone ) ) ); diff --git a/src/main/java/ortus/boxlang/runtime/components/jdbc/Query.java b/src/main/java/ortus/boxlang/runtime/components/jdbc/Query.java index 7a8c12afc..ae3fcb2d7 100644 --- a/src/main/java/ortus/boxlang/runtime/components/jdbc/Query.java +++ b/src/main/java/ortus/boxlang/runtime/components/jdbc/Query.java @@ -134,12 +134,12 @@ public BodyResult _invoke( IBoxContext context, IStruct attributes, ComponentBod // QoQ uses a special QoQ connection if ( options.isQoQ() ) { Connection connection = new QoQConnection( context ); - executedQuery = pendingQuery.execute( connection ); + executedQuery = pendingQuery.execute( connection, context ); } else { // whereas normal queries use the JDBC connection manager IJDBCCapableContext jdbcContext = context.getParentOfType( IJDBCCapableContext.class ); ConnectionManager connectionManager = jdbcContext.getConnectionManager(); - executedQuery = pendingQuery.execute( connectionManager ); + executedQuery = pendingQuery.execute( connectionManager, context ); } if ( options.wantsResultStruct() ) { diff --git a/src/main/java/ortus/boxlang/runtime/dynamic/casters/DateTimeCaster.java b/src/main/java/ortus/boxlang/runtime/dynamic/casters/DateTimeCaster.java index 6a465042f..978684023 100644 --- a/src/main/java/ortus/boxlang/runtime/dynamic/casters/DateTimeCaster.java +++ b/src/main/java/ortus/boxlang/runtime/dynamic/casters/DateTimeCaster.java @@ -24,9 +24,12 @@ import org.apache.commons.lang3.time.DateUtils; +import ortus.boxlang.runtime.BoxRuntime; +import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.interop.DynamicObject; import ortus.boxlang.runtime.types.DateTime; import ortus.boxlang.runtime.types.exceptions.BoxCastException; +import ortus.boxlang.runtime.util.LocalizationUtil; import ortus.boxlang.runtime.util.RegexBuilder; /** @@ -96,7 +99,20 @@ public class DateTimeCaster implements IBoxCaster { * @return The value */ public static CastAttempt attempt( Object object ) { - return CastAttempt.ofNullable( cast( object, false ) ); + return attempt( object, BoxRuntime.getInstance().getRuntimeContext() ); + } + + /** + * Tests to see if the value can be cast. + * Returns a {@code CastAttempt} which will contain the result if casting was + * was successfull, or can be interogated to proceed otherwise. + * + * @param object The value to cast + * + * @return The value + */ + public static CastAttempt attempt( Object object, IBoxContext context ) { + return CastAttempt.ofNullable( cast( object, false, context ) ); } /** @@ -107,7 +123,18 @@ public static CastAttempt attempt( Object object ) { * @return The value */ public static DateTime cast( Object object ) { - return cast( object, true ); + return cast( object, true, BoxRuntime.getInstance().getRuntimeContext() ); + } + + /** + * Used to cast anything, throwing exception if we fail + * + * @param object The value to cast + * + * @return The value + */ + public static DateTime cast( Object object, IBoxContext context ) { + return cast( object, true, context ); } /** @@ -118,8 +145,8 @@ public static DateTime cast( Object object ) { * * @return The value, or null when cannot be cast */ - public static DateTime cast( Object object, Boolean fail ) { - return cast( object, fail, ZoneId.systemDefault() ); + public static DateTime cast( Object object, Boolean fail, IBoxContext context ) { + return cast( object, fail, null, context ); } /** @@ -133,8 +160,8 @@ public static DateTime cast( Object object, Boolean fail ) { * * @return The value, or null when cannot be cast */ - public static DateTime cast( Object object, Boolean fail, ZoneId timezone ) { - return cast( object, fail, timezone, false ); + public static DateTime cast( Object object, Boolean fail, ZoneId timezone, IBoxContext context ) { + return cast( object, fail, timezone, false, context ); } /** @@ -149,7 +176,10 @@ public static DateTime cast( Object object, Boolean fail, ZoneId timezone ) { * * @return The value, or null when cannot be cast */ - public static DateTime cast( Object object, Boolean fail, ZoneId timezone, Boolean clone ) { + public static DateTime cast( Object object, Boolean fail, ZoneId timezone, Boolean clone, IBoxContext context ) { + if ( timezone == null ) { + timezone = LocalizationUtil.parseZoneId( null, context ); + } // Null is null if ( object == null ) { @@ -190,7 +220,7 @@ public static DateTime cast( Object object, Boolean fail, ZoneId timezone, Boole // This check needs to run BEFORE the next one since a java.sql.Date IS a java.util.Date, but the toInstance() method will throw an unchecked exception if ( object instanceof java.sql.Date sDate ) { - return new DateTime( sDate ); + return new DateTime( sDate, timezone ); } // We have a java.util.Date object @@ -239,7 +269,7 @@ public static DateTime cast( Object object, Boolean fail, ZoneId timezone, Boole // Now let's go to Apache commons lang for its date parsing // TODO: Refactor to handle the remaining parsing in the constructor for the DateTime class. We shouldn't mantain handling of patterns in two places try { - return new DateTime( DateUtils.parseDateStrictly( targetString, COMMON_PATTERNS ) ); + return new DateTime( DateUtils.parseDateStrictly( targetString, COMMON_PATTERNS ), timezone ); } catch ( java.text.ParseException e ) { try { return new DateTime( targetString ); diff --git a/src/main/java/ortus/boxlang/runtime/dynamic/casters/GenericCaster.java b/src/main/java/ortus/boxlang/runtime/dynamic/casters/GenericCaster.java index 70e1f34f4..f973e1d4f 100644 --- a/src/main/java/ortus/boxlang/runtime/dynamic/casters/GenericCaster.java +++ b/src/main/java/ortus/boxlang/runtime/dynamic/casters/GenericCaster.java @@ -215,7 +215,7 @@ public static Object cast( IBoxContext context, Object object, Object oType, Boo } // BL-640 - if we have a DateTime object provided, we use that reference rather than strip the date by using the timecaster if ( object instanceof DateTime || type.equals( "datetime" ) || type.equals( "date" ) || type.equals( "timestamp" ) ) { - return DateTimeCaster.cast( object, fail ); + return DateTimeCaster.cast( object, fail, context ); } if ( type.equals( "time" ) ) { return TimeCaster.cast( object, fail ); diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/DataSource.java b/src/main/java/ortus/boxlang/runtime/jdbc/DataSource.java index b80866d0d..024f9909f 100644 --- a/src/main/java/ortus/boxlang/runtime/jdbc/DataSource.java +++ b/src/main/java/ortus/boxlang/runtime/jdbc/DataSource.java @@ -23,6 +23,7 @@ import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.config.segments.DatasourceConfig; +import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.events.BoxEvent; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.Array; @@ -242,9 +243,9 @@ public DataSource shutdown() { * @return An array of Structs, each representing a row of the result set (if any). If there are no results (say, for an UPDATE statement), an empty * array is returned. */ - public ExecutedQuery execute( String query ) { + public ExecutedQuery execute( String query, IBoxContext context ) { try ( Connection conn = getConnection() ) { - return execute( query, conn ); + return execute( query, conn, context ); } catch ( SQLException e ) { throw new DatabaseException( e.getMessage(), e ); } @@ -263,25 +264,25 @@ public ExecutedQuery execute( String query ) { * @return An array of Structs, each representing a row of the result set (if any). If there are no results (say, for an UPDATE statement), an empty * array is returned. */ - public ExecutedQuery execute( String query, Connection conn ) { + public ExecutedQuery execute( String query, Connection conn, IBoxContext context ) { PendingQuery pendingQuery = new PendingQuery( query, new ArrayList<>() ); - return pendingQuery.execute( conn ); + return pendingQuery.execute( conn, context ); } /** * Execute a query with a List of parameters on a given connection. */ - public ExecutedQuery execute( String query, List parameters, Connection conn ) { + public ExecutedQuery execute( String query, List parameters, Connection conn, IBoxContext context ) { PendingQuery pendingQuery = new PendingQuery( query, parameters ); - return pendingQuery.execute( conn ); + return pendingQuery.execute( conn, context ); } /** * Execute a query with a List of parameters on the default connection. */ - public ExecutedQuery execute( String query, List parameters ) { + public ExecutedQuery execute( String query, List parameters, IBoxContext context ) { try ( Connection conn = getConnection() ) { - return execute( query, parameters, conn ); + return execute( query, parameters, conn, context ); } catch ( SQLException e ) { throw new DatabaseException( e.getMessage(), e ); } @@ -290,17 +291,17 @@ public ExecutedQuery execute( String query, List parameters ) { /** * Execute a query with an array of parameters on a given connection. */ - public ExecutedQuery execute( String query, Array parameters, Connection conn ) { + public ExecutedQuery execute( String query, Array parameters, Connection conn, IBoxContext context ) { PendingQuery pendingQuery = new PendingQuery( query, parameters, new QueryOptions( new Struct() ) ); - return pendingQuery.execute( conn ); + return pendingQuery.execute( conn, context ); } /** * Execute a query with an array of parameters on the default connection. */ - public ExecutedQuery execute( String query, Array parameters ) { + public ExecutedQuery execute( String query, Array parameters, IBoxContext context ) { try ( Connection conn = getConnection() ) { - return execute( query, parameters, conn ); + return execute( query, parameters, conn, context ); } catch ( SQLException e ) { throw new DatabaseException( e.getMessage(), e ); } @@ -309,17 +310,17 @@ public ExecutedQuery execute( String query, Array parameters ) { /** * Execute a query with a struct of parameters on a given connection. */ - public ExecutedQuery execute( String query, IStruct parameters, Connection conn ) { + public ExecutedQuery execute( String query, IStruct parameters, Connection conn, IBoxContext context ) { PendingQuery pendingQuery = new PendingQuery( query, parameters, new QueryOptions( new Struct() ) ); - return pendingQuery.execute( conn ); + return pendingQuery.execute( conn, context ); } /** * Execute a query with a struct of parameters on the default connection. */ - public ExecutedQuery execute( String query, IStruct parameters ) { + public ExecutedQuery execute( String query, IStruct parameters, IBoxContext context ) { try ( Connection conn = getConnection() ) { - return execute( query, parameters, conn ); + return execute( query, parameters, conn, context ); } catch ( SQLException e ) { throw new DatabaseException( e.getMessage(), e ); } diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/PendingQuery.java b/src/main/java/ortus/boxlang/runtime/jdbc/PendingQuery.java index ccbacb5b8..409c8b674 100644 --- a/src/main/java/ortus/boxlang/runtime/jdbc/PendingQuery.java +++ b/src/main/java/ortus/boxlang/runtime/jdbc/PendingQuery.java @@ -31,6 +31,7 @@ import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.cache.providers.ICacheProvider; +import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.dynamic.Attempt; import ortus.boxlang.runtime.dynamic.casters.ArrayCaster; import ortus.boxlang.runtime.dynamic.casters.CastAttempt; @@ -293,7 +294,7 @@ private List buildParameterList( @Nonnull IStruct parameters ) { * * @see ExecutedQuery */ - public @Nonnull ExecutedQuery execute( ConnectionManager connectionManager ) { + public @Nonnull ExecutedQuery execute( ConnectionManager connectionManager, IBoxContext context ) { // We do an early cache check here to avoid the overhead of creating a connection if we already have a matching cached query. if ( isCacheable() ) { logger.debug( "Checking cache for query: {}", this.cacheKey ); @@ -306,7 +307,7 @@ private List buildParameterList( @Nonnull IStruct parameters ) { Connection connection = connectionManager.getConnection( this.queryOptions ); try { - return execute( connection ); + return execute( connection, context ); } finally { if ( connection != null ) { connectionManager.releaseConnection( connection ); @@ -325,7 +326,7 @@ private List buildParameterList( @Nonnull IStruct parameters ) { * * @see ExecutedQuery */ - public @Nonnull ExecutedQuery execute( Connection connection ) { + public @Nonnull ExecutedQuery execute( Connection connection, IBoxContext context ) { if ( isCacheable() ) { // we use separate get() and set() calls over a .getOrSet() so we can run `.setIsCached()` on discovered/cached results. Attempt cachedQuery = this.cacheProvider.get( this.cacheKey ); @@ -333,11 +334,11 @@ private List buildParameterList( @Nonnull IStruct parameters ) { return respondWithCachedQuery( ( ExecutedQuery ) cachedQuery.get() ); } - ExecutedQuery executedQuery = executeStatement( connection ); + ExecutedQuery executedQuery = executeStatement( connection, context ); this.cacheProvider.set( this.cacheKey, executedQuery, this.queryOptions.cacheTimeout, this.queryOptions.cacheLastAccessTimeout ); return executedQuery; } - return executeStatement( connection ); + return executeStatement( connection, context ); } /** @@ -346,7 +347,7 @@ private List buildParameterList( @Nonnull IStruct parameters ) { * * If query parameters are present, a {@link PreparedStatement} will be utilized and populated with the paremeter bindings. Otherwise, a standard {@link Statement} object will be used. * * Will announce a `PRE_QUERY_EXECUTE` event before executing the query. */ - private ExecutedQuery executeStatement( Connection connection ) { + private ExecutedQuery executeStatement( Connection connection, IBoxContext context ) { try { ArrayList queries = new ArrayList<>(); for ( String sqlStatement : this.sql.split( ";" ) ) { @@ -355,7 +356,7 @@ private ExecutedQuery executeStatement( Connection connection ) { ? connection.createStatement() : connection.prepareStatement( this.sql, Statement.RETURN_GENERATED_KEYS ); ) { - applyParameters( statement ); + applyParameters( statement, context ); applyStatementOptions( statement ); interceptorService.announce( @@ -426,7 +427,7 @@ private ExecutedQuery respondWithCachedQuery( ExecutedQuery cachedQuery ) { *

    * Will only take action if 1) there are parameters to apply, and 2) the Statement object is a PreparedStatement. */ - private void applyParameters( Statement statement ) throws SQLException { + private void applyParameters( Statement statement, IBoxContext context ) throws SQLException { if ( this.parameters.isEmpty() ) { return; } @@ -437,9 +438,9 @@ private void applyParameters( Statement statement ) throws SQLException { QueryParameter param = this.parameters.get( i - 1 ); Integer scaleOrLength = param.getScaleOrLength(); if ( scaleOrLength == null ) { - preparedStatement.setObject( i, param.toSQLType(), param.getSqlTypeAsInt() ); + preparedStatement.setObject( i, param.toSQLType( context ), param.getSqlTypeAsInt() ); } else { - preparedStatement.setObject( i, param.toSQLType(), param.getSqlTypeAsInt(), scaleOrLength ); + preparedStatement.setObject( i, param.toSQLType( context ), param.getSqlTypeAsInt(), scaleOrLength ); } } } diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/QueryParameter.java b/src/main/java/ortus/boxlang/runtime/jdbc/QueryParameter.java index 1977302d0..dbd7517cb 100644 --- a/src/main/java/ortus/boxlang/runtime/jdbc/QueryParameter.java +++ b/src/main/java/ortus/boxlang/runtime/jdbc/QueryParameter.java @@ -14,6 +14,7 @@ */ package ortus.boxlang.runtime.jdbc; +import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.dynamic.casters.BigIntegerCaster; import ortus.boxlang.runtime.dynamic.casters.BooleanCaster; import ortus.boxlang.runtime.dynamic.casters.CastAttempt; @@ -120,7 +121,7 @@ public Object getValue() { /** * Retrieve the value casted to the declared SQL type of the parameter.. */ - public Object toSQLType() { + public Object toSQLType( IBoxContext context ) { if ( this.value == null ) { return null; } @@ -132,9 +133,9 @@ public Object toSQLType() { case QueryColumnType.CHAR, VARCHAR -> StringCaster.cast( this.value ); case QueryColumnType.BINARY -> this.value; // @TODO: Will this work? case QueryColumnType.BIT -> BooleanCaster.cast( this.value ); - case QueryColumnType.TIME -> DateTimeCaster.cast( this.value ); - case QueryColumnType.DATE -> DateTimeCaster.cast( this.value ); - case QueryColumnType.TIMESTAMP -> new java.sql.Timestamp( DateTimeCaster.cast( this.value ).toEpochMillis() ); + case QueryColumnType.TIME -> DateTimeCaster.cast( this.value, context ); + case QueryColumnType.DATE -> DateTimeCaster.cast( this.value, context ); + case QueryColumnType.TIMESTAMP -> new java.sql.Timestamp( DateTimeCaster.cast( this.value, context ).toEpochMillis() ); case QueryColumnType.OBJECT -> this.value; case QueryColumnType.OTHER -> this.value; case QueryColumnType.NULL -> null; diff --git a/src/main/java/ortus/boxlang/runtime/types/DateTime.java b/src/main/java/ortus/boxlang/runtime/types/DateTime.java index 81bde3ee3..1271d5857 100644 --- a/src/main/java/ortus/boxlang/runtime/types/DateTime.java +++ b/src/main/java/ortus/boxlang/runtime/types/DateTime.java @@ -201,13 +201,13 @@ public DateTime( ZonedDateTime dateTime ) { * * @param date The date object */ - public DateTime( java.util.Date date ) { + public DateTime( java.util.Date date, ZoneId timezone ) { this( ( date instanceof java.sql.Date sqlDate ) - ? ZonedDateTime.of( sqlDate.toLocalDate(), LocalTime.of( 0, 0 ), ZoneId.systemDefault() ) + ? ZonedDateTime.of( sqlDate.toLocalDate(), LocalTime.of( 0, 0 ), timezone ) : ( date instanceof java.sql.Time sqlTime ) - ? ZonedDateTime.of( LocalDate.EPOCH, sqlTime.toLocalTime(), ZoneId.systemDefault() ) - : date.toInstant().atZone( ZoneId.systemDefault() ) + ? ZonedDateTime.of( LocalDate.EPOCH, sqlTime.toLocalTime(), timezone ) + : date.toInstant().atZone( timezone ) ); } @@ -217,8 +217,18 @@ public DateTime( java.util.Date date ) { * * @param date The date object */ - public DateTime( java.sql.Date date ) { - this( ZonedDateTime.of( date.toLocalDate(), LocalTime.of( 0, 0 ), ZoneId.systemDefault() ) ); + public DateTime( java.sql.Date date, IBoxContext context ) { + this( ZonedDateTime.of( date.toLocalDate(), LocalTime.of( 0, 0 ), LocalizationUtil.parseZoneId( null, context ) ) ); + } + + /** + * Constructor to create DateTime from a java.sql.Date object which has no time component + * This will use the system default timezone + * + * @param date The date object + */ + public DateTime( java.sql.Date date, ZoneId timezone ) { + this( ZonedDateTime.of( date.toLocalDate(), LocalTime.of( 0, 0 ), timezone ) ); } /** @@ -886,7 +896,8 @@ public int compareTo( ChronoZonedDateTime other ) { if ( other instanceof ZonedDateTime castedDateTime ) { return getWrapped().compareTo( castedDateTime ); } - return getWrapped().compareTo( DateTimeCaster.cast( other ).getWrapped() ); + + return getWrapped().compareTo( DateTimeCaster.cast( other, BoxRuntime.getInstance().getRuntimeContext() ).getWrapped() ); } /** diff --git a/src/main/java/ortus/boxlang/runtime/util/DumpUtil.java b/src/main/java/ortus/boxlang/runtime/util/DumpUtil.java index 60e675a8f..0e286e136 100644 --- a/src/main/java/ortus/boxlang/runtime/util/DumpUtil.java +++ b/src/main/java/ortus/boxlang/runtime/util/DumpUtil.java @@ -351,7 +351,7 @@ public static void dumpHTMLToBuffer( String posInCode = ""; String dumpTemplate = null; StringBuffer buffer = null; - String templateName = discoverTemplateName( target ); + String templateName = discoverTemplateName( target, context ); try { // Get and Compile the Dump template to execute. @@ -403,7 +403,7 @@ public static void dumpHTMLToBuffer( * * @return The template name found in the resources folder */ - private static String discoverTemplateName( Object target ) { + private static String discoverTemplateName( Object target, IBoxContext context ) { if ( target == null || target instanceof NullValue ) { return "Null.bxm"; } else if ( target instanceof Throwable ) { @@ -422,7 +422,7 @@ private static String discoverTemplateName( Object target ) { return "DateTime.bxm"; } else if ( target instanceof LocalDate || target instanceof LocalDateTime || target instanceof ZonedDateTime || target instanceof java.sql.Date || target instanceof java.sql.Timestamp || target instanceof java.util.Date ) { - target = DateTimeCaster.cast( target ); + target = DateTimeCaster.cast( target, context ); return "DateTime.bxm"; } // These classes will just dump the class name and the `toString()` equivalent diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/jdbc/BaseJDBCTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/jdbc/BaseJDBCTest.java index 69aef749a..6f13bfe8a 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/jdbc/BaseJDBCTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/jdbc/BaseJDBCTest.java @@ -32,10 +32,11 @@ public class BaseJDBCTest { @BeforeAll public static void setUp() { - instance = BoxRuntime.getInstance( true ); - datasourceService = instance.getDataSourceService(); + instance = BoxRuntime.getInstance( true ); + IBoxContext setUpContext = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); + datasourceService = instance.getDataSourceService(); String uniqueName = UUID.randomUUID().toString(); - datasource = JDBCTestUtils.constructTestDataSource( uniqueName ); + datasource = JDBCTestUtils.constructTestDataSource( uniqueName, setUpContext ); datasourceService.register( Key.of( uniqueName ), datasource ); if ( JDBCTestUtils.hasMSSQLModule() ) { @@ -54,7 +55,7 @@ public static void setUp() { mssqlDatasource.getConfiguration() ); datasourceService.register( mssqlName, mssqlDatasource ); - JDBCTestUtils.ensureTestTableExists( mssqlDatasource ); + JDBCTestUtils.ensureTestTableExists( mssqlDatasource, setUpContext ); } if ( JDBCTestUtils.hasMySQLModule() ) { @@ -81,20 +82,21 @@ public static void setUp() { mysqlDatasource.getConfiguration() ); datasourceService.register( mysqlName, mysqlDatasource ); - JDBCTestUtils.ensureTestTableExists( mysqlDatasource ); + JDBCTestUtils.ensureTestTableExists( mysqlDatasource, setUpContext ); } } @AfterAll public static void teardown() throws SQLException { - JDBCTestUtils.dropDevelopersTable( datasource ); + IBoxContext tearDownContext = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); + JDBCTestUtils.dropDevelopersTable( datasource, tearDownContext ); datasource.shutdown(); if ( mssqlDatasource != null ) { - JDBCTestUtils.dropDevelopersTable( mssqlDatasource ); + JDBCTestUtils.dropDevelopersTable( mssqlDatasource, tearDownContext ); mssqlDatasource.shutdown(); } if ( mysqlDatasource != null ) { - JDBCTestUtils.dropDevelopersTable( mysqlDatasource ); + JDBCTestUtils.dropDevelopersTable( mysqlDatasource, tearDownContext ); mysqlDatasource.shutdown(); } } @@ -104,7 +106,7 @@ public void setupEach() { context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); context.getConnectionManager().setDefaultDatasource( datasource ); variables = context.getScopeNearby( VariablesScope.name ); - assertDoesNotThrow( () -> JDBCTestUtils.resetDevelopersTable( datasource ) ); + assertDoesNotThrow( () -> JDBCTestUtils.resetDevelopersTable( datasource, context ) ); // Clear the caches instance.getCacheService().getDefaultCache().clearAll(); } diff --git a/src/test/java/ortus/boxlang/runtime/components/jdbc/DBInfoTest.java b/src/test/java/ortus/boxlang/runtime/components/jdbc/DBInfoTest.java index 15b418c6b..11dc5b216 100644 --- a/src/test/java/ortus/boxlang/runtime/components/jdbc/DBInfoTest.java +++ b/src/test/java/ortus/boxlang/runtime/components/jdbc/DBInfoTest.java @@ -34,7 +34,10 @@ import org.junit.jupiter.api.condition.EnabledIf; import ortus.boxlang.compiler.parser.BoxSourceType; +import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.bifs.global.jdbc.BaseJDBCTest; +import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; import ortus.boxlang.runtime.jdbc.DataSource; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.IStruct; @@ -47,14 +50,21 @@ public class DBInfoTest extends BaseJDBCTest { static Key result = new Key( "result" ); static DataSource MySQLDataSource; + static BoxRuntime instance; + IBoxContext context; @BeforeAll public static void additionalSetup() { - getDatasource().execute( "CREATE TABLE admins ( id INTEGER PRIMARY KEY, name VARCHAR(155) )" ); + instance = BoxRuntime.getInstance( true ); + IBoxContext setUpContext = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); + + getDatasource().execute( "CREATE TABLE admins ( id INTEGER PRIMARY KEY, name VARCHAR(155) )", setUpContext ); getDatasource().execute( - "CREATE TABLE projects ( id INTEGER PRIMARY KEY, name VARCHAR(155), leadDev INTEGER, CONSTRAINT devID FOREIGN KEY (leadDev) REFERENCES admins(id) )" ); + "CREATE TABLE projects ( id INTEGER PRIMARY KEY, name VARCHAR(155), leadDev INTEGER, CONSTRAINT devID FOREIGN KEY (leadDev) REFERENCES admins(id) )", + setUpContext ); getDatasource().execute( - "CREATE PROCEDURE FOO(IN S_MONTH INTEGER, IN S_YEAR INTEGER, OUT TOTAL DECIMAL(10,2)) PARAMETER STYLE JAVA READS SQL DATA LANGUAGE JAVA EXTERNAL NAME 'com.example.sales.calculateRevenueByMonth'" ); + "CREATE PROCEDURE FOO(IN S_MONTH INTEGER, IN S_YEAR INTEGER, OUT TOTAL DECIMAL(10,2)) PARAMETER STYLE JAVA READS SQL DATA LANGUAGE JAVA EXTERNAL NAME 'com.example.sales.calculateRevenueByMonth'", + setUpContext ); } @DisplayName( "It requires a non-null `type` argument matching a valid type" ) diff --git a/src/test/java/ortus/boxlang/runtime/components/jdbc/StoredProcTest.java b/src/test/java/ortus/boxlang/runtime/components/jdbc/StoredProcTest.java index 27f44ec2c..adc852ae1 100644 --- a/src/test/java/ortus/boxlang/runtime/components/jdbc/StoredProcTest.java +++ b/src/test/java/ortus/boxlang/runtime/components/jdbc/StoredProcTest.java @@ -9,22 +9,28 @@ import java.sql.SQLException; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledIf; import ortus.boxlang.compiler.parser.BoxSourceType; +import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.bifs.global.jdbc.BaseJDBCTest; +import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; import ortus.boxlang.runtime.jdbc.DataSource; import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.types.Struct; import ortus.boxlang.runtime.types.Query; +import ortus.boxlang.runtime.types.Struct; import ortus.boxlang.runtime.types.exceptions.BoxValidationException; import ortus.boxlang.runtime.types.exceptions.DatabaseException; public class StoredProcTest extends BaseJDBCTest { + static BoxRuntime instance; + IBoxContext context; static Key result = new Key( "result" ); private DataSource mysqlDatasource; @@ -52,7 +58,9 @@ public static void withResultSet( ResultSet[] rs ) { @BeforeAll public static void setUpStoredProc() { - DataSource ds = getDatasource(); + instance = BoxRuntime.getInstance( true ); + IBoxContext setUpContext = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); + DataSource ds = getDatasource(); ds.execute( """ CREATE PROCEDURE withInParam( IN TOTAL Integer ) @@ -60,7 +68,8 @@ CREATE PROCEDURE withInParam( IN TOTAL Integer ) READS SQL DATA LANGUAGE JAVA EXTERNAL NAME 'ortus.boxlang.runtime.components.jdbc.StoredProcTest.withInParam' - """ + """, + setUpContext ); ds.execute( """ @@ -69,7 +78,8 @@ CREATE PROCEDURE withOutParam( OUT int ) READS SQL DATA LANGUAGE JAVA EXTERNAL NAME 'ortus.boxlang.runtime.components.jdbc.StoredProcTest.withOutParam' - """ + """, + setUpContext ); ds.execute( """ @@ -79,17 +89,24 @@ CREATE PROCEDURE withResultSet() LANGUAGE JAVA EXTERNAL NAME 'ortus.boxlang.runtime.components.jdbc.StoredProcTest.withResultSet' DYNAMIC RESULT SETS 1 - """ + """, + setUpContext ); ds.execute( """ CREATE PROCEDURE doNothing() PARAMETER STYLE JAVA READS SQL DATA LANGUAGE JAVA EXTERNAL NAME 'ortus.boxlang.runtime.components.jdbc.StoredProcTest.doNothing' - """ + """, + setUpContext ); } + @BeforeEach + public void setupEach() { + context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); + } + public void setupMySQLTest() { mysqlDatasource = DataSource.fromStruct( "MysqlStoredProcTest", @@ -114,7 +131,8 @@ public void setupMySQLTest() { WHERE name <> companyName order by name desc; END$$ - """ + """, + context ); mysqlDatasource.execute( """ @@ -123,7 +141,8 @@ public void setupMySQLTest() { `name` text NOT NULL, `active` tinyint(1) NOT NULL DEFAULT '1' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; - """ + """, + context ); mysqlDatasource.execute( """ @@ -132,7 +151,8 @@ public void setupMySQLTest() { (2, 'SEGA', 0), (3, 'Sony', 1), (4, 'Microsoft', 1); - """ + """, + context ); } diff --git a/src/test/java/ortus/boxlang/runtime/jdbc/DataSourceTest.java b/src/test/java/ortus/boxlang/runtime/jdbc/DataSourceTest.java index d5dcacff9..3e2d3f6bd 100644 --- a/src/test/java/ortus/boxlang/runtime/jdbc/DataSourceTest.java +++ b/src/test/java/ortus/boxlang/runtime/jdbc/DataSourceTest.java @@ -60,9 +60,10 @@ public class DataSourceTest { @BeforeAll public static void setUp() { - instance = BoxRuntime.getInstance( true ); - datasource = JDBCTestUtils.constructTestDataSource( java.lang.invoke.MethodHandles.lookup().lookupClass().getSimpleName() ); - testDB = JDBCTestUtils.constructTestDataSource( "testDB" ); + instance = BoxRuntime.getInstance( true ); + IBoxContext setUpContext = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); + datasource = JDBCTestUtils.constructTestDataSource( java.lang.invoke.MethodHandles.lookup().lookupClass().getSimpleName(), setUpContext ); + testDB = JDBCTestUtils.constructTestDataSource( "testDB", setUpContext ); } @AfterAll @@ -78,9 +79,9 @@ public static void teardown() throws SQLException { @BeforeEach public void resetTable() { - assertDoesNotThrow( () -> JDBCTestUtils.resetDevelopersTable( datasource ) ); context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); variables = context.getScopeNearby( VariablesScope.name ); + assertDoesNotThrow( () -> JDBCTestUtils.resetDevelopersTable( datasource, context ) ); } @DisplayName( "It can get an Apache Derby JDBC connection" ) @@ -171,7 +172,7 @@ void testDataSourceClose() throws SQLException { void testDataSourceExecute() { try ( Connection conn = datasource.getConnection() ) { assertDoesNotThrow( () -> { - ExecutedQuery executedQuery = datasource.execute( "SELECT * FROM developers", conn ); + ExecutedQuery executedQuery = datasource.execute( "SELECT * FROM developers", conn, context ); assertEquals( 4, executedQuery.getRecordCount() ); } ); } catch ( SQLException e ) { @@ -187,7 +188,8 @@ void testDataSourceWithParamsExecute() { ExecutedQuery executedQuery = datasource.execute( "SELECT * FROM developers WHERE name = ?", Array.of( "Michael Born" ), - conn + conn, + context ); assertEquals( 1, executedQuery.getRecordCount() ); Query results = executedQuery.getResults(); @@ -209,7 +211,8 @@ void testDataSourceWithNamedParamsExecute() { ExecutedQuery executedQuery = datasource.execute( "SELECT * FROM developers WHERE name = :name", Struct.of( "name", "Michael Born" ), - conn + conn, + context ); assertEquals( 1, executedQuery.getRecordCount() ); Query results = executedQuery.getResults(); @@ -231,7 +234,8 @@ void testDatasourceWithMissingNamedParams() { datasource.execute( "SELECT * FROM developers WHERE name = :name", Struct.of( "developer", "Michael Born" ), - conn + conn, + context ); } ); assertEquals( "Missing param in query: [name]. SQL: SELECT * FROM developers WHERE name = :name", exception.getMessage() ); @@ -243,7 +247,7 @@ void testDatasourceWithMissingNamedParams() { @DisplayName( "It can get results in query form" ) @Test void testQueryExecuteQueryResults() { - ExecutedQuery executedQuery = datasource.execute( "SELECT * FROM developers WHERE id=1" ); + ExecutedQuery executedQuery = datasource.execute( "SELECT * FROM developers WHERE id=1", context ); Query queryResults = executedQuery.getResults(); assertNotEquals( 0, queryResults.size() ); @@ -258,13 +262,13 @@ void testQueryExecuteQueryResults() { @DisplayName( "It can get results in query form" ) @Test void testQueryExecuteException() { - assertThrows( DatabaseException.class, () -> datasource.execute( "SELECT * FROM foobar WHERE id=1" ) ); + assertThrows( DatabaseException.class, () -> datasource.execute( "SELECT * FROM foobar WHERE id=1", context ) ); } @DisplayName( "It can get results in array form" ) @Test void testQueryExecuteArrayResults() { - ExecutedQuery executedQuery = datasource.execute( "SELECT * FROM developers WHERE id=1" ); + ExecutedQuery executedQuery = datasource.execute( "SELECT * FROM developers WHERE id=1", context ); Array results = executedQuery.getResultsAsArray(); assertNotEquals( 0, results.size() ); @@ -284,8 +288,9 @@ void testGeneratedKeysOnInsert() { try ( Connection conn = datasource.getConnection() ) { assertDoesNotThrow( () -> { datasource.execute( - "CREATE TABLE developers2 (id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY(START WITH 1, INCREMENT BY 1), name VARCHAR(155) NOT NULL)" ); - ExecutedQuery executedQuery = datasource.execute( "INSERT INTO developers2 (name) VALUES ('Eric Peterson')", conn ); + "CREATE TABLE developers2 (id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY(START WITH 1, INCREMENT BY 1), name VARCHAR(155) NOT NULL)", + context ); + ExecutedQuery executedQuery = datasource.execute( "INSERT INTO developers2 (name) VALUES ('Eric Peterson')", conn, context ); assertEquals( 0, executedQuery.getRecordCount() ); BigDecimal generatedKey = ( BigDecimal ) executedQuery.getGeneratedKey(); assert generatedKey != null; @@ -294,7 +299,7 @@ void testGeneratedKeysOnInsert() { } catch ( SQLException e ) { throw new RuntimeException( e ); } finally { - datasource.execute( "DROP TABLE developers2" ); + datasource.execute( "DROP TABLE developers2", context ); } } diff --git a/src/test/java/ortus/boxlang/runtime/jdbc/DerbyModuleTest.java b/src/test/java/ortus/boxlang/runtime/jdbc/DerbyModuleTest.java index 53c533d45..d9a5fbc40 100644 --- a/src/test/java/ortus/boxlang/runtime/jdbc/DerbyModuleTest.java +++ b/src/test/java/ortus/boxlang/runtime/jdbc/DerbyModuleTest.java @@ -57,7 +57,8 @@ void testDerbyConnection() throws SQLException { moduleService.onStartup(); DataSource datasource = JDBCTestUtils.constructTestDataSource( - "DerbyModuleTest" + "DerbyModuleTest", + context ); context.getConnectionManager().setDefaultDatasource( datasource ); diff --git a/src/test/java/ortus/boxlang/runtime/jdbc/TransactionTest.java b/src/test/java/ortus/boxlang/runtime/jdbc/TransactionTest.java index f52d617b2..432e1f38c 100644 --- a/src/test/java/ortus/boxlang/runtime/jdbc/TransactionTest.java +++ b/src/test/java/ortus/boxlang/runtime/jdbc/TransactionTest.java @@ -21,18 +21,23 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; -import ortus.boxlang.runtime.context.IJDBCCapableContext; import ortus.boxlang.compiler.parser.BoxSourceType; +import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.bifs.global.jdbc.BaseJDBCTest; +import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.context.IJDBCCapableContext; +import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.Query; @@ -41,7 +46,19 @@ public class TransactionTest extends BaseJDBCTest { - static Key result = new Key( "result" ); + static Key result = new Key( "result" ); + static BoxRuntime instance; + IBoxContext context; + + @BeforeAll + public static void setUp() { + instance = BoxRuntime.getInstance( true ); + } + + @BeforeEach + public void setupEach() { + context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); + } @ParameterizedTest @org.junit.jupiter.params.provider.ValueSource( strings = { @@ -376,10 +393,9 @@ public void testActionEqualsSetSavepoint() { @Test public void testCustomQueryDatasource() { - getInstance().getConfiguration().datasources.put( Key.of( "myOtherDatasource" ), - JDBCTestUtils.constructTestDataSource( "myOtherDatasource" ).getConfiguration() + JDBCTestUtils.constructTestDataSource( "myOtherDatasource", context ).getConfiguration() ); getInstance().executeSource( @@ -409,7 +425,7 @@ public void testTransactionDatasource() { // Set up a datasource getInstance().getConfiguration().datasources.put( Key.of( "fooey" ), - JDBCTestUtils.constructTestDataSource( "fooey" ).getConfiguration() + JDBCTestUtils.constructTestDataSource( "fooey", context ).getConfiguration() ); getInstance().executeSource( diff --git a/src/test/java/ortus/boxlang/runtime/types/DateTimeTest.java b/src/test/java/ortus/boxlang/runtime/types/DateTimeTest.java index 63a2b6756..cff863237 100644 --- a/src/test/java/ortus/boxlang/runtime/types/DateTimeTest.java +++ b/src/test/java/ortus/boxlang/runtime/types/DateTimeTest.java @@ -26,13 +26,31 @@ import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import ortus.boxlang.runtime.BoxRuntime; +import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; import ortus.boxlang.runtime.dynamic.casters.LongCaster; public class DateTimeTest { + static BoxRuntime instance; + IBoxContext context; + + @BeforeAll + public static void setUp() { + instance = BoxRuntime.getInstance( true ); + } + + @BeforeEach + public void setupEach() { + context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); + } + @DisplayName( "Test Constructors" ) @Test void testConstructors() { @@ -48,7 +66,7 @@ void testConstructors() { assertThat( dateTimeFromParts.setFormat( "yyyy-MM-dd HH:mm:ss" ).toString() ).isEqualTo( "2023-12-31 12:30:30" ); DateTime dateFromParts = new DateTime( 2023, 12, 31 ); assertThat( dateFromParts.setFormat( "yyyy-MM-dd HH:mm:ss" ).toString() ).isEqualTo( "2023-12-31 00:00:00" ); - DateTime dateFromSQLDate = new DateTime( Date.valueOf( "2023-12-31" ) ); + DateTime dateFromSQLDate = new DateTime( Date.valueOf( "2023-12-31" ), context ); assertThat( dateFromSQLDate.setFormat( "yyyy-MM-dd HH:mm:ss" ).toString() ).isEqualTo( "2023-12-31 00:00:00" ); DateTime datefromSQLTime = new DateTime( Time.valueOf( "23:00:00" ) ); assertThat( datefromSQLTime.setFormat( "yyyy-MM-dd HH:mm:ss" ).toString() ).isEqualTo( "1970-01-01 23:00:00" ); diff --git a/src/test/java/tools/JDBCTestUtils.java b/src/test/java/tools/JDBCTestUtils.java index f27221c87..3c23a557e 100644 --- a/src/test/java/tools/JDBCTestUtils.java +++ b/src/test/java/tools/JDBCTestUtils.java @@ -2,6 +2,7 @@ import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.config.segments.DatasourceConfig; +import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.jdbc.DataSource; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.IStruct; @@ -153,7 +154,7 @@ public static DataSource buildDatasource( String databaseName ) { * * @return A DataSource instance with a consistent `DEVELOPERS` table created. */ - public static DataSource constructTestDataSource( String databaseName ) { + public static DataSource constructTestDataSource( String databaseName, IBoxContext context ) { DataSource datasource = DataSource.fromStruct( databaseName, Struct.of( @@ -161,7 +162,7 @@ public static DataSource constructTestDataSource( String databaseName ) { "driver", "derby", "connectionString", "jdbc:derby:memory:" + databaseName + ";create=true" ) ); - ensureTestTableExists( datasource ); + ensureTestTableExists( datasource, context ); return datasource; } @@ -170,16 +171,16 @@ public static DataSource constructTestDataSource( String databaseName ) { * * @param datasource */ - public static void dropDevelopersTable( DataSource datasource ) { - datasource.execute( "DROP TABLE developers" ); + public static void dropDevelopersTable( DataSource datasource, IBoxContext context ) { + datasource.execute( "DROP TABLE developers", context ); } /** * Ensure various tables exist in the database for testing purposes. */ - public static void ensureTestTableExists( DataSource datasource ) { + public static void ensureTestTableExists( DataSource datasource, IBoxContext context ) { try { - datasource.execute( "CREATE TABLE developers ( id INTEGER, name VARCHAR(155), role VARCHAR(155), createdAt TIMESTAMP )" ); + datasource.execute( "CREATE TABLE developers ( id INTEGER, name VARCHAR(155), role VARCHAR(155), createdAt TIMESTAMP )", context ); } catch ( DatabaseException e ) { // Ignore the exception if the table already exists } @@ -190,11 +191,11 @@ public static void ensureTestTableExists( DataSource datasource ) { * * @param datasource */ - public static void resetDevelopersTable( DataSource datasource ) { - datasource.execute( "TRUNCATE TABLE developers" ); - datasource.execute( "INSERT INTO developers ( id, name, role ) VALUES ( 77, 'Michael Born', 'Developer' )" ); - datasource.execute( "INSERT INTO developers ( id, name, role ) VALUES ( 1, 'Luis Majano', 'CEO' )" ); - datasource.execute( "INSERT INTO developers ( id, name, role ) VALUES ( 42, 'Eric Peterson', 'Developer' )" ); - datasource.execute( "INSERT INTO developers ( id, name, role ) VALUES ( 9001, 'Bob O''Reily', 'QA' )" ); + public static void resetDevelopersTable( DataSource datasource, IBoxContext context ) { + datasource.execute( "TRUNCATE TABLE developers", context ); + datasource.execute( "INSERT INTO developers ( id, name, role ) VALUES ( 77, 'Michael Born', 'Developer' )", context ); + datasource.execute( "INSERT INTO developers ( id, name, role ) VALUES ( 1, 'Luis Majano', 'CEO' )", context ); + datasource.execute( "INSERT INTO developers ( id, name, role ) VALUES ( 42, 'Eric Peterson', 'Developer' )", context ); + datasource.execute( "INSERT INTO developers ( id, name, role ) VALUES ( 9001, 'Bob O''Reily', 'QA' )", context ); } } From 63d8cc2dce709af5dd387c96e999ba6781260efb Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 6 Dec 2024 08:42:33 -0600 Subject: [PATCH 112/193] Have interrupted exceptions use the string "interrupted" in them so we can catch it in ColdBox --- .../java/ortus/boxlang/runtime/bifs/global/system/Sleep.java | 3 ++- .../boxlang/runtime/bifs/global/system/SystemExecute.java | 3 ++- src/main/java/ortus/boxlang/runtime/components/net/HTTP.java | 4 +++- src/main/java/ortus/boxlang/runtime/net/HttpManager.java | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/system/Sleep.java b/src/main/java/ortus/boxlang/runtime/bifs/global/system/Sleep.java index 3d629529e..9b967be47 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/system/Sleep.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/system/Sleep.java @@ -53,7 +53,8 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { Thread.sleep( duration ); } catch ( InterruptedException e ) { throw new BoxRuntimeException( - "An unexpected error occurred while attempting to sleep the thread", + "The sleep thread was interrupted", + "InterruptedException", e ); } diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/system/SystemExecute.java b/src/main/java/ortus/boxlang/runtime/bifs/global/system/SystemExecute.java index 579dc23f6..b454ed6b2 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/system/SystemExecute.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/system/SystemExecute.java @@ -177,7 +177,8 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { ); } catch ( InterruptedException ie ) { throw new BoxRuntimeException( - "An error occurred while attempting to wait for process completion", + "The process was interrupted while waiting for the command to complete", + "InterruptedException", ie ); } diff --git a/src/main/java/ortus/boxlang/runtime/components/net/HTTP.java b/src/main/java/ortus/boxlang/runtime/components/net/HTTP.java index ceee8939b..e21a0ad69 100644 --- a/src/main/java/ortus/boxlang/runtime/components/net/HTTP.java +++ b/src/main/java/ortus/boxlang/runtime/components/net/HTTP.java @@ -316,7 +316,9 @@ public BodyResult _invoke( IBoxContext context, IStruct attributes, ComponentBod throw new BoxRuntimeException( innerException.getMessage() ); } return DEFAULT_RETURN; - } catch ( URISyntaxException | InterruptedException | IOException e ) { + } catch ( InterruptedException e ) { + throw new BoxRuntimeException( "The request was interrupted", "InterruptedException", e ); + } catch ( URISyntaxException | IOException e ) { throw new BoxRuntimeException( e.getMessage(), e ); } } diff --git a/src/main/java/ortus/boxlang/runtime/net/HttpManager.java b/src/main/java/ortus/boxlang/runtime/net/HttpManager.java index 41c4aa525..1e2206321 100644 --- a/src/main/java/ortus/boxlang/runtime/net/HttpManager.java +++ b/src/main/java/ortus/boxlang/runtime/net/HttpManager.java @@ -92,7 +92,7 @@ public HttpClient.Version version() { } }; } catch ( InterruptedException e ) { - throw new BoxRuntimeException( e.getMessage(), e ); + throw new BoxRuntimeException( "The request was interrupted", "InterruptedException", e ); } } From 3e1456722bfd2c155fd00d90365384942595e78f Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 6 Dec 2024 10:24:08 -0600 Subject: [PATCH 113/193] Fix skipping getters and setters from property metadata --- .../runtime/runnables/BoxClassSupport.java | 8 ++++++-- src/test/java/TestCases/phase3/ClassTest.java | 17 +++++++++++++++++ .../java/TestCases/phase3/GeneratedAccessor.bx | 7 +++++++ 3 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 src/test/java/TestCases/phase3/GeneratedAccessor.bx diff --git a/src/main/java/ortus/boxlang/runtime/runnables/BoxClassSupport.java b/src/main/java/ortus/boxlang/runtime/runnables/BoxClassSupport.java index f12795337..a54ef9c8a 100644 --- a/src/main/java/ortus/boxlang/runtime/runnables/BoxClassSupport.java +++ b/src/main/java/ortus/boxlang/runtime/runnables/BoxClassSupport.java @@ -89,8 +89,12 @@ public static void defaultProperties( IClassRunnable thisClass, IBoxContext cont } if ( hasAccessors( thisClass ) ) { // Don't override UDFs from a parent class which may already be defined - context.registerUDF( property.generatedGetter(), false ); - context.registerUDF( property.generatedSetter(), false ); + if ( thisClass.getGetterLookup().containsKey( property.getterName() ) ) { + context.registerUDF( property.generatedGetter(), false ); + } + if ( thisClass.getSetterLookup().containsKey( property.setterName() ) ) { + context.registerUDF( property.generatedSetter(), false ); + } } } } diff --git a/src/test/java/TestCases/phase3/ClassTest.java b/src/test/java/TestCases/phase3/ClassTest.java index c97ac9dd6..44efe9a76 100644 --- a/src/test/java/TestCases/phase3/ClassTest.java +++ b/src/test/java/TestCases/phase3/ClassTest.java @@ -1091,6 +1091,23 @@ public void testInlineJavaExtendsFieldPublic() { } + @Test + public void testGeneratedAccessors() { + // @formatter:off + instance.executeSource( + """ + cfc = new src.test.java.TestCases.phase3.GeneratedAccessor(); + """, + context ); + IStruct cfc = (IStruct) variables.get( Key.of( "cfc" ) ); + assertThat( cfc.containsKey( Key.of( "getName" ) ) ).isEqualTo( true ); + assertThat( cfc.containsKey( Key.of( "setName" ) ) ).isEqualTo( true ); + assertThat( cfc.containsKey( Key.of( "getAge" ) ) ).isEqualTo( false ); + assertThat( cfc.containsKey( Key.of( "setAge" ) ) ).isEqualTo( true ); + assertThat( cfc.containsKey( Key.of( "getEmail" ) ) ).isEqualTo( true ); + assertThat( cfc.containsKey( Key.of( "setEmail" ) ) ).isEqualTo( false ); + } + @Test public void testImplicitAccessor() { instance.executeSource( diff --git a/src/test/java/TestCases/phase3/GeneratedAccessor.bx b/src/test/java/TestCases/phase3/GeneratedAccessor.bx new file mode 100644 index 000000000..ce7c9bcfd --- /dev/null +++ b/src/test/java/TestCases/phase3/GeneratedAccessor.bx @@ -0,0 +1,7 @@ +class accessors="true" { + + property string name; + property integer age getter="false"; + property string email setter="false"; + +} \ No newline at end of file From 22fa113012f52ec2f87a8f4c13f79a05bb7de163 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 6 Dec 2024 10:36:40 -0600 Subject: [PATCH 114/193] small optimizations --- .../java/ortus/boxlang/runtime/BoxRunner.java | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/BoxRunner.java b/src/main/java/ortus/boxlang/runtime/BoxRunner.java index a337cdf61..10aa7dcf2 100644 --- a/src/main/java/ortus/boxlang/runtime/BoxRunner.java +++ b/src/main/java/ortus/boxlang/runtime/BoxRunner.java @@ -29,8 +29,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import org.apache.commons.lang3.StringUtils; - import com.fasterxml.jackson.jr.ob.JSONObjectException; import ortus.boxlang.compiler.BXCompiler; @@ -79,7 +77,12 @@ public class BoxRunner { /** * A list of action commands that can be executed by the BoxRunner: compile, cftranspile, featureAudit */ - private static final List ACTION_COMMANDS = List.of( "compile", "cftranspile", "featureaudit" ); + private static final List ACTION_COMMANDS = List.of( "compile", "cftranspile", "featureaudit" ); + + /** + * The allowed template extensions that can be executed by the BoxRunner + */ + private static final List ALLOWED_TEMPLATE_EXECUTIONS = List.of( ".cfm", ".cfs", ".bxm", ".bx", ".bxs" ); /** * Main entry point for the BoxLang runtime. @@ -315,9 +318,7 @@ private static CLIOptions parseCommandLineOptions( String[] args ) { } // Template to execute? - // If the current ends with .bx/bxs/bxm then it's a template - // TODO: contribute cfm and cfs from compat module - if ( actionCommand == null && StringUtils.endsWithAny( current, ".cfm", ".cfs", ".bxm", ".bx", ".bxs" ) ) { + if ( actionCommand == null && ALLOWED_TEMPLATE_EXECUTIONS.contains( current ) ) { file = templateToAbsolute( current ); continue; } @@ -347,20 +348,8 @@ private static CLIOptions parseCommandLineOptions( String[] args ) { cliArgs.add( current ); } - return new CLIOptions( - file, - debug, - code, - configFile, - printAST, - transpile, - runtimeHome, - showVersion, - cliArgs, - args, - targetModule, - actionCommand - ); + return new CLIOptions( file, debug, code, configFile, printAST, transpile, runtimeHome, showVersion, cliArgs, args, targetModule, actionCommand ); + } /** From 197c942d3a67657386b7aec1276e82c7a27e44a7 Mon Sep 17 00:00:00 2001 From: Jon Clausen Date: Fri, 6 Dec 2024 10:38:20 -0600 Subject: [PATCH 115/193] BL-825 Resolve - fixes issue with TZ casting of String dates in DateConvert --- .../bifs/global/temporal/DateConvert.java | 19 ++++---- .../bifs/global/temporal/DateConvertTest.java | 44 ++++++++++++++++++- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/DateConvert.java b/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/DateConvert.java index 0672bcdee..5301e1fb8 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/DateConvert.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/temporal/DateConvert.java @@ -32,7 +32,8 @@ @BoxBIF public class DateConvert extends BIF { - private static final Key utc2Local = Key.of( "utc2Local" ); + private static final Key utc2Local = Key.of( "utc2Local" ); + private static final ZoneId utcZone = ZoneId.of( "UTC" ); /** * Constructor @@ -59,14 +60,16 @@ public DateConvert() { public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { Key conversion = Key.of( arguments.getAsString( Key.conversionType ) ); ZoneId localZone = LocalizationUtil.parseZoneId( null, context ); - DateTime dateRef = DateTimeCaster.cast( - arguments.get( Key.date ), - true, - localZone, - context - ); + ZoneId refZone = conversion.equals( utc2Local ) ? utcZone : localZone; + Object dateObject = arguments.get( Key.date ); + DateTime dateRef = null; + if ( dateObject instanceof String stringDate ) { + dateRef = new DateTime( stringDate, refZone ); + } else { + dateRef = DateTimeCaster.cast( dateObject, context ); + } - return dateRef.convertToZone( conversion.equals( utc2Local ) ? localZone : ZoneId.of( "UTC" ) ); + return dateRef.convertToZone( conversion.equals( utc2Local ) ? localZone : utcZone ); } diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/temporal/DateConvertTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/temporal/DateConvertTest.java index 92fcd4b84..418a1e698 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/temporal/DateConvertTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/temporal/DateConvertTest.java @@ -67,7 +67,7 @@ public void setupEach() { @DisplayName( "It tests the BIF DateConvert local2UTC" ) @Test - public void testDateCompareLocal2Utc() { + public void testDateConvertLocal2Utc() { var localZone = ZoneId.of( "America/Los_Angeles" ); var dateRef = new DateTime( localZone ); assertEquals( dateRef.getWrapped().getZone(), localZone ); @@ -85,7 +85,7 @@ public void testDateCompareLocal2Utc() { @DisplayName( "It tests the BIF DateConvert with utc2Local" ) @Test - public void testDateCompareUtcToLocal() { + public void testDateConvertUtcToLocal() { var utcZone = ZoneId.of( "UTC" ); var localZone = ZoneId.of( "America/Los_Angeles" ); var dateRef = new DateTime( utcZone ); @@ -105,4 +105,44 @@ public void testDateCompareUtcToLocal() { assertTrue( result.getWrapped().equals( conversionRef.getWrapped() ) ); } + @DisplayName( "It tests the BIF DateConvert with utc2Local on epoch date" ) + @Test + public void testDateConvertUtcToLocalEpoch() { + var utcZone = ZoneId.of( "UTC" ); + var localZone = ZoneId.of( "America/Los_Angeles" ); + var dateRef = "1970-01-01T00:00"; + variables.put( Key.of( "date" ), dateRef ); + instance.executeSource( + """ + setTimezone( "America/Los_Angeles" ); + result = dateConvert( "utc2local", date ); + """, + context ); + + DateTime result = DateTimeCaster.cast( variables.get( Key.of( "result" ) ) ); + assertNotEquals( result.getWrapped().getZone(), utcZone ); + assertEquals( result.getWrapped().getZone(), localZone ); + assertEquals( "1969-12-31T16:00", result.format( "yyyy-MM-dd'T'HH:mm" ) ); + } + + @DisplayName( "It tests the BIF DateConvert with utc2Local on epoch date" ) + @Test + public void testDateConvertLocalToUTCEpoch() { + var utcZone = ZoneId.of( "UTC" ); + var localZone = ZoneId.of( "America/Los_Angeles" ); + var dateRef = "1969-12-31T16:00:00"; + variables.put( Key.of( "date" ), dateRef ); + instance.executeSource( + """ + setTimezone( "America/Los_Angeles" ); + result = dateConvert( "local2utc", date ); + """, + context ); + + DateTime result = variables.getAsDateTime( Key.of( "result" ) ); + assertNotEquals( result.getWrapped().getZone(), localZone ); + assertEquals( result.getWrapped().getZone(), utcZone ); + assertEquals( "1970-01-01T00:00", result.format( "yyyy-MM-dd'T'HH:mm" ) ); + } + } From e28924cada92fadebcdaa5ec9b4fdf53f477ccc6 Mon Sep 17 00:00:00 2001 From: Jon Clausen Date: Fri, 6 Dec 2024 11:01:36 -0600 Subject: [PATCH 116/193] remove BeforeEach blocks that were overriding BaseJDBCTest --- .../runtime/bifs/global/jdbc/BaseJDBCTest.java | 14 +++++++------- .../runtime/components/jdbc/StoredProcTest.java | 7 ------- .../boxlang/runtime/jdbc/TransactionTest.java | 9 --------- 3 files changed, 7 insertions(+), 23 deletions(-) diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/jdbc/BaseJDBCTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/jdbc/BaseJDBCTest.java index 6f13bfe8a..9e1b71d52 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/jdbc/BaseJDBCTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/jdbc/BaseJDBCTest.java @@ -22,13 +22,13 @@ public class BaseJDBCTest { - static BoxRuntime instance; - ScriptingRequestBoxContext context; - IScope variables; - static DataSource datasource; - static DataSource mssqlDatasource; - static DataSource mysqlDatasource; - static DatasourceService datasourceService; + static BoxRuntime instance; + protected ScriptingRequestBoxContext context; + IScope variables; + static DataSource datasource; + static DataSource mssqlDatasource; + static DataSource mysqlDatasource; + static DatasourceService datasourceService; @BeforeAll public static void setUp() { diff --git a/src/test/java/ortus/boxlang/runtime/components/jdbc/StoredProcTest.java b/src/test/java/ortus/boxlang/runtime/components/jdbc/StoredProcTest.java index adc852ae1..72a1b6d7b 100644 --- a/src/test/java/ortus/boxlang/runtime/components/jdbc/StoredProcTest.java +++ b/src/test/java/ortus/boxlang/runtime/components/jdbc/StoredProcTest.java @@ -9,7 +9,6 @@ import java.sql.SQLException; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -30,7 +29,6 @@ public class StoredProcTest extends BaseJDBCTest { static BoxRuntime instance; - IBoxContext context; static Key result = new Key( "result" ); private DataSource mysqlDatasource; @@ -102,11 +100,6 @@ CREATE PROCEDURE doNothing() ); } - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - } - public void setupMySQLTest() { mysqlDatasource = DataSource.fromStruct( "MysqlStoredProcTest", diff --git a/src/test/java/ortus/boxlang/runtime/jdbc/TransactionTest.java b/src/test/java/ortus/boxlang/runtime/jdbc/TransactionTest.java index 432e1f38c..be3a73613 100644 --- a/src/test/java/ortus/boxlang/runtime/jdbc/TransactionTest.java +++ b/src/test/java/ortus/boxlang/runtime/jdbc/TransactionTest.java @@ -26,7 +26,6 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -35,9 +34,7 @@ import ortus.boxlang.compiler.parser.BoxSourceType; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.bifs.global.jdbc.BaseJDBCTest; -import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.context.IJDBCCapableContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.Query; @@ -48,18 +45,12 @@ public class TransactionTest extends BaseJDBCTest { static Key result = new Key( "result" ); static BoxRuntime instance; - IBoxContext context; @BeforeAll public static void setUp() { instance = BoxRuntime.getInstance( true ); } - @BeforeEach - public void setupEach() { - context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); - } - @ParameterizedTest @org.junit.jupiter.params.provider.ValueSource( strings = { "read_uncommitted", From 46f6ec4652ee0ff5d2581a061fe86a74f3d4bdda Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 6 Dec 2024 11:13:08 -0600 Subject: [PATCH 117/193] Only add getters and setters to metadata if they weren't disabled by annotation --- .../ast/visitor/ClassMetadataVisitor.java | 61 +++++++++++-------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/ast/visitor/ClassMetadataVisitor.java b/src/main/java/ortus/boxlang/compiler/ast/visitor/ClassMetadataVisitor.java index b929311c8..e1a359ffa 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/visitor/ClassMetadataVisitor.java +++ b/src/main/java/ortus/boxlang/compiler/ast/visitor/ClassMetadataVisitor.java @@ -173,6 +173,10 @@ public void visit( BoxProperty prop ) { .orElseThrow( () -> new ExpressionException( "Property [" + prop.getSourceText() + "] missing type annotation", prop ) ); BoxAnnotation defaultAnnotation = finalAnnotations.stream().filter( it -> it.getKey().getValue().equalsIgnoreCase( "default" ) ).findFirst() .orElse( null ); + BoxAnnotation getterAnnotation = finalAnnotations.stream().filter( it -> it.getKey().getValue().equalsIgnoreCase( "getter" ) ).findFirst() + .orElse( null ); + BoxAnnotation setterAnnotation = finalAnnotations.stream().filter( it -> it.getKey().getValue().equalsIgnoreCase( "setter" ) ).findFirst() + .orElse( null ); String name = getBoxExprAsSimpleValue( nameAnnotation.getValue() ).toString(); String type = getBoxExprAsSimpleValue( typeAnnotation.getValue() ).toString(); @@ -188,33 +192,36 @@ Key.documentation, processDocumentation( prop.getDocumentation() ) ); if ( accessors ) { - this.meta.getAsArray( Key.functions ).add( - Struct.of( - Key._NAME, "get" + name, - Key.nameAsKey, Key.of( "get" + name ), - Key.returnType, type, - Key.access, "public", - Key.documentation, Struct.of(), - Key.annotations, Struct.of(), - Key.parameters, Array.of(), - Key.closure, false, - Key.lambda, false - ) - ); - this.meta.getAsArray( Key.functions ).add( - Struct.of( - Key._NAME, "set" + name, - Key.nameAsKey, Key.of( "set" + name ), - Key.returnType, "void", - Key.access, "public", - Key.documentation, Struct.of(), - Key.annotations, Struct.of(), - Key.parameters, processArguments( List.of( new BoxArgumentDeclaration( true, type, name, null, List.of(), List.of(), null, null ) ) ), - Key.closure, false, - Key.lambda, false - ) - ); - + if ( getterAnnotation == null || BooleanCaster.cast( getBoxExprAsSimpleValue( getterAnnotation.getValue() ) ) ) { + this.meta.getAsArray( Key.functions ).add( + Struct.of( + Key._NAME, "get" + name, + Key.nameAsKey, Key.of( "get" + name ), + Key.returnType, type, + Key.access, "public", + Key.documentation, Struct.of(), + Key.annotations, Struct.of(), + Key.parameters, Array.of(), + Key.closure, false, + Key.lambda, false + ) + ); + } + if ( setterAnnotation == null || BooleanCaster.cast( getBoxExprAsSimpleValue( setterAnnotation.getValue() ) ) ) { + this.meta.getAsArray( Key.functions ).add( + Struct.of( + Key._NAME, "set" + name, + Key.nameAsKey, Key.of( "set" + name ), + Key.returnType, "void", + Key.access, "public", + Key.documentation, Struct.of(), + Key.annotations, Struct.of(), + Key.parameters, processArguments( List.of( new BoxArgumentDeclaration( true, type, name, null, List.of(), List.of(), null, null ) ) ), + Key.closure, false, + Key.lambda, false + ) + ); + } } } From ecc9ab01b0a480304a6c47a90d3428a8e5eb3ee8 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 6 Dec 2024 11:55:14 -0600 Subject: [PATCH 118/193] test out minimal message --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 92030381a..cdee55451 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -95,6 +95,7 @@ jobs: SLACK_TITLE: "${{ github.repository }} Build Failure" SLACK_USERNAME: CI SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }} + MSG_MINIMAL: true publish-test-results: name: Publish Test Results From b2171176f302a86c168504ef08fa749d98af873d Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 6 Dec 2024 12:20:49 -0600 Subject: [PATCH 119/193] BL-818 --- .../boxlang/runtime/bifs/BoxLangBIFProxy.java | 3 +- .../components/BoxLangComponentProxy.java | 3 +- .../runtime/context/FunctionBoxContext.java | 4 +- .../runtime/runnables/BoxClassSupport.java | 40 +++++++++++++++++-- src/test/java/TestCases/phase3/ClassTest.java | 1 - 5 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/bifs/BoxLangBIFProxy.java b/src/main/java/ortus/boxlang/runtime/bifs/BoxLangBIFProxy.java index 87d4f20ea..bda780541 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/BoxLangBIFProxy.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/BoxLangBIFProxy.java @@ -86,10 +86,9 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { context.getFunctionParentContext(), Key.invoke, arguments, - null, + this.target, null ); - fContext.setThisClass( this.target ); fContext.pushTemplate( this.target ); try { diff --git a/src/main/java/ortus/boxlang/runtime/components/BoxLangComponentProxy.java b/src/main/java/ortus/boxlang/runtime/components/BoxLangComponentProxy.java index 8cc2b35b6..aab1b7ecd 100644 --- a/src/main/java/ortus/boxlang/runtime/components/BoxLangComponentProxy.java +++ b/src/main/java/ortus/boxlang/runtime/components/BoxLangComponentProxy.java @@ -95,10 +95,9 @@ public BodyResult _invoke( IBoxContext context, IStruct attributes, ComponentBod context.getFunctionParentContext(), Key.invoke, arguments, - null, + this.target, null ); - fContext.setThisClass( this.target ); fContext.pushTemplate( this.target ); try { var bodyResult = this.bxFunction.invoke( fContext ); diff --git a/src/main/java/ortus/boxlang/runtime/context/FunctionBoxContext.java b/src/main/java/ortus/boxlang/runtime/context/FunctionBoxContext.java index d7982d434..f5eeaf384 100644 --- a/src/main/java/ortus/boxlang/runtime/context/FunctionBoxContext.java +++ b/src/main/java/ortus/boxlang/runtime/context/FunctionBoxContext.java @@ -172,7 +172,7 @@ public FunctionBoxContext( IBoxContext parent, Function function, Key functionCa this.argumentsScope = new ArgumentsScope(); this.function = function; this.functionCalledName = functionCalledName; - setThisClass( thisClass ); + setThisClass( BoxClassSupport.resolveClassForUDF( thisClass, function ) ); pushTemplate( function ); try { ArgumentUtil.createArgumentsScope( this, positionalArguments, function.getArguments(), this.argumentsScope, @@ -204,7 +204,7 @@ public FunctionBoxContext( IBoxContext parent, Function function, Key functionCa this.argumentsScope = new ArgumentsScope(); this.function = function; this.functionCalledName = functionCalledName; - setThisClass( thisClass ); + setThisClass( BoxClassSupport.resolveClassForUDF( thisClass, function ) ); pushTemplate( function ); try { ArgumentUtil.createArgumentsScope( this, namedArguments, function.getArguments(), this.argumentsScope, diff --git a/src/main/java/ortus/boxlang/runtime/runnables/BoxClassSupport.java b/src/main/java/ortus/boxlang/runtime/runnables/BoxClassSupport.java index a54ef9c8a..134306c7e 100644 --- a/src/main/java/ortus/boxlang/runtime/runnables/BoxClassSupport.java +++ b/src/main/java/ortus/boxlang/runtime/runnables/BoxClassSupport.java @@ -38,6 +38,7 @@ import ortus.boxlang.runtime.types.Function; import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.Struct; +import ortus.boxlang.runtime.types.UDF; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.types.exceptions.BoxValidationException; import ortus.boxlang.runtime.types.exceptions.KeyNotFoundException; @@ -183,7 +184,7 @@ public static Boolean canInvokeImplicitAccessor( IClassRunnable thisClass, IBoxC public static void setSuper( IClassRunnable thisClass, IClassRunnable _super ) { thisClass._setSuper( _super ); _super.setChild( thisClass ); - // This runs before the psedu constructor and init, so the base class will override anything it declares + // This runs before the pseudo constructor and init, so the base class will override anything it declares thisClass.getVariablesScope().addAll( _super.getVariablesScope().getWrapped() ); thisClass.getThisScope().addAll( _super.getThisScope().getWrapped() ); @@ -323,7 +324,6 @@ public static Object dereferenceAndInvoke( IClassRunnable thisClass, IBoxContext null ); - functionContext.setThisClass( thisClass ); return function.invoke( functionContext ); } @@ -407,7 +407,6 @@ public static Object dereferenceAndInvoke( IClassRunnable thisClass, IBoxContext null ); - functionContext.setThisClass( thisClass ); return function.invoke( functionContext ); } @@ -765,4 +764,39 @@ public static void validateAbstractMethods( IClassRunnable thisClass, Map enclosingClass = udf.getClass().getEnclosingClass(); + + // If the enclosing class is the same as the current class, then we're good + if ( enclosingClass == thisClass.getClass() ) { + return thisClass; + } + + // Otherwise, let's climb the supers (if they even exist) and see if one of them declared it + IClassRunnable thisSuper = thisClass.getSuper(); + while ( thisSuper != null ) { + if ( enclosingClass == thisSuper.getClass() ) { + return thisSuper; + } + thisSuper = thisSuper.getSuper(); + } + // If the original class and no supers were the enclosing class, then this is prolly a mixin. Just return the original value. + return thisClass; + } + } diff --git a/src/test/java/TestCases/phase3/ClassTest.java b/src/test/java/TestCases/phase3/ClassTest.java index 44efe9a76..130273989 100644 --- a/src/test/java/TestCases/phase3/ClassTest.java +++ b/src/test/java/TestCases/phase3/ClassTest.java @@ -971,7 +971,6 @@ public void testFunctionMeta() { } @Test - @Disabled public void testSuperHeadlessFunctionInvocationToChild() { instance.executeSource( From 9d5216587d8c4838e3479ef3bec8c9d1e52bfb64 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Fri, 6 Dec 2024 13:28:20 -0600 Subject: [PATCH 120/193] BL-760 second round of ASM line numbers --- .../expression/BoxBreakTransformer.java | 9 +- .../expression/BoxClosureTransformer.java | 3 +- .../expression/BoxContinueTransformer.java | 12 +- .../BoxFunctionInvocationTransformer.java | 4 +- .../BoxMethodInvocationTransformer.java | 2 +- .../expression/BoxReturnTransformer.java | 2 +- .../BoxStatementBlockTransformer.java | 2 +- .../expression/BoxSwitchTransformer.java | 2 +- .../statement/BoxBufferOutputTransformer.java | 2 +- .../statement/BoxComponentTransformer.java | 20 +-- .../statement/BoxDoTransformer.java | 3 +- .../statement/BoxForInTransformer.java | 2 +- .../statement/BoxForIndexTransformer.java | 2 +- .../BoxFunctionDeclarationTransformer.java | 2 +- .../statement/BoxIfElseTransformer.java | 2 +- .../statement/BoxRethrowTransformer.java | 3 +- .../statement/BoxScriptIslandTransformer.java | 3 +- .../BoxTemplateIslandTransformer.java | 3 +- .../statement/BoxThrowTransformer.java | 5 +- .../statement/BoxTryTransformer.java | 2 +- .../statement/BoxWhileTransformer.java | 2 +- .../ortus/boxlang/compiler/SourceMapTest.java | 117 ++++++++++++++++++ .../sourcemaptests/ComponentInTemplate.bxm | 8 ++ .../compiler/sourcemaptests/MissingVar.bx | 5 + .../sourcemaptests/MissingVarTemplate.bxm | 17 +++ 25 files changed, 182 insertions(+), 52 deletions(-) create mode 100644 src/test/java/ortus/boxlang/compiler/SourceMapTest.java create mode 100644 src/test/java/ortus/boxlang/compiler/sourcemaptests/ComponentInTemplate.bxm create mode 100644 src/test/java/ortus/boxlang/compiler/sourcemaptests/MissingVar.bx create mode 100644 src/test/java/ortus/boxlang/compiler/sourcemaptests/MissingVarTemplate.bxm diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxBreakTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxBreakTransformer.java index 4f7ed2870..4ba934e57 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxBreakTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxBreakTransformer.java @@ -26,6 +26,7 @@ import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.MethodInsnNode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.MethodContextTracker; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; @@ -75,7 +76,7 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); } nodes.add( new JumpInsnNode( Opcodes.GOTO, currentBreak ) ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } if ( exitsAllowed.equals( ExitsAllowed.COMPONENT ) ) { @@ -87,14 +88,14 @@ public List transform( BoxNode node, TransformerContext contex false ) ); nodes.add( new InsnNode( Opcodes.ARETURN ) ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } else if ( exitsAllowed.equals( ExitsAllowed.LOOP ) ) { // template = "if(true) break " + breakLabel + ";"; nodes.add( new InsnNode( Opcodes.ARETURN ) ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } else if ( exitsAllowed.equals( ExitsAllowed.FUNCTION ) ) { nodes.add( new InsnNode( Opcodes.ARETURN ) ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } else { // template = "if(true) return;"; } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxClosureTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxClosureTransformer.java index dc28b952f..a5490f507 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxClosureTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxClosureTransformer.java @@ -235,7 +235,6 @@ public List transform( BoxNode node, TransformerContext contex Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( IBoxContext.class ) ), false ) ); - return nodes; - // return AsmHelper.addLineNumberLabels( nodes, node ); + return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxContinueTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxContinueTransformer.java index 594be2b47..02b5d9981 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxContinueTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxContinueTransformer.java @@ -75,7 +75,7 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); } nodes.add( new JumpInsnNode( Opcodes.GOTO, currentBreak ) ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } if ( exitsAllowed.equals( ExitsAllowed.COMPONENT ) ) { @@ -87,14 +87,14 @@ public List transform( BoxNode node, TransformerContext contex false ) ); nodes.add( new InsnNode( Opcodes.ARETURN ) ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } else if ( exitsAllowed.equals( ExitsAllowed.LOOP ) ) { nodes.add( new JumpInsnNode( Opcodes.GOTO, currentBreak ) ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); // template = "if(true) break " + breakLabel + ";"; } else if ( exitsAllowed.equals( ExitsAllowed.FUNCTION ) ) { nodes.add( new InsnNode( Opcodes.ARETURN ) ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } throw new RuntimeException( "Cannot continue from current location - processing: " + transpiler.getProperty( "relativePath" ) ); @@ -107,8 +107,4 @@ public BoxNode getTargetAncestor( BoxNode node ) { BoxForIndex.class, BoxForIn.class, BoxWhile.class, BoxSwitch.class ); } - - private boolean isLoop( BoxNode node ) { - return node instanceof BoxForIndex; - } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxFunctionInvocationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxFunctionInvocationTransformer.java index 037295aa2..e5639c5fc 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxFunctionInvocationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxFunctionInvocationTransformer.java @@ -54,8 +54,6 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.POP ) ); } - return nodes; - // TODO: this causes issues in testProperties - // return AsmHelper.addLineNumberLabels( nodes, node ); + return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxMethodInvocationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxMethodInvocationTransformer.java index 2b32d1771..d00f16d39 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxMethodInvocationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxMethodInvocationTransformer.java @@ -63,6 +63,6 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.POP ) ); } - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxReturnTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxReturnTransformer.java index 7c91c1552..42026846b 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxReturnTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxReturnTransformer.java @@ -50,7 +50,7 @@ public List transform( BoxNode node, TransformerContext contex if ( returnContext.nullable ) { nodes.add( new InsnNode( Opcodes.ARETURN ) ); } - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } if ( boxReturn.getExpression() == null ) { diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStatementBlockTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStatementBlockTransformer.java index 7b233cee9..bdcb1ff1d 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStatementBlockTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStatementBlockTransformer.java @@ -44,7 +44,7 @@ public List transform( BoxNode node, TransformerContext contex if ( boxStatementBlock.getBody().size() == 0 && ReturnValueContext.VALUE_OR_NULL == returnContext ) { nodes.add( new InsnNode( Opcode.ACONST_NULL ) ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } nodes.addAll( AsmHelper.transformBodyExpressions( transpiler, boxStatementBlock.getBody(), context, returnContext ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxSwitchTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxSwitchTransformer.java index a0cd56725..742a2da22 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxSwitchTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxSwitchTransformer.java @@ -175,6 +175,6 @@ public List transform( BoxNode node, TransformerContext contex AsmHelper.addDebugLabel( nodes, "BoxSwitch - done" ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxBufferOutputTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxBufferOutputTransformer.java index a4dbc8230..95640b2a2 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxBufferOutputTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxBufferOutputTransformer.java @@ -62,6 +62,6 @@ public List transform( BoxNode node, TransformerContext contex AsmHelper.addDebugLabel( nodes, "BoxBufferOutput - end" ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java index 5c42d3a90..938e56d9d 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxComponentTransformer.java @@ -85,18 +85,6 @@ public List transform( BoxNode node, TransformerContext contex Type.getType( Component.ComponentBody.class ) ), true ) ); - // if ( boxComponent.getBody() == null || boxComponent.getBody().size() == 0 ) { - // if ( returnContext != ReturnValueContext.VALUE && returnContext != ReturnValueContext.VALUE_OR_NULL ) { - // nodes.add( new InsnNode( Opcodes.POP ) ); - // } - - // transpiler.decrementComponentCounter(); - - // return nodes; - // TODO: this causes CoreLangTest.unicode - // return AsmHelper.addLineNumberLabels( nodes, node ); - // } - if ( transpiler.isInsideComponent() ) { LabelNode ifLabel = new LabelNode(); @@ -123,7 +111,7 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.POP ) ); } - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } else if ( transpiler.canReturn() ) { LabelNode ifLabel = new LabelNode(); @@ -166,11 +154,9 @@ public List transform( BoxNode node, TransformerContext contex } } - // transpiler.decrementComponentCounter(); AsmHelper.addDebugLabel( nodes, "BoxComponent - done" ); - return nodes; - // TODO: this causes CoreLangTest.unicode - // return AsmHelper.addLineNumberLabels( nodes, node ); + + return AsmHelper.addLineNumberLabels( nodes, node ); } private List generateBodyNodes( List body ) { diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxDoTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxDoTransformer.java index c668f9de6..d221f1a2c 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxDoTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxDoTransformer.java @@ -25,6 +25,7 @@ import org.objectweb.asm.tree.LabelNode; import org.objectweb.asm.tree.MethodInsnNode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; @@ -87,6 +88,6 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( end ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java index 1e3999ca7..c9eae8896 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForInTransformer.java @@ -251,7 +251,7 @@ public List transform( BoxNode node, TransformerContext contex AsmHelper.addDebugLabel( nodes, "BoxForIn - end" ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } private List assignVar( BoxForIn forIn, int iteratorIndex, TransformerContext context ) { diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForIndexTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForIndexTransformer.java index 0f86460b3..4d5870bc2 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForIndexTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxForIndexTransformer.java @@ -140,7 +140,7 @@ public List transform( BoxNode node, TransformerContext contex AsmHelper.addDebugLabel( nodes, "BoxForIndex - done" ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } } \ No newline at end of file diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java index c9fb0b782..75b4304b4 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java @@ -256,7 +256,7 @@ public List transform( BoxNode node, TransformerContext contex } if ( function.getModifiers().contains( BoxMethodDeclarationModifier.STATIC ) ) { - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } else { return new ArrayList<>(); } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxIfElseTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxIfElseTransformer.java index c49b1bce5..d55ca38a7 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxIfElseTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxIfElseTransformer.java @@ -80,7 +80,7 @@ public List transform( BoxNode node, TransformerContext contex AsmHelper.addDebugLabel( nodes, "BoxIfElse - end" ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxRethrowTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxRethrowTransformer.java index 24dc660c1..5656b94b8 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxRethrowTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxRethrowTransformer.java @@ -23,6 +23,7 @@ import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.MethodInsnNode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; @@ -55,7 +56,7 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); } - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxScriptIslandTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxScriptIslandTransformer.java index 39e4c62c5..406e6faf5 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxScriptIslandTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxScriptIslandTransformer.java @@ -22,6 +22,7 @@ import org.objectweb.asm.tree.AbstractInsnNode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; @@ -45,6 +46,6 @@ public List transform( BoxNode node, TransformerContext contex nodes.addAll( transpiler.transform( statement, context, returnContext ) ); } - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTemplateIslandTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTemplateIslandTransformer.java index b9a2fbc76..804416ebd 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTemplateIslandTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTemplateIslandTransformer.java @@ -22,6 +22,7 @@ import org.objectweb.asm.tree.AbstractInsnNode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; @@ -45,6 +46,6 @@ public List transform( BoxNode node, TransformerContext contex nodes.addAll( transpiler.transform( statement, context, returnContext ) ); } - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxThrowTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxThrowTransformer.java index 5440ac464..6f2baf126 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxThrowTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxThrowTransformer.java @@ -26,6 +26,7 @@ import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.TypeInsnNode; +import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; import ortus.boxlang.compiler.asmboxpiler.transformer.ReturnValueContext; @@ -64,9 +65,7 @@ public List transform( BoxNode node, TransformerContext contex } - // this is a noop but needs to be present for validation purposes - - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTryTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTryTransformer.java index f591a978c..f3e110f7b 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTryTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxTryTransformer.java @@ -138,7 +138,7 @@ public List transform( BoxNode node, TransformerContext contex tracker.addTryCatchBlock( new TryCatchBlockNode( tryStartLabel, tryEndLabel, finallyStartLabel, null ) ); - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxWhileTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxWhileTransformer.java index fab598025..84a88149d 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxWhileTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxWhileTransformer.java @@ -112,6 +112,6 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.POP ) ); } - return nodes; + return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/test/java/ortus/boxlang/compiler/SourceMapTest.java b/src/test/java/ortus/boxlang/compiler/SourceMapTest.java new file mode 100644 index 000000000..4cad511ff --- /dev/null +++ b/src/test/java/ortus/boxlang/compiler/SourceMapTest.java @@ -0,0 +1,117 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.compiler; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import ortus.boxlang.runtime.BoxRuntime; +import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; +import ortus.boxlang.runtime.scopes.IScope; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.scopes.VariablesScope; +import ortus.boxlang.runtime.types.exceptions.KeyNotFoundException; + +public class SourceMapTest { + + static BoxRuntime instance; + IBoxContext context; + IScope variables; + static Key result = new Key( "result" ); + + @BeforeAll + public static void setUp() { + instance = BoxRuntime.getInstance( true ); + } + + @AfterAll + public static void teardown() { + + } + + @BeforeEach + public void setupEach() { + context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); + variables = context.getScopeNearby( VariablesScope.name ); + } + + @Test + public void testVariableLineNumber() { + + Exception e = assertThrows( KeyNotFoundException.class, () -> { + instance.executeSource( + """ + new src.test.java.ortus.boxlang.compiler.sourcemaptests.MissingVar(); + """, + context ); + } ); + + StringWriter out = new StringWriter(); + PrintWriter printer = new PrintWriter( out ); + e.printStackTrace( printer ); + + assertThat( out.toString().contains( "MissingVar.bx:3" ) ).isTrue(); + } + + @Test + public void testTemmplateMissingVariable() { + + Exception e = assertThrows( KeyNotFoundException.class, () -> { + instance.executeSource( + """ + include template="src/test/java/ortus/boxlang/compiler/sourcemaptests/MissingVarTemplate.bxm"; + """, + context ); + } ); + + StringWriter out = new StringWriter(); + PrintWriter printer = new PrintWriter( out ); + e.printStackTrace( printer ); + + assertThat( out.toString().contains( "MissingVarTemplate.bxm:14" ) ).isTrue(); + } + + @Test + public void testTemmplateMissingVariableInComponent() { + + Exception e = assertThrows( KeyNotFoundException.class, () -> { + instance.executeSource( + """ + include template="src/test/java/ortus/boxlang/compiler/sourcemaptests/ComponentInTemplate.bxm"; + """, + context ); + } ); + + StringWriter out = new StringWriter(); + PrintWriter printer = new PrintWriter( out ); + e.printStackTrace( printer ); + + String errorOutput = out.toString(); + + assertThat( errorOutput.contains( "ComponentInTemplate.bxm:5" ) ).isTrue(); + assertThat( errorOutput.contains( "ComponentInTemplate.bxm:4" ) ).isTrue(); + assertThat( errorOutput.contains( "ComponentInTemplate.bxm:1" ) ).isTrue(); + } + +} diff --git a/src/test/java/ortus/boxlang/compiler/sourcemaptests/ComponentInTemplate.bxm b/src/test/java/ortus/boxlang/compiler/sourcemaptests/ComponentInTemplate.bxm new file mode 100644 index 000000000..407d3392f --- /dev/null +++ b/src/test/java/ortus/boxlang/compiler/sourcemaptests/ComponentInTemplate.bxm @@ -0,0 +1,8 @@ + + + + + #x# + + + diff --git a/src/test/java/ortus/boxlang/compiler/sourcemaptests/MissingVar.bx b/src/test/java/ortus/boxlang/compiler/sourcemaptests/MissingVar.bx new file mode 100644 index 000000000..325f37b40 --- /dev/null +++ b/src/test/java/ortus/boxlang/compiler/sourcemaptests/MissingVar.bx @@ -0,0 +1,5 @@ +class { + function init(){ + foo + bar; + } +} \ No newline at end of file diff --git a/src/test/java/ortus/boxlang/compiler/sourcemaptests/MissingVarTemplate.bxm b/src/test/java/ortus/boxlang/compiler/sourcemaptests/MissingVarTemplate.bxm new file mode 100644 index 000000000..4f3884249 --- /dev/null +++ b/src/test/java/ortus/boxlang/compiler/sourcemaptests/MissingVarTemplate.bxm @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + From 2321c48487a6185f94abd0c4802044aee25a9e0a Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 6 Dec 2024 13:55:31 -0600 Subject: [PATCH 121/193] BL-818 failing test --- src/test/java/TestCases/phase3/ClassTest.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/test/java/TestCases/phase3/ClassTest.java b/src/test/java/TestCases/phase3/ClassTest.java index 130273989..5cf177e69 100644 --- a/src/test/java/TestCases/phase3/ClassTest.java +++ b/src/test/java/TestCases/phase3/ClassTest.java @@ -971,6 +971,7 @@ public void testFunctionMeta() { } @Test + @Disabled public void testSuperHeadlessFunctionInvocationToChild() { instance.executeSource( @@ -1520,4 +1521,26 @@ public void testBXResolverPrefixImport() { context ); } + @DisplayName( "udf class has enclosing class reference" ) + @Test + @Disabled + public void testUDFClassEnclosingClassReference() { + + instance.executeSource( + """ + import bx:src.test.java.TestCases.phase3.PropertyTestCF as brad; + b = new brad() + outerClass = b.$bx.$class; + innerClass = b.init.getClass(); + innerClassesOuterClass = b.init.getClass().getEnclosingClass(); + println(outerclass) + println(innerClass) + """, + context ); + assertThat(((Class)variables.get( "outerClass" )).getName() ).isEqualTo( "boxgenerated.boxclass.src.test.java.testcases.phase3.Propertytestcf$cfc" ); + assertThat(((Class)variables.get( "innerClassesOuterClass" )).getName() ).isEqualTo( "boxgenerated.boxclass.src.test.java.testcases.phase3.Propertytestcf$cfc" ); + assertThat(((Class)variables.get( "innerClass" )).getName() ).isEqualTo( "boxgenerated.boxclass.src.test.java.testcases.phase3.Propertytestcf$cfc$Func_init" ); + assertThat( variables.get( "outerClass" ) ).isEqualTo( variables.get("innerClassesOuterClass") ); + } + } From 0301818227ad7f790f4a6404b5a875eb4062e222 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 6 Dec 2024 13:27:25 -0600 Subject: [PATCH 122/193] BL-828 resolve scheduler logs are now being reported --- .../runtime/async/tasks/BaseScheduler.java | 49 ++++++++++--------- .../runtime/async/tasks/IScheduler.java | 6 +-- .../runtime/async/tasks/ScheduledTask.java | 4 +- .../boxlang/runtime/modules/ModuleRecord.java | 2 +- .../runtime/services/SchedulerService.java | 26 +++++----- .../runtime/async/tasks/SchedulerTest.java | 14 ++++-- 6 files changed, 52 insertions(+), 49 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/async/tasks/BaseScheduler.java b/src/main/java/ortus/boxlang/runtime/async/tasks/BaseScheduler.java index 42c6c0c8e..1f67e5876 100644 --- a/src/main/java/ortus/boxlang/runtime/async/tasks/BaseScheduler.java +++ b/src/main/java/ortus/boxlang/runtime/async/tasks/BaseScheduler.java @@ -31,7 +31,6 @@ import org.apache.commons.lang3.RandomStringUtils; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.async.executors.ExecutorRecord; @@ -100,7 +99,7 @@ public class BaseScheduler implements IScheduler { /** * Logger */ - protected static final Logger logger = LoggerFactory.getLogger( BaseScheduler.class ); + protected final Logger logger; /** * -------------------------------------------------------------------------- @@ -113,7 +112,7 @@ public class BaseScheduler implements IScheduler { * Auto-generated name, system default timezone and the default async service */ public BaseScheduler() { - this( "boxlang-scheduler-" + RandomStringUtils.randomAlphanumeric( 10 ) ); + this( "boxlang-scheduler-" + RandomStringUtils.secure().nextAlphanumeric( 10 ) ); } /** @@ -135,8 +134,10 @@ public BaseScheduler( String name, ZoneId timezone ) { this.name = name; this.timezone = timezone; this.asyncService = BoxRuntime.getInstance().getAsyncService(); + this.logger = BoxRuntime.getInstance().getLoggingService().getLogger( "scheduler" ); + // Log it - logger.info( "Created scheduler [{}] with a [{}] timezone", name, timezone.getId() ); + this.logger.info( "Created scheduler [{}] with a [{}] timezone", name, timezone.getId() ); } /** @@ -246,7 +247,7 @@ public synchronized BaseScheduler startup() { this.onStartup(); // Log it - logger.info( "Scheduler [{}] has started!", this.name ); + this.logger.info( "Scheduler [{}] has started!", this.name ); } return this; @@ -261,7 +262,7 @@ public synchronized BaseScheduler startup() { * @return */ public synchronized BaseScheduler restart( boolean force, long timeout ) { - logger.info( "+ Restarting scheduler [{}] with force: {} and timeout: {}", this.name, force, timeout ); + this.logger.info( "+ Restarting scheduler [{}] with force: {} and timeout: {}", this.name, force, timeout ); // Shutdown first shutdown( force, timeout ); // Clear tasks @@ -270,7 +271,7 @@ public synchronized BaseScheduler restart( boolean force, long timeout ) { configure(); // Startup startup(); - logger.info( "+ Scheduler [{}] has been restarted!", this.name ); + this.logger.info( "+ Scheduler [{}] has been restarted!", this.name ); return this; } @@ -308,7 +309,7 @@ public TaskRecord startupTask( String taskName ) { // Verify we can start it up the task or not if ( taskRecord.task.isDisabled() ) { taskRecord.disabled = true; - logger.warn( + this.logger.warn( "- Scheduler ({}) skipping task ({}) as it is disabled.", this.name, taskName @@ -316,7 +317,7 @@ public TaskRecord startupTask( String taskName ) { return taskRecord; } else { // Log scheduling startup - logger.info( + this.logger.info( "- Scheduler ({}) scheduling task ({})...", this.name, taskName @@ -325,7 +326,7 @@ public TaskRecord startupTask( String taskName ) { // Verify that the task record: scheduledAt is null if ( taskRecord.scheduledAt != null ) { - logger.warn( + this.logger.warn( "- Scheduler ({}) skipping task ({}) as it has already been scheduled.", this.name, taskName @@ -337,12 +338,12 @@ public TaskRecord startupTask( String taskName ) { try { taskRecord.future = taskRecord.task.start(); taskRecord.scheduledAt = LocalDateTime.now( this.timezone ); - logger.debug( + this.logger.debug( "√ Task ({}) scheduled successfully.", taskName ); } catch ( Exception e ) { - logger.error( + this.logger.error( "X Error scheduling task ({}}) => {}", this.name + "." + taskName, e.getMessage() @@ -367,7 +368,7 @@ public TaskRecord startupTask( String taskName ) { public BaseScheduler shutdown( boolean force, long timeout ) { // If started, then we can shutdown if ( !this.started ) { - logger.info( "Scheduler [{}] has not been started yet. Skipping shutdown.", this.name ); + this.logger.info( "Scheduler [{}] has not been started yet. Skipping shutdown.", this.name ); return this; } @@ -391,7 +392,7 @@ public BaseScheduler shutdown( boolean force, long timeout ) { this.startedAt = null; // Log it - logger.info( "Scheduler [{}] has been shutdown!", this.name ); + this.logger.info( "Scheduler [{}] has been shutdown!", this.name ); return this; } @@ -427,14 +428,14 @@ public BaseScheduler shutdown() { * Called before the scheduler is going to be shutdown */ public void onShutdown() { - logger.info( "Shutting down scheduler [{}]", this.name ); + this.logger.info( "Shutting down scheduler [{}]", this.name ); } /** * Called after the scheduler has registered all schedules */ public void onStartup() { - logger.info( "Starting up scheduler [{}]", this.name ); + this.logger.info( "Starting up scheduler [{}]", this.name ); } /** @@ -444,9 +445,9 @@ public void onStartup() { * @param exception The exception object */ public void onAnyTaskError( ScheduledTask task, Exception exception ) { - logger.error( + this.logger.error( "Task [{}.{}] has failed with {}", - getName(), + getSchedulerName(), task.getName(), exception.getMessage(), exception @@ -460,7 +461,7 @@ public void onAnyTaskError( ScheduledTask task, Exception exception ) { * @param result The result (if any) that the task produced */ public void onAnyTaskSuccess( ScheduledTask task, Optional result ) { - logger.info( "Task [{}.{}] has succeeded", getName(), task.getName() ); + this.logger.info( "Task [{}.{}] has succeeded", getSchedulerName(), task.getName() ); } /** @@ -469,7 +470,7 @@ public void onAnyTaskSuccess( ScheduledTask task, Optional result ) { * @param task The task about to be executed */ public void beforeAnyTask( ScheduledTask task ) { - logger.debug( "Task [{}.{}] is about to run", getName(), task.getName() ); + this.logger.debug( "Task [{}.{}] is about to run", getSchedulerName(), task.getName() ); } /** @@ -480,9 +481,9 @@ public void beforeAnyTask( ScheduledTask task ) { * @param result The result (if any) that the task produced */ public void afterAnyTask( ScheduledTask task, Optional result ) { - logger.debug( + this.logger.debug( "Task [{}.{}] has run with result [{}]", - getName(), + getSchedulerName(), task.getName(), result.isPresent() ? result.get() : "no result" ); @@ -619,7 +620,7 @@ public Instant getStartedAt() { * * @return the name */ - public String getName() { + public String getSchedulerName() { return this.name; } @@ -628,7 +629,7 @@ public String getName() { * * @param name the name to set */ - public BaseScheduler setName( String name ) { + public BaseScheduler setSchedulerName( String name ) { this.name = name; return this; } diff --git a/src/main/java/ortus/boxlang/runtime/async/tasks/IScheduler.java b/src/main/java/ortus/boxlang/runtime/async/tasks/IScheduler.java index a09a0ad8e..f2742c04e 100644 --- a/src/main/java/ortus/boxlang/runtime/async/tasks/IScheduler.java +++ b/src/main/java/ortus/boxlang/runtime/async/tasks/IScheduler.java @@ -159,7 +159,7 @@ public interface IScheduler { * * @return the name */ - public String getName(); + public String getSchedulerName(); /** * Set the scheduler name @@ -168,7 +168,7 @@ public interface IScheduler { * * @return the scheduler object */ - public BaseScheduler setName( String name ); + public BaseScheduler setSchedulerName( String name ); /** * Get the scheduler timezone @@ -181,7 +181,7 @@ public interface IScheduler { * Set the scheduler's timezone * * @param timezone the timezone to set - * + * * @return the scheduler object */ public BaseScheduler setTimezone( ZoneId timezone ); diff --git a/src/main/java/ortus/boxlang/runtime/async/tasks/ScheduledTask.java b/src/main/java/ortus/boxlang/runtime/async/tasks/ScheduledTask.java index 29a1a8e46..5b10759b0 100644 --- a/src/main/java/ortus/boxlang/runtime/async/tasks/ScheduledTask.java +++ b/src/main/java/ortus/boxlang/runtime/async/tasks/ScheduledTask.java @@ -35,7 +35,6 @@ import javax.management.InvalidAttributeValueException; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.async.executors.ExecutorRecord; @@ -248,7 +247,7 @@ public class ScheduledTask implements Runnable { /** * Logger */ - private static final Logger logger = LoggerFactory.getLogger( ScheduledTask.class ); + private final Logger logger; /** * BoxLang Timer utility @@ -280,6 +279,7 @@ public ScheduledTask( String name, String group, ExecutorRecord executor, BaseSc this.group = group; this.executor = executor; this.scheduler = scheduler; + this.logger = BoxRuntime.getInstance().getLoggingService().getLogger( "scheduler" ); // Init the stats this.stats = Struct.of( diff --git a/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java b/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java index d64b25924..1d15ff1c6 100644 --- a/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java +++ b/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java @@ -502,7 +502,7 @@ public ModuleRecord register( IBoxContext context ) { ServiceLoader.load( IScheduler.class, this.classLoader ) .stream() .map( ServiceLoader.Provider::get ) - .forEach( scheduler -> runtime.getSchedulerService().loadScheduler( Key.of( scheduler.getName() + "@" + this.name ), scheduler ) ); + .forEach( scheduler -> runtime.getSchedulerService().loadScheduler( Key.of( scheduler.getSchedulerName() + "@" + this.name ), scheduler ) ); // Do we have any Java ICacheProviders to register in the CacheService ServiceLoader.load( ICacheProvider.class, this.classLoader ) diff --git a/src/main/java/ortus/boxlang/runtime/services/SchedulerService.java b/src/main/java/ortus/boxlang/runtime/services/SchedulerService.java index 03cedf94e..6c0b547da 100644 --- a/src/main/java/ortus/boxlang/runtime/services/SchedulerService.java +++ b/src/main/java/ortus/boxlang/runtime/services/SchedulerService.java @@ -21,7 +21,6 @@ import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.async.tasks.IScheduler; @@ -43,7 +42,7 @@ public class SchedulerService extends BaseService { /** * Logger */ - private static final Logger logger = LoggerFactory.getLogger( SchedulerService.class ); + private Logger logger; /** * -------------------------------------------------------------------------- @@ -71,8 +70,9 @@ public SchedulerService( BoxRuntime runtime ) { */ @Override public void onStartup() { + this.logger = runtime.getLoggingService().getLogger( "scheduler" ); BoxRuntime.timerUtil.start( "schedulerservice-startup" ); - logger.debug( "+ Starting up Scheduler Service..." ); + this.logger.info( "+ Starting up Scheduler Service..." ); // Register the Global Scheduler // This will look in the configuration for the global scheduler and start it up @@ -87,7 +87,7 @@ public void onStartup() { ); // Let it be known! - logger.info( "+ Scheduler Service started in [{}] ms", BoxRuntime.timerUtil.stopAndGetMillis( "schedulerservice-startup" ) ); + this.logger.info( "+ Scheduler Service started in [{}] ms", BoxRuntime.timerUtil.stopAndGetMillis( "schedulerservice-startup" ) ); } /** @@ -105,7 +105,7 @@ public void onShutdown( Boolean force ) { // Call shutdown on each scheduler in parallel schedulers.values().parallelStream().forEach( scheduler -> shutdownScheduler( scheduler, false, 0L ) ); // Log it - logger.debug( "+ Scheduler Service shutdown" ); + this.logger.info( "+ Scheduler Service shutdown" ); } /** @@ -126,7 +126,7 @@ public SchedulerService startupSchedulers() { .filter( scheduler -> !scheduler.hasStarted() ) // Start them up .forEach( scheduler -> { - logger.debug( "+ Starting up scheduler [{}] ...", scheduler.getName() ); + this.logger.info( "+ Starting up scheduler [{}] ...", scheduler.getSchedulerName() ); scheduler.startup(); // Announce it announce( @@ -205,10 +205,11 @@ public IScheduler registerScheduler( IScheduler scheduler ) { * @return The scheduler */ public IScheduler registerScheduler( IScheduler scheduler, Boolean force ) { - if ( this.schedulers.containsKey( Key.of( scheduler.getName() ) ) && !force ) { - throw new BoxRuntimeException( "A scheduler with the name [" + scheduler.getName() + "] already exists" ); + if ( this.schedulers.containsKey( Key.of( scheduler.getSchedulerName() ) ) && !force ) { + throw new BoxRuntimeException( "A scheduler with the name [" + scheduler.getSchedulerName() + "] already exists" ); } - this.schedulers.put( Key.of( scheduler.getName() ), scheduler ); + this.schedulers.put( Key.of( scheduler.getSchedulerName() ), scheduler ); + this.logger.info( "+ Registered scheduler [{}]", scheduler.getSchedulerName() ); // Announce it announce( BoxEvent.ON_SCHEDULER_REGISTRATION, @@ -227,11 +228,8 @@ public IScheduler registerScheduler( IScheduler scheduler, Boolean force ) { * @return */ public IScheduler loadScheduler( Key name, IScheduler scheduler ) { - - // System.out.println( "Loading scheduler [" + name + "]" ); - // Register it - registerScheduler( scheduler.setName( name.getName() ), true ); + registerScheduler( scheduler.setSchedulerName( name.getName() ), true ); // Configure it scheduler.configure(); @@ -334,7 +332,7 @@ public boolean restartScheduler( Key name ) { * @param timeout The timeout in milliseconds to wait for the scheduler to shutdown */ private void shutdownScheduler( IScheduler scheduler, Boolean force, Long timeout ) { - logger.debug( "+ Shutting down scheduler [{}]", scheduler.getName() ); + this.logger.info( "+ Shutting down scheduler [{}]", scheduler.getSchedulerName() ); // Announce it announce( BoxEvent.ON_SCHEDULER_SHUTDOWN, diff --git a/src/test/java/ortus/boxlang/runtime/async/tasks/SchedulerTest.java b/src/test/java/ortus/boxlang/runtime/async/tasks/SchedulerTest.java index dcd833409..04bc90ca7 100644 --- a/src/test/java/ortus/boxlang/runtime/async/tasks/SchedulerTest.java +++ b/src/test/java/ortus/boxlang/runtime/async/tasks/SchedulerTest.java @@ -24,10 +24,10 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; import org.mockito.Mockito; import org.mockito.Spy; @@ -36,11 +36,15 @@ class SchedulerTest { - BaseScheduler scheduler; + BaseScheduler scheduler; @Spy - @InjectMocks - private BoxRuntime runtime; + private static BoxRuntime instance; + + @BeforeAll + public static void setupBeforeAll() { + instance = BoxRuntime.getInstance( true ); + } @BeforeEach public void setupBeforeEach() { @@ -52,7 +56,7 @@ public void setupBeforeEach() { @Test void testItCanCreateIt() { assertThat( scheduler ).isNotNull(); - assertThat( scheduler.getName() ).isEqualTo( "bdd" ); + assertThat( scheduler.getSchedulerName() ).isEqualTo( "bdd" ); assertThat( scheduler.getAsyncService() ).isNotNull(); assertThat( scheduler.getExecutor() ).isNull(); scheduler.startup(); From c5591d39be893f1bdb79efb84e56f04cb2faf534 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 6 Dec 2024 14:32:23 -0600 Subject: [PATCH 123/193] BL-831 #resolve struct collectors not returning IStruct but Struct --- .../java/ortus/boxlang/runtime/types/util/BLCollector.java | 4 ++-- .../java/ortus/boxlang/runtime/types/util/StructUtil.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/types/util/BLCollector.java b/src/main/java/ortus/boxlang/runtime/types/util/BLCollector.java index b272bb990..3cdada80a 100644 --- a/src/main/java/ortus/boxlang/runtime/types/util/BLCollector.java +++ b/src/main/java/ortus/boxlang/runtime/types/util/BLCollector.java @@ -54,7 +54,7 @@ private BLCollector() { * * @return The populated Struct */ - public static Collector, ?, Struct> toStruct() { + public static Collector, ?, IStruct> toStruct() { return Collector.of( Struct::new, // supplier ( struct, entry ) -> struct.put( entry.getKey(), entry.getValue() ), // accumulator @@ -75,7 +75,7 @@ private BLCollector() { * * @return The populated Struct */ - public static Collector, ?, Struct> toStruct( IStruct.TYPES type ) { + public static Collector, ?, IStruct> toStruct( IStruct.TYPES type ) { Collector.Characteristics[] characteristics; if ( type == IStruct.TYPES.LINKED ) { characteristics = new Collector.Characteristics[] { Collector.Characteristics.IDENTITY_FINISH, Collector.Characteristics.CONCURRENT }; diff --git a/src/main/java/ortus/boxlang/runtime/types/util/StructUtil.java b/src/main/java/ortus/boxlang/runtime/types/util/StructUtil.java index 1dce48228..aebd6a513 100644 --- a/src/main/java/ortus/boxlang/runtime/types/util/StructUtil.java +++ b/src/main/java/ortus/boxlang/runtime/types/util/StructUtil.java @@ -204,7 +204,7 @@ public static Boolean every( * @return A filtered array */ @SuppressWarnings( "unchecked" ) - public static Struct filter( + public static IStruct filter( IStruct struct, Function callback, IBoxContext callbackContext, From 027f6366fceb2351ea764cda71ab6f06b48076d7 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 6 Dec 2024 14:33:08 -0600 Subject: [PATCH 124/193] BL-830 #resolve Add defensive coding for module unloading in case they fail, so the runtime can gracefully shutdown. BL-829 #resolve modules logs are now being used --- .../runtime/config/segments/LoggerConfig.java | 2 +- .../config/segments/LoggingConfig.java | 17 +++- .../runtime/services/ModuleService.java | 91 ++++++++++--------- src/main/resources/config/boxlang.json | 4 +- 4 files changed, 67 insertions(+), 47 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/config/segments/LoggerConfig.java b/src/main/java/ortus/boxlang/runtime/config/segments/LoggerConfig.java index 1949ce72f..db508e87e 100644 --- a/src/main/java/ortus/boxlang/runtime/config/segments/LoggerConfig.java +++ b/src/main/java/ortus/boxlang/runtime/config/segments/LoggerConfig.java @@ -96,7 +96,7 @@ public LoggerConfig( Key name, LoggingConfig loggingConfig ) { } @Override - public IConfigSegment process( IStruct config ) { + public LoggerConfig process( IStruct config ) { this.level = LogLevel.valueOf( PropertyHelper.processString( config, Key.level, this.loggingConfig.rootLevel.getName() ), false ); this.appender = Key.of( PropertyHelper.processString( config, Key.appender, DEFAULT_APPENDER.getName(), VALID_APPENDERS ) ); this.encoder = Key.of( diff --git a/src/main/java/ortus/boxlang/runtime/config/segments/LoggingConfig.java b/src/main/java/ortus/boxlang/runtime/config/segments/LoggingConfig.java index 63497a9d9..45e5519fd 100644 --- a/src/main/java/ortus/boxlang/runtime/config/segments/LoggingConfig.java +++ b/src/main/java/ortus/boxlang/runtime/config/segments/LoggingConfig.java @@ -108,6 +108,17 @@ public IConfigSegment process( IStruct config ) { this.totalCapSize = PropertyHelper.processString( config, Key.totalCapSize, this.totalCapSize ); this.rootLevel = LogLevel.valueOf( PropertyHelper.processString( config, Key.rootLevel, this.rootLevel.getName(), VALID_LOG_LEVELS ), false ); this.defaultEncoder = Key.of( PropertyHelper.processString( config, Key.defaultEncoder, DEFAULT_ENCODER.getName(), VALID_ENCODERS ) ); + // process loggers now + PropertyHelper + .processToStruct( config, Key.loggers ) + .entrySet() + .forEach( entry -> { + if ( entry.getValue() instanceof IStruct castedStruct ) { + LoggerConfig loggerConfig = new LoggerConfig( entry.getKey(), this ).process( castedStruct ); + this.loggers.put( entry.getKey(), loggerConfig ); + } + } ); + return this; } @@ -116,10 +127,14 @@ public IConfigSegment process( IStruct config ) { */ @Override public IStruct asStruct() { + IStruct loggersCopy = new Struct(); + this.loggers.entrySet() + .forEach( entry -> loggersCopy.put( entry.getKey(), ( ( LoggerConfig ) entry.getValue() ).asStruct() ) ); + return Struct.of( Key.defaultEncoder, this.defaultEncoder.getName(), Key.logsDirectory, this.logsDirectory, - Key.loggers, this.loggers, + Key.loggers, loggersCopy, Key.maxLogDays, this.maxLogDays, Key.maxFileSize, this.maxFileSize, Key.rootLevel, this.rootLevel.getName(), diff --git a/src/main/java/ortus/boxlang/runtime/services/ModuleService.java b/src/main/java/ortus/boxlang/runtime/services/ModuleService.java index 43a845f7a..bd030234a 100644 --- a/src/main/java/ortus/boxlang/runtime/services/ModuleService.java +++ b/src/main/java/ortus/boxlang/runtime/services/ModuleService.java @@ -29,7 +29,6 @@ import org.semver4j.Semver; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.events.BoxEvent; @@ -79,7 +78,7 @@ public class ModuleService extends BaseService { /** * Logger */ - private static final Logger logger = LoggerFactory.getLogger( ModuleService.class ); + private Logger logger; /** * Module registry @@ -132,8 +131,9 @@ public List getModulePaths() { */ @Override public void onStartup() { + this.logger = runtime.getLoggingService().getLogger( "modules" ); BoxRuntime.timerUtil.start( "moduleservice-startup" ); - logger.debug( "+ Starting up Module Service..." ); + this.logger.info( "+ Starting up Module Service..." ); // Store the running BoxLang version this.runtimeSemver = new Semver( getRuntime().getVersionInfo().getAsString( Key.version ) ); @@ -154,7 +154,7 @@ public void onStartup() { ); // Let it be known! - logger.info( "+ Module Service started in [{}] ms", BoxRuntime.timerUtil.stopAndGetMillis( "moduleservice-startup" ) ); + this.logger.info( "+ Module Service started in [{}] ms", BoxRuntime.timerUtil.stopAndGetMillis( "moduleservice-startup" ) ); } /** @@ -173,7 +173,7 @@ public void onShutdown( Boolean force ) { // Unload all modules unloadAll(); - logger.debug( "+ Module Service shutdown" ); + this.logger.info( "+ Module Service shutdown" ); } /** @@ -201,7 +201,7 @@ public void registerAll() { .forEach( this::register ); // Log it - logger.debug( + this.logger.debug( "+ Module Service: Registered [{}] modules in [{}] ms", this.registry.size(), BoxRuntime.timerUtil.stopAndGetMillis( timerLabel ) @@ -229,10 +229,13 @@ public void register( Key name ) { // Check if the module is in the registry if ( !this.registry.containsKey( name ) ) { - throw new BoxRuntimeException( - "Cannot register the module [" + name + "] is not in the module registry." + - "Valid modules are: " + this.registry.keySet().toString() + var errorMessage = String.format( + "Cannot register the module [%s] is not in the module registry. Valid modules are: %s", + name, + this.registry.keySet().toString() ); + this.logger.error( errorMessage ); + throw new BoxRuntimeException( errorMessage ); } // Get the module record and context of execution for modules @@ -251,7 +254,7 @@ public void register( Key name ) { // Check if the module is disabled, if so, skip it if ( !moduleRecord.isEnabled() ) { - logger.warn( + this.logger.warn( "+ Module Service: Module [{}] is disabled, skipping registration", moduleRecord.name ); @@ -271,7 +274,7 @@ public void register( Key name ) { ); // Log it - logger.debug( + this.logger.info( "+ Module Service: Registered module [{}@{}] in [{}] ms from [{}]", moduleRecord.name.getName(), moduleRecord.version, @@ -300,7 +303,7 @@ public void activateAll() { .forEach( this::activate ); // Log it - logger.debug( + this.logger.info( "+ Module Service: Activated [{}] modules in [{}] ms", this.registry.size(), BoxRuntime.timerUtil.stopAndGetMillis( timerLabel ) @@ -326,15 +329,18 @@ public void activate( Key name ) { // Check if the module is in the registry if ( !this.registry.containsKey( name ) ) { - throw new BoxRuntimeException( - "Cannot activate the module [" + name + "] is not in the module registry." + - "Valid modules are: " + this.registry.keySet().toString() + var errorMessage = String.format( + "Cannot activate the module [%s] as it is not in the module registry. Valid modules are: %s", + name, + this.registry.keySet().toString() ); + this.logger.warn( errorMessage ); + throw new BoxRuntimeException( errorMessage ); } // Check if the module is already activated if ( this.registry.get( name ).isActivated() ) { - logger.warn( + this.logger.warn( "+ Module Service: Module [{}] is already activated, skipping re-activation", name ); @@ -343,7 +349,7 @@ public void activate( Key name ) { // Check if the module is disabled if ( !this.registry.get( name ).isEnabled() ) { - logger.debug( + this.logger.warn( "+ Module Service: Module [{}] is disabled, skipping activation", name ); @@ -374,7 +380,7 @@ public void activate( Key name ) { ); // Log it - logger.debug( + this.logger.info( "+ Module Service: Activated module [{}@{}] in [{}] ms", moduleRecord.name.getName(), moduleRecord.version, @@ -420,8 +426,17 @@ public void unload( Key name ) { Struct.of( "moduleRecord", moduleRecord, "moduleName", name ) ); - // Call onUnload() - moduleRecord.unload( runtimeContext ); + // We try/catch it in case it bongs, as we want all modules to unload + try { + moduleRecord.unload( runtimeContext ); + } catch ( Exception e ) { + this.logger.error( + "+ Module Service: Error unloading module [{}@{}]: {}", + moduleRecord.name, + moduleRecord.version, + e.getMessage() + ); + } // Announce it announce( @@ -430,8 +445,8 @@ public void unload( Key name ) { ); // Log it - logger.debug( - "+ Module Service: Unload module [{}@{}]", + this.logger.info( + "+ Module Service: Unloaded module [{}@{}]", moduleRecord.name, moduleRecord.version ); @@ -537,7 +552,7 @@ public ModuleService addModulePath( Path path ) { Files.createDirectories( path ); } catch ( IOException e ) { if ( e instanceof FileSystemException && e.getMessage().contains( "Read-only file system" ) ) { - logger.warn( "ModuleService: Cannot create module path [{}] as it is on a read-only file system", path.toString() ); + this.logger.warn( "ModuleService: Cannot create module path [{}] as it is on a read-only file system", path ); return this; } else { throw new BoxRuntimeException( "Error creating module path: " + path.toString(), e ); @@ -549,9 +564,9 @@ public ModuleService addModulePath( Path path ) { if ( Files.isDirectory( path ) ) { // Add a module path to the list this.modulePaths.add( path ); - logger.debug( "+ ModuleService: Added an external module path: [{}]", path.toString() ); + this.logger.info( "+ ModuleService: Added an external module path: [{}]", path ); } else { - logger.warn( "ModuleService: Requested addModulePath [{}] does not exist or is not a directory", path.toString() ); + this.logger.warn( "ModuleService: Requested addModulePath [{}] does not exist or is not a directory", path ); } return this; @@ -572,7 +587,7 @@ public ModuleService addModulePath( Path path ) { public void verifyModuleAndBoxLangVersion( String moduleVersion, Path directoryPath ) { // Early exit if the module version is null or blank if ( moduleVersion == null || moduleVersion.isBlank() ) { - logger.warn( "Module [{}] does not have a BoxLang [minimumVersion] specified in the ModuleConfig.bx file", directoryPath.getFileName() ); + this.logger.warn( "Module [{}] does not have a BoxLang [minimumVersion] specified in the ModuleConfig.bx file", directoryPath.getFileName() ); return; } @@ -584,26 +599,16 @@ public void verifyModuleAndBoxLangVersion( String moduleVersion, Path directoryP // Module minimum version = 3 // Runtime version = 4 allow it, < 3 throw exception if ( this.runtimeSemver.getMajor() < minimumVersion.getMajor() ) { - throw new BoxRuntimeException( - String.format( - "Module [%s] requires BoxLang version [%s] but we are running [%s]", - directoryPath.getFileName(), - minimumVersion, - this.runtimeSemver - ) + var errorMessage = String.format( + "Module [%s] requires BoxLang version [%s] but we are running [%s]", + directoryPath.getFileName(), + minimumVersion, + this.runtimeSemver ); + this.logger.error( errorMessage ); + throw new BoxRuntimeException( errorMessage ); } - // Minor and Patch version check - if ( this.runtimeSemver.getMinor() != minimumVersion.getMinor() || this.runtimeSemver.getPatch() != minimumVersion.getPatch() ) { - // logger.trace( - // "Module [{}] requires a minimum BoxLang version [{}] or later, but we are running [{}]. " + - // "There may be compatibility issues with newer features or bug fixes.", - // directoryPath.getFileName(), - // minimumVersion, - // this.runtimeSemver - // ); - } } } diff --git a/src/main/resources/config/boxlang.json b/src/main/resources/config/boxlang.json index a772e1ea4..83a9de907 100644 --- a/src/main/resources/config/boxlang.json +++ b/src/main/resources/config/boxlang.json @@ -103,7 +103,7 @@ "runtime": { // Valid values are in order of severity: ERROR, WARN, INFO, DEBUG, TRACE, OFF // Leave out if it should inherit from the root logger - //"level": "WARN", + "level": "INFO", // Valid values are: "file", "console", // Coming soon: "smtp", "socket", "db", "syslog" or "java class name" // Please note that we only use Rolling File Appenders @@ -120,7 +120,7 @@ "modules": { // Valid values are in order of severity: ERROR, WARN, INFO, DEBUG, TRACE, OFF // Leave out if it should inherit from the root logger - //"level": "WARN", + "level": "INFO", // Valid values are: "file", "console", // Coming soon: "smtp", "socket", "db", "syslog" or "java class name" // Please note that we only use Rolling File Appenders From b5bec8270ae8badcf303bb328d8e54f132b9df5c Mon Sep 17 00:00:00 2001 From: Jon Clausen Date: Fri, 6 Dec 2024 15:04:31 -0600 Subject: [PATCH 125/193] BL-827 Resolve - Ensure BOMs arestripped from strings when input stream is passed to caster --- .../runtime/dynamic/casters/StringCaster.java | 39 ++++++++++++++++++- .../boxlang/runtime/util/FileSystemUtil.java | 38 +++++++++++------- .../conversion/JSONDeserializeTest.java | 13 +++++++ .../bifs/global/decision/IsJSONTest.java | 26 +------------ .../test-templates/json_withBOM.json | 19 +++++++++ 5 files changed, 96 insertions(+), 39 deletions(-) create mode 100644 src/test/resources/test-templates/json_withBOM.json diff --git a/src/main/java/ortus/boxlang/runtime/dynamic/casters/StringCaster.java b/src/main/java/ortus/boxlang/runtime/dynamic/casters/StringCaster.java index a2cceb968..13072ea09 100644 --- a/src/main/java/ortus/boxlang/runtime/dynamic/casters/StringCaster.java +++ b/src/main/java/ortus/boxlang/runtime/dynamic/casters/StringCaster.java @@ -17,23 +17,32 @@ */ package ortus.boxlang.runtime.dynamic.casters; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; import java.math.BigDecimal; import java.math.BigInteger; import java.net.InetSocketAddress; import java.net.URI; import java.net.URL; +import java.nio.charset.Charset; import java.nio.file.Path; import java.time.Instant; import java.time.LocalTime; import java.time.ZoneId; import java.util.Arrays; import java.util.Locale; +import java.util.stream.Collectors; + +import org.apache.commons.io.ByteOrderMark; +import org.apache.commons.io.input.BOMInputStream; import ortus.boxlang.runtime.interop.DynamicObject; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.DateTime; import ortus.boxlang.runtime.types.XML; import ortus.boxlang.runtime.types.exceptions.BoxCastException; +import ortus.boxlang.runtime.util.FileSystemUtil; /** * I handle casting anything to a string @@ -117,12 +126,38 @@ public static String cast( Object object, String encoding, Boolean fail ) { } } object = DynamicObject.unWrap( object ); + Charset charset = null; + if ( encoding != null ) { + charset = Charset.forName( encoding ); + } if ( object instanceof Key key ) { return key.getName(); } if ( object instanceof String str ) { return str; } + if ( object instanceof InputStream is ) { + try ( + BOMInputStream inputStream = BOMInputStream.builder() + .setInputStream( is ) + .setByteOrderMarks( ByteOrderMark.UTF_8, ByteOrderMark.UTF_16BE, ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_32BE, + ByteOrderMark.UTF_32LE ) + .setInclude( false ) + .get() ) { + InputStreamReader inputReader = null; + if ( charset != null ) { + inputReader = new InputStreamReader( inputStream, charset ); + } else { + inputReader = new InputStreamReader( inputStream ); + } + try ( BufferedReader reader = new BufferedReader( inputReader ) ) { + return reader.lines().collect( Collectors.joining( FileSystemUtil.LINE_SEPARATOR ) ); + } + + } catch ( Exception e ) { + throw new BoxCastException( "Failed to read input stream as a string.", e ); + } + } if ( object instanceof Boolean bool ) { return bool ? "true" : "false"; } @@ -200,8 +235,8 @@ public static String cast( Object object, String encoding, Boolean fail ) { return object.toString(); } if ( object instanceof byte[] b ) { - if ( encoding != null && !encoding.isEmpty() ) { - return new String( b, java.nio.charset.Charset.forName( encoding ) ); + if ( charset != null ) { + return new String( b, charset ); } else { return new String( b ); } diff --git a/src/main/java/ortus/boxlang/runtime/util/FileSystemUtil.java b/src/main/java/ortus/boxlang/runtime/util/FileSystemUtil.java index ee130fc81..e2b62a854 100644 --- a/src/main/java/ortus/boxlang/runtime/util/FileSystemUtil.java +++ b/src/main/java/ortus/boxlang/runtime/util/FileSystemUtil.java @@ -53,7 +53,9 @@ import java.util.stream.IntStream; import java.util.stream.Stream; +import org.apache.commons.io.ByteOrderMark; import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.BOMInputStream; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.SystemUtils; @@ -157,10 +159,6 @@ public static Object read( String filePath, String charset, Integer bufferSize ) } else { path = Path.of( filePath ); } - Charset cs = StandardCharsets.UTF_8; - if ( charset != null ) { - cs = Charset.forName( charset ); - } try { if ( isURL ) { @@ -169,10 +167,7 @@ public static Object read( String filePath, String charset, Integer bufferSize ) if ( isBinaryFile( filePath ) ) { return IOUtils.toByteArray( fileURL.openStream() ); } else { - InputStreamReader inputReader = new InputStreamReader( fileURL.openStream() ); - try ( BufferedReader reader = new BufferedReader( inputReader ) ) { - return reader.lines().parallel().collect( Collectors.joining( LINE_SEPARATOR ) ); - } + return StringCaster.cast( fileURL.openStream(), charset, true ); } } catch ( MalformedURLException e ) { throw new BoxRuntimeException( @@ -184,11 +179,28 @@ public static Object read( String filePath, String charset, Integer bufferSize ) if ( isBinaryFile( filePath ) ) { return Files.readAllBytes( path ); } else if ( bufferSize == null ) { - return Files.readString( path, cs ); + return StringCaster.cast( Files.newInputStream( path ), charset, true ); } else { - try ( BufferedReader reader = new BufferedReader( Files.newBufferedReader( path, cs ), bufferSize ) ) { - return reader.lines().parallel().collect( Collectors.joining( LINE_SEPARATOR ) ); - } + // @formatter:off + try ( + BOMInputStream inputStream = BOMInputStream.builder() + .setPath( path ) + .setByteOrderMarks( ByteOrderMark.UTF_8, ByteOrderMark.UTF_16BE, ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_32BE, + ByteOrderMark.UTF_32LE ) + .setInclude( false ) + .get() + ) { + InputStreamReader inputReader = null; + if ( charset != null ) { + inputReader = new InputStreamReader( inputStream, charset ); + } else { + inputReader = new InputStreamReader( inputStream ); + } + try ( BufferedReader reader = new BufferedReader( inputReader, bufferSize ) ) { + return reader.lines().collect( Collectors.joining( FileSystemUtil.LINE_SEPARATOR ) ); + } + } + // @formatter:on } } @@ -586,7 +598,7 @@ public static Boolean isBinaryFile( String filePath ) { Object[] mimeParts = mimeType.split( "/" ); return !TEXT_MIME_PREFIXES.contains( mimeParts[ 0 ] ) - && !TEXT_MIME_PREFIXES.contains( mimeParts[ mimeParts.length - 1 ] ); + && !TEXT_MIME_SUFFIXES.contains( mimeParts[ mimeParts.length - 1 ] ); } /** diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/conversion/JSONDeserializeTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/conversion/JSONDeserializeTest.java index 32f5a2590..24d2aa345 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/conversion/JSONDeserializeTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/conversion/JSONDeserializeTest.java @@ -298,4 +298,17 @@ public void testCanUseCustomDeserializers() { } + @DisplayName( "It can deserialize valid JSON with escape characters and a BOM" ) + @Test + public void testEscapeCharacters() { + instance.executeSource( + """ + result = JSONDeserialize( fileRead( "src/test/resources/test-templates/json_withBOM.json" ) ); + """, + context ); + + Object result = variables.get( Key.of( "result" ) ); + assertThat( result ).isInstanceOf( Array.class ); + } + } diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/decision/IsJSONTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/decision/IsJSONTest.java index bcab864ca..e3a52b58f 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/decision/IsJSONTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/decision/IsJSONTest.java @@ -101,34 +101,12 @@ public void testFalseConditions() { assertThat( ( Boolean ) variables.get( Key.of( "aStructWithSingleQuotedKeys" ) ) ).isFalse(); } - @DisplayName( "It will return true with valid JSON with escape characters" ) + @DisplayName( "It will return true when reading a JSON file with a BOM and escape characters" ) @Test public void testEscapeCharacters() { - String testJSON = """ - [ - { - "whitelist": "user\\.login,user\\.logout,^main.*", - "securelist": "^user\\.*, ^admin", - "match": "event", - "roles": "admin", - "permissions": "", - "redirect": "user.login" - }, - { - "whitelist": "", - "securelist": "^shopping", - "match": "url", - "roles": "", - "permissions": "shop,checkout", - "redirect": "user.login", - "useSSL": true - } - ] - """; - variables.put( Key.of( "testJSON" ), testJSON ); instance.executeSource( """ - assert isJSON( trim( testJSON ) ) == true; + assert isJSON( fileRead( "src/test/resources/test-templates/json_withBOM.json" ) ) == true; """, context ); } diff --git a/src/test/resources/test-templates/json_withBOM.json b/src/test/resources/test-templates/json_withBOM.json new file mode 100644 index 000000000..eb87e2108 --- /dev/null +++ b/src/test/resources/test-templates/json_withBOM.json @@ -0,0 +1,19 @@ +[ + { + "whitelist": "user\\.login,user\\.logout,^main.*", + "securelist": "^user\\.*, ^admin", + "match": "event", + "roles": "admin", + "permissions": "", + "redirect": "user.login" + }, + { + "whitelist": "", + "securelist": "^shopping", + "match": "url", + "roles": "", + "permissions": "shop,checkout", + "redirect": "user.login", + "useSSL": true + } +] From 9de7a095e1d66ac8f9574d0eba6e6c7ee5864375 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 6 Dec 2024 15:27:56 -0600 Subject: [PATCH 126/193] Fix for wrong `NullValue` import --- src/main/java/ortus/boxlang/runtime/util/DuplicationUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/util/DuplicationUtil.java b/src/main/java/ortus/boxlang/runtime/util/DuplicationUtil.java index aa42165e0..d3b4f0237 100644 --- a/src/main/java/ortus/boxlang/runtime/util/DuplicationUtil.java +++ b/src/main/java/ortus/boxlang/runtime/util/DuplicationUtil.java @@ -27,7 +27,6 @@ import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.SerializationUtils; -import ortus.boxlang.runtime.bifs.global.type.NullValue; import ortus.boxlang.runtime.dynamic.casters.ArrayCaster; import ortus.boxlang.runtime.dynamic.casters.StructCaster; import ortus.boxlang.runtime.runnables.IClassRunnable; @@ -36,6 +35,7 @@ import ortus.boxlang.runtime.types.DateTime; import ortus.boxlang.runtime.types.Function; import ortus.boxlang.runtime.types.IStruct; +import ortus.boxlang.runtime.types.NullValue; import ortus.boxlang.runtime.types.Query; import ortus.boxlang.runtime.types.Struct; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; From febd987fc0a2bd9679dc666c5df53504384f6233 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 6 Dec 2024 15:28:06 -0600 Subject: [PATCH 127/193] Fix for client scope (in compat) --- .../application/BaseApplicationListener.java | 20 +++++++++++-------- .../boxlang/runtime/events/BoxEvent.java | 1 + 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java b/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java index c9d2feb65..3d69cddf1 100644 --- a/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java +++ b/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java @@ -121,14 +121,7 @@ public abstract class BaseApplicationListener { */ protected IStruct settings = Struct.of( "applicationTimeout", runtime.getConfiguration().applicationTimeout, - // CLIENT WILL BE REMOVED IN BOXLANG - // Kept here for now - "clientManagement", false, - "clientStorage", "cookie", - "clientTimeout", 1, - // END: CLIENT "classPaths", new Array(), - // TODO: move this logic to compat "componentPaths", new Array(), "customTagPaths", new Array(), "datasource", runtime.getConfiguration().defaultDatasource, @@ -208,6 +201,13 @@ public Application getApplication() { return this.application; } + /** + * Gets the Request Context + */ + public RequestBoxContext getRequestContext() { + return this.context; + } + /** * Verifies if the application is defined or not * @@ -260,6 +260,10 @@ public void defineApplication() { // also remove any session context context.removeParentContext( SessionBoxContext.class ); } + + BoxRuntime.getInstance().getInterceptorService().announce( BoxEvent.ON_APPLICATION_DEFINED, Struct.of( + "listener", this + ) ); } /** @@ -431,7 +435,7 @@ public void invalidateSession( Key newID ) { } /** - * Intializes a new session, also called by every new request via the {@link BaseApplicationListener#defineApplication} method + * Initializes a new session, also called by every new request via the {@link BaseApplicationListener#defineApplication} method * * @param newID The new session identifier */ diff --git a/src/main/java/ortus/boxlang/runtime/events/BoxEvent.java b/src/main/java/ortus/boxlang/runtime/events/BoxEvent.java index 462aaab1d..067ffadc9 100644 --- a/src/main/java/ortus/boxlang/runtime/events/BoxEvent.java +++ b/src/main/java/ortus/boxlang/runtime/events/BoxEvent.java @@ -60,6 +60,7 @@ public enum BoxEvent { ON_APPLICATION_START( "onApplicationStart" ), ON_APPLICATION_END( "onApplicationEnd" ), ON_APPLICATION_RESTART( "onApplicationRestart" ), + ON_APPLICATION_DEFINED( "onApplicationDefined" ), BEFORE_APPLICATION_LISTENER_LOAD( "beforeApplicationListenerLoad" ), AFTER_APPLICATION_LISTENER_LOAD( "afterApplicationListenerLoad" ), ON_REQUEST_FLUSH_BUFFER( "onRequestFlushBuffer" ), From 224305abdcd1d45b0b93883d2b00ed3d00013bc0 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 6 Dec 2024 15:38:54 -0600 Subject: [PATCH 128/193] fixed regression on loading of configuration items BL-832 #resolve onConfigurationLoad new event life-cycle for IServices --- src/main/java/ortus/boxlang/runtime/BoxRuntime.java | 11 +++++++++++ .../boxlang/runtime/services/ApplicationService.java | 12 +++++++++--- .../ortus/boxlang/runtime/services/AsyncService.java | 11 +++++++++-- .../ortus/boxlang/runtime/services/BaseService.java | 5 +++++ .../ortus/boxlang/runtime/services/CacheService.java | 11 +++++++++-- .../boxlang/runtime/services/ComponentService.java | 11 +++++++++-- .../boxlang/runtime/services/DatasourceService.java | 11 +++++++++-- .../boxlang/runtime/services/FunctionService.java | 11 +++++++++-- .../ortus/boxlang/runtime/services/IService.java | 5 +++++ .../boxlang/runtime/services/InterceptorService.java | 11 +++++++++-- .../boxlang/runtime/services/ModuleService.java | 9 ++++++++- .../boxlang/runtime/services/SchedulerService.java | 9 ++++++++- 12 files changed, 100 insertions(+), 17 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/BoxRuntime.java b/src/main/java/ortus/boxlang/runtime/BoxRuntime.java index f0d1025cc..50d5dbf4c 100644 --- a/src/main/java/ortus/boxlang/runtime/BoxRuntime.java +++ b/src/main/java/ortus/boxlang/runtime/BoxRuntime.java @@ -485,6 +485,17 @@ private void startup( Boolean debugMode ) { loadConfiguration( debugMode, this.configPath ); // Anythying below might use configuration items + // Announce it to the services + this.interceptorService.onConfigurationLoad(); + this.asyncService.onConfigurationLoad(); + this.cacheService.onConfigurationLoad(); + this.functionService.onConfigurationLoad(); + this.componentService.onConfigurationLoad(); + this.applicationService.onConfigurationLoad(); + this.moduleService.onConfigurationLoad(); + this.schedulerService.onConfigurationLoad(); + this.dataSourceService.onConfigurationLoad(); + // Ensure home assets ensureHomeAssets(); diff --git a/src/main/java/ortus/boxlang/runtime/services/ApplicationService.java b/src/main/java/ortus/boxlang/runtime/services/ApplicationService.java index 7946b54be..9f3983089 100644 --- a/src/main/java/ortus/boxlang/runtime/services/ApplicationService.java +++ b/src/main/java/ortus/boxlang/runtime/services/ApplicationService.java @@ -26,7 +26,6 @@ import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.application.Application; @@ -81,8 +80,7 @@ private enum ApplicationDescriptorType { /** * Logger */ - @SuppressWarnings( "unused" ) - private static final Logger logger = LoggerFactory.getLogger( ApplicationService.class ); + private Logger logger; /** * -------------------------------------------------------------------------- @@ -178,6 +176,14 @@ public String[] getApplicationNames() { * -------------------------------------------------------------------------- */ + /** + * The configuration load event is fired when the runtime loads the configuration + */ + @Override + public void onConfigurationLoad() { + this.logger = runtime.getLoggingService().getLogger( "application" ); + } + /** * The startup event is fired when the runtime starts up */ diff --git a/src/main/java/ortus/boxlang/runtime/services/AsyncService.java b/src/main/java/ortus/boxlang/runtime/services/AsyncService.java index 61f03456d..22565f191 100644 --- a/src/main/java/ortus/boxlang/runtime/services/AsyncService.java +++ b/src/main/java/ortus/boxlang/runtime/services/AsyncService.java @@ -27,7 +27,6 @@ import java.util.stream.Collectors; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.async.executors.BoxScheduledExecutor; @@ -109,7 +108,7 @@ public enum ExecutorType { /** * Logger */ - private static final Logger logger = LoggerFactory.getLogger( AsyncService.class ); + private Logger logger; /** * -------------------------------------------------------------------------- @@ -132,6 +131,14 @@ public AsyncService( BoxRuntime runtime ) { * -------------------------------------------------------------------------- */ + /** + * The configuration load event is fired when the runtime loads the configuration + */ + @Override + public void onConfigurationLoad() { + this.logger = runtime.getLoggingService().getLogger( "async" ); + } + /** * The startup event is fired when the runtime starts up */ diff --git a/src/main/java/ortus/boxlang/runtime/services/BaseService.java b/src/main/java/ortus/boxlang/runtime/services/BaseService.java index 542c7a2ff..3f4b41f37 100644 --- a/src/main/java/ortus/boxlang/runtime/services/BaseService.java +++ b/src/main/java/ortus/boxlang/runtime/services/BaseService.java @@ -87,6 +87,11 @@ public Key getName() { * -------------------------------------------------------------------------- */ + /** + * The configuration load event is fired when the runtime loads the configuration + */ + public abstract void onConfigurationLoad(); + /** * The startup event is fired when the runtime starts up */ diff --git a/src/main/java/ortus/boxlang/runtime/services/CacheService.java b/src/main/java/ortus/boxlang/runtime/services/CacheService.java index c7a770abd..518aa11ef 100644 --- a/src/main/java/ortus/boxlang/runtime/services/CacheService.java +++ b/src/main/java/ortus/boxlang/runtime/services/CacheService.java @@ -21,7 +21,6 @@ import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.async.executors.ExecutorRecord; @@ -62,7 +61,7 @@ public class CacheService extends BaseService { /** * Logger */ - private static final Logger logger = LoggerFactory.getLogger( CacheService.class ); + private Logger logger; /** * The async service @@ -132,6 +131,14 @@ public ExecutorRecord getTaskScheduler() { * -------------------------------------------------------------------------- */ + /** + * The configuration load event is fired when the runtime loads the configuration + */ + @Override + public void onConfigurationLoad() { + this.logger = runtime.getLoggingService().getLogger( "cache" ); + } + /** * The startup event is fired when the runtime starts up * This will create all the core caches and register them diff --git a/src/main/java/ortus/boxlang/runtime/services/ComponentService.java b/src/main/java/ortus/boxlang/runtime/services/ComponentService.java index 1fbfaeb69..d51a045ee 100644 --- a/src/main/java/ortus/boxlang/runtime/services/ComponentService.java +++ b/src/main/java/ortus/boxlang/runtime/services/ComponentService.java @@ -23,7 +23,6 @@ import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.components.BoxComponent; @@ -47,7 +46,7 @@ public class ComponentService extends BaseService { /** * Logger */ - private static final Logger logger = LoggerFactory.getLogger( ComponentService.class ); + private Logger logger; /** * The set of components registered with the service @@ -75,6 +74,14 @@ public ComponentService( BoxRuntime runtime ) { * -------------------------------------------------------------------------- */ + /** + * The configuration load event is fired when the runtime loads the configuration + */ + @Override + public void onConfigurationLoad() { + this.logger = runtime.getLoggingService().getLogger( "runtime" ); + } + /** * The startup event is fired when the runtime starts up */ diff --git a/src/main/java/ortus/boxlang/runtime/services/DatasourceService.java b/src/main/java/ortus/boxlang/runtime/services/DatasourceService.java index 2b6fbb9fb..d504987b8 100644 --- a/src/main/java/ortus/boxlang/runtime/services/DatasourceService.java +++ b/src/main/java/ortus/boxlang/runtime/services/DatasourceService.java @@ -19,7 +19,6 @@ import java.util.concurrent.ConcurrentHashMap; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.config.segments.DatasourceConfig; @@ -49,7 +48,7 @@ public class DatasourceService extends BaseService { /** * Logger */ - private static final Logger logger = LoggerFactory.getLogger( DatasourceService.class ); + private Logger logger; /** * Map of datasources registered with the service. @@ -67,6 +66,14 @@ public class DatasourceService extends BaseService { * -------------------------------------------------------------------------- */ + /** + * The configuration load event is fired when the runtime loads the configuration + */ + @Override + public void onConfigurationLoad() { + this.logger = runtime.getLoggingService().getLogger( "datasource" ); + } + /** * Constructor * diff --git a/src/main/java/ortus/boxlang/runtime/services/FunctionService.java b/src/main/java/ortus/boxlang/runtime/services/FunctionService.java index 05f976a42..6a8091eae 100644 --- a/src/main/java/ortus/boxlang/runtime/services/FunctionService.java +++ b/src/main/java/ortus/boxlang/runtime/services/FunctionService.java @@ -26,7 +26,6 @@ import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.bifs.BIF; @@ -58,7 +57,7 @@ public class FunctionService extends BaseService { /** * Logger */ - private static final Logger logger = LoggerFactory.getLogger( FunctionService.class ); + private Logger logger; /** * The set of global functions registered with the service @@ -102,6 +101,14 @@ public FunctionService( BoxRuntime runtime ) { * -------------------------------------------------------------------------- */ + /** + * The configuration load event is fired when the runtime loads the configuration + */ + @Override + public void onConfigurationLoad() { + this.logger = runtime.getLoggingService().getLogger( "runtime" ); + } + /** * The startup event is fired when the runtime starts up */ diff --git a/src/main/java/ortus/boxlang/runtime/services/IService.java b/src/main/java/ortus/boxlang/runtime/services/IService.java index 11ea1daef..829458f00 100644 --- a/src/main/java/ortus/boxlang/runtime/services/IService.java +++ b/src/main/java/ortus/boxlang/runtime/services/IService.java @@ -34,6 +34,11 @@ public interface IService { */ public Key getName(); + /** + * The configuration load event is fired when the runtime loads the configuration + */ + public void onConfigurationLoad(); + /** * The startup event is fired when the runtime starts up */ diff --git a/src/main/java/ortus/boxlang/runtime/services/InterceptorService.java b/src/main/java/ortus/boxlang/runtime/services/InterceptorService.java index 0c8b1360b..9343ffaa8 100644 --- a/src/main/java/ortus/boxlang/runtime/services/InterceptorService.java +++ b/src/main/java/ortus/boxlang/runtime/services/InterceptorService.java @@ -18,7 +18,6 @@ package ortus.boxlang.runtime.services; import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.events.BoxEvent; @@ -46,7 +45,7 @@ public class InterceptorService extends InterceptorPool implements IService { /** * Logger */ - private static final Logger logger = LoggerFactory.getLogger( InterceptorService.class ); + private Logger logger; /** * -------------------------------------------------------------------------- @@ -70,6 +69,14 @@ public InterceptorService( BoxRuntime runtime ) { * -------------------------------------------------------------------------- */ + /** + * The configuration load event is fired when the runtime loads the configuration + */ + @Override + public void onConfigurationLoad() { + this.logger = runtime.getLoggingService().getLogger( "runtime" ); + } + /** * The startup event is fired when the runtime starts up */ diff --git a/src/main/java/ortus/boxlang/runtime/services/ModuleService.java b/src/main/java/ortus/boxlang/runtime/services/ModuleService.java index bd030234a..80c24c85b 100644 --- a/src/main/java/ortus/boxlang/runtime/services/ModuleService.java +++ b/src/main/java/ortus/boxlang/runtime/services/ModuleService.java @@ -126,12 +126,19 @@ public List getModulePaths() { * -------------------------------------------------------------------------- */ + /** + * The configuration load event is fired when the runtime loads the configuration + */ + @Override + public void onConfigurationLoad() { + this.logger = runtime.getLoggingService().getLogger( "modules" ); + } + /** * The startup event is fired when the runtime starts up */ @Override public void onStartup() { - this.logger = runtime.getLoggingService().getLogger( "modules" ); BoxRuntime.timerUtil.start( "moduleservice-startup" ); this.logger.info( "+ Starting up Module Service..." ); diff --git a/src/main/java/ortus/boxlang/runtime/services/SchedulerService.java b/src/main/java/ortus/boxlang/runtime/services/SchedulerService.java index 6c0b547da..72db9f69a 100644 --- a/src/main/java/ortus/boxlang/runtime/services/SchedulerService.java +++ b/src/main/java/ortus/boxlang/runtime/services/SchedulerService.java @@ -65,12 +65,19 @@ public SchedulerService( BoxRuntime runtime ) { * -------------------------------------------------------------------------- */ + /** + * The configuration load event is fired when the runtime loads the configuration + */ + @Override + public void onConfigurationLoad() { + this.logger = runtime.getLoggingService().getLogger( "scheduler" ); + } + /** * The startup event is fired when the runtime starts up */ @Override public void onStartup() { - this.logger = runtime.getLoggingService().getLogger( "scheduler" ); BoxRuntime.timerUtil.start( "schedulerservice-startup" ); this.logger.info( "+ Starting up Scheduler Service..." ); From 0973361648cb12efc4db74573cf126ad828d9d38 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Fri, 6 Dec 2024 15:34:43 -0600 Subject: [PATCH 129/193] BL-818 associate inner and outer classes in ASM --- .../boxlang/compiler/asmboxpiler/AsmHelper.java | 10 ++++++++++ .../boxlang/compiler/asmboxpiler/Transpiler.java | 9 +++++++++ .../transformer/statement/BoxClassTransformer.java | 7 +++++-- .../BoxFunctionDeclarationTransformer.java | 13 +++++++++++-- src/test/java/TestCases/phase3/ClassTest.java | 2 -- 5 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java index 1c10f54d4..c78ffedb0 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java @@ -512,6 +512,12 @@ public static List callDynamicObjectInvokeConstructor( Transpi } public static void init( ClassVisitor classVisitor, boolean singleton, Type type, Type superClass, Consumer onConstruction, + Type... interfaces ) { + init( classVisitor, singleton, type, superClass, null, onConstruction, interfaces ); + } + + public static void init( ClassVisitor classVisitor, boolean singleton, Type type, Type superClass, Consumer postVisit, + Consumer onConstruction, Type... interfaces ) { classVisitor.visit( Opcodes.V17, @@ -521,6 +527,10 @@ public static void init( ClassVisitor classVisitor, boolean singleton, Type type superClass.getInternalName(), interfaces.length == 0 ? null : Arrays.stream( interfaces ).map( Type::getInternalName ).toArray( String[]::new ) ); + if ( postVisit != null ) { + postVisit.accept( classVisitor ); + } + if ( singleton ) { addGetInstance( classVisitor, type ); } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java index 8f683e5f8..26535fb0a 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java @@ -54,6 +54,7 @@ public abstract class Transpiler implements ITranspiler { private List imports = new ArrayList<>(); private List methodContextTrackers = new ArrayList(); private List staticInitializers = new ArrayList<>(); + private ClassNode owningClassNode = null; /** * Set a property @@ -65,6 +66,14 @@ public void setProperty( String key, String value ) { properties.put( key, value ); } + public void setOwningCLass( ClassNode node ) { + owningClassNode = node; + } + + public ClassNode getOwningClass() { + return owningClassNode; + } + public boolean canReturn() { String returnType = getProperty( "returnType" ); if ( returnType != null && !returnType.equals( "void" ) ) { diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java index 73d99850c..60257c934 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java @@ -194,9 +194,12 @@ public static ClassNode transpile( Transpiler transpiler, BoxClass boxClass ) th } ClassNode classNode = new ClassNode(); - classNode.visitSource( filePath, null ); + transpiler.setOwningCLass( classNode ); + transpiler.setProperty( "enclosingClassInternalName", type.getInternalName() ); - AsmHelper.init( classNode, false, type, superclass, methodVisitor -> { + AsmHelper.init( classNode, false, type, superclass, cv -> { + classNode.visitSource( filePath, null ); + }, methodVisitor -> { methodVisitor.visitVarInsn( Opcodes.ALOAD, 0 ); methodVisitor.visitTypeInsn( Opcodes.NEW, Type.getInternalName( ClassVariablesScope.class ) ); methodVisitor.visitInsn( Opcodes.DUP ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java index 75b4304b4..9691a636f 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java @@ -81,9 +81,18 @@ public List transform( BoxNode node, TransformerContext contex BoxAccessModifier access = function.getAccessModifier() == null ? BoxAccessModifier.Public : function.getAccessModifier(); ClassNode classNode = new ClassNode(); - classNode.visitSource( transpiler.getProperty( "filePath" ), null ); - AsmHelper.init( classNode, true, type, Type.getType( UDF.class ), methodVisitor -> { + AsmHelper.init( classNode, true, type, Type.getType( UDF.class ), cv -> { + cv.visitSource( transpiler.getProperty( "filePath" ), null ); + cv.visitNestHost( transpiler.getProperty( "enclosingClassInternalName" ) ); + cv.visitInnerClass( type.getInternalName(), transpiler.getProperty( "enclosingClassInternalName" ), + "Func_" + function.getName(), + Opcodes.ACC_PUBLIC ); + }, methodVisitor -> { } ); + transpiler.getOwningClass().visitInnerClass( type.getInternalName(), transpiler.getProperty( "enclosingClassInternalName" ), + "Func_" + function.getName(), + Opcodes.ACC_PUBLIC ); + transpiler.setAuxiliary( type.getClassName(), classNode ); AsmHelper.addStaticFieldGetter( classNode, diff --git a/src/test/java/TestCases/phase3/ClassTest.java b/src/test/java/TestCases/phase3/ClassTest.java index 5cf177e69..4992ecfd8 100644 --- a/src/test/java/TestCases/phase3/ClassTest.java +++ b/src/test/java/TestCases/phase3/ClassTest.java @@ -395,7 +395,6 @@ public void testBasicClassFile() { @DisplayName( "parent super.init will preserve child variables scope" ) @Test - @Disabled( "For Brad's super duper ref map fix" ) public void superInitTest() { // @formatter:off instance.executeSource( @@ -1523,7 +1522,6 @@ public void testBXResolverPrefixImport() { @DisplayName( "udf class has enclosing class reference" ) @Test - @Disabled public void testUDFClassEnclosingClassReference() { instance.executeSource( From c16ee087c8e1865b6d476ed596310c77b3b7503f Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Fri, 6 Dec 2024 15:38:56 -0600 Subject: [PATCH 130/193] BL-760 fix tests --- .../ortus/boxlang/compiler/SourceMapTest.java | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/test/java/ortus/boxlang/compiler/SourceMapTest.java b/src/test/java/ortus/boxlang/compiler/SourceMapTest.java index 4cad511ff..78392c443 100644 --- a/src/test/java/ortus/boxlang/compiler/SourceMapTest.java +++ b/src/test/java/ortus/boxlang/compiler/SourceMapTest.java @@ -25,6 +25,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import ortus.boxlang.compiler.javaboxpiler.JavaBoxpiler; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; @@ -58,7 +59,9 @@ public void setupEach() { @Test public void testVariableLineNumber() { - + if ( instance.getCompiler() instanceof JavaBoxpiler ) { + return; + } Exception e = assertThrows( KeyNotFoundException.class, () -> { instance.executeSource( """ @@ -71,11 +74,14 @@ public void testVariableLineNumber() { PrintWriter printer = new PrintWriter( out ); e.printStackTrace( printer ); - assertThat( out.toString().contains( "MissingVar.bx:3" ) ).isTrue(); + assertThat( out.toString() ).contains( "MissingVar.bx:3" ); } @Test public void testTemmplateMissingVariable() { + if ( instance.getCompiler() instanceof JavaBoxpiler ) { + return; + } Exception e = assertThrows( KeyNotFoundException.class, () -> { instance.executeSource( @@ -89,12 +95,14 @@ public void testTemmplateMissingVariable() { PrintWriter printer = new PrintWriter( out ); e.printStackTrace( printer ); - assertThat( out.toString().contains( "MissingVarTemplate.bxm:14" ) ).isTrue(); + assertThat( out.toString() ).contains( "MissingVarTemplate.bxm:14" ); } @Test public void testTemmplateMissingVariableInComponent() { - + if ( instance.getCompiler() instanceof JavaBoxpiler ) { + return; + } Exception e = assertThrows( KeyNotFoundException.class, () -> { instance.executeSource( """ @@ -109,9 +117,9 @@ public void testTemmplateMissingVariableInComponent() { String errorOutput = out.toString(); - assertThat( errorOutput.contains( "ComponentInTemplate.bxm:5" ) ).isTrue(); - assertThat( errorOutput.contains( "ComponentInTemplate.bxm:4" ) ).isTrue(); - assertThat( errorOutput.contains( "ComponentInTemplate.bxm:1" ) ).isTrue(); + assertThat( errorOutput ).contains( "ComponentInTemplate.bxm:5" ); + assertThat( errorOutput ).contains( "ComponentInTemplate.bxm:4" ); + assertThat( errorOutput ).contains( "ComponentInTemplate.bxm:1" ); } } From 7d829e1219eba9b762ab7c3cf99ff13e32e196a9 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 6 Dec 2024 15:59:12 -0600 Subject: [PATCH 131/193] remove logger from base, let concretes do it. --- .../runtime/events/BaseInterceptor.java | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/events/BaseInterceptor.java b/src/main/java/ortus/boxlang/runtime/events/BaseInterceptor.java index d3e888f33..371c60d53 100644 --- a/src/main/java/ortus/boxlang/runtime/events/BaseInterceptor.java +++ b/src/main/java/ortus/boxlang/runtime/events/BaseInterceptor.java @@ -17,9 +17,6 @@ */ package ortus.boxlang.runtime.events; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.interop.DynamicObject; import ortus.boxlang.runtime.scopes.Key; @@ -34,12 +31,7 @@ public abstract class BaseInterceptor implements IInterceptor { /** * The properties to configure the interceptor with (if any) */ - protected IStruct properties = new Struct(); - - /** - * A logger class - */ - protected Logger logger; + protected IStruct properties = new Struct(); /** * This method is called by the BoxLang runtime to configure the interceptor @@ -48,8 +40,7 @@ public abstract class BaseInterceptor implements IInterceptor { * @param properties The properties to configure the interceptor with (if any) */ public void configure( IStruct properties ) { - this.properties = properties; - this.logger = LoggerFactory.getLogger( this.getClass() ); + this.properties = properties; } /** @@ -113,15 +104,6 @@ public IInterceptor setProperty( Key name, Object value ) { return this; } - /** - * Get the logger - * - * @return The logger - */ - public Logger getLogger() { - return this.logger; - } - /** * Get the runtime * From 7b3e1afdabe7ccc1a4a945058d2f5f0cd0f625bf Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 6 Dec 2024 16:03:00 -0600 Subject: [PATCH 132/193] fix on logger for interceptors --- .../com/ortussolutions/interceptors/ExampleInterceptor.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/modules/test/src/main/java/com/ortussolutions/interceptors/ExampleInterceptor.java b/src/modules/test/src/main/java/com/ortussolutions/interceptors/ExampleInterceptor.java index ce827d05a..3dc6a4906 100644 --- a/src/modules/test/src/main/java/com/ortussolutions/interceptors/ExampleInterceptor.java +++ b/src/modules/test/src/main/java/com/ortussolutions/interceptors/ExampleInterceptor.java @@ -1,13 +1,16 @@ package com.ortussolutions.interceptors; +import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ortus.boxlang.runtime.events.BaseInterceptor; -import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.events.InterceptionPoint; +import ortus.boxlang.runtime.types.IStruct; public class ExampleInterceptor extends BaseInterceptor { + private Logger logger; + /** * This method is called by the BoxLang runtime to configure the interceptor * with a Struct of properties From 99f716328203d0f30ce5c3b84da59766052f8f76 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 6 Dec 2024 16:04:25 -0600 Subject: [PATCH 133/193] more fixes --- .../runtime/events/BaseInterceptor.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/events/BaseInterceptor.java b/src/main/java/ortus/boxlang/runtime/events/BaseInterceptor.java index 371c60d53..108a0c2d2 100644 --- a/src/main/java/ortus/boxlang/runtime/events/BaseInterceptor.java +++ b/src/main/java/ortus/boxlang/runtime/events/BaseInterceptor.java @@ -17,6 +17,8 @@ */ package ortus.boxlang.runtime.events; +import org.slf4j.Logger; + import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.interop.DynamicObject; import ortus.boxlang.runtime.scopes.Key; @@ -31,7 +33,12 @@ public abstract class BaseInterceptor implements IInterceptor { /** * The properties to configure the interceptor with (if any) */ - protected IStruct properties = new Struct(); + protected IStruct properties = new Struct(); + + /** + * The logger + */ + protected Logger logger; /** * This method is called by the BoxLang runtime to configure the interceptor @@ -40,7 +47,8 @@ public abstract class BaseInterceptor implements IInterceptor { * @param properties The properties to configure the interceptor with (if any) */ public void configure( IStruct properties ) { - this.properties = properties; + this.properties = properties; + this.logger = getRuntime().getLoggingService().getLogger( "runtime" ); } /** @@ -113,6 +121,13 @@ public BoxRuntime getRuntime() { return BoxRuntime.getInstance(); } + /** + * Get the logger + */ + public Logger getLogger() { + return this.logger; + } + /** * Unregister the interceptor from all states */ From 7b9cae0dc11cf49905f28805cd15625eb2b1b001 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 6 Dec 2024 16:29:50 -0600 Subject: [PATCH 134/193] Try to string cast before url encode --- .../boxlang/runtime/bifs/global/system/URLEncodedFormat.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/system/URLEncodedFormat.java b/src/main/java/ortus/boxlang/runtime/bifs/global/system/URLEncodedFormat.java index 1108d635a..cba7076d7 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/system/URLEncodedFormat.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/system/URLEncodedFormat.java @@ -23,6 +23,7 @@ import ortus.boxlang.runtime.bifs.BoxBIF; import ortus.boxlang.runtime.bifs.BoxMember; import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.dynamic.casters.StringCaster; import ortus.boxlang.runtime.scopes.ArgumentsScope; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.Argument; @@ -52,7 +53,7 @@ public URLEncodedFormat() { * @argument.String */ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { - String str = arguments.getAsString( Key.string ); + String str = StringCaster.cast( arguments.get( Key.string ) ); try { // W3C says to use UTF-8 for all encoding: http://www.w3.org/TR/html40/appendix/notes.html#non-ascii-chars return java.net.URLEncoder.encode( str, "utf-8" ); From f8af76daf77838ef62009c889bc5d306e7a6954b Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 6 Dec 2024 16:30:06 -0600 Subject: [PATCH 135/193] Handle null context for datetime casting --- .../runtime/dynamic/casters/DateTimeCaster.java | 13 ++++++++++++- .../java/ortus/boxlang/runtime/jdbc/DataSource.java | 8 ++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/dynamic/casters/DateTimeCaster.java b/src/main/java/ortus/boxlang/runtime/dynamic/casters/DateTimeCaster.java index 978684023..f4523b496 100644 --- a/src/main/java/ortus/boxlang/runtime/dynamic/casters/DateTimeCaster.java +++ b/src/main/java/ortus/boxlang/runtime/dynamic/casters/DateTimeCaster.java @@ -26,6 +26,7 @@ import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.context.IBoxContext; +import ortus.boxlang.runtime.context.RequestBoxContext; import ortus.boxlang.runtime.interop.DynamicObject; import ortus.boxlang.runtime.types.DateTime; import ortus.boxlang.runtime.types.exceptions.BoxCastException; @@ -123,7 +124,11 @@ public static CastAttempt attempt( Object object, IBoxContext context * @return The value */ public static DateTime cast( Object object ) { - return cast( object, true, BoxRuntime.getInstance().getRuntimeContext() ); + IBoxContext context = RequestBoxContext.getCurrent(); + if ( context == null ) { + context = BoxRuntime.getInstance().getRuntimeContext(); + } + return cast( object, true, context ); } /** @@ -178,6 +183,12 @@ public static DateTime cast( Object object, Boolean fail, ZoneId timezone, IBoxC */ public static DateTime cast( Object object, Boolean fail, ZoneId timezone, Boolean clone, IBoxContext context ) { if ( timezone == null ) { + if ( context == null ) { + context = RequestBoxContext.getCurrent(); + if ( context == null ) { + context = BoxRuntime.getInstance().getRuntimeContext(); + } + } timezone = LocalizationUtil.parseZoneId( null, context ); } diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/DataSource.java b/src/main/java/ortus/boxlang/runtime/jdbc/DataSource.java index 024f9909f..740c4c6ee 100644 --- a/src/main/java/ortus/boxlang/runtime/jdbc/DataSource.java +++ b/src/main/java/ortus/boxlang/runtime/jdbc/DataSource.java @@ -243,6 +243,14 @@ public DataSource shutdown() { * @return An array of Structs, each representing a row of the result set (if any). If there are no results (say, for an UPDATE statement), an empty * array is returned. */ + public ExecutedQuery execute( String query ) { + try ( Connection conn = getConnection() ) { + return execute( query, conn, null ); + } catch ( SQLException e ) { + throw new DatabaseException( e.getMessage(), e ); + } + } + public ExecutedQuery execute( String query, IBoxContext context ) { try ( Connection conn = getConnection() ) { return execute( query, conn, context ); From b171c7e1c940f7538bf7dfae9bbd8ca849549f6a Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 6 Dec 2024 16:47:38 -0600 Subject: [PATCH 136/193] Pretending I'm Jacob --- .../boxlang/compiler/asmboxpiler/Transpiler.java | 2 +- .../transformer/statement/BoxClassTransformer.java | 2 +- .../statement/BoxFunctionDeclarationTransformer.java | 11 ++++++++--- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java index 26535fb0a..8b8e63b2a 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/Transpiler.java @@ -66,7 +66,7 @@ public void setProperty( String key, String value ) { properties.put( key, value ); } - public void setOwningCLass( ClassNode node ) { + public void setOwningClass( ClassNode node ) { owningClassNode = node; } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java index 60257c934..ecf0ff2d3 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java @@ -194,7 +194,7 @@ public static ClassNode transpile( Transpiler transpiler, BoxClass boxClass ) th } ClassNode classNode = new ClassNode(); - transpiler.setOwningCLass( classNode ); + transpiler.setOwningClass( classNode ); transpiler.setProperty( "enclosingClassInternalName", type.getInternalName() ); AsmHelper.init( classNode, false, type, superclass, cv -> { diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java index 9691a636f..55f8fcdb4 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java @@ -89,9 +89,14 @@ public List transform( BoxNode node, TransformerContext contex Opcodes.ACC_PUBLIC ); }, methodVisitor -> { } ); - transpiler.getOwningClass().visitInnerClass( type.getInternalName(), transpiler.getProperty( "enclosingClassInternalName" ), - "Func_" + function.getName(), - Opcodes.ACC_PUBLIC ); + + ClassNode owningClass = transpiler.getOwningClass(); + if ( owningClass != null ) { + owningClass.visitInnerClass( type.getInternalName(), transpiler.getProperty( "enclosingClassInternalName" ), + "Func_" + function.getName(), + Opcodes.ACC_PUBLIC ); + } + transpiler.setAuxiliary( type.getClassName(), classNode ); From dce456c218365fcabf0e74f935b29c0fadb4334f Mon Sep 17 00:00:00 2001 From: elpete Date: Fri, 6 Dec 2024 22:48:28 +0000 Subject: [PATCH 137/193] Apply cfformat changes --- .../statement/BoxFunctionDeclarationTransformer.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java index 55f8fcdb4..1533d6edb 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java @@ -93,11 +93,10 @@ public List transform( BoxNode node, TransformerContext contex ClassNode owningClass = transpiler.getOwningClass(); if ( owningClass != null ) { owningClass.visitInnerClass( type.getInternalName(), transpiler.getProperty( "enclosingClassInternalName" ), - "Func_" + function.getName(), - Opcodes.ACC_PUBLIC ); + "Func_" + function.getName(), + Opcodes.ACC_PUBLIC ); } - transpiler.setAuxiliary( type.getClassName(), classNode ); AsmHelper.addStaticFieldGetter( classNode, From 8b99845af5ce6f9ba752d0e55f433891d31318b1 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 6 Dec 2024 17:10:04 -0600 Subject: [PATCH 138/193] fixed tests due to regression on how services are configured --- .../java/ortus/boxlang/runtime/logging/LoggingService.java | 6 +++--- .../ortus/boxlang/runtime/services/AsyncServiceTest.java | 1 + .../boxlang/runtime/services/DataSourceServiceTest.java | 7 ++++--- .../boxlang/runtime/services/FunctionServiceTest.java | 5 ++--- .../boxlang/runtime/services/InterceptorServiceTest.java | 3 +-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index 5bf6f5c7b..026e17f74 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -412,7 +412,7 @@ public LoggingService logMessage( } // Compute and get the logger - Logger oLogger = getLogger( logger ); + Logger oLogger = ( Logger ) getLogger( logger ); // Log according to the level switch ( targetLogLevel.getNameNoCase() ) { @@ -438,7 +438,7 @@ public LoggingService logMessage( * * @return The logger requested */ - public Logger getLogger( String logger ) { + public org.slf4j.Logger getLogger( String logger ) { // The incoming logger can be: // 1. A named logger: "scheduler", "application", "orm", etc // 2. A relative path: "scheduler.log", "application.log", "orm.log" @@ -456,7 +456,7 @@ public Logger getLogger( String logger ) { Key loggerKey = Key.of( FilenameUtils.getBaseName( loggerFilePath.toLowerCase() ) ); // Compute it or return it - return ( Logger ) this.loggersMap.computeIfAbsent( Key.of( loggerFilePath ), key -> createLogger( loggerKey, loggerFilePath ) ); + return ( org.slf4j.Logger ) this.loggersMap.computeIfAbsent( Key.of( loggerFilePath ), key -> createLogger( loggerKey, loggerFilePath ) ); } /** diff --git a/src/test/java/ortus/boxlang/runtime/services/AsyncServiceTest.java b/src/test/java/ortus/boxlang/runtime/services/AsyncServiceTest.java index 835e554c6..4187a2bfa 100644 --- a/src/test/java/ortus/boxlang/runtime/services/AsyncServiceTest.java +++ b/src/test/java/ortus/boxlang/runtime/services/AsyncServiceTest.java @@ -45,6 +45,7 @@ class AsyncServiceTest { public void setupBeforeEach() { runtime = BoxRuntime.getInstance(); asyncService = new AsyncService( runtime ); + asyncService.onConfigurationLoad(); } @DisplayName( "It can create the async service" ) diff --git a/src/test/java/ortus/boxlang/runtime/services/DataSourceServiceTest.java b/src/test/java/ortus/boxlang/runtime/services/DataSourceServiceTest.java index c060df16e..e2b9ec632 100644 --- a/src/test/java/ortus/boxlang/runtime/services/DataSourceServiceTest.java +++ b/src/test/java/ortus/boxlang/runtime/services/DataSourceServiceTest.java @@ -44,9 +44,10 @@ public class DataSourceServiceTest { @BeforeAll public static void setUp() { - runtime = BoxRuntime.getInstance( true ); - service = new DatasourceService( runtime ); - datasourceName = Key.of( "foobar" ); + runtime = BoxRuntime.getInstance( true ); + service = new DatasourceService( runtime ); + service.onConfigurationLoad(); + datasourceName = Key.of( "foobar" ); } @BeforeEach diff --git a/src/test/java/ortus/boxlang/runtime/services/FunctionServiceTest.java b/src/test/java/ortus/boxlang/runtime/services/FunctionServiceTest.java index 97b5508eb..c02a75aca 100644 --- a/src/test/java/ortus/boxlang/runtime/services/FunctionServiceTest.java +++ b/src/test/java/ortus/boxlang/runtime/services/FunctionServiceTest.java @@ -23,7 +23,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.mockito.InjectMocks; import org.mockito.Spy; import ortus.boxlang.runtime.BoxRuntime; @@ -35,12 +34,12 @@ class FunctionServiceTest { FunctionService service; @Spy - @InjectMocks - private BoxRuntime runtime; + private BoxRuntime runtime = BoxRuntime.getInstance(); @BeforeEach public void setupBeforeEach() { service = new FunctionService( runtime ); + service.onConfigurationLoad(); service.onStartup(); } diff --git a/src/test/java/ortus/boxlang/runtime/services/InterceptorServiceTest.java b/src/test/java/ortus/boxlang/runtime/services/InterceptorServiceTest.java index 064b47e23..252ea6a4f 100644 --- a/src/test/java/ortus/boxlang/runtime/services/InterceptorServiceTest.java +++ b/src/test/java/ortus/boxlang/runtime/services/InterceptorServiceTest.java @@ -46,10 +46,9 @@ class InterceptorServiceTest { @BeforeEach public void setupBeforeEach() { - Mockito.doReturn( new ScriptingRequestBoxContext() ).when( runtime ).getRuntimeContext(); - service = new InterceptorService( runtime ); + service.onConfigurationLoad(); } @DisplayName( "Test it can get an instance of the service" ) From 1287bda31eecd7739e24f80290a739012e6e9c61 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 6 Dec 2024 17:23:17 -0600 Subject: [PATCH 139/193] all tests green --- src/main/resources/config/boxlang.json | 8 ++++---- .../boxlang/runtime/bifs/global/system/WriteLogTest.java | 4 ---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/resources/config/boxlang.json b/src/main/resources/config/boxlang.json index 83a9de907..4a7097aa4 100644 --- a/src/main/resources/config/boxlang.json +++ b/src/main/resources/config/boxlang.json @@ -103,7 +103,7 @@ "runtime": { // Valid values are in order of severity: ERROR, WARN, INFO, DEBUG, TRACE, OFF // Leave out if it should inherit from the root logger - "level": "INFO", + "level": "DEBUG", // Valid values are: "file", "console", // Coming soon: "smtp", "socket", "db", "syslog" or "java class name" // Please note that we only use Rolling File Appenders @@ -120,7 +120,7 @@ "modules": { // Valid values are in order of severity: ERROR, WARN, INFO, DEBUG, TRACE, OFF // Leave out if it should inherit from the root logger - "level": "INFO", + "level": "DEBUG", // Valid values are: "file", "console", // Coming soon: "smtp", "socket", "db", "syslog" or "java class name" // Please note that we only use Rolling File Appenders @@ -137,7 +137,7 @@ "application": { // Valid values are in order of severity: ERROR, WARN, INFO, DEBUG, TRACE, OFF // Leave out if it should inherit from the root logger - "level": "TRACE", + "level": "DEBUG", // Valid values are: "file", "console", // Coming soon: "smtp", "socket", "db", "syslog" or "java class name" // Please note that we only use Rolling File Appenders @@ -154,7 +154,7 @@ "scheduler": { // Valid values are in order of severity: ERROR, WARN, INFO, DEBUG, TRACE, OFF // Leave out if it should inherit from the root logger - "level": "INFO", + "level": "DEBUG", // Valid values are: "file", "console", // Coming soon: "smtp", "socket", "db", "syslog" or "java class name" // Please note that we only use Rolling File Appenders diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java index a8c444381..7c90ccb9f 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java @@ -62,10 +62,6 @@ public static void setUp() { System.setOut( new PrintStream( outContent ) ); logFilePath = Paths.get( logsDirectory, "/writelog.log" ).normalize().toString(); defaultLogFilePath = Paths.get( logsDirectory, "/runtime.log" ).normalize().toString(); - - if ( FileSystemUtil.exists( defaultLogFilePath ) ) { - FileSystemUtil.deleteFile( defaultLogFilePath ); - } } @AfterAll From 501db8fc1d09cf2345499d081b71683cc4579eb2 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 6 Dec 2024 17:33:51 -0600 Subject: [PATCH 140/193] BL-833 --- .../expression/BoxAssignmentTransformer.java | 12 ++++++--- .../expression/BoxIdentifierTransformer.java | 5 +++- .../BoxUnaryOperationTransformer.java | 5 +++- .../expression/BoxAssignmentTransformer.java | 6 ++--- .../expression/BoxIdentifierTransformer.java | 4 +-- .../BoxUnaryOperationTransformer.java | 2 +- .../bifs/global/struct/StructKeyExists.java | 2 +- .../context/ApplicationBoxContext.java | 8 +++--- .../runtime/context/BaseBoxContext.java | 15 +++++------ .../runtime/context/CatchBoxContext.java | 12 ++++----- .../runtime/context/ClassBoxContext.java | 10 ++++---- .../runtime/context/ClosureBoxContext.java | 8 +++--- .../runtime/context/ContainerBoxContext.java | 10 ++++---- .../runtime/context/CustomTagBoxContext.java | 10 ++++---- .../runtime/context/FunctionBoxContext.java | 18 ++++++------- .../boxlang/runtime/context/IBoxContext.java | 17 ++++++++----- .../runtime/context/InterfaceBoxContext.java | 10 ++++---- .../runtime/context/LambdaBoxContext.java | 10 ++++---- .../runtime/context/RequestBoxContext.java | 4 +-- .../runtime/context/RuntimeBoxContext.java | 8 +++--- .../context/ScriptingRequestBoxContext.java | 10 ++++---- .../runtime/context/SessionBoxContext.java | 8 +++--- .../context/StaticClassBoxContext.java | 8 +++--- .../runtime/context/ThreadBoxContext.java | 14 +++++------ .../dynamic/ExpressionInterpreter.java | 4 +-- .../ortus/boxlang/runtime/testing/Phase1.java | 2 +- .../runtime/testing/Phase1TryCatch.java | 8 +++--- .../testing/Phase2Closure$closure1.java | 8 +++--- .../runtime/testing/Phase2Lambda$lambda1.java | 4 +-- .../runtime/testing/Phase2UDF$greet.java | 8 +++--- .../java/TestCases/phase1/CoreLangTest.java | 25 +++++++++++++++++++ .../ortus/boxlang/compiler/QoQParseTest.java | 2 +- .../global/system/RunThreadInContextTest.java | 19 ++++++++------ .../runtime/context/CatchBoxContextTest.java | 2 +- .../context/ClosureBoxContextTest.java | 12 ++++----- .../context/FunctionBoxContextTest.java | 8 +++--- .../runtime/context/LambdaBoxContextTest.java | 6 ++--- .../ScriptingRequestBoxContextTest.java | 6 ++--- 38 files changed, 188 insertions(+), 142 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAssignmentTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAssignmentTransformer.java index 98fe01117..3447266e1 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAssignmentTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxAssignmentTransformer.java @@ -26,6 +26,7 @@ import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import ortus.boxlang.compiler.asmboxpiler.AsmHelper; @@ -258,10 +259,12 @@ public List transformEquals( BoxExpression left, List transformCompoundEquals( BoxAssignment assigment "getDefaultAssignmentScope", Type.getMethodDescriptor( Type.getType( IScope.class ) ), true ) ); + nodes.add( new LdcInsnNode( true ) ); nodes.add( new MethodInsnNode( Opcodes.INVOKEINTERFACE, Type.getInternalName( IBoxContext.class ), "scopeFindNearby", Type.getMethodDescriptor( Type.getType( IBoxContext.ScopeSearchResult.class ), Type.getType( Key.class ), - Type.getType( IScope.class ) ), + Type.getType( IScope.class ), Type.BOOLEAN_TYPE ), true ) ); nodes.add( new MethodInsnNode( Opcodes.INVOKEVIRTUAL, Type.getInternalName( IBoxContext.ScopeSearchResult.class ), @@ -452,13 +456,15 @@ private List assignNullValue( BoxIdentifier name ) { "name", Type.getDescriptor( Key.class ) ) ); nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); + nodes.add( new LdcInsnNode( true ) ); nodes.add( new MethodInsnNode( Opcodes.INVOKEINTERFACE, Type.getInternalName( IBoxContext.class ), "scopeFindNearby", Type.getMethodDescriptor( Type.getType( IBoxContext.ScopeSearchResult.class ), Type.getType( Key.class ), - Type.getType( IScope.class ) ), + Type.getType( IScope.class ), + Type.BOOLEAN_TYPE ), true ) ); nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxIdentifierTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxIdentifierTransformer.java index ee6b56ba6..3c377af99 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxIdentifierTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxIdentifierTransformer.java @@ -21,6 +21,7 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import ortus.boxlang.compiler.asmboxpiler.AsmHelper; @@ -62,10 +63,12 @@ public List transform( BoxNode node, TransformerContext contex } else { nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); } + nodes.add( new LdcInsnNode( true ) ); nodes.add( new MethodInsnNode( Opcodes.INVOKEINTERFACE, Type.getInternalName( IBoxContext.class ), "scopeFindNearby", - Type.getMethodDescriptor( Type.getType( IBoxContext.ScopeSearchResult.class ), Type.getType( Key.class ), Type.getType( IScope.class ) ), + Type.getMethodDescriptor( Type.getType( IBoxContext.ScopeSearchResult.class ), Type.getType( Key.class ), Type.getType( IScope.class ), + Type.BOOLEAN_TYPE ), true ) ); nodes.add( new MethodInsnNode( Opcodes.INVOKEVIRTUAL, Type.getInternalName( IBoxContext.ScopeSearchResult.class ), diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxUnaryOperationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxUnaryOperationTransformer.java index 0424d2103..6894926b2 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxUnaryOperationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxUnaryOperationTransformer.java @@ -21,6 +21,7 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.LdcInsnNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.VarInsnNode; @@ -83,10 +84,12 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new VarInsnNode( Opcodes.ALOAD, 1 ) ); nodes.addAll( transpiler.createKey( id.getName() ) ); nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); + nodes.add( new LdcInsnNode( true ) ); nodes.add( new MethodInsnNode( Opcodes.INVOKEINTERFACE, Type.getInternalName( IBoxContext.class ), "scopeFindNearby", - Type.getMethodDescriptor( Type.getType( IBoxContext.ScopeSearchResult.class ), Type.getType( Key.class ), Type.getType( IScope.class ) ), + Type.getMethodDescriptor( Type.getType( IBoxContext.ScopeSearchResult.class ), Type.getType( Key.class ), Type.getType( IScope.class ), + Type.BOOLEAN_TYPE ), true ) ); nodes.add( new MethodInsnNode( Opcodes.INVOKEVIRTUAL, Type.getInternalName( IBoxContext.ScopeSearchResult.class ), diff --git a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxAssignmentTransformer.java b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxAssignmentTransformer.java index 8b9c3662d..46612360b 100644 --- a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxAssignmentTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxAssignmentTransformer.java @@ -73,7 +73,7 @@ public Node transform( BoxNode node, TransformerContext context ) throws Illegal String template = """ Referencer.setDeep( ${contextName}, - ${contextName}.scopeFindNearby( LocalScope.name, null ), + ${contextName}.scopeFindNearby( LocalScope.name, null, true ), null, ${accessKey} ) @@ -213,7 +213,7 @@ public Node transformEquals( BoxExpression left, Expression jRight, BoxAssignmen idl.getPosition(), idl.getSourceText() ); } - String baseObjTemplate = "${contextName}.scopeFindNearby( ${accessKey}, ${contextName}.getDefaultAssignmentScope() ),"; + String baseObjTemplate = "${contextName}.scopeFindNearby( ${accessKey}, ${contextName}.getDefaultAssignmentScope(), true ),"; // imported.foo needs to swap out the furthest left object if ( transpiler.matchesImport( id.getName() ) && isBoxSyntax ) { baseObjTemplate = "classLocator.load( ${contextName}, \"${accessName}\", imports ),"; @@ -297,7 +297,7 @@ private Node transformCompoundEquals( BoxAssignment assignment, TransformerConte accessKey = createKey( id.getName() ); values.put( "accessKey", accessKey.toString() ); String obj = PlaceholderHelper.resolve( - "${contextName}.scopeFindNearby( ${accessKey}, ${contextName}.getDefaultAssignmentScope() ).scope()", + "${contextName}.scopeFindNearby( ${accessKey}, ${contextName}.getDefaultAssignmentScope(), true ).scope()", values ); values.put( "obj", obj ); diff --git a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxIdentifierTransformer.java b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxIdentifierTransformer.java index e6b3ddd67..541f64b7b 100644 --- a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxIdentifierTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxIdentifierTransformer.java @@ -52,8 +52,8 @@ public Node transform( BoxNode node, TransformerContext context ) throws Illegal template = "classLocator.load( ${contextName}, \"${id}\", imports )"; } else { template = switch ( context ) { - case SAFE -> "${contextName}.scopeFindNearby( ${accessKey}, ${contextName}.getDefaultAssignmentScope()).value()"; - default -> "${contextName}.scopeFindNearby( ${accessKey}, null).value()"; + case SAFE -> "${contextName}.scopeFindNearby( ${accessKey}, ${contextName}.getDefaultAssignmentScope(), false ).value()"; + default -> "${contextName}.scopeFindNearby( ${accessKey}, null, false ).value()"; }; } diff --git a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxUnaryOperationTransformer.java b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxUnaryOperationTransformer.java index 3233ad441..43ea32c6a 100644 --- a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxUnaryOperationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/expression/BoxUnaryOperationTransformer.java @@ -79,7 +79,7 @@ public Node transform( BoxNode node, TransformerContext context ) throws Illegal accessKey = createKey( id.getName() ); values.put( "accessKey", accessKey.toString() ); String obj = PlaceholderHelper.resolve( - "${contextName}.scopeFindNearby( ${accessKey}, null ).scope()", + "${contextName}.scopeFindNearby( ${accessKey}, null, true ).scope()", values ); values.put( "obj", obj ); } else if ( expr instanceof BoxAccess objectAccess && operator != BoxUnaryOperator.Not && operator != BoxUnaryOperator.Minus diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/struct/StructKeyExists.java b/src/main/java/ortus/boxlang/runtime/bifs/global/struct/StructKeyExists.java index bfc896e65..a48456fff 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/struct/StructKeyExists.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/struct/StructKeyExists.java @@ -65,7 +65,7 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { // If the key exists, then we need to check if the value is defined based on our current null settings. Object result = struct.getRaw( keyKey ); - return context.isDefined( result ); + return context.isDefined( result, false ); } } diff --git a/src/main/java/ortus/boxlang/runtime/context/ApplicationBoxContext.java b/src/main/java/ortus/boxlang/runtime/context/ApplicationBoxContext.java index bc91e7297..fcbceed32 100644 --- a/src/main/java/ortus/boxlang/runtime/context/ApplicationBoxContext.java +++ b/src/main/java/ortus/boxlang/runtime/context/ApplicationBoxContext.java @@ -141,7 +141,7 @@ public IStruct getVisibleScopes( IStruct scopes, boolean nearby, boolean shallow * @return The value of the key if found */ @Override - public ScopeSearchResult scopeFindNearby( Key key, IScope defaultScope, boolean shallow ) { + public ScopeSearchResult scopeFindNearby( Key key, IScope defaultScope, boolean shallow, boolean forAssign ) { // There are no near-by scopes in the application context. Everything is global here. @@ -149,7 +149,7 @@ public ScopeSearchResult scopeFindNearby( Key key, IScope defaultScope, boolean return null; } - return scopeFind( key, defaultScope ); + return scopeFind( key, defaultScope, forAssign ); } /** @@ -161,11 +161,11 @@ public ScopeSearchResult scopeFindNearby( Key key, IScope defaultScope, boolean * @return The value of the key if found */ @Override - public ScopeSearchResult scopeFind( Key key, IScope defaultScope ) { + public ScopeSearchResult scopeFind( Key key, IScope defaultScope, boolean forAssign ) { if ( key.equals( applicationScope.getName() ) ) { return new ScopeSearchResult( applicationScope, applicationScope, key, true ); } - return parent.scopeFind( key, defaultScope ); + return parent.scopeFind( key, defaultScope, forAssign ); } /** diff --git a/src/main/java/ortus/boxlang/runtime/context/BaseBoxContext.java b/src/main/java/ortus/boxlang/runtime/context/BaseBoxContext.java index 40060f20b..e6a586f33 100644 --- a/src/main/java/ortus/boxlang/runtime/context/BaseBoxContext.java +++ b/src/main/java/ortus/boxlang/runtime/context/BaseBoxContext.java @@ -594,7 +594,7 @@ public Object invokeFunction( Function function, Key calledName, Map{ - println( "running in application #application.applicationname#") - }); - """, + application name="myApp"; + runThreadInContext( applicationName="myApp", callback=()=>{ + println( "running in application #application.applicationname#") + }); + """, context ); } @@ -75,10 +76,12 @@ public void testCanRunCodeInApplication() { public void testCanRunCodeInApplicationSession() { instance.executeSource( """ - runThreadInContext( applicationName="myApp", sessionID="12345", callback=()=>{ - println( "running in application/session #application.applicationname#/#session.sessionID#") - }); - """, + application name="myApp" sessionManagement=true; + sessionID = session.jsessionID; + runThreadInContext( applicationName="myApp", sessionID=sessionID, callback=()=>{ + println( "running in application/session #application.applicationname#/#session.sessionID#") + }); + """, context ); } } diff --git a/src/test/java/ortus/boxlang/runtime/context/CatchBoxContextTest.java b/src/test/java/ortus/boxlang/runtime/context/CatchBoxContextTest.java index 22419abfd..a16039475 100644 --- a/src/test/java/ortus/boxlang/runtime/context/CatchBoxContextTest.java +++ b/src/test/java/ortus/boxlang/runtime/context/CatchBoxContextTest.java @@ -33,7 +33,7 @@ public class CatchBoxContextTest { void testDefaultConstructor() { CatchBoxContext context = new CatchBoxContext( new ScriptingRequestBoxContext(), Key.of( "e" ), new Exception() ); assertThat( context.getParent() ).isNotNull(); - assertThat( context.scopeFindNearby( Key.of( "e" ), null ).value() instanceof Exception ).isTrue(); + assertThat( context.scopeFindNearby( Key.of( "e" ), null, false ).value() instanceof Exception ).isTrue(); } @Test diff --git a/src/test/java/ortus/boxlang/runtime/context/ClosureBoxContextTest.java b/src/test/java/ortus/boxlang/runtime/context/ClosureBoxContextTest.java index 44f093e2d..bf1d60a25 100644 --- a/src/test/java/ortus/boxlang/runtime/context/ClosureBoxContextTest.java +++ b/src/test/java/ortus/boxlang/runtime/context/ClosureBoxContextTest.java @@ -87,7 +87,7 @@ void testScopeLookup() { assertThat( variablesScope ).isEqualTo( declaringDeclaringContext.getScopeNearby( VariablesScope.name ) ); // ambiguous finds local scope - assertThat( context.scopeFindNearby( ambiguous, null ).value() ).isEqualTo( "local scope ambiguous" ); + assertThat( context.scopeFindNearby( ambiguous, null, false ).value() ).isEqualTo( "local scope ambiguous" ); // local.ambiguous works assertThat( context.getScopeNearby( LocalScope.name ).get( ambiguous ) ).isEqualTo( "local scope ambiguous" ); @@ -97,15 +97,15 @@ void testScopeLookup() { assertThat( context.getScopeNearby( ArgumentsScope.name ).get( ambiguous ) ).isEqualTo( "arguments scope ambiguous" ); // find var in local - assertThat( context.scopeFindNearby( localOnly, null ).value() ).isEqualTo( "local scope only" ); + assertThat( context.scopeFindNearby( localOnly, null, false ).value() ).isEqualTo( "local scope only" ); // find var in arguments - assertThat( context.scopeFindNearby( argsOnly, null ).value() ).isEqualTo( "arguments scope only" ); + assertThat( context.scopeFindNearby( argsOnly, null, false ).value() ).isEqualTo( "arguments scope only" ); // find var in variables - assertThat( context.scopeFindNearby( variablesOnly, null ).value() ).isEqualTo( "declaring declaring variables scope only" ); + assertThat( context.scopeFindNearby( variablesOnly, null, false ).value() ).isEqualTo( "declaring declaring variables scope only" ); // find var in declaring scope - assertThat( context.scopeFindNearby( declaringOnly, null ).value() ).isEqualTo( "declaring scope only" ); + assertThat( context.scopeFindNearby( declaringOnly, null, false ).value() ).isEqualTo( "declaring scope only" ); // find var in declaring closure's declaring scope - assertThat( context.scopeFindNearby( declaringDeclaringOnly, null ).value() ).isEqualTo( "declaring declaring scope only" ); + assertThat( context.scopeFindNearby( declaringDeclaringOnly, null, false ).value() ).isEqualTo( "declaring declaring scope only" ); } @Test diff --git a/src/test/java/ortus/boxlang/runtime/context/FunctionBoxContextTest.java b/src/test/java/ortus/boxlang/runtime/context/FunctionBoxContextTest.java index 1ccb575cb..180e53b49 100644 --- a/src/test/java/ortus/boxlang/runtime/context/FunctionBoxContextTest.java +++ b/src/test/java/ortus/boxlang/runtime/context/FunctionBoxContextTest.java @@ -77,7 +77,7 @@ void testScopeLookup() { variablesScope.put( variablesOnly, "variables scope only" ); // ambiguous finds local scope - assertThat( context.scopeFindNearby( ambiguous, null ).value() ).isEqualTo( "local scope ambiguous" ); + assertThat( context.scopeFindNearby( ambiguous, null, false ).value() ).isEqualTo( "local scope ambiguous" ); // local.ambiguous works assertThat( context.getScopeNearby( LocalScope.name ).get( ambiguous ) ).isEqualTo( "local scope ambiguous" ); @@ -87,11 +87,11 @@ void testScopeLookup() { assertThat( context.getScopeNearby( ArgumentsScope.name ).get( ambiguous ) ).isEqualTo( "arguments scope ambiguous" ); // find var in local - assertThat( context.scopeFindNearby( localOnly, null ).value() ).isEqualTo( "local scope only" ); + assertThat( context.scopeFindNearby( localOnly, null, false ).value() ).isEqualTo( "local scope only" ); // find var in arguments - assertThat( context.scopeFindNearby( argsOnly, null ).value() ).isEqualTo( "arguments scope only" ); + assertThat( context.scopeFindNearby( argsOnly, null, false ).value() ).isEqualTo( "arguments scope only" ); // find var in variables - assertThat( context.scopeFindNearby( variablesOnly, null ).value() ).isEqualTo( "variables scope only" ); + assertThat( context.scopeFindNearby( variablesOnly, null, false ).value() ).isEqualTo( "variables scope only" ); } @Test diff --git a/src/test/java/ortus/boxlang/runtime/context/LambdaBoxContextTest.java b/src/test/java/ortus/boxlang/runtime/context/LambdaBoxContextTest.java index d092b7d8f..441bb8982 100644 --- a/src/test/java/ortus/boxlang/runtime/context/LambdaBoxContextTest.java +++ b/src/test/java/ortus/boxlang/runtime/context/LambdaBoxContextTest.java @@ -79,7 +79,7 @@ void testScopeLookup() { variablesScope.put( variablesOnly, "variables scope only" ); // ambiguous finds local scope - assertThat( context.scopeFindNearby( ambiguous, null ).value() ).isEqualTo( "local scope ambiguous" ); + assertThat( context.scopeFindNearby( ambiguous, null, false ).value() ).isEqualTo( "local scope ambiguous" ); // local.ambiguous works assertThat( context.getScopeNearby( LocalScope.name ).get( ambiguous ) ).isEqualTo( "local scope ambiguous" ); @@ -89,9 +89,9 @@ void testScopeLookup() { assertThat( context.getScopeNearby( ArgumentsScope.name ).get( ambiguous ) ).isEqualTo( "arguments scope ambiguous" ); // find var in local - assertThat( context.scopeFindNearby( localOnly, null ).value() ).isEqualTo( "local scope only" ); + assertThat( context.scopeFindNearby( localOnly, null, false ).value() ).isEqualTo( "local scope only" ); // find var in arguments - assertThat( context.scopeFindNearby( argsOnly, null ).value() ).isEqualTo( "arguments scope only" ); + assertThat( context.scopeFindNearby( argsOnly, null, false ).value() ).isEqualTo( "arguments scope only" ); // Lambda has no visiblity to variables scope assertThrows( Throwable.class, () -> context.getScopeNearby( VariablesScope.name ).get( ambiguous ) ); diff --git a/src/test/java/ortus/boxlang/runtime/context/ScriptingRequestBoxContextTest.java b/src/test/java/ortus/boxlang/runtime/context/ScriptingRequestBoxContextTest.java index 4f9f5ff7b..a0c71b320 100644 --- a/src/test/java/ortus/boxlang/runtime/context/ScriptingRequestBoxContextTest.java +++ b/src/test/java/ortus/boxlang/runtime/context/ScriptingRequestBoxContextTest.java @@ -100,7 +100,7 @@ void testScopeFindExistingKey() { Key key = Key.of( "testIt" ); IScope variablesScope = context.getScopeNearby( Key.of( "variables" ) ); variablesScope.put( key, "value" ); - ScopeSearchResult result = context.scopeFindNearby( key, null ); + ScopeSearchResult result = context.scopeFindNearby( key, null, false ); assertThat( result.value() ).isEqualTo( "value" ); assertThat( result.scope() ).isEqualTo( variablesScope ); } @@ -111,7 +111,7 @@ void testScopeFindDefaultScope() { ScriptingRequestBoxContext context = new ScriptingRequestBoxContext(); Key key = Key.of( "testIt" ); IScope variablesScope = context.getScopeNearby( Key.of( "variables" ) ); - ScopeSearchResult result = context.scopeFindNearby( key, variablesScope ); + ScopeSearchResult result = context.scopeFindNearby( key, variablesScope, false ); assertThat( result.value() ).isEqualTo( null ); assertThat( result.scope() ).isEqualTo( variablesScope ); } @@ -120,7 +120,7 @@ void testScopeFindDefaultScope() { @DisplayName( "Test scopeFind with missing key" ) void testScopeFindMissingKey() { ScriptingRequestBoxContext context = new ScriptingRequestBoxContext(); - assertThrows( KeyNotFoundException.class, () -> context.scopeFindNearby( new Key( "nonExistentKey" ), null ) ); + assertThrows( KeyNotFoundException.class, () -> context.scopeFindNearby( new Key( "nonExistentKey" ), null, false ) ); } @Test From e99ec4d59e462f3b240f50afb8bb84ade24295b0 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 6 Dec 2024 17:42:51 -0600 Subject: [PATCH 141/193] Disable --- src/test/java/ortus/boxlang/compiler/QoQParseTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/test/java/ortus/boxlang/compiler/QoQParseTest.java b/src/test/java/ortus/boxlang/compiler/QoQParseTest.java index 80d45fb65..579fc76f8 100644 --- a/src/test/java/ortus/boxlang/compiler/QoQParseTest.java +++ b/src/test/java/ortus/boxlang/compiler/QoQParseTest.java @@ -19,6 +19,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import ortus.boxlang.compiler.parser.ParsingResult; @@ -79,6 +80,7 @@ public void testMetadataVisitor() { } @Test + @Disabled public void testRunQoQ() { instance.executeSource( """ @@ -86,7 +88,7 @@ public void testRunQoQ() { q = queryExecute( " select col, 5 as brad, col2 luis from myQry - where (col = 'foo') and col2 IS 42 + where (col = 'foo') or col2 IS 9001 ", [], { dbType : "query", maxRows = 1 } From e8074be3911d4427d1eabbefb96acf48764628e5 Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Fri, 6 Dec 2024 18:14:23 -0600 Subject: [PATCH 142/193] Enable running cli scripts again --- src/main/java/ortus/boxlang/runtime/BoxRunner.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/BoxRunner.java b/src/main/java/ortus/boxlang/runtime/BoxRunner.java index 10aa7dcf2..529a2a3a4 100644 --- a/src/main/java/ortus/boxlang/runtime/BoxRunner.java +++ b/src/main/java/ortus/boxlang/runtime/BoxRunner.java @@ -317,14 +317,21 @@ private static CLIOptions parseCommandLineOptions( String[] args ) { break; } + String[] currentParts = current.split( "\\." ); + String currentExt = ""; + + if ( currentParts.length > 0 ) { + currentExt = "." + currentParts[ currentParts.length - 1 ]; + } + // Template to execute? - if ( actionCommand == null && ALLOWED_TEMPLATE_EXECUTIONS.contains( current ) ) { + if ( actionCommand == null && ALLOWED_TEMPLATE_EXECUTIONS.contains( currentExt ) ) { file = templateToAbsolute( current ); continue; } // Is it a shebang script to execute - if ( actionCommand == null && isShebangScript( current ) ) { + if ( actionCommand == null && isShebangScript( currentExt ) ) { file = getSheBangScript( current ); continue; } From 9a39a9ce3152c49aa55db576e166e88d4553f4ad Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 6 Dec 2024 18:35:48 -0600 Subject: [PATCH 143/193] Pass along the contextg --- .../boxlang/compiler/ast/visitor/ClassMetadataVisitor.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/compiler/ast/visitor/ClassMetadataVisitor.java b/src/main/java/ortus/boxlang/compiler/ast/visitor/ClassMetadataVisitor.java index e1a359ffa..bce33c655 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/visitor/ClassMetadataVisitor.java +++ b/src/main/java/ortus/boxlang/compiler/ast/visitor/ClassMetadataVisitor.java @@ -284,7 +284,7 @@ private IStruct processSuper( String superName ) { if ( !result.isCorrect() ) { throw new ParseException( result.getIssues(), "" ); } - ClassMetadataVisitor visitor = new ClassMetadataVisitor(); + ClassMetadataVisitor visitor = new ClassMetadataVisitor( context ); result.getRoot().accept( visitor ); return visitor.getMetadata(); From 8489c6e1a28eefec6a0b55bf94d37fff192f4708 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 6 Dec 2024 18:37:00 -0600 Subject: [PATCH 144/193] BL-833 --- .../transformer/expression/BoxIdentifierTransformer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxIdentifierTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxIdentifierTransformer.java index 3c377af99..ec7401af8 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxIdentifierTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxIdentifierTransformer.java @@ -63,7 +63,7 @@ public List transform( BoxNode node, TransformerContext contex } else { nodes.add( new InsnNode( Opcodes.ACONST_NULL ) ); } - nodes.add( new LdcInsnNode( true ) ); + nodes.add( new LdcInsnNode( false ) ); nodes.add( new MethodInsnNode( Opcodes.INVOKEINTERFACE, Type.getInternalName( IBoxContext.class ), "scopeFindNearby", From 9e28239d9a213cba4790ec1f1dd1b7148bc71ef2 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 6 Dec 2024 18:43:49 -0600 Subject: [PATCH 145/193] new boxlang logger for avoiding class loading hell --- .../runtime/context/IJDBCCapableContext.java | 2 +- .../runtime/logging/BoxLangLogger.java | 391 ++++++++++++++++++ .../runtime/logging/LoggingService.java | 11 +- 3 files changed, 398 insertions(+), 6 deletions(-) create mode 100644 src/main/java/ortus/boxlang/runtime/logging/BoxLangLogger.java diff --git a/src/main/java/ortus/boxlang/runtime/context/IJDBCCapableContext.java b/src/main/java/ortus/boxlang/runtime/context/IJDBCCapableContext.java index 86c5b2338..f4713f4c0 100644 --- a/src/main/java/ortus/boxlang/runtime/context/IJDBCCapableContext.java +++ b/src/main/java/ortus/boxlang/runtime/context/IJDBCCapableContext.java @@ -23,7 +23,7 @@ * This interface is used mostly on the RequestBoxContext and ThreadBoxContext classes to provide access to the ConnectionManager * and other JDBC-related functionality. */ -public interface IJDBCCapableContext { +public interface IJDBCCapableContext extends IBoxContext { /** * Get the ConnectionManager (connection and transaction tracker) for this context. diff --git a/src/main/java/ortus/boxlang/runtime/logging/BoxLangLogger.java b/src/main/java/ortus/boxlang/runtime/logging/BoxLangLogger.java new file mode 100644 index 000000000..bae809b22 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/logging/BoxLangLogger.java @@ -0,0 +1,391 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.logging; + +import java.io.Serializable; +import java.util.Iterator; + +import org.slf4j.Marker; +import org.slf4j.event.LoggingEvent; +import org.slf4j.spi.LocationAwareLogger; +import org.slf4j.spi.LoggingEventAware; + +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.Appender; +import ch.qos.logback.core.spi.AppenderAttachable; + +public class BoxLangLogger implements LocationAwareLogger, LoggingEventAware, AppenderAttachable, Serializable { + + private Logger logger; + + public BoxLangLogger( Logger logger ) { + this.logger = logger; + } + + @Override + public void addAppender( Appender newAppender ) { + this.logger.addAppender( newAppender ); + } + + @Override + public Iterator> iteratorForAppenders() { + return this.logger.iteratorForAppenders(); + } + + @Override + public Appender getAppender( String name ) { + return this.logger.getAppender( name ); + } + + @Override + public boolean isAttached( Appender appender ) { + return this.logger.isAttached( appender ); + } + + @Override + public void detachAndStopAllAppenders() { + this.logger.detachAndStopAllAppenders(); + } + + @Override + public boolean detachAppender( Appender appender ) { + return this.logger.detachAppender( appender ); + } + + @Override + public boolean detachAppender( String name ) { + return this.logger.detachAppender( name ); + } + + @Override + public void log( LoggingEvent event ) { + this.logger.log( event ); + } + + @Override + public void log( Marker marker, String fqcn, int level, String message, Object[] argArray, Throwable t ) { + this.logger.log( marker, fqcn, level, message, argArray, t ); + } + + @Override + public String getName() { + return this.logger.getName(); + } + + @Override + public boolean isTraceEnabled() { + return this.logger.isTraceEnabled(); + } + + @Override + public void trace( String msg ) { + this.logger.trace( msg ); + } + + @Override + public void trace( String format, Object arg ) { + this.logger.trace( format, arg ); + } + + @Override + public void trace( String format, Object arg1, Object arg2 ) { + this.logger.trace( format, arg1, arg2 ); + } + + @Override + public void trace( String format, Object... arguments ) { + this.logger.trace( format, arguments ); + } + + @Override + public void trace( String msg, Throwable t ) { + this.logger.trace( msg, t ); + } + + @Override + public boolean isTraceEnabled( Marker marker ) { + return this.logger.isTraceEnabled( marker ); + } + + @Override + public void trace( Marker marker, String msg ) { + this.logger.trace( marker, msg ); + } + + @Override + public void trace( Marker marker, String format, Object arg ) { + this.logger.trace( marker, format, arg ); + } + + @Override + public void trace( Marker marker, String format, Object arg1, Object arg2 ) { + this.logger.trace( marker, format, arg1, arg2 ); + } + + @Override + public void trace( Marker marker, String format, Object... argArray ) { + this.logger.trace( marker, format, argArray ); + } + + @Override + public void trace( Marker marker, String msg, Throwable t ) { + this.logger.trace( marker, msg, t ); + } + + @Override + public boolean isDebugEnabled() { + return this.logger.isDebugEnabled(); + } + + @Override + public void debug( String msg ) { + this.logger.debug( msg ); + } + + @Override + public void debug( String format, Object arg ) { + this.logger.debug( format, arg ); + } + + @Override + public void debug( String format, Object arg1, Object arg2 ) { + this.logger.debug( format, arg1, arg2 ); + } + + @Override + public void debug( String format, Object... arguments ) { + this.logger.debug( format, arguments ); + } + + @Override + public void debug( String msg, Throwable t ) { + this.logger.debug( msg, t ); + } + + @Override + public boolean isDebugEnabled( Marker marker ) { + return this.logger.isDebugEnabled( marker ); + } + + @Override + public void debug( Marker marker, String msg ) { + this.logger.debug( marker, msg ); + } + + @Override + public void debug( Marker marker, String format, Object arg ) { + this.logger.debug( marker, format, arg ); + } + + @Override + public void debug( Marker marker, String format, Object arg1, Object arg2 ) { + this.logger.debug( marker, format, arg1, arg2 ); + } + + @Override + public void debug( Marker marker, String format, Object... arguments ) { + this.logger.debug( marker, format, arguments ); + } + + @Override + public void debug( Marker marker, String msg, Throwable t ) { + this.logger.debug( marker, msg, t ); + } + + @Override + public boolean isInfoEnabled() { + return this.logger.isInfoEnabled(); + } + + @Override + public void info( String msg ) { + this.logger.info( msg ); + } + + @Override + public void info( String format, Object arg ) { + this.logger.info( format, arg ); + } + + @Override + public void info( String format, Object arg1, Object arg2 ) { + this.logger.info( format, arg1, arg2 ); + } + + @Override + public void info( String format, Object... arguments ) { + this.logger.info( format, arguments ); + } + + @Override + public void info( String msg, Throwable t ) { + this.logger.info( msg, t ); + } + + @Override + public boolean isInfoEnabled( Marker marker ) { + return this.logger.isInfoEnabled( marker ); + } + + @Override + public void info( Marker marker, String msg ) { + this.logger.info( marker, msg ); + } + + @Override + public void info( Marker marker, String format, Object arg ) { + this.logger.info( marker, format, arg ); + } + + @Override + public void info( Marker marker, String format, Object arg1, Object arg2 ) { + this.logger.info( marker, format, arg1, arg2 ); + } + + @Override + public void info( Marker marker, String format, Object... arguments ) { + this.logger.info( marker, format, arguments ); + } + + @Override + public void info( Marker marker, String msg, Throwable t ) { + this.logger.info( marker, msg, t ); + } + + @Override + public boolean isWarnEnabled() { + return this.logger.isWarnEnabled(); + } + + @Override + public void warn( String msg ) { + this.logger.warn( msg ); + } + + @Override + public void warn( String format, Object arg ) { + this.logger.warn( format, arg ); + } + + @Override + public void warn( String format, Object... arguments ) { + this.logger.warn( format, arguments ); + } + + @Override + public void warn( String format, Object arg1, Object arg2 ) { + this.logger.warn( format, arg1, arg2 ); + } + + @Override + public void warn( String msg, Throwable t ) { + this.logger.warn( msg, t ); + } + + @Override + public boolean isWarnEnabled( Marker marker ) { + return this.logger.isWarnEnabled( marker ); + } + + @Override + public void warn( Marker marker, String msg ) { + this.logger.warn( marker, msg ); + } + + @Override + public void warn( Marker marker, String format, Object arg ) { + this.logger.warn( marker, format, arg ); + } + + @Override + public void warn( Marker marker, String format, Object arg1, Object arg2 ) { + this.logger.warn( marker, format, arg1, arg2 ); + } + + @Override + public void warn( Marker marker, String format, Object... arguments ) { + this.logger.warn( marker, format, arguments ); + } + + @Override + public void warn( Marker marker, String msg, Throwable t ) { + this.logger.warn( marker, msg, t ); + } + + @Override + public boolean isErrorEnabled() { + return this.logger.isErrorEnabled(); + } + + @Override + public void error( String msg ) { + this.logger.error( msg ); + } + + @Override + public void error( String format, Object arg ) { + this.logger.error( format, arg ); + } + + @Override + public void error( String format, Object arg1, Object arg2 ) { + this.logger.error( format, arg1, arg2 ); + } + + @Override + public void error( String format, Object... arguments ) { + this.logger.error( format, arguments ); + } + + @Override + public void error( String msg, Throwable t ) { + this.logger.error( msg, t ); + } + + @Override + public boolean isErrorEnabled( Marker marker ) { + return this.logger.isErrorEnabled( marker ); + } + + @Override + public void error( Marker marker, String msg ) { + this.logger.error( marker, msg ); + } + + @Override + public void error( Marker marker, String format, Object arg ) { + this.logger.error( marker, format, arg ); + } + + @Override + public void error( Marker marker, String format, Object arg1, Object arg2 ) { + this.logger.error( marker, format, arg1, arg2 ); + } + + @Override + public void error( Marker marker, String format, Object... arguments ) { + this.logger.error( marker, format, arguments ); + } + + @Override + public void error( Marker marker, String msg, Throwable t ) { + this.logger.error( marker, msg, t ); + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index 026e17f74..50caa23e0 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -412,7 +412,7 @@ public LoggingService logMessage( } // Compute and get the logger - Logger oLogger = ( Logger ) getLogger( logger ); + BoxLangLogger oLogger = getLogger( logger ); // Log according to the level switch ( targetLogLevel.getNameNoCase() ) { @@ -438,7 +438,7 @@ public LoggingService logMessage( * * @return The logger requested */ - public org.slf4j.Logger getLogger( String logger ) { + public BoxLangLogger getLogger( String logger ) { // The incoming logger can be: // 1. A named logger: "scheduler", "application", "orm", etc // 2. A relative path: "scheduler.log", "application.log", "orm.log" @@ -456,7 +456,7 @@ public org.slf4j.Logger getLogger( String logger ) { Key loggerKey = Key.of( FilenameUtils.getBaseName( loggerFilePath.toLowerCase() ) ); // Compute it or return it - return ( org.slf4j.Logger ) this.loggersMap.computeIfAbsent( Key.of( loggerFilePath ), key -> createLogger( loggerKey, loggerFilePath ) ); + return ( BoxLangLogger ) this.loggersMap.computeIfAbsent( Key.of( loggerFilePath ), key -> createLogger( loggerKey, loggerFilePath ) ); } /** @@ -646,7 +646,7 @@ private JsonEncoder buildJsonEncoder() { * * @return The logger requested */ - private Logger createLogger( Key loggerKey, String loggerFilePath ) { + private BoxLangLogger createLogger( Key loggerKey, String loggerFilePath ) { LoggerContext targetContext = getLoggerContext(); Logger oLogger = targetContext.getLogger( loggerKey.getNameNoCase() ); @@ -660,7 +660,8 @@ private Logger createLogger( Key loggerKey, String loggerFilePath ) { oLogger.setLevel( configLevel ); oLogger.setAdditive( loggerConfig.additive ); oLogger.addAppender( getOrBuildAppender( loggerFilePath, targetContext, loggerConfig ) ); - return oLogger; + + return new BoxLangLogger( oLogger ); } } From 513273ef1754b72490df61aa71a98dc16118a1cc Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 6 Dec 2024 21:48:04 -0600 Subject: [PATCH 146/193] Improved top support for dump --- .../boxlang/runtime/bifs/global/system/Dump.java | 6 ++++-- .../java/ortus/boxlang/runtime/util/DumpUtil.java | 14 +++++++++++--- src/main/resources/dump/html/BoxClass.bxm | 10 +++++----- src/main/resources/dump/html/Function.bxm | 8 ++++---- src/main/resources/dump/html/List.bxm | 4 ++-- src/main/resources/dump/html/Map.bxm | 8 ++++---- src/main/resources/dump/html/Query.bxm | 4 ++-- src/main/resources/dump/html/Struct.bxm | 14 +++++++------- src/main/resources/dump/html/XML.bxm | 2 +- 9 files changed, 40 insertions(+), 30 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/system/Dump.java b/src/main/java/ortus/boxlang/runtime/bifs/global/system/Dump.java index 62a235456..165b773e1 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/system/Dump.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/system/Dump.java @@ -79,7 +79,7 @@ public Dump() { // A custom label to display above the dump (Only in HTML output) new Argument( false, Argument.STRING, Key.label, "" ), // The number of levels to display when dumping collections. Great to avoid dumping the entire world! - new Argument( false, Argument.NUMERIC, Key.top, 0 ), + new Argument( false, Argument.NUMERIC, Key.top ), // Whether to expand the dump. By default, the dump is expanded on the first level only new Argument( false, Argument.BOOLEAN, Key.expand, true ), // Whether to do a hard abort the request after dumping @@ -130,12 +130,14 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { arguments.put( Key.abort, true ); } + Object top = arguments.get( Key.top ); + // Dump the object DumpUtil.dump( context, DynamicObject.unWrap( arguments.get( Key.var ) ), arguments.getAsString( Key.label ), - IntegerCaster.cast( arguments.get( Key.top ) ), + top == null ? null : IntegerCaster.cast( top ), arguments.getAsBoolean( Key.expand ), BooleanCaster.cast( arguments.get( Key.abort ) ), arguments.getAsString( Key.output ), diff --git a/src/main/java/ortus/boxlang/runtime/util/DumpUtil.java b/src/main/java/ortus/boxlang/runtime/util/DumpUtil.java index 0e286e136..aa15ca5b6 100644 --- a/src/main/java/ortus/boxlang/runtime/util/DumpUtil.java +++ b/src/main/java/ortus/boxlang/runtime/util/DumpUtil.java @@ -38,6 +38,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import javax.annotation.Nullable; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -114,7 +116,7 @@ public static void dump( IBoxContext context, Object target, String label, - Integer top, + @Nullable Integer top, Boolean expand, Boolean abort, String output, @@ -243,7 +245,7 @@ public static void dumpHTMLToConsole( IBoxContext context, Object target, String label, - Integer top, + @Nullable Integer top, Boolean expand, Boolean abort, String output, @@ -330,7 +332,7 @@ public static void dumpHTMLToBuffer( IBoxContext context, Object target, String label, - Integer top, + @Nullable Integer top, Boolean expand, Boolean abort, String output, @@ -347,6 +349,12 @@ public static void dumpHTMLToBuffer( return; } + // Reached the top limit, so return to prevent dumping the entire world + if ( top != null && top <= 0 ) { + context.writeToBuffer( "

    Top Limit reached (Skipping dump)
    ", true ); + return; + } + // Prep variables to use for dumping String posInCode = ""; String dumpTemplate = null; diff --git a/src/main/resources/dump/html/BoxClass.bxm b/src/main/resources/dump/html/BoxClass.bxm index 2db903476..b9d872671 100644 --- a/src/main/resources/dump/html/BoxClass.bxm +++ b/src/main/resources/dump/html/BoxClass.bxm @@ -172,7 +172,7 @@ #writeDump( var : variablesScope[ prop.name ] ?: null, - top : top, + top : top == null ? null : top - 1, expand : expand, showUDFs : showUDFs )# @@ -227,7 +227,7 @@ #writeDump( var : dataProperty ?: null, - top : top, + top : top == null ? null : top - 1, expand : expand, showUDFs : showUDFs )# @@ -277,7 +277,7 @@ #encodeForHTML( methodName )# - #writeDump( var : thisMethod ?: null, top : top, expand : expand ?: false )# + #writeDump( var : thisMethod ?: null, top : top == null ? null : top - 1, expand : expand ?: false )# @@ -327,7 +327,7 @@ #encodeForHTML( methodName )# - #writeDump( var : thisMethod ?: null, top : top, expand : expand )# + #writeDump( var : thisMethod ?: null, top : top == null ? null : top - 1, expand : expand )# @@ -378,7 +378,7 @@ #encodeForHTML( methodName )# - #writeDump( var : thisMethod ?: null, top : top, expand : expand )# + #writeDump( var : thisMethod ?: null, top : top == null ? null : top - 1, expand : expand )# diff --git a/src/main/resources/dump/html/Function.bxm b/src/main/resources/dump/html/Function.bxm index 9cc783308..13433cea2 100644 --- a/src/main/resources/dump/html/Function.bxm +++ b/src/main/resources/dump/html/Function.bxm @@ -25,7 +25,7 @@ > - - - #writedump( var: thisArg.defaultValue(), top: top, expand: expand )# + #writedump( var: thisArg.defaultValue(), top: top == null ? null : top - 1, expand: expand )# #encodeForHTML( argHint )# @@ -103,7 +103,7 @@ - for ( i = 0; i < var.size(); i++ ) { // Top limit only if > 0 - if( top > 0 && i > top ) { + if( top != null && i > top ) { break; } ``` @@ -46,7 +46,7 @@
    - +
    diff --git a/src/main/resources/dump/html/Map.bxm b/src/main/resources/dump/html/Map.bxm index 74d7a5a5f..14e830a35 100644 --- a/src/main/resources/dump/html/Map.bxm +++ b/src/main/resources/dump/html/Map.bxm @@ -14,7 +14,7 @@ #label# - #var.getClass().getSimpleName()#: - #top#/#var.size()# items + #top#/#var.size()# items @@ -22,7 +22,7 @@ #label# - #var.getClass().getSimpleName()#: - #top#/#var.size()# items + #top#/#var.size()# items
    @@ -33,7 +33,7 @@ index = 1; for ( key in var ) { // Top limit only if > 0 - if( top > 0 && index++ > top ) { + if( top != null && index++ > top ) { break; } ``` @@ -50,7 +50,7 @@
    - +
    diff --git a/src/main/resources/dump/html/Query.bxm b/src/main/resources/dump/html/Query.bxm index 09ea49c46..4fa3e2bf8 100644 --- a/src/main/resources/dump/html/Query.bxm +++ b/src/main/resources/dump/html/Query.bxm @@ -13,7 +13,7 @@ #label# - Query: - #top#/#var.recordcount# rows + #top#/#var.recordcount# rows class="d-none" > - + diff --git a/src/main/resources/dump/html/Struct.bxm b/src/main/resources/dump/html/Struct.bxm index aaf59d8f2..feb0412af 100644 --- a/src/main/resources/dump/html/Struct.bxm +++ b/src/main/resources/dump/html/Struct.bxm @@ -19,9 +19,9 @@ #label# - #encodeForHTML( var.getName().getName().toUpperCase() )# Scope: - #top#/#var.getDumpKeys().size()# items + #top#/#var.getDumpKeys().size()# items - #top#/#var.len()# items + #top#/#var.len()# items @@ -30,7 +30,7 @@ #label# - #encodeForHTML( var.getName().getName().toUpperCase() )# Scope: - #top#/#var.len()# items + #top#/#var.len()# items @@ -47,7 +47,7 @@ #label# - Struct: - #top#/#var.len()# items + #top#/#var.len()# items @@ -55,7 +55,7 @@ #label# - Struct: - #top#/#var.len()# items + #top#/#var.len()# items @@ -72,7 +72,7 @@ } index = 1; for ( key in theCollection ) { - if( top > 0 && index++ > top ) { + if( top != null && index++ > top ) { break; } ``` @@ -89,7 +89,7 @@
    - +
    diff --git a/src/main/resources/dump/html/XML.bxm b/src/main/resources/dump/html/XML.bxm index a6ccf4f93..01011b72f 100644 --- a/src/main/resources/dump/html/XML.bxm +++ b/src/main/resources/dump/html/XML.bxm @@ -38,7 +38,7 @@
    - +
    From 7ebecc06d440013210ab37d3d2717949f52e47b4 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 6 Dec 2024 21:55:36 -0600 Subject: [PATCH 147/193] Update dump component with new top behavior --- .../java/ortus/boxlang/runtime/components/system/Dump.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/components/system/Dump.java b/src/main/java/ortus/boxlang/runtime/components/system/Dump.java index a86f5d517..47bd9478d 100644 --- a/src/main/java/ortus/boxlang/runtime/components/system/Dump.java +++ b/src/main/java/ortus/boxlang/runtime/components/system/Dump.java @@ -45,7 +45,7 @@ public Dump() { declaredAttributes = new Attribute[] { new Attribute( Key.var, "any" ), new Attribute( Key.label, "string", "" ), - new Attribute( Key.top, "numeric", 0 ), + new Attribute( Key.top, "numeric" ), new Attribute( Key.expand, "boolean" ), new Attribute( Key.abort, "any", false ), new Attribute( Key.output, "string", Set.of( Validator.NON_EMPTY ) ), @@ -94,11 +94,13 @@ public BodyResult _invoke( IBoxContext context, IStruct attributes, ComponentBod attributes.put( Key.abort, true ); } + Object top = attributes.get( Key.top ); + DumpUtil.dump( context, DynamicObject.unWrap( attributes.get( Key.var ) ), attributes.getAsString( Key.label ), - IntegerCaster.cast( attributes.get( Key.top ) ), + top == null ? null : IntegerCaster.cast( top ), attributes.getAsBoolean( Key.expand ), BooleanCaster.cast( attributes.get( Key.abort ) ), attributes.getAsString( Key.output ), From 8dbf1223daf55cfa7acef28faaf30a2179e169e6 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Sat, 7 Dec 2024 10:20:44 -0600 Subject: [PATCH 148/193] added all missing logs to the config for easy user usage. Made the default logging file for writelog() and log() components to be the application logs. --- .../runtime/bifs/global/system/WriteLog.java | 2 +- .../runtime/components/system/Log.java | 2 +- .../boxlang/runtime/interceptors/Logging.java | 14 +++-- .../runtime/logging/LoggingService.java | 2 +- src/main/resources/config/boxlang.json | 61 +++++++++++++++++-- 5 files changed, 67 insertions(+), 14 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/system/WriteLog.java b/src/main/java/ortus/boxlang/runtime/bifs/global/system/WriteLog.java index 5b91bcb54..de85ff1be 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/system/WriteLog.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/system/WriteLog.java @@ -55,7 +55,7 @@ public WriteLog() { * * @argument.application If true, it logs the application name alongside the message. Default is true. * - * @argument.log The destination logger to use. If not passed, we use the default logger (runtime.log). + * @argument.log The destination logger to use. If not passed, we use the default logger (application.log). * If the logger is a file appender and it doesn't exist it will create it for you. * If the value is an absolue path, it will create a file appender for you at that location. * diff --git a/src/main/java/ortus/boxlang/runtime/components/system/Log.java b/src/main/java/ortus/boxlang/runtime/components/system/Log.java index 773948c5f..43fadbd83 100644 --- a/src/main/java/ortus/boxlang/runtime/components/system/Log.java +++ b/src/main/java/ortus/boxlang/runtime/components/system/Log.java @@ -61,7 +61,7 @@ public Log() { * * @attribute.type The log level of the entry. One of "Information", "Warning", "Error", "Debug", "Trace" * - * @attribute.log The destination logger to use. If not passed, we use the default logger (runtime.log). + * @attribute.log The destination logger to use. If not passed, we use the default logger (application.log). * If the logger is a file appender and it doesn't exist it will create it for you. * If the value is an absolue path, it will create a file appender for you at that location. * diff --git a/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java b/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java index 87f020927..b035397d5 100644 --- a/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java +++ b/src/main/java/ortus/boxlang/runtime/interceptors/Logging.java @@ -29,8 +29,10 @@ */ public class Logging extends BaseInterceptor { - private LoggingService loggingService; - private BoxRuntime runtime; + private static final String DEFAULT_LOGGER = "application"; + + private LoggingService loggingService; + private BoxRuntime runtime; /** * Constructor @@ -50,7 +52,7 @@ public Logging( BoxRuntime instance ) { *
  • applicationName: The name of the application requesting the log messasge. Can be empty
  • *
  • text: The text of the log message
  • *
  • type: The severity log level ( fatal, error, info, warn, debug, trace )
  • - *
  • file: The file to log to. If empty, the "log" key is used
  • + *
  • log: The logger to log to.
  • * * *

    @@ -79,8 +81,8 @@ public void logMessage( IStruct data ) { if ( file == null ) { file = ""; } - if ( logger == null ) { - logger = ""; + if ( logger == null || logger.isEmpty() ) { + logger = DEFAULT_LOGGER; } // COMPAT MODE: if you have a file, we transpile it to the logger @@ -91,7 +93,7 @@ public void logMessage( IStruct data ) { LoggingService.getInstance().logMessage( text, type, - ( applicationName instanceof Key ) ? ( ( Key ) applicationName ).getName() : "", + ( applicationName instanceof Key appNameKey ) ? ( appNameKey ).getName() : "", logger ); } diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index 50caa23e0..95a293621 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -653,7 +653,7 @@ private BoxLangLogger createLogger( Key loggerKey, String loggerFilePath ) { // Check if we have the logger configuration or else build a vanilla one LoggerConfig loggerConfig = ( LoggerConfig ) this.runtime .getConfiguration().logging.loggers - .computeIfAbsent( loggerKey, key -> new LoggerConfig( key.getNameNoCase(), this.runtime.getConfiguration().logging ) ); + .computeIfAbsent( loggerKey, key -> new LoggerConfig( key.getNameNoCase(), this.runtime.getConfiguration().logging ) ); Level configLevel = Level.toLevel( LogLevel.valueOf( loggerConfig.level.getName(), false ).getName() ); // Seed the properties diff --git a/src/main/resources/config/boxlang.json b/src/main/resources/config/boxlang.json index 4a7097aa4..ed02cd7ca 100644 --- a/src/main/resources/config/boxlang.json +++ b/src/main/resources/config/boxlang.json @@ -101,6 +101,23 @@ "loggers": { // The runtime main and default log "runtime": { + // Valid values are in order of severity: ERROR, WARN, INFO, DEBUG, TRACE, OFF + // Leave out if it should inherit from the root logger + "level": "INFO", + // Valid values are: "file", "console", + // Coming soon: "smtp", "socket", "db", "syslog" or "java class name" + // Please note that we only use Rolling File Appenders + "appender": "file", + // Use the defaults from the runtime + "appenderArguments": {}, + // The available options are "text" and "json" + "encoder": "text", + // Additive logging: true means that this logger will inherit the appenders from the root logger + // If false, it will only use the appenders defined in this logger + "additive": true + }, + // All async operations and facilities will log here. + "async": { // Valid values are in order of severity: ERROR, WARN, INFO, DEBUG, TRACE, OFF // Leave out if it should inherit from the root logger "level": "DEBUG", @@ -116,11 +133,45 @@ // If false, it will only use the appenders defined in this logger "additive": true }, - // The modules log + // All cache operations and facilities will log here. + "cache": { + // Valid values are in order of severity: ERROR, WARN, INFO, DEBUG, TRACE, OFF + // Leave out if it should inherit from the root logger + "level": "WARN", + // Valid values are: "file", "console", + // Coming soon: "smtp", "socket", "db", "syslog" or "java class name" + // Please note that we only use Rolling File Appenders + "appender": "file", + // Use the defaults from the runtime + "appenderArguments": {}, + // The available options are "text" and "json" + "encoder": "text", + // Additive logging: true means that this logger will inherit the appenders from the root logger + // If false, it will only use the appenders defined in this logger + "additive": true + }, + // The datasource log is used by the creation, debugging, and management of datasources + "datasource": { + // Valid values are in order of severity: ERROR, WARN, INFO, DEBUG, TRACE, OFF + // Leave out if it should inherit from the root logger + "level": "WARN", + // Valid values are: "file", "console", + // Coming soon: "smtp", "socket", "db", "syslog" or "java class name" + // Please note that we only use Rolling File Appenders + "appender": "file", + // Use the defaults from the runtime + "appenderArguments": {}, + // The available options are "text" and "json" + "encoder": "text", + // Additive logging: true means that this logger will inherit the appenders from the root logger + // If false, it will only use the appenders defined in this logger + "additive": true + }, + // The modules log is used by the module service and records all module activity "modules": { // Valid values are in order of severity: ERROR, WARN, INFO, DEBUG, TRACE, OFF // Leave out if it should inherit from the root logger - "level": "DEBUG", + "level": "INFO", // Valid values are: "file", "console", // Coming soon: "smtp", "socket", "db", "syslog" or "java class name" // Please note that we only use Rolling File Appenders @@ -133,7 +184,7 @@ // If false, it will only use the appenders defined in this logger "additive": true }, - // All applications will use this logger + // All applications will use this logger for any custom logging "application": { // Valid values are in order of severity: ERROR, WARN, INFO, DEBUG, TRACE, OFF // Leave out if it should inherit from the root logger @@ -150,11 +201,11 @@ // If false, it will only use the appenders defined in this logger "additive": true }, - // All scheduled tasks logging + // All scheduled tasks logging will go here "scheduler": { // Valid values are in order of severity: ERROR, WARN, INFO, DEBUG, TRACE, OFF // Leave out if it should inherit from the root logger - "level": "DEBUG", + "level": "INFO", // Valid values are: "file", "console", // Coming soon: "smtp", "socket", "db", "syslog" or "java class name" // Please note that we only use Rolling File Appenders From 4b86397050dd2a6e4f9bc89860d4097299482b81 Mon Sep 17 00:00:00 2001 From: lmajano Date: Sat, 7 Dec 2024 16:21:32 +0000 Subject: [PATCH 149/193] Apply cfformat changes --- src/main/java/ortus/boxlang/runtime/logging/LoggingService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index 95a293621..50caa23e0 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -653,7 +653,7 @@ private BoxLangLogger createLogger( Key loggerKey, String loggerFilePath ) { // Check if we have the logger configuration or else build a vanilla one LoggerConfig loggerConfig = ( LoggerConfig ) this.runtime .getConfiguration().logging.loggers - .computeIfAbsent( loggerKey, key -> new LoggerConfig( key.getNameNoCase(), this.runtime.getConfiguration().logging ) ); + .computeIfAbsent( loggerKey, key -> new LoggerConfig( key.getNameNoCase(), this.runtime.getConfiguration().logging ) ); Level configLevel = Level.toLevel( LogLevel.valueOf( loggerConfig.level.getName(), false ).getName() ); // Seed the properties From 206b3f79f4feabbf345042545ca4b44f314bbffd Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Sat, 7 Dec 2024 10:43:05 -0600 Subject: [PATCH 150/193] kickstart build --- .../java/ortus/boxlang/runtime/config/Configuration.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/ortus/boxlang/runtime/config/Configuration.java b/src/main/java/ortus/boxlang/runtime/config/Configuration.java index fa7a96edc..40265a572 100644 --- a/src/main/java/ortus/boxlang/runtime/config/Configuration.java +++ b/src/main/java/ortus/boxlang/runtime/config/Configuration.java @@ -265,6 +265,11 @@ public class Configuration implements IConfigSegment { */ public LoggingConfig logging = new LoggingConfig(); + /** + * Scheduled tasks configuration + */ + // public ScheduledTasksConfig scheduledTasks = new ScheduledTasksConfig(); + /** * -------------------------------------------------------------------------- * Private Properties From bca4eb404b21b0034481b22eff48e8f9693b50f1 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 9 Dec 2024 11:30:21 +0100 Subject: [PATCH 151/193] unecessary typing --- .../java/ortus/boxlang/runtime/context/RequestBoxContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/context/RequestBoxContext.java b/src/main/java/ortus/boxlang/runtime/context/RequestBoxContext.java index dde815e7d..1e7fd86ff 100644 --- a/src/main/java/ortus/boxlang/runtime/context/RequestBoxContext.java +++ b/src/main/java/ortus/boxlang/runtime/context/RequestBoxContext.java @@ -55,7 +55,7 @@ public abstract class RequestBoxContext extends BaseBoxContext implements IJDBCC /** * Track the current request box context for the thread. Allow more than one as a stack. */ - private static final ThreadLocal> current = new ThreadLocal>(); + private static final ThreadLocal> current = new ThreadLocal<>(); /** * The locale for this request From 1a2bc30b9836acab8fc10d597ebb6ddc07b242db Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 9 Dec 2024 11:32:24 +0100 Subject: [PATCH 152/193] docs --- .../runtime/context/RequestBoxContext.java | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/context/RequestBoxContext.java b/src/main/java/ortus/boxlang/runtime/context/RequestBoxContext.java index 1e7fd86ff..8b4e0f6c2 100644 --- a/src/main/java/ortus/boxlang/runtime/context/RequestBoxContext.java +++ b/src/main/java/ortus/boxlang/runtime/context/RequestBoxContext.java @@ -163,6 +163,9 @@ public PrintStream getOut() { return this.out; } + /** + * @InheritDoc + */ @Override public IStruct getVisibleScopes( IStruct scopes, boolean nearby, boolean shallow ) { if ( this.threadManager != null && this.threadManager.hasThreads() ) { @@ -175,6 +178,9 @@ public IStruct getVisibleScopes( IStruct scopes, boolean nearby, boolean shallow return super.getVisibleScopes( scopes, nearby, shallow ); } + /** + * @InheritDoc + */ @Override public ScopeSearchResult scopeFind( Key key, IScope defaultScope, boolean forAssign ) { @@ -527,6 +533,12 @@ public boolean isShowDebugOutput() { return this.showDebugOutput; } + /** + * Look at the current thread and see if it has a request context and return it + * Else return null + * + * @return The current request context or null + */ public static RequestBoxContext getCurrent() { ArrayDeque stack = current.get(); if ( stack == null || stack.isEmpty() ) { @@ -535,16 +547,25 @@ public static RequestBoxContext getCurrent() { return stack.peek(); } + /** + * Set the current request context for the thread + * + * @param context The request context + */ public static void setCurrent( RequestBoxContext context ) { ArrayDeque stack = current.get(); // No synchronization is needed here since only one thread can access a threadlocal var at a time. if ( stack == null ) { - stack = new ArrayDeque(); + stack = new ArrayDeque<>(); current.set( stack ); } stack.push( context ); } + /** + * Remove the current request context from the thread + * This cleanup is done by the runtime once a thread is done processing a request + */ public static void removeCurrent() { ArrayDeque stack = current.get(); if ( stack != null ) { From 91b585812ae637bd0cce3b7fe161b4dffb40ba12 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 9 Dec 2024 11:54:18 +0100 Subject: [PATCH 153/193] BL-835 #resolve Defensive coding when doing getConfig() overrides in request box context from Application settings --- .../runtime/context/RequestBoxContext.java | 70 +++++++++++-------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/context/RequestBoxContext.java b/src/main/java/ortus/boxlang/runtime/context/RequestBoxContext.java index 8b4e0f6c2..fe3cb2964 100644 --- a/src/main/java/ortus/boxlang/runtime/context/RequestBoxContext.java +++ b/src/main/java/ortus/boxlang/runtime/context/RequestBoxContext.java @@ -26,6 +26,8 @@ import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.application.ApplicationDefaultListener; import ortus.boxlang.runtime.application.BaseApplicationListener; +import ortus.boxlang.runtime.dynamic.casters.ArrayCaster; +import ortus.boxlang.runtime.dynamic.casters.StructCaster; import ortus.boxlang.runtime.events.BoxEvent; import ortus.boxlang.runtime.jdbc.ConnectionManager; import ortus.boxlang.runtime.loader.DynamicClassLoader; @@ -33,7 +35,6 @@ import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.scopes.ThreadScope; import ortus.boxlang.runtime.services.ApplicationService; -import ortus.boxlang.runtime.types.Array; import ortus.boxlang.runtime.types.DateTime; import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.Struct; @@ -323,14 +324,19 @@ public RequestBoxContext setTimezone( ZoneId timezone ) { * It depends on whether the context wants its changes to exist for the rest of the entire * request or only for code that executes in the current context and below. * + * IMPORTANT: This method could be run multiple times during a request. + * BE CONGNIIZANT OF PERFORMANCE. + * * @return A struct of configuration */ @Override public IStruct getConfig() { IStruct config = super.getConfig(); - // Apply request-specific overrides - // These can happen from BIF calls specifically + // Apply request-specific overrides, this happens after some BIF calls override the following in the context: + // - locale : setLocale() + // - timezone : setTimezone() + // - requestTimeout : setRequestTimeout() if ( this.locale != null ) { config.put( Key.locale, this.locale ); } @@ -342,12 +348,25 @@ public IStruct getConfig() { } config.put( Key.enforceExplicitOutput, this.enforceExplicitOutput ); + /** + * -------------------------------------------------------------------------- + * Get the Application.bx settings and apply them to the config struct as overrides + * -------------------------------------------------------------------------- + */ IStruct appSettings = getApplicationListener().getSettings(); // Make the request settings generically available in the config struct. // This doesn't mean we won't strategically place specific settings like mappings into specific parts // of the config struct, but this at least ensure everything is available for whomever wants to use it config.put( Key.applicationSettings, appSettings ); + /** + * -------------------------------------------------------------------------- + * Datasource Overrides + * -------------------------------------------------------------------------- + * - A string pointing to a datasource in the datasources struct + * - A struct defining the datasource inline + */ + // Default Datasource as string pointing to a datasource in the datasources struct // this.datasource = "coldbox" if ( appSettings.get( Key.datasource ) instanceof String castedDSN && castedDSN.length() > 0 ) { @@ -366,40 +385,31 @@ public IStruct getConfig() { } // Datasource overrides - IStruct datasources = appSettings.getAsStruct( Key.datasources ); - if ( !datasources.isEmpty() ) { - config.getAsStruct( Key.datasources ).putAll( datasources ); - } - - String timezone = appSettings.getAsString( Key.timezone ); - if ( timezone != null ) { - setTimezone( LocalizationUtil.parseZoneId( timezone ) ); + StructCaster.attempt( appSettings.get( Key.datasources ) ) + .ifPresent( datasources -> config.getAsStruct( Key.datasources ).putAll( datasources ) ); + // ---------------------------------------------------------------------------------- + + // Timezone override + String appTimezone = appSettings.getAsString( Key.timezone ); + if ( appTimezone != null && !appTimezone.isEmpty() ) { + setTimezone( LocalizationUtil.parseZoneId( appTimezone ) ); } // Mapping overrides - IStruct mappings = appSettings.getAsStruct( Key.mappings ); - if ( !mappings.isEmpty() ) { - config.getAsStruct( Key.mappings ).putAll( mappings ); - } + StructCaster.attempt( appSettings.get( Key.mappings ) ) + .ifPresent( mappings -> config.getAsStruct( Key.mappings ).putAll( mappings ) ); - // Add in custom tag paths. We calld them customTagsDirectory, but CF calls them customTagPaths. Maybe support both? - Array customTagPaths = appSettings.getAsArray( Key.customTagPaths ); - if ( !customTagPaths.isEmpty() ) { - config.getAsArray( Key.customTagsDirectory ).addAll( customTagPaths ); - } + // Add in custom tag paths. We called them customTagsDirectory, but CF calls them customTagPaths. Maybe support both? + ArrayCaster.attempt( appSettings.get( Key.customTagPaths ) ) + .ifPresent( customTagPaths -> config.getAsArray( Key.customTagsDirectory ).addAll( customTagPaths ) ); // Add in classPaths and componentPaths (for CF compat) to the classPaths array - Array classPaths = appSettings.getAsArray( Key.classPaths ); - if ( classPaths != null && !classPaths.isEmpty() ) { - // Expand each path in case it's relative - config.getAsArray( Key.classPaths ).addAll( classPaths ); - } + ArrayCaster.attempt( appSettings.get( Key.classPaths ) ) + .ifPresent( classPaths -> config.getAsArray( Key.classPaths ).addAll( classPaths ) ); + // TODO: move componentPaths logic to compat - Array componentPaths = appSettings.getAsArray( Key.componentPaths ); - if ( componentPaths != null && !componentPaths.isEmpty() ) { - // Expand each path in case it's relative - config.getAsArray( Key.classPaths ).addAll( componentPaths ); - } + ArrayCaster.attempt( appSettings.get( Key.componentPaths ) ) + .ifPresent( componentPaths -> config.getAsArray( Key.classPaths ).addAll( componentPaths ) ); // OTHER OVERRIDES go here From ae3680d3a93d1aaf8910d3fae904c98c512be039 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:26:01 +0000 Subject: [PATCH 154/193] Bump com.fasterxml.jackson.jr:jackson-jr-annotation-support Bumps [com.fasterxml.jackson.jr:jackson-jr-annotation-support](https://github.com/FasterXML/jackson-jr) from 2.18.1 to 2.18.2. - [Commits](https://github.com/FasterXML/jackson-jr/compare/jackson-jr-parent-2.18.1...jackson-jr-parent-2.18.2) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.jr:jackson-jr-annotation-support dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d7b019f6d..7cfe09336 100644 --- a/build.gradle +++ b/build.gradle @@ -124,7 +124,7 @@ dependencies { // https://mvnrepository.com/artifact/com.fasterxml.jackson.jr/jackson-jr-stree implementation 'com.fasterxml.jackson.jr:jackson-jr-stree:2.18.1' // https://mvnrepository.com/artifact/com.fasterxml.jackson.jr/jackson-jr-annotation-support - implementation 'com.fasterxml.jackson.jr:jackson-jr-annotation-support:2.18.1' + implementation 'com.fasterxml.jackson.jr:jackson-jr-annotation-support:2.18.2' // https://mvnrepository.com/artifact/org.slf4j/slf4j-api implementation 'org.slf4j:slf4j-api:2.0.16' // https://mvnrepository.com/artifact/ch.qos.logback/logback-classic From 2d55a7b31e893a9ed70b8fd38cb0f848123ea0f8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:26:06 +0000 Subject: [PATCH 155/193] Bump com.fasterxml.jackson.jr:jackson-jr-stree from 2.18.1 to 2.18.2 Bumps [com.fasterxml.jackson.jr:jackson-jr-stree](https://github.com/FasterXML/jackson-jr) from 2.18.1 to 2.18.2. - [Commits](https://github.com/FasterXML/jackson-jr/compare/jackson-jr-parent-2.18.1...jackson-jr-parent-2.18.2) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.jr:jackson-jr-stree dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d7b019f6d..1abdec7e6 100644 --- a/build.gradle +++ b/build.gradle @@ -122,7 +122,7 @@ dependencies { // https://mvnrepository.com/artifact/com.fasterxml.jackson.jr/jackson-jr-extension-javatime implementation 'com.fasterxml.jackson.jr:jackson-jr-extension-javatime:2.18.1' // https://mvnrepository.com/artifact/com.fasterxml.jackson.jr/jackson-jr-stree - implementation 'com.fasterxml.jackson.jr:jackson-jr-stree:2.18.1' + implementation 'com.fasterxml.jackson.jr:jackson-jr-stree:2.18.2' // https://mvnrepository.com/artifact/com.fasterxml.jackson.jr/jackson-jr-annotation-support implementation 'com.fasterxml.jackson.jr:jackson-jr-annotation-support:2.18.1' // https://mvnrepository.com/artifact/org.slf4j/slf4j-api From 5c45b16386df15f6b0ddfce2b528a15b3a81eb0d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:26:09 +0000 Subject: [PATCH 156/193] Bump com.fasterxml.jackson.jr:jackson-jr-extension-javatime Bumps [com.fasterxml.jackson.jr:jackson-jr-extension-javatime](https://github.com/FasterXML/jackson-jr) from 2.18.1 to 2.18.2. - [Commits](https://github.com/FasterXML/jackson-jr/compare/jackson-jr-parent-2.18.1...jackson-jr-parent-2.18.2) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.jr:jackson-jr-extension-javatime dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d7b019f6d..a2c33f8ef 100644 --- a/build.gradle +++ b/build.gradle @@ -120,7 +120,7 @@ dependencies { // https://mvnrepository.com/artifact/com.fasterxml.jackson.jr/jackson-jr-objects implementation 'com.fasterxml.jackson.jr:jackson-jr-objects:2.18.1' // https://mvnrepository.com/artifact/com.fasterxml.jackson.jr/jackson-jr-extension-javatime - implementation 'com.fasterxml.jackson.jr:jackson-jr-extension-javatime:2.18.1' + implementation 'com.fasterxml.jackson.jr:jackson-jr-extension-javatime:2.18.2' // https://mvnrepository.com/artifact/com.fasterxml.jackson.jr/jackson-jr-stree implementation 'com.fasterxml.jackson.jr:jackson-jr-stree:2.18.1' // https://mvnrepository.com/artifact/com.fasterxml.jackson.jr/jackson-jr-annotation-support From bcb4ad1cd9f1cec80413b3b9d24f2065e1fc0241 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:26:12 +0000 Subject: [PATCH 157/193] Bump org.wiremock:wiremock from 3.9.2 to 3.10.0 Bumps [org.wiremock:wiremock](https://github.com/wiremock/wiremock) from 3.9.2 to 3.10.0. - [Release notes](https://github.com/wiremock/wiremock/releases) - [Commits](https://github.com/wiremock/wiremock/compare/3.9.2...3.10.0) --- updated-dependencies: - dependency-name: org.wiremock:wiremock dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d7b019f6d..00a1ed28a 100644 --- a/build.gradle +++ b/build.gradle @@ -97,7 +97,7 @@ dependencies { testImplementation "com.google.truth:truth:1.+" testImplementation "commons-cli:commons-cli:1.9.0" // https://wiremock.org/ - testImplementation "org.wiremock:wiremock:3.9.2" + testImplementation "org.wiremock:wiremock:3.10.0" // https://mvnrepository.com/artifact/org.apache.derby/derby testImplementation 'org.apache.derby:derby:10.17.1.0' testImplementation 'io.undertow:undertow-core:2.3.18.Final' From a25126051dc823bcff136c547982016b7e47d469 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:26:14 +0000 Subject: [PATCH 158/193] Bump com.fasterxml.jackson.jr:jackson-jr-objects from 2.18.1 to 2.18.2 Bumps [com.fasterxml.jackson.jr:jackson-jr-objects](https://github.com/FasterXML/jackson-jr) from 2.18.1 to 2.18.2. - [Commits](https://github.com/FasterXML/jackson-jr/compare/jackson-jr-parent-2.18.1...jackson-jr-parent-2.18.2) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.jr:jackson-jr-objects dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d7b019f6d..e5cba19e0 100644 --- a/build.gradle +++ b/build.gradle @@ -118,7 +118,7 @@ dependencies { // https://mvnrepository.com/artifact/org.apache.commons/commons-cli implementation "commons-cli:commons-cli:1.9.0" // https://mvnrepository.com/artifact/com.fasterxml.jackson.jr/jackson-jr-objects - implementation 'com.fasterxml.jackson.jr:jackson-jr-objects:2.18.1' + implementation 'com.fasterxml.jackson.jr:jackson-jr-objects:2.18.2' // https://mvnrepository.com/artifact/com.fasterxml.jackson.jr/jackson-jr-extension-javatime implementation 'com.fasterxml.jackson.jr:jackson-jr-extension-javatime:2.18.1' // https://mvnrepository.com/artifact/com.fasterxml.jackson.jr/jackson-jr-stree From c358200506a03a1a88c44c21a9672d5e63f71842 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 9 Dec 2024 17:53:48 +0100 Subject: [PATCH 159/193] BL-826 Coldbox CacheBox Reaping Tasks fail with NullPointerException --- .../boxlang/runtime/application/Application.java | 8 +++++--- .../runtime/application/BaseApplicationListener.java | 6 ++++-- .../ortus/boxlang/runtime/application/Session.java | 12 ++++++++---- .../runtime/context/ApplicationBoxContext.java | 8 +++++++- 4 files changed, 24 insertions(+), 10 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/application/Application.java b/src/main/java/ortus/boxlang/runtime/application/Application.java index 0e6cf5c0a..18bc0e35f 100644 --- a/src/main/java/ortus/boxlang/runtime/application/Application.java +++ b/src/main/java/ortus/boxlang/runtime/application/Application.java @@ -345,13 +345,15 @@ private void startupSessionStorage( ApplicationBoxContext appContext ) { // Now store it this.sessionsCache = this.cacheService.getCache( sessionCacheName ); - // Register the session cleanup interceptor - this.sessionsCache.getInterceptorPool() + // Register the session cleanup interceptor for: BEFORE_CACHE_ELEMENT_REMOVED + this.sessionsCache + .getInterceptorPool() .register( data -> { ICacheProvider targetCache = ( ICacheProvider ) data.get( "cache" ); String key = ( String ) data.get( "key" ); - logger.debug( "Session cache interceptor [{}] cleared key [{}]", targetCache.getName(), key ); + if ( logger.isDebugEnabled() ) + logger.debug( "Session cache interceptor [{}] cleared key [{}]", targetCache.getName(), key ); targetCache .get( key ) diff --git a/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java b/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java index 3d69cddf1..8630a6a61 100644 --- a/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java +++ b/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java @@ -440,8 +440,10 @@ public void invalidateSession( Key newID ) { * @param newID The new session identifier */ public void initializeSession( Key newID ) { - ApplicationBoxContext appContext = this.context.getParentOfType( ApplicationBoxContext.class ); - Session targetSession = appContext.getApplication().getOrCreateSession( newID ); + Session targetSession = this.context + .getApplicationContext() + .getApplication() + .getOrCreateSession( newID ); this.context.removeParentContext( SessionBoxContext.class ); this.context.injectTopParentContext( new SessionBoxContext( targetSession ) ); targetSession.start( this.context ); diff --git a/src/main/java/ortus/boxlang/runtime/application/Session.java b/src/main/java/ortus/boxlang/runtime/application/Session.java index f8df9d984..d1ff3afec 100644 --- a/src/main/java/ortus/boxlang/runtime/application/Session.java +++ b/src/main/java/ortus/boxlang/runtime/application/Session.java @@ -22,7 +22,6 @@ import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.context.ApplicationBoxContext; import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.RequestBoxContext; import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; import ortus.boxlang.runtime.events.BoxEvent; import ortus.boxlang.runtime.scopes.Key; @@ -155,7 +154,7 @@ public Session start( IBoxContext context ) { try { // Announce it's start - BaseApplicationListener listener = context.getParentOfType( RequestBoxContext.class ).getApplicationListener(); + BaseApplicationListener listener = context.getRequestContext().getApplicationListener(); listener.onSessionStart( context, new Object[] { this.ID } ); } catch ( Exception e ) { // If startup errored, flag the session as not intialized. The next thread can try again. @@ -207,7 +206,7 @@ public String getCacheKey() { * @param listener The listener that is shutting down the session */ public void shutdown( BaseApplicationListener listener ) { - // Announce it's destruction + // Announce it's destruction to the runtime first BoxRuntime.getInstance() .getInterceptorService() .announce( BoxEvent.ON_SESSION_DESTROYED, Struct.of( @@ -225,7 +224,12 @@ public void shutdown( BaseApplicationListener listener ) { ), listener ), - new Object[] { sessionScope != null ? sessionScope : Struct.of(), listener.getApplication().getApplicationScope() } + new Object[] { + // If the session scope is null, just pass an empty struct + sessionScope != null ? sessionScope : Struct.of(), + // Pass the application scope + listener.getApplication().getApplicationScope() + } ); // Clear the session scope diff --git a/src/main/java/ortus/boxlang/runtime/context/ApplicationBoxContext.java b/src/main/java/ortus/boxlang/runtime/context/ApplicationBoxContext.java index fcbceed32..09ef1a070 100644 --- a/src/main/java/ortus/boxlang/runtime/context/ApplicationBoxContext.java +++ b/src/main/java/ortus/boxlang/runtime/context/ApplicationBoxContext.java @@ -103,7 +103,13 @@ public IScope getApplicationScope() { public void updateApplication( Application application ) { this.application = application; this.applicationScope = application.getApplicationScope(); - this.applicationScope.put( Key.applicationName, application.getName() ); + try { + this.applicationScope.put( Key.applicationName, application.getName() ); + } catch ( Throwable e ) { + System.err.println( "error application scope null, app name is: " + application.getName() ); + // This should never happen, adding debugging for now. + e.printStackTrace(); + } } /** From d6319469af7c5c091a8420661bfed048f7e253ad Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 9 Dec 2024 22:05:18 +0100 Subject: [PATCH 160/193] update to use the BoxLang Logger --- .../ortus/boxlang/runtime/events/BaseInterceptor.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/events/BaseInterceptor.java b/src/main/java/ortus/boxlang/runtime/events/BaseInterceptor.java index 108a0c2d2..7c42f6d54 100644 --- a/src/main/java/ortus/boxlang/runtime/events/BaseInterceptor.java +++ b/src/main/java/ortus/boxlang/runtime/events/BaseInterceptor.java @@ -17,10 +17,9 @@ */ package ortus.boxlang.runtime.events; -import org.slf4j.Logger; - import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.interop.DynamicObject; +import ortus.boxlang.runtime.logging.BoxLangLogger; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.Struct; @@ -33,12 +32,12 @@ public abstract class BaseInterceptor implements IInterceptor { /** * The properties to configure the interceptor with (if any) */ - protected IStruct properties = new Struct(); + protected IStruct properties = new Struct(); /** * The logger */ - protected Logger logger; + protected BoxLangLogger logger; /** * This method is called by the BoxLang runtime to configure the interceptor @@ -54,6 +53,7 @@ public void configure( IStruct properties ) { /** * This method is called by the BoxLang runtime to configure the interceptor */ + @Override public void configure() { configure( new Struct() ); } @@ -124,7 +124,7 @@ public BoxRuntime getRuntime() { /** * Get the logger */ - public Logger getLogger() { + public BoxLangLogger getLogger() { return this.logger; } From 6d22eb67a685ba5412c10feafa162085db388bc6 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 9 Dec 2024 22:45:59 +0100 Subject: [PATCH 161/193] adding helpers for logging --- .../boxlang/runtime/logging/BoxLangLogger.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/main/java/ortus/boxlang/runtime/logging/BoxLangLogger.java b/src/main/java/ortus/boxlang/runtime/logging/BoxLangLogger.java index bae809b22..1f5bf111f 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/BoxLangLogger.java +++ b/src/main/java/ortus/boxlang/runtime/logging/BoxLangLogger.java @@ -25,6 +25,7 @@ import org.slf4j.spi.LocationAwareLogger; import org.slf4j.spi.LoggingEventAware; +import ch.qos.logback.classic.Level; import ch.qos.logback.classic.Logger; import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.Appender; @@ -38,6 +39,19 @@ public BoxLangLogger( Logger logger ) { this.logger = logger; } + public void setLevel( int level ) { + Level newLevel = Level.toLevel( level ); + this.logger.setLevel( newLevel ); + } + + public void setLevel( ch.qos.logback.classic.Level level ) { + this.logger.setLevel( level ); + } + + public void setLevel( org.slf4j.event.Level level ) { + this.logger.setLevel( Level.toLevel( level.toInt() ) ); + } + @Override public void addAppender( Appender newAppender ) { this.logger.addAppender( newAppender ); From 8250d9425d1d1fb9457e9e7cfa42a36b6fbd84ab Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Mon, 9 Dec 2024 22:48:18 +0100 Subject: [PATCH 162/193] more helpers --- .../boxlang/runtime/logging/BoxLangLogger.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/main/java/ortus/boxlang/runtime/logging/BoxLangLogger.java b/src/main/java/ortus/boxlang/runtime/logging/BoxLangLogger.java index 1f5bf111f..8e3db50c5 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/BoxLangLogger.java +++ b/src/main/java/ortus/boxlang/runtime/logging/BoxLangLogger.java @@ -39,6 +39,12 @@ public BoxLangLogger( Logger logger ) { this.logger = logger; } + /** + * *************************************************************************** + * Custom Methods not in interfaces, but are of great help! + * *************************************************************************** + */ + public void setLevel( int level ) { Level newLevel = Level.toLevel( level ); this.logger.setLevel( newLevel ); @@ -52,6 +58,16 @@ public void setLevel( org.slf4j.event.Level level ) { this.logger.setLevel( Level.toLevel( level.toInt() ) ); } + public void setAdditive( boolean additive ) { + this.logger.setAdditive( additive ); + } + + /** + * *************************************************************************** + * END Custom Methods + * *************************************************************************** + */ + @Override public void addAppender( Appender newAppender ) { this.logger.addAppender( newAppender ); From 1e5281e00ddc343b0b9d50fe736307dedf979ce4 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Tue, 10 Dec 2024 00:12:42 +0100 Subject: [PATCH 163/193] BL-839 #RESOLVE Add a way for module class loaders to be able to delegate the location of certain parent classes to the parent ONLY, espeically for logging --- .../runtime/loader/DynamicClassLoader.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/ortus/boxlang/runtime/loader/DynamicClassLoader.java b/src/main/java/ortus/boxlang/runtime/loader/DynamicClassLoader.java index 2f11f3a7b..455618084 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/DynamicClassLoader.java +++ b/src/main/java/ortus/boxlang/runtime/loader/DynamicClassLoader.java @@ -79,6 +79,15 @@ public class DynamicClassLoader extends URLClassLoader { */ private static Logger logger = null; + /** + * Runtime special prefixes Set that MUST come from the parent class loader + * THIS IS SPECIAL CASE FOR LOGGING FRAMEWORKS WHERE THIRD PARTY JARS MAY BE LOADED AND DELEGATED TO THE PARENT + */ + private static final Set PARENT_CLASSES = Set.of( + "ch.qos.logback", + "org.slf4j" + ); + /** * Construct the class loader * @@ -186,6 +195,12 @@ public Class findClass( String className, Boolean safe ) throws ClassNotFound throw new ClassNotFoundException( String.format( "Class [%s] not found in class loader [%s]", className, this.nameAsKey.getName() ) ); } + // 2.5. Special case for Logback/SL4j so we are guaranteed to use the same interfaces as the BoxLang Runtime. + if ( this.parent != null && PARENT_CLASSES.stream().anyMatch( className::startsWith ) ) { + // logger.trace( "[{}].[{}] : Class is a special parent class, delegating to parent", this.nameAsKey.getName(), className ); + return getDynamicParent().loadClass( className ); + } + // 3. Attempt to load from JARs/classes in the seeded URLs try { cachedClass = super.findClass( className ); From 9cc95847247af0109dcffabbc0eb0361b1970d7d Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 9 Dec 2024 16:52:59 -0700 Subject: [PATCH 164/193] Potential fix for arrays not happy with `top` in dump templates --- src/main/resources/dump/html/Array.bxm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/resources/dump/html/Array.bxm b/src/main/resources/dump/html/Array.bxm index 1d0055226..5654449c5 100644 --- a/src/main/resources/dump/html/Array.bxm +++ b/src/main/resources/dump/html/Array.bxm @@ -14,7 +14,7 @@ #label# - Array: - #top#/#var.len()# items + #top#/#var.len()# items @@ -31,7 +31,7 @@ for ( i = 1; i <= var.len(); i++ ) { // Top limit only if > 0 - if( top > 0 && i > top ) { + if( top != null && i > top ) { break; } ``` @@ -49,7 +49,7 @@

    From a44f7440cc3f40a1a22040390cb6cc3ad9de926a Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 9 Dec 2024 21:51:54 -0700 Subject: [PATCH 165/193] Use `isNull` over `== null` for compatibility with `bx-compat-cfml` --- src/main/resources/dump/html/Array.bxm | 6 +++--- src/main/resources/dump/html/BoxClass.bxm | 10 +++++----- src/main/resources/dump/html/Function.bxm | 2 +- src/main/resources/dump/html/List.bxm | 4 ++-- src/main/resources/dump/html/Map.bxm | 8 ++++---- src/main/resources/dump/html/Query.bxm | 4 ++-- src/main/resources/dump/html/Struct.bxm | 14 +++++++------- src/main/resources/dump/html/XML.bxm | 2 +- .../runtime/components/system/DumpTest.java | 18 ++++++++++++++++++ 9 files changed, 43 insertions(+), 25 deletions(-) diff --git a/src/main/resources/dump/html/Array.bxm b/src/main/resources/dump/html/Array.bxm index 5654449c5..db02b83bb 100644 --- a/src/main/resources/dump/html/Array.bxm +++ b/src/main/resources/dump/html/Array.bxm @@ -14,7 +14,7 @@ #label# - Array: - #top#/#var.len()# items + #top#/#var.len()# items @@ -31,7 +31,7 @@ for ( i = 1; i <= var.len(); i++ ) { // Top limit only if > 0 - if( top != null && i > top ) { + if( !isNull( top ) && i > top ) { break; } ``` @@ -49,7 +49,7 @@
    diff --git a/src/main/resources/dump/html/BoxClass.bxm b/src/main/resources/dump/html/BoxClass.bxm index b9d872671..62ce8d0f8 100644 --- a/src/main/resources/dump/html/BoxClass.bxm +++ b/src/main/resources/dump/html/BoxClass.bxm @@ -172,7 +172,7 @@ #writeDump( var : variablesScope[ prop.name ] ?: null, - top : top == null ? null : top - 1, + top : isNull( top ) ? null : top - 1, expand : expand, showUDFs : showUDFs )# @@ -227,7 +227,7 @@ #writeDump( var : dataProperty ?: null, - top : top == null ? null : top - 1, + top : isNull( top ) ? null : top - 1, expand : expand, showUDFs : showUDFs )# @@ -277,7 +277,7 @@ #encodeForHTML( methodName )# - #writeDump( var : thisMethod ?: null, top : top == null ? null : top - 1, expand : expand ?: false )# + #writeDump( var : thisMethod ?: null, top : isNull( top ) ? null : top - 1, expand : expand ?: false )#
    @@ -327,7 +327,7 @@ #encodeForHTML( methodName )# - #writeDump( var : thisMethod ?: null, top : top == null ? null : top - 1, expand : expand )# + #writeDump( var : thisMethod ?: null, top : isNull( top ) ? null : top - 1, expand : expand )# @@ -378,7 +378,7 @@ #encodeForHTML( methodName )# - #writeDump( var : thisMethod ?: null, top : top == null ? null : top - 1, expand : expand )# + #writeDump( var : thisMethod ?: null, top : isNull( top ) ? null : top - 1, expand : expand )# diff --git a/src/main/resources/dump/html/Function.bxm b/src/main/resources/dump/html/Function.bxm index 13433cea2..00f48a620 100644 --- a/src/main/resources/dump/html/Function.bxm +++ b/src/main/resources/dump/html/Function.bxm @@ -82,7 +82,7 @@ #encodeForHTML( thisArg.name() )# - #writedump( var: thisArg.defaultValue(), top: top == null ? null : top - 1, expand: expand )# + #writedump( var: thisArg.defaultValue(), top: isNull( top ) ? null : top - 1, expand: expand )# #encodeForHTML( argHint )# diff --git a/src/main/resources/dump/html/List.bxm b/src/main/resources/dump/html/List.bxm index fb2f37874..fa53d1fac 100644 --- a/src/main/resources/dump/html/List.bxm +++ b/src/main/resources/dump/html/List.bxm @@ -30,7 +30,7 @@ for ( i = 0; i < var.size(); i++ ) { // Top limit only if > 0 - if( top != null && i > top ) { + if( !isNull( top ) && i > top ) { break; } ``` @@ -46,7 +46,7 @@
    - +
    diff --git a/src/main/resources/dump/html/Map.bxm b/src/main/resources/dump/html/Map.bxm index 14e830a35..0f468884b 100644 --- a/src/main/resources/dump/html/Map.bxm +++ b/src/main/resources/dump/html/Map.bxm @@ -14,7 +14,7 @@ #label# - #var.getClass().getSimpleName()#: - #top#/#var.size()# items + #top#/#var.size()# items @@ -22,7 +22,7 @@ #label# - #var.getClass().getSimpleName()#: - #top#/#var.size()# items + #top#/#var.size()# items @@ -33,7 +33,7 @@ index = 1; for ( key in var ) { // Top limit only if > 0 - if( top != null && index++ > top ) { + if( !isNull( top ) && index++ > top ) { break; } ``` @@ -50,7 +50,7 @@
    - +
    diff --git a/src/main/resources/dump/html/Query.bxm b/src/main/resources/dump/html/Query.bxm index 4fa3e2bf8..1399fa5e9 100644 --- a/src/main/resources/dump/html/Query.bxm +++ b/src/main/resources/dump/html/Query.bxm @@ -13,7 +13,7 @@ #label# - Query: - #top#/#var.recordcount# rows + #top#/#var.recordcount# rows class="d-none" > - + diff --git a/src/main/resources/dump/html/Struct.bxm b/src/main/resources/dump/html/Struct.bxm index feb0412af..e8f486bb3 100644 --- a/src/main/resources/dump/html/Struct.bxm +++ b/src/main/resources/dump/html/Struct.bxm @@ -19,9 +19,9 @@ #label# - #encodeForHTML( var.getName().getName().toUpperCase() )# Scope: - #top#/#var.getDumpKeys().size()# items + #top#/#var.getDumpKeys().size()# items - #top#/#var.len()# items + #top#/#var.len()# items @@ -30,7 +30,7 @@ #label# - #encodeForHTML( var.getName().getName().toUpperCase() )# Scope: - #top#/#var.len()# items + #top#/#var.len()# items @@ -47,7 +47,7 @@ #label# - Struct: - #top#/#var.len()# items + #top#/#var.len()# items @@ -55,7 +55,7 @@ #label# - Struct: - #top#/#var.len()# items + #top#/#var.len()# items @@ -72,7 +72,7 @@ } index = 1; for ( key in theCollection ) { - if( top != null && index++ > top ) { + if( !isNull( top ) && index++ > top ) { break; } ``` @@ -89,7 +89,7 @@
    - +
    diff --git a/src/main/resources/dump/html/XML.bxm b/src/main/resources/dump/html/XML.bxm index 01011b72f..6181a4690 100644 --- a/src/main/resources/dump/html/XML.bxm +++ b/src/main/resources/dump/html/XML.bxm @@ -38,7 +38,7 @@
    - +
    diff --git a/src/test/java/ortus/boxlang/runtime/components/system/DumpTest.java b/src/test/java/ortus/boxlang/runtime/components/system/DumpTest.java index be8e5c8af..e16761d56 100644 --- a/src/test/java/ortus/boxlang/runtime/components/system/DumpTest.java +++ b/src/test/java/ortus/boxlang/runtime/components/system/DumpTest.java @@ -89,6 +89,24 @@ public void testCanDumpBLTag() { assertThat( baos.toString() ).contains( "My Value" ); } + @DisplayName( "It can dump BL tag 2" ) + @Test + public void testCanDumpBLTag2() { + // @formatter:off + instance.executeSource( + """ + + + + + + + """, + context, BoxSourceType.BOXTEMPLATE ); + // @formatter:on + assertThat( baos.toString() ).contains( "inner" ); + } + @DisplayName( "It can dump script" ) @Test public void testCanDumpScript() { From 627686cc638ae48a428a7471783d24bdae9ef650 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Tue, 10 Dec 2024 13:30:40 +0100 Subject: [PATCH 166/193] make sure imports are not null when class locating --- .../ortus/boxlang/runtime/loader/ClassLocator.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/loader/ClassLocator.java b/src/main/java/ortus/boxlang/runtime/loader/ClassLocator.java index 923a98e0d..ee6103def 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/ClassLocator.java +++ b/src/main/java/ortus/boxlang/runtime/loader/ClassLocator.java @@ -398,6 +398,10 @@ public DynamicObject load( IBoxContext context, String name ) { * */ public DynamicObject load( IBoxContext context, String name, List imports ) { + // If the imports are null, set them to an empty list + if ( imports == null ) { + imports = List.of(); + } // Check to see if our incoming name has a resolver prefix int resolverDelimiterPos = name.indexOf( ":" ); // If not, use our system lookup order @@ -465,9 +469,11 @@ public DynamicObject load( Boolean throwException, List imports ) { + // If the imports are null, set them to an empty list if ( imports == null ) { imports = List.of(); } + // Must be final for the lambda to use it final List thisImports = imports; // Unique Cache Key @@ -660,7 +666,10 @@ public Class findClass( IBoxContext context, String name, List imports ) { // Try to get it from cache From b2bc3bfdb2dc002de5c4e36f623ee33ff2095be5 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Tue, 10 Dec 2024 13:53:01 +0100 Subject: [PATCH 167/193] adding debugging --- .../boxlang/runtime/loader/resolvers/BoxResolver.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java b/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java index 33ebe61ce..ba09a65d4 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java +++ b/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java @@ -32,6 +32,7 @@ import ortus.boxlang.runtime.loader.ClassLocator; import ortus.boxlang.runtime.loader.ClassLocator.ClassLocation; import ortus.boxlang.runtime.loader.ImportDefinition; +import ortus.boxlang.runtime.logging.BoxLangLogger; import ortus.boxlang.runtime.modules.ModuleRecord; import ortus.boxlang.runtime.runnables.RunnableLoader; import ortus.boxlang.runtime.scopes.Key; @@ -62,7 +63,8 @@ public class BoxResolver extends BaseResolver { /** * Empty list of imports */ - private static final List EMPTY_IMPORTS = List.of(); + private static final List EMPTY_IMPORTS = List.of(); + BoxLangLogger logger; /** * -------------------------------------------------------------------------- @@ -77,6 +79,7 @@ public class BoxResolver extends BaseResolver { */ public BoxResolver( ClassLocator classLocator ) { super( "BoxResolver", "bx", classLocator ); + this.logger = this.runtime.getLoggingService().getLogger( "boxresolver" ); } /** @@ -318,6 +321,7 @@ private Optional findByMapping( // System.out.println( "mappings: " + mappings ); // System.out.println( "slashName: " + slashName ); + this.logger.debug( "Resolving [{}], mappings: [{}]", slashName, mappings ); // Maybe if we have > 20 mappings we should use parallel streams @@ -369,7 +373,6 @@ private Optional findByMapping( } ) // Map it to a ClassLocation object .map( possibleMatch -> { - // System.out.println( "found: " + possibleMatch.absolutePath().toAbsolutePath().toString() ); // System.out.println( "found package: " + possibleMatch.getPackage().toString() ); return new ClassLocation( From 484feea91119c02d5e28b7977b2d3c0f8b9e8e54 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Tue, 10 Dec 2024 14:51:57 +0100 Subject: [PATCH 168/193] encapsulating logger for all resolvers --- .../loader/resolvers/BaseResolver.java | 20 +++++++++++++++++++ .../runtime/loader/resolvers/BoxResolver.java | 7 ++----- .../TestCases/phase2/ClosureFunctionTest.java | 8 +------- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/loader/resolvers/BaseResolver.java b/src/main/java/ortus/boxlang/runtime/loader/resolvers/BaseResolver.java index 6964f7f30..8aabc1603 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/resolvers/BaseResolver.java +++ b/src/main/java/ortus/boxlang/runtime/loader/resolvers/BaseResolver.java @@ -33,6 +33,7 @@ import ortus.boxlang.runtime.loader.DynamicClassLoader; import ortus.boxlang.runtime.loader.ImportDefinition; import ortus.boxlang.runtime.loader.util.ClassDiscovery; +import ortus.boxlang.runtime.logging.BoxLangLogger; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; /** @@ -65,6 +66,11 @@ public class BaseResolver implements IClassResolver { */ protected Set importCache = ConcurrentHashMap.newKeySet(); + /** + * Logging Class + */ + protected BoxLangLogger logger; + /** * -------------------------------------------------------------------------- * Constructor @@ -301,4 +307,18 @@ protected static DynamicClassLoader getSystemClassLoader() { return BoxRuntime.getInstance().getRuntimeLoader(); } + /** + * Get the resolver loggers + */ + protected BoxLangLogger getLogger() { + if ( this.logger == null ) { + synchronized ( this ) { + if ( this.logger == null ) { + this.logger = BoxRuntime.getInstance().getLoggingService().getLogger( "resolver" ); + } + } + } + return this.logger; + } + } diff --git a/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java b/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java index ba09a65d4..9bff82216 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java +++ b/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java @@ -32,7 +32,6 @@ import ortus.boxlang.runtime.loader.ClassLocator; import ortus.boxlang.runtime.loader.ClassLocator.ClassLocation; import ortus.boxlang.runtime.loader.ImportDefinition; -import ortus.boxlang.runtime.logging.BoxLangLogger; import ortus.boxlang.runtime.modules.ModuleRecord; import ortus.boxlang.runtime.runnables.RunnableLoader; import ortus.boxlang.runtime.scopes.Key; @@ -63,8 +62,7 @@ public class BoxResolver extends BaseResolver { /** * Empty list of imports */ - private static final List EMPTY_IMPORTS = List.of(); - BoxLangLogger logger; + private static final List EMPTY_IMPORTS = List.of(); /** * -------------------------------------------------------------------------- @@ -79,7 +77,6 @@ public class BoxResolver extends BaseResolver { */ public BoxResolver( ClassLocator classLocator ) { super( "BoxResolver", "bx", classLocator ); - this.logger = this.runtime.getLoggingService().getLogger( "boxresolver" ); } /** @@ -321,7 +318,7 @@ private Optional findByMapping( // System.out.println( "mappings: " + mappings ); // System.out.println( "slashName: " + slashName ); - this.logger.debug( "Resolving [{}], mappings: [{}]", slashName, mappings ); + getLogger().debug( "Resolving [{}], mappings: [{}]", slashName, mappings ); // Maybe if we have > 20 mappings we should use parallel streams diff --git a/src/test/java/TestCases/phase2/ClosureFunctionTest.java b/src/test/java/TestCases/phase2/ClosureFunctionTest.java index 7706b7f8a..5cfc5a884 100644 --- a/src/test/java/TestCases/phase2/ClosureFunctionTest.java +++ b/src/test/java/TestCases/phase2/ClosureFunctionTest.java @@ -21,7 +21,6 @@ import java.util.List; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -54,11 +53,6 @@ public static void setUp() { instance = BoxRuntime.getInstance( true ); } - @AfterAll - public static void teardown() { - - } - @BeforeEach public void setupEach() { context = new ScriptingRequestBoxContext( instance.getRuntimeContext() ); @@ -717,4 +711,4 @@ function announceToModules( required event, args = {} ){ } -} \ No newline at end of file +} From 6678aa2154d4218e3720e865eaa9e47ad027436d Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Tue, 10 Dec 2024 19:23:36 +0100 Subject: [PATCH 169/193] move to trace for resolveer debug data --- .../ortus/boxlang/runtime/loader/resolvers/BoxResolver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java b/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java index 9bff82216..9d1197fc8 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java +++ b/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java @@ -318,7 +318,7 @@ private Optional findByMapping( // System.out.println( "mappings: " + mappings ); // System.out.println( "slashName: " + slashName ); - getLogger().debug( "Resolving [{}], mappings: [{}]", slashName, mappings ); + getLogger().trace( "Resolving [{}] against mappings: [{}]", slashName, mappings ); // Maybe if we have > 20 mappings we should use parallel streams From d280c5b476975d76dd89de297cffd9a1e9123f8b Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Tue, 10 Dec 2024 20:16:42 +0100 Subject: [PATCH 170/193] update the right logger name --- .../java/ortus/boxlang/runtime/context/RuntimeBoxContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/context/RuntimeBoxContext.java b/src/main/java/ortus/boxlang/runtime/context/RuntimeBoxContext.java index 442c8c959..043f7cc9c 100644 --- a/src/main/java/ortus/boxlang/runtime/context/RuntimeBoxContext.java +++ b/src/main/java/ortus/boxlang/runtime/context/RuntimeBoxContext.java @@ -45,7 +45,7 @@ public class RuntimeBoxContext extends BaseBoxContext { * -------------------------------------------------------------------------- */ - private static final Logger logger = LoggerFactory.getLogger( ServerScope.class ); + private static final Logger logger = LoggerFactory.getLogger( RuntimeBoxContext.class ); /** * The variables scope From 4687d75683f9dc13b0b1bdbfec69b1584fa81ad7 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Tue, 10 Dec 2024 20:27:02 +0100 Subject: [PATCH 171/193] all loggers now point to the modules file --- .../boxlang/runtime/modules/ModuleRecord.java | 104 ++++++++++-------- 1 file changed, 58 insertions(+), 46 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java b/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java index 1d15ff1c6..62428fda4 100644 --- a/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java +++ b/src/main/java/ortus/boxlang/runtime/modules/ModuleRecord.java @@ -33,8 +33,6 @@ import java.util.regex.Matcher; import org.apache.commons.io.FilenameUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.async.tasks.IScheduler; @@ -55,6 +53,7 @@ import ortus.boxlang.runtime.jdbc.drivers.DriverShim; import ortus.boxlang.runtime.jdbc.drivers.IJDBCDriver; import ortus.boxlang.runtime.loader.DynamicClassLoader; +import ortus.boxlang.runtime.logging.BoxLangLogger; import ortus.boxlang.runtime.runnables.IClassRunnable; import ortus.boxlang.runtime.runnables.RunnableLoader; import ortus.boxlang.runtime.scopes.ArgumentsScope; @@ -220,7 +219,7 @@ public class ModuleRecord { */ public IClassRunnable moduleConfig; - /* + /** * -------------------------------------------------------------------------- * Private Properties * -------------------------------------------------------------------------- @@ -239,11 +238,16 @@ public class ModuleRecord { /** * Logger */ - private static final Logger logger = LoggerFactory.getLogger( ModuleRecord.class ); + private BoxLangLogger logger; + + /** + * Runtime + */ + private BoxRuntime runtime; - /* + /** * -------------------------------------------------------------------------- - * Constructors + * Constructor(s) * -------------------------------------------------------------------------- */ @@ -253,6 +257,9 @@ public class ModuleRecord { * @param physicalPath The physical path of the module */ public ModuleRecord( String physicalPath ) { + this.runtime = BoxRuntime.getInstance(); + this.logger = this.runtime.getLoggingService().getLogger( "modules" ); + Path directoryPath = Path.of( physicalPath ); Path boxjsonPath = directoryPath.resolve( MODULE_CONFIG_FILE ); @@ -263,7 +270,7 @@ public ModuleRecord( String physicalPath ) { .from( "boxlang" ) .ifPresent( "moduleName", value -> this.name = Key.of( value ) ) .ifPresent( "minimumVersion", - value -> BoxRuntime.getInstance().getModuleService().verifyModuleAndBoxLangVersion( ( String ) value, directoryPath ) + value -> this.runtime.getModuleService().verifyModuleAndBoxLangVersion( ( String ) value, directoryPath ) ); } @@ -295,9 +302,8 @@ public ModuleRecord( String physicalPath ) { * @return The ModuleRecord */ public ModuleRecord loadDescriptor( IBoxContext context ) { - BoxRuntime runtime = BoxRuntime.getInstance(); - Path descriptorPath = physicalPath.resolve( ModuleService.MODULE_DESCRIPTOR ); - String packageName = MODULE_PACKAGE_NAME + this.name.getNameNoCase() + EncryptionUtil.hash( physicalPath.toString() ); + Path descriptorPath = physicalPath.resolve( ModuleService.MODULE_DESCRIPTOR ); + String packageName = MODULE_PACKAGE_NAME + this.name.getNameNoCase() + EncryptionUtil.hash( physicalPath.toString() ); // Load the Class, Construct it and store it this.moduleConfig = ( IClassRunnable ) DynamicObject.of( @@ -325,8 +331,8 @@ public ModuleRecord loadDescriptor( IBoxContext context ) { this.enabled = ( Boolean ) thisScope.getOrDefault( Key.enabled, true ); // Verify if we disabled the loading of the module in the runtime config - if ( runtime.getConfiguration().modules.containsKey( this.name ) ) { - ModuleConfig config = ( ModuleConfig ) runtime.getConfiguration().modules.get( this.name ); + if ( this.runtime.getConfiguration().modules.containsKey( this.name ) ) { + ModuleConfig config = ( ModuleConfig ) this.runtime.getConfiguration().modules.get( this.name ); this.enabled = config.enabled; } @@ -358,9 +364,9 @@ public ModuleRecord loadDescriptor( IBoxContext context ) { */ variablesScope.put( Key.moduleRecord, this ); - variablesScope.put( Key.boxRuntime, BoxRuntime.getInstance() ); - variablesScope.put( Key.interceptorService, BoxRuntime.getInstance().getInterceptorService() ); - variablesScope.put( Key.log, LoggerFactory.getLogger( this.moduleConfig.getClass() ) ); + variablesScope.put( Key.boxRuntime, this.runtime ); + variablesScope.put( Key.interceptorService, this.runtime.getInterceptorService() ); + variablesScope.put( Key.log, this.logger ); return this; } @@ -377,14 +383,13 @@ public ModuleRecord register( IBoxContext context ) { // Convenience References ThisScope thisScope = this.moduleConfig.getThisScope(); VariablesScope variablesScope = this.moduleConfig.getVariablesScope(); - BoxRuntime runtime = BoxRuntime.getInstance(); - InterceptorService interceptorService = runtime.getInterceptorService(); - FunctionService functionService = runtime.getFunctionService(); - ComponentService componentService = runtime.getComponentService(); + InterceptorService interceptorService = this.runtime.getInterceptorService(); + FunctionService functionService = this.runtime.getFunctionService(); + ComponentService componentService = this.runtime.getComponentService(); - // Register the module mapping in the runtime + // Register the module mapping in the this.runtime // Called first in case this is used in the `configure` method - runtime.getConfiguration().registerMapping( this.mapping, this.path ); + this.runtime.getConfiguration().registerMapping( this.mapping, this.path ); // Create the module class loader and seed it with the physical path to the module // This traverses the module and looks for *.class files to load (NOT JARs) @@ -394,11 +399,11 @@ public ModuleRecord register( IBoxContext context ) { this.classLoader = new DynamicClassLoader( this.name, this.physicalPath.toUri().toURL(), - runtime.getRuntimeLoader(), + this.runtime.getRuntimeLoader(), false ); } catch ( MalformedURLException e ) { - logger.error( "Error creating module [{}] class loader.", this.name, e ); + this.logger.error( "Error creating module [{}] class loader.", this.name, e ); throw new BoxRuntimeException( "Error creating module [" + this.name + "] class loader", e ); } @@ -409,7 +414,7 @@ public ModuleRecord register( IBoxContext context ) { try { this.classLoader.addURLs( DynamicClassLoader.getJarURLs( libsPath ) ); } catch ( IOException e ) { - logger.error( "Error while seeding the module [{}] class loader with the libs folder.", this.name, e ); + this.logger.error( "Error while seeding the module [{}] class loader with the libs folder.", this.name, e ); throw new BoxRuntimeException( "Error while seeding the module [" + this.name + "] class loader with the libs folder", e ); } } @@ -428,8 +433,8 @@ public ModuleRecord register( IBoxContext context ) { this.settings = ( Struct ) variablesScope.getAsStruct( Key.settings ); // Append any module settings found in the runtime configuration - if ( runtime.getConfiguration().modules.containsKey( this.name ) ) { - ModuleConfig config = ( ModuleConfig ) runtime.getConfiguration().modules.get( this.name ); + if ( this.runtime.getConfiguration().modules.containsKey( this.name ) ) { + ModuleConfig config = ( ModuleConfig ) this.runtime.getConfiguration().modules.get( this.name ); StructUtil.deepMerge( this.settings, config.settings, true ); } @@ -466,7 +471,7 @@ public ModuleRecord register( IBoxContext context ) { ServiceLoader.load( IService.class, this.classLoader ) .stream() .map( ServiceLoader.Provider::get ) - .forEach( service -> runtime.putGlobalService( service.getName(), service ) ); + .forEach( service -> this.runtime.putGlobalService( service.getName(), service ) ); // Load any JDBC drivers into the JVM ServiceLoader.load( Driver.class, this.classLoader ) @@ -484,7 +489,7 @@ public ModuleRecord register( IBoxContext context ) { ServiceLoader.load( IJDBCDriver.class, this.classLoader ) .stream() .map( ServiceLoader.Provider::get ) - .forEach( driver -> runtime.getDataSourceService().registerDriver( driver ) ); + .forEach( driver -> this.runtime.getDataSourceService().registerDriver( driver ) ); // Do we have any Java BIFs to load? ServiceLoader.load( BIF.class, this.classLoader ) @@ -502,13 +507,13 @@ public ModuleRecord register( IBoxContext context ) { ServiceLoader.load( IScheduler.class, this.classLoader ) .stream() .map( ServiceLoader.Provider::get ) - .forEach( scheduler -> runtime.getSchedulerService().loadScheduler( Key.of( scheduler.getSchedulerName() + "@" + this.name ), scheduler ) ); + .forEach( scheduler -> this.runtime.getSchedulerService().loadScheduler( Key.of( scheduler.getSchedulerName() + "@" + this.name ), scheduler ) ); // Do we have any Java ICacheProviders to register in the CacheService ServiceLoader.load( ICacheProvider.class, this.classLoader ) .stream() .map( ServiceLoader.Provider::type ) - .forEach( provider -> runtime.getCacheService().registerProvider( Key.of( provider.getSimpleName() ), provider ) ); + .forEach( provider -> this.runtime.getCacheService().registerProvider( Key.of( provider.getSimpleName() ), provider ) ); // Do we have any Java IInterceptor to register in the InterceptorService ServiceLoader.load( IInterceptor.class, this.classLoader ) @@ -532,7 +537,7 @@ public ModuleRecord register( IBoxContext context ) { public ModuleRecord unload( IBoxContext context ) { // Convenience References ThisScope thisScope = this.moduleConfig.getThisScope(); - InterceptorService interceptorService = BoxRuntime.getInstance().getInterceptorService(); + InterceptorService interceptorService = this.runtime.getInterceptorService(); // Call the onLoad() method if it exists in the descriptor if ( thisScope.containsKey( Key.onUnload ) ) { @@ -544,7 +549,7 @@ public ModuleRecord unload( IBoxContext context ) { false ); } catch ( Exception e ) { - logger.error( "Error while unloading module [{}]", this.name, e ); + this.logger.error( "Error while unloading module [{}]", this.name, e ); } } @@ -566,7 +571,7 @@ public ModuleRecord unload( IBoxContext context ) { try { this.classLoader.close(); } catch ( IOException e ) { - logger.error( "Error while closing the DynamicClassLoader for module [{}]", this.name, e ); + this.logger.error( "Error while closing the DynamicClassLoader for module [{}]", this.name, e ); } finally { this.classLoader = null; } @@ -603,7 +608,7 @@ public Class findModuleClass( String className, Boolean safe, IBoxContext con public ModuleRecord activate( IBoxContext context ) { // Convenience References ThisScope thisScope = this.moduleConfig.getThisScope(); - InterceptorService interceptorService = BoxRuntime.getInstance().getInterceptorService(); + InterceptorService interceptorService = this.runtime.getInterceptorService(); /* * -------------------------------------------------------------------------- @@ -771,8 +776,7 @@ private ModuleRecord registerComponent( File targetFile, IBoxContext context ) { } // Nice References - BoxRuntime runtime = BoxRuntime.getInstance(); - ComponentService componentService = runtime.getComponentService(); + ComponentService componentService = this.runtime.getComponentService(); // Try to load the BoxLang class and proxy Key className = Key.of( FilenameUtils.getBaseName( targetFile.getAbsolutePath() ) ); IClassRunnable oComponent = loadClassRunnable( targetFile, ModuleService.MODULE_COMPONENTS, context ); @@ -836,7 +840,7 @@ private ModuleRecord registerComponent( File targetFile, IBoxContext context ) { // Register all components with their aliases for ( Key thisAlias : componentAliases ) { componentService.registerComponent( descriptor, thisAlias, true ); - logger.info( + this.logger.info( "> Registered Module [{}] Component [{}] with alias [{}]", this.name.getName(), className.getName(), @@ -866,8 +870,7 @@ private ModuleRecord registerBIF( File targetFile, IBoxContext context ) { } // Nice References - BoxRuntime runtime = BoxRuntime.getInstance(); - FunctionService functionService = runtime.getFunctionService(); + FunctionService functionService = this.runtime.getFunctionService(); // Try to load the BoxLang class Key className = Key.of( FilenameUtils.getBaseName( targetFile.getAbsolutePath() ) ); IClassRunnable oBIF = loadClassRunnable( targetFile, ModuleService.MODULE_BIFS, context ); @@ -893,7 +896,7 @@ private ModuleRecord registerBIF( File targetFile, IBoxContext context ) { bifAlias, true ); - logger.info( + this.logger.info( "> Registered Module [{}] BIF [{}] with alias [{}]", this.name.getName(), className.getName(), @@ -925,7 +928,7 @@ private ModuleRecord registerBIF( File targetFile, IBoxContext context ) { bifDescriptor ) ); - logger.info( + this.logger.info( "> Registered Module [{}] MemberMethod [{}]", this.name.getName(), memberMethod @@ -1094,19 +1097,28 @@ private IClassRunnable loadClassRunnable( File targetFile, String conventionsPat * - boxRuntime : BoxLangRuntime * - log : A logger * - functionService : The BoxLang FunctionService + * - componentService : The BoxLang ComponentService * - interceptorService : The BoxLang InterceptorService + * - cacheService : The BoxLang CacheService + * - asyncService : The BoxLang AsyncService + * - schedulerService : The BoxLang SchedulerService + * - dataSourceService : The BoxLang DataSourceService * - moduleRecord : The ModuleRecord instance */ - BoxRuntime runtime = BoxRuntime.getInstance(); - FunctionService functionService = runtime.getFunctionService(); - InterceptorService interceptorService = runtime.getInterceptorService(); + FunctionService functionService = this.runtime.getFunctionService(); + InterceptorService interceptorService = this.runtime.getInterceptorService(); oTargetObject.getVariablesScope().put( Key.moduleRecord, this ); - oTargetObject.getVariablesScope().put( Key.boxRuntime, runtime ); + oTargetObject.getVariablesScope().put( Key.boxRuntime, this.runtime ); oTargetObject.getVariablesScope().put( Key.functionService, functionService ); + oTargetObject.getVariablesScope().put( Key.componentService, this.runtime.getComponentService() ); oTargetObject.getVariablesScope().put( Key.interceptorService, interceptorService ); - oTargetObject.getVariablesScope().put( Key.log, LoggerFactory.getLogger( oTargetObject.getClass() ) ); + oTargetObject.getVariablesScope().put( Key.asyncService, this.runtime.getAsyncService() ); + oTargetObject.getVariablesScope().put( Key.schedulerService, this.runtime.getSchedulerService() ); + oTargetObject.getVariablesScope().put( Key.datasourceService, this.runtime.getDataSourceService() ); + oTargetObject.getVariablesScope().put( Key.cacheService, this.runtime.getCacheService() ); + oTargetObject.getVariablesScope().put( Key.log, this.logger ); return oTargetObject; } From 9dea1fade3823d42f0a2730fc04a371c21120bf3 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 10 Dec 2024 21:58:31 -0600 Subject: [PATCH 172/193] BL-836 --- .../runtime/context/ClassBoxContext.java | 4 ++-- .../runtime/context/CustomTagBoxContext.java | 4 ++-- .../runtime/context/FunctionBoxContext.java | 20 ++++++++++++------- .../runtime/context/ThreadBoxContext.java | 4 ++-- .../runtime/runnables/BoxClassSupport.java | 13 ++++++++++++ .../ortus/boxlang/runtime/types/Function.java | 2 +- src/test/java/TestCases/phase3/ClassTest.java | 14 +++++++++++++ src/test/java/TestCases/phase3/MixinTest.bx | 2 ++ .../java/TestCases/phase3/MixinTestParent.bx | 7 +++++++ src/test/java/TestCases/phase3/mixin.bxs | 3 +++ 10 files changed, 59 insertions(+), 14 deletions(-) create mode 100644 src/test/java/TestCases/phase3/MixinTest.bx create mode 100644 src/test/java/TestCases/phase3/MixinTestParent.bx create mode 100644 src/test/java/TestCases/phase3/mixin.bxs diff --git a/src/main/java/ortus/boxlang/runtime/context/ClassBoxContext.java b/src/main/java/ortus/boxlang/runtime/context/ClassBoxContext.java index 9b2407e47..4e888d039 100644 --- a/src/main/java/ortus/boxlang/runtime/context/ClassBoxContext.java +++ b/src/main/java/ortus/boxlang/runtime/context/ClassBoxContext.java @@ -129,7 +129,7 @@ public ScopeSearchResult scopeFindNearby( Key key, IScope defaultScope, boolean // Special check for $bx if ( key.equals( BoxMeta.key ) ) { - return new ScopeSearchResult( getThisClass(), getThisClass().getBoxMeta(), BoxMeta.key, false ); + return new ScopeSearchResult( getThisClass(), getThisClass().getBottomClass().getBoxMeta(), BoxMeta.key, false ); } // In query loop? @@ -319,7 +319,7 @@ public IBoxContext flushBuffer( boolean force ) { */ @Override public Boolean canOutput() { - return getThisClass().canOutput(); + return getThisClass().getBottomClass().canOutput(); } /** diff --git a/src/main/java/ortus/boxlang/runtime/context/CustomTagBoxContext.java b/src/main/java/ortus/boxlang/runtime/context/CustomTagBoxContext.java index 91585d403..bdc488e4e 100644 --- a/src/main/java/ortus/boxlang/runtime/context/CustomTagBoxContext.java +++ b/src/main/java/ortus/boxlang/runtime/context/CustomTagBoxContext.java @@ -61,9 +61,9 @@ public CustomTagBoxContext( IBoxContext parent, Key tagName ) { thisScope = null; if ( parent instanceof FunctionBoxContext context && context.isInClass() ) { - thisScope = context.getThisClass().getThisScope(); + thisScope = context.getThisClass().getBottomClass().getThisScope(); } else if ( parent instanceof ClassBoxContext context ) { - thisScope = context.getThisClass().getThisScope(); + thisScope = context.getThisClass().getBottomClass().getThisScope(); } } diff --git a/src/main/java/ortus/boxlang/runtime/context/FunctionBoxContext.java b/src/main/java/ortus/boxlang/runtime/context/FunctionBoxContext.java index c54b6a70d..ebffbb5c0 100644 --- a/src/main/java/ortus/boxlang/runtime/context/FunctionBoxContext.java +++ b/src/main/java/ortus/boxlang/runtime/context/FunctionBoxContext.java @@ -587,8 +587,8 @@ public Object invokeFunction( Key name, Object[] positionalArguments ) { Function function = findFunction( name ); if ( function == null ) { - if ( isInClass() && getThisClass().getVariablesScope().containsKey( Key.onMissingMethod ) ) { - return getThisClass().getVariablesScope().dereferenceAndInvoke( + if ( isInClass() && getThisClass().getBottomClass().getVariablesScope().containsKey( Key.onMissingMethod ) ) { + return getThisClass().getBottomClass().getVariablesScope().dereferenceAndInvoke( this, Key.onMissingMethod, new Object[] { name.getName(), ArgumentUtil.createArgumentsScope( this, positionalArguments ) }, @@ -616,11 +616,11 @@ public Object invokeFunction( Key name, Map namedArguments ) { Function function = findFunction( name ); if ( function == null ) { - if ( isInClass() && getThisClass().getVariablesScope().containsKey( Key.onMissingMethod ) ) { + if ( isInClass() && getThisClass().getBottomClass().getVariablesScope().containsKey( Key.onMissingMethod ) ) { Map args = new HashMap<>(); args.put( Key.missingMethodName, name.getName() ); args.put( Key.missingMethodArguments, ArgumentUtil.createArgumentsScope( this, namedArguments ) ); - return getThisClass().getVariablesScope().dereferenceAndInvoke( this, Key.onMissingMethod, args, false ); + return getThisClass().getBottomClass().getVariablesScope().dereferenceAndInvoke( this, Key.onMissingMethod, args, false ); } else { throw new BoxRuntimeException( "Function [" + name + "] not found" ); } @@ -641,8 +641,8 @@ public Object invokeFunction( Key name ) { Function function = findFunction( name ); if ( function == null ) { - if ( isInClass() && getThisClass().getVariablesScope().containsKey( Key.onMissingMethod ) ) { - return getThisClass().getVariablesScope().dereferenceAndInvoke( + if ( isInClass() && getThisClass().getBottomClass().getVariablesScope().containsKey( Key.onMissingMethod ) ) { + return getThisClass().getBottomClass().getVariablesScope().dereferenceAndInvoke( this, Key.onMissingMethod, new Object[] { name.getName(), ArgumentUtil.createArgumentsScope( this, new Object[] {} ) }, @@ -726,7 +726,13 @@ public void registerUDF( UDF udf, boolean override ) { registerUDF( boxClass.getStaticScope(), udf, override ); return; } - registerUDF( boxClass.getVariablesScope(), udf, override ); + // Register in variables (private) + registerUDF( boxClass.getBottomClass().getVariablesScope(), udf, override ); + + // if public, put there as well + if ( udf.getAccess().isEffectivePublic() ) { + registerUDF( boxClass.getBottomClass().getThisScope(), udf, override ); + } } else { // else, defer to parent context getParent().registerUDF( udf, override ); diff --git a/src/main/java/ortus/boxlang/runtime/context/ThreadBoxContext.java b/src/main/java/ortus/boxlang/runtime/context/ThreadBoxContext.java index 6bfaeab30..9c53120df 100644 --- a/src/main/java/ortus/boxlang/runtime/context/ThreadBoxContext.java +++ b/src/main/java/ortus/boxlang/runtime/context/ThreadBoxContext.java @@ -128,7 +128,7 @@ public IStruct getVisibleScopes( IStruct scopes, boolean nearby, boolean shallow scopes.getAsStruct( Key.contextual ).put( VariablesScope.name, variablesScope ); if ( getParent() instanceof FunctionBoxContext fbc && fbc.isInClass() ) { - scopes.getAsStruct( Key.contextual ).put( ThisScope.name, fbc.getThisClass().getThisScope() ); + scopes.getAsStruct( Key.contextual ).put( ThisScope.name, fbc.getThisClass().getBottomClass().getThisScope() ); } if ( getParent() instanceof ClassBoxContext cbc ) { scopes.getAsStruct( Key.contextual ).put( ThisScope.name, cbc.getThisScope() ); @@ -281,7 +281,7 @@ public IScope getScopeNearby( Key name, boolean shallow ) throws ScopeNotFoundEx if ( name.equals( ThisScope.name ) ) { if ( getParent() instanceof FunctionBoxContext fbc && fbc.isInClass() ) { - return fbc.getThisClass().getThisScope(); + return fbc.getThisClass().getBottomClass().getThisScope(); } if ( getParent() instanceof ClassBoxContext cbc ) { return cbc.getThisScope(); diff --git a/src/main/java/ortus/boxlang/runtime/runnables/BoxClassSupport.java b/src/main/java/ortus/boxlang/runtime/runnables/BoxClassSupport.java index 134306c7e..88e4699d7 100644 --- a/src/main/java/ortus/boxlang/runtime/runnables/BoxClassSupport.java +++ b/src/main/java/ortus/boxlang/runtime/runnables/BoxClassSupport.java @@ -234,6 +234,10 @@ public static IClassRunnable getBottomClass( IClassRunnable thisClass ) { * @return The assigned value */ public static Object assign( IClassRunnable thisClass, IBoxContext context, Key key, Object value ) { + + // This would only matter if we called super.myField and we'd want the bottom class's this scope + thisClass = thisClass.getBottomClass(); + // If invokeImplicitAccessor is enabled, and the key is a property, invoke the setter method. // This may call either a generated setter or a user-defined setter if ( thisClass.canInvokeImplicitAccessor( context ) && thisClass.getProperties().containsKey( key ) ) { @@ -264,6 +268,9 @@ public static Object dereference( IClassRunnable thisClass, IBoxContext context, return thisClass.getBoxMeta(); } + // This would only matter if we called super.myField and we'd want the bottom class's this scope + thisClass = thisClass.getBottomClass(); + // If invokeImplicitAccessor is enabled, and the key is a property, invoke the getter method. // This may call either a generated getter or a user-defined getter if ( thisClass.canInvokeImplicitAccessor( context ) && thisClass.getProperties().containsKey( key ) ) { @@ -304,9 +311,12 @@ public static Object dereference( IClassRunnable thisClass, IBoxContext context, */ public static Object dereferenceAndInvoke( IClassRunnable thisClass, IBoxContext context, Key name, Object[] positionalArguments, Boolean safe ) { // Where to look for the functions + // This should always be the "bottom" class since "super" is the only way to get a direct reference to a parent class BaseScope scope = thisClass.getThisScope(); // we are a super class, so we reached here via super.method() if ( thisClass.getChild() != null ) { + // Don't use getBottomClass() as we want to get the actual UDFs at this level. + // When the UDF runs, the scopes it "sees" will still be from the bottom (except super, of course) scope = thisClass.getVariablesScope(); } @@ -387,9 +397,12 @@ public static Object dereferenceAndInvoke( IClassRunnable thisClass, IBoxContext */ public static Object dereferenceAndInvoke( IClassRunnable thisClass, IBoxContext context, Key name, Map namedArguments, Boolean safe ) { // Where to look for the functions + // This should always be the "bottom" class since "super" is the only way to get a direct reference to a parent class BaseScope scope = thisClass.getThisScope(); // we are a super class, so we reached here via super.method() if ( thisClass.getChild() != null ) { + // Don't use getBottomClass() as we want to get the actual UDFs at this level. + // When the UDF runs, the scopes it "sees" will still be from the bottom (except super, of course) scope = thisClass.getVariablesScope(); } diff --git a/src/main/java/ortus/boxlang/runtime/types/Function.java b/src/main/java/ortus/boxlang/runtime/types/Function.java index 44507b32f..1ae6e36c8 100644 --- a/src/main/java/ortus/boxlang/runtime/types/Function.java +++ b/src/main/java/ortus/boxlang/runtime/types/Function.java @@ -458,7 +458,7 @@ public boolean canOutput( FunctionBoxContext context ) { // at a time. Each class has its own caching later for the output annotation. if ( context != null && context.isInClass() ) { // If we're in a class, we need to check the class output annotation - this.canOutput = context.getThisClass().canOutput(); + this.canOutput = context.getThisClass().getBottomClass().canOutput(); } } diff --git a/src/test/java/TestCases/phase3/ClassTest.java b/src/test/java/TestCases/phase3/ClassTest.java index 4992ecfd8..32f645c27 100644 --- a/src/test/java/TestCases/phase3/ClassTest.java +++ b/src/test/java/TestCases/phase3/ClassTest.java @@ -1541,4 +1541,18 @@ public void testUDFClassEnclosingClassReference() { assertThat( variables.get( "outerClass" ) ).isEqualTo( variables.get("innerClassesOuterClass") ); } + @DisplayName( "mixins should be public" ) + @Test + public void testMixinsPublic() { + + instance.executeSource( + """ + mt = new src.test.java.TestCases.phase3.MixinTest() + mt.includeMixin(); + result = mt.mixed() + """, + context ); + assertThat( variables.get( "result" ) ).isEqualTo( "mixed up" ); + } + } diff --git a/src/test/java/TestCases/phase3/MixinTest.bx b/src/test/java/TestCases/phase3/MixinTest.bx new file mode 100644 index 000000000..b075afd7b --- /dev/null +++ b/src/test/java/TestCases/phase3/MixinTest.bx @@ -0,0 +1,2 @@ +class extends=MixinTestParent { +} \ No newline at end of file diff --git a/src/test/java/TestCases/phase3/MixinTestParent.bx b/src/test/java/TestCases/phase3/MixinTestParent.bx new file mode 100644 index 000000000..e0167d398 --- /dev/null +++ b/src/test/java/TestCases/phase3/MixinTestParent.bx @@ -0,0 +1,7 @@ +class { + + function includeMixin() { + include "mixin.bxs"; + } + +} \ No newline at end of file diff --git a/src/test/java/TestCases/phase3/mixin.bxs b/src/test/java/TestCases/phase3/mixin.bxs new file mode 100644 index 000000000..db2dd40f2 --- /dev/null +++ b/src/test/java/TestCases/phase3/mixin.bxs @@ -0,0 +1,3 @@ +function mixed() { + return "mixed up"; +} \ No newline at end of file From 41a817a3b2798540b155c60fe9aa8e3c3347f8ac Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 10 Dec 2024 21:59:42 -0600 Subject: [PATCH 173/193] BL-823 --- src/main/antlr/SQLGrammar.g4 | 11 +- src/main/antlr/SQLLexer.g4 | 1 + .../ast/sql/select/expression/SQLColumn.java | 11 ++ .../select/expression/SQLCountFunction.java | 11 ++ .../sql/select/expression/SQLExpression.java | 22 ++++ .../sql/select/expression/SQLParenthesis.java | 22 ++++ .../expression/literal/SQLNumberLiteral.java | 11 ++ .../operation/SQLBetweenOperation.java | 45 ++++++- .../operation/SQLBinaryOperation.java | 117 +++++++++++++++--- .../operation/SQLBinaryOperator.java | 11 +- .../expression/operation/SQLInOperation.java | 12 +- .../operation/SQLUnaryOperation.java | 71 ++++++++++- .../compiler/parser/ParserErrorStrategy.java | 11 +- .../compiler/toolchain/SQLVisitor.java | 74 ++++++++++- .../boxlang/runtime/operators/Compare.java | 6 +- 15 files changed, 398 insertions(+), 38 deletions(-) diff --git a/src/main/antlr/SQLGrammar.g4 b/src/main/antlr/SQLGrammar.g4 index fef3f3d91..d91dcb7f5 100644 --- a/src/main/antlr/SQLGrammar.g4 +++ b/src/main/antlr/SQLGrammar.g4 @@ -230,16 +230,16 @@ expr: | unary_operator expr | expr PIPE2 expr | expr ( STAR | DIV | MOD) expr - | expr ( PLUS | MINUS) expr - | expr ( LT2 | GT2 | AMP | PIPE) expr + | expr (PLUS | MINUS) expr + // | expr ( LT2 | GT2 | AMP | PIPE) expr | expr ( LT | LT_EQ | GT | GT_EQ) expr | expr ( ASSIGN | EQ | NOT_EQ1 | NOT_EQ2 - | IS_ | IS_ NOT_ + | IS_ // | IS_ NOT_? DISTINCT_ FROM_ // | IN_ | LIKE_ @@ -254,8 +254,7 @@ expr: //| OPEN_PAR expr (COMMA expr)* CLOSE_PAR // | CAST_ OPEN_PAR expr AS_ type_name CLOSE_PAR // | expr COLLATE_ collation_name - | expr NOT_? (LIKE_ | GLOB_ | REGEXP_ | MATCH_) expr (ESCAPE_ expr)? - | expr ( ISNULL_ | NOTNULL_ | NOT_ NULL_) + | expr NOT_? LIKE_ expr (ESCAPE_ expr)? | expr IS_ NOT_? expr | expr NOT_? BETWEEN_ expr AND_ expr | expr NOT_? IN_ ( @@ -589,7 +588,7 @@ unary_operator: MINUS | PLUS // | TILDE - | NOT_ + | BANG ; error_message: diff --git a/src/main/antlr/SQLLexer.g4 b/src/main/antlr/SQLLexer.g4 index 74ef24c58..68b4a1162 100644 --- a/src/main/antlr/SQLLexer.g4 +++ b/src/main/antlr/SQLLexer.g4 @@ -35,6 +35,7 @@ GT: '>'; GT_EQ: '>='; EQ: '=='; NOT_EQ1: '!='; +BANG: '!'; NOT_EQ2: '<>'; ABORT_: 'ABORT'; diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLColumn.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLColumn.java index d1645d412..be040eb9e 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLColumn.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLColumn.java @@ -93,6 +93,17 @@ public Object evaluate( Map tableLookup, int i ) { return tableLookup.get( table ).getCell( name, i - 1 ); } + /** + * Runtime check if the expression evaluates to a boolean value and works for columns as well + * + * @param tableLookup lookup for tables + * + * @return true if the expression evaluates to a boolean value + */ + public boolean isBoolean( Map tableLookup ) { + return getType( tableLookup ) == QueryColumnType.BIT; + } + @Override public void accept( VoidBoxVisitor v ) { // TODO Auto-generated method stub diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLCountFunction.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLCountFunction.java index cd5e7f7db..913c335b5 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLCountFunction.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLCountFunction.java @@ -64,6 +64,17 @@ public QueryColumnType getType( Map tableLookup ) { return QueryColumnType.INTEGER; } + /** + * Runtime check if the expression evaluates to a numeric value and works for columns as well + * + * @param tableLookup lookup for tables + * + * @return true if the expression evaluates to a numeric value + */ + public boolean isNumeric( Map tableLookup ) { + return true; + } + @Override public void accept( VoidBoxVisitor v ) { // TODO Auto-generated method stub diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLExpression.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLExpression.java index d4ad6647d..f9e48439e 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLExpression.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLExpression.java @@ -51,6 +51,28 @@ public boolean isBoolean() { return false; } + /** + * Runtime check if the expression evaluates to a boolean value and works for columns as well + * + * @param tableLookup lookup for tables + * + * @return true if the expression evaluates to a boolean value + */ + public boolean isBoolean( Map tableLookup ) { + return isBoolean(); + } + + /** + * Runtime check if the expression evaluates to a numeric value and works for columns as well + * + * @param tableLookup lookup for tables + * + * @return true if the expression evaluates to a numeric value + */ + public boolean isNumeric( Map tableLookup ) { + return false; + } + /** * What type does this expression evaluate to */ diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLParenthesis.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLParenthesis.java index 72c46cad9..7562faee1 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLParenthesis.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLParenthesis.java @@ -65,6 +65,28 @@ public boolean isBoolean() { return expression.isBoolean(); } + /** + * Runtime check if the expression evaluates to a boolean value and works for columns as well + * + * @param tableLookup lookup for tables + * + * @return true if the expression evaluates to a boolean value + */ + public boolean isBoolean( Map tableLookup ) { + return expression.isBoolean( tableLookup ); + } + + /** + * Runtime check if the expression evaluates to a numeric value and works for columns as well + * + * @param tableLookup lookup for tables + * + * @return true if the expression evaluates to a numeric value + */ + public boolean isNumeric( Map tableLookup ) { + return expression.isNumeric( tableLookup ); + } + /** * What type does this expression evaluate to */ diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNumberLiteral.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNumberLiteral.java index 69a90e524..d9669b5c9 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNumberLiteral.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNumberLiteral.java @@ -77,6 +77,17 @@ public Object evaluate( Map tableLookup, int i ) { return value; } + /** + * Runtime check if the expression evaluates to a numeric value and works for columns as well + * + * @param tableLookup lookup for tables + * + * @return true if the expression evaluates to a numeric value + */ + public boolean isNumeric( Map tableLookup ) { + return true; + } + @Override public void accept( VoidBoxVisitor v ) { // TODO Auto-generated method stub diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBetweenOperation.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBetweenOperation.java index a330fb630..ef023a6cb 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBetweenOperation.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBetweenOperation.java @@ -22,8 +22,8 @@ import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.operators.Compare; import ortus.boxlang.runtime.types.Query; -import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; /** * Abstract Node class representing SQL BETWEEN operation @@ -36,17 +36,20 @@ public class SQLBetweenOperation extends SQLExpression { private SQLExpression right; + private boolean not; + /** * Constructor * * @param position position of the statement in the source code * @param sourceText source code of the statement */ - public SQLBetweenOperation( SQLExpression expression, SQLExpression left, SQLExpression right, Position position, String sourceText ) { + public SQLBetweenOperation( SQLExpression expression, SQLExpression left, SQLExpression right, boolean not, Position position, String sourceText ) { super( position, sourceText ); setExpression( expression ); setLeft( left ); setRight( right ); + setNot( not ); } /** @@ -97,6 +100,20 @@ public void setRight( SQLExpression right ) { this.right.setParent( this ); } + /** + * Get the not + */ + public boolean isNot() { + return not; + } + + /** + * Set the not + */ + public void setNot( boolean not ) { + this.not = not; + } + /** * Check if the expression evaluates to a boolean value */ @@ -108,7 +125,29 @@ public boolean isBoolean() { * Evaluate the expression */ public Object evaluate( Map tableLookup, int i ) { - throw new BoxRuntimeException( "not implemented" ); + Object leftValue = left.evaluate( tableLookup, i ); + Object rightValue = right.evaluate( tableLookup, i ); + Object expressionValue = expression.evaluate( tableLookup, i ); + // The ^ not inverses the result if the not flag is true + return doBetween( leftValue, rightValue, expressionValue ) ^ not; + } + + /** + * Helper for evaluating an expression as a number + * + * @param left the left operand value + * @param right the right operand value + * @param value the value to check if it's between the left and right operands + * + * @return true if the value is between the left and right operands + */ + private boolean doBetween( Object left, Object right, Object value ) { + int result = Compare.invoke( left, value, true ); + if ( result == 1 ) { + return false; + } + result = Compare.invoke( value, right, true ); + return result != 1; } @Override diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBinaryOperation.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBinaryOperation.java index c01ed27e5..86dac959c 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBinaryOperation.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBinaryOperation.java @@ -23,6 +23,8 @@ import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.operators.Compare; +import ortus.boxlang.runtime.operators.Concat; import ortus.boxlang.runtime.operators.EqualsEquals; import ortus.boxlang.runtime.types.Query; import ortus.boxlang.runtime.types.QueryColumnType; @@ -133,46 +135,133 @@ public QueryColumnType getType( Map tableLookup ) { return QueryColumnType.OBJECT; } + /** + * Runtime check if the expression evaluates to a numeric value and works for columns as well + * + * @param tableLookup lookup for tables + * + * @return true if the expression evaluates to a numeric value + */ + public boolean isNumeric( Map tableLookup ) { + return getType( tableLookup ) == QueryColumnType.DOUBLE; + } + /** * Evaluate the expression */ public Object evaluate( Map tableLookup, int i ) { + Object leftValue; + Object rightValue; + int compareResult; // Implement each binary operator switch ( operator ) { - case AND : - break; case DIVIDE : - break; + ensureNumericOperands( tableLookup ); + return evalAsNumber( left, tableLookup, i ) / evalAsNumber( right, tableLookup, i ); case EQUAL : - Object leftValue = left.evaluate( tableLookup, i ); - Object rightValue = right.evaluate( tableLookup, i ); + leftValue = left.evaluate( tableLookup, i ); + rightValue = right.evaluate( tableLookup, i ); return EqualsEquals.invoke( leftValue, rightValue, true ); case GREATERTHAN : - break; + return Compare.invoke( left.evaluate( tableLookup, i ), right.evaluate( tableLookup, i ), true ) == 1; case GREATERTHANOREQUAL : - break; + compareResult = Compare.invoke( left.evaluate( tableLookup, i ), right.evaluate( tableLookup, i ), true ); + return compareResult == 1 || compareResult == 0; case LESSTHAN : - break; + return Compare.invoke( left.evaluate( tableLookup, i ), right.evaluate( tableLookup, i ), true ) == -1; case LESSTHANOREQUAL : - break; + compareResult = Compare.invoke( left.evaluate( tableLookup, i ), right.evaluate( tableLookup, i ), true ); + return compareResult == -1 || compareResult == 0; case MINUS : - break; + ensureNumericOperands( tableLookup ); + return evalAsNumber( left, tableLookup, i ) - evalAsNumber( right, tableLookup, i ); case MODULO : - break; + ensureNumericOperands( tableLookup ); + return evalAsNumber( left, tableLookup, i ) % evalAsNumber( right, tableLookup, i ); case MULTIPLY : - break; + ensureNumericOperands( tableLookup ); + return evalAsNumber( left, tableLookup, i ) * evalAsNumber( right, tableLookup, i ); case NOTEQUAL : - break; + leftValue = left.evaluate( tableLookup, i ); + rightValue = right.evaluate( tableLookup, i ); + return !EqualsEquals.invoke( leftValue, rightValue, true ); + case AND : + ensureBooleanOperands( tableLookup ); + leftValue = left.evaluate( tableLookup, i ); + // Short circuit, don't eval right if left is false + if ( ( Boolean ) leftValue ) { + return ( Boolean ) right.evaluate( tableLookup, i ); + } else { + return false; + } case OR : - break; + ensureBooleanOperands( tableLookup ); + if ( ( Boolean ) left.evaluate( tableLookup, i ) ) { + return true; + } + if ( ( Boolean ) right.evaluate( tableLookup, i ) ) { + return true; + } + return false; case PLUS : + if ( left.isNumeric( tableLookup ) && right.isNumeric( tableLookup ) ) { + return evalAsNumber( left, tableLookup, i ) + evalAsNumber( right, tableLookup, i ); + } else { + return Concat.invoke( left.evaluate( tableLookup, i ), right.evaluate( tableLookup, i ) ); + } + case LIKE : break; + case NOTLIKE : + break; + case CONCAT : + return Concat.invoke( left.evaluate( tableLookup, i ), right.evaluate( tableLookup, i ) ); default : throw new BoxRuntimeException( "Unknown binary operator: " + operator ); } throw new UnsupportedOperationException( "Unimplemented binary operator: " + operator ); } + /** + * Reusable helper method to ensure that the left and right operands are boolean expressions or bit columns + * + * @return true if the left and right operands are boolean expressions or bit columns + */ + private void ensureBooleanOperands( Map tableLookup ) { + // These checks may or may not work. If we can't get away with this, then we can boolean cast the values + // but SQL doesn't really have the same concept of truthiness and mostly expects to always get booleans from boolean columns or boolean expressions + if ( !left.isBoolean( tableLookup ) ) { + throw new BoxRuntimeException( "Left side of a boolean [" + operator.getSymbol() + "] operation must be a boolean expression or bit column" ); + } + if ( !right.isBoolean( tableLookup ) ) { + throw new BoxRuntimeException( "Right side of a boolean [" + operator.getSymbol() + "] operation must be a boolean expression or bit column" ); + } + } + + /** + * Reusable helper method to ensure that the left and right operands are numeric expressions or numeric columns + */ + private void ensureNumericOperands( Map tableLookup ) { + if ( !left.isNumeric( tableLookup ) ) { + throw new BoxRuntimeException( "Left side of a math [" + operator.getSymbol() + "] operation must be a numeric expression or numeric column" ); + } + if ( !right.isNumeric( tableLookup ) ) { + throw new BoxRuntimeException( "Right side of a math [" + operator.getSymbol() + "] operation must be a numeric expression or numeric column" ); + } + } + + /** + * Helper for evaluating an expression as a number + * + * @param tableLookup + * @param expression + * @param i + * + * @return + */ + private double evalAsNumber( SQLExpression expression, Map tableLookup, int i ) { + return ( ( Number ) expression.evaluate( tableLookup, i ) ).doubleValue(); + } + @Override public void accept( VoidBoxVisitor v ) { // TODO Auto-generated method stub diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBinaryOperator.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBinaryOperator.java index 2768438c6..75aa6637e 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBinaryOperator.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBinaryOperator.java @@ -28,7 +28,10 @@ public enum SQLBinaryOperator { GREATERTHAN, GREATERTHANOREQUAL, AND, - OR; + OR, + LIKE, + NOTLIKE, + CONCAT; public String getSymbol() { switch ( this ) { @@ -58,6 +61,12 @@ public String getSymbol() { return "AND"; case OR : return "OR"; + case LIKE : + return "LIKE"; + case NOTLIKE : + return "NOT LIKE"; + case CONCAT : + return "||"; default : return ""; } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLInOperation.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLInOperation.java index 306a5822f..18c4dc777 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLInOperation.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLInOperation.java @@ -23,8 +23,8 @@ import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.operators.EqualsEquals; import ortus.boxlang.runtime.types.Query; -import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; /** * Abstract Node class representing SQL IN operation @@ -43,7 +43,7 @@ public class SQLInOperation extends SQLExpression { * @param position position of the statement in the source code * @param sourceText source code of the statement */ - public SQLInOperation( boolean not, SQLExpression expression, List values, Position position, String sourceText ) { + public SQLInOperation( SQLExpression expression, List values, boolean not, Position position, String sourceText ) { super( position, sourceText ); setExpression( expression ); setValues( values ); @@ -107,7 +107,13 @@ public boolean isBoolean() { * Evaluate the expression */ public Object evaluate( Map tableLookup, int i ) { - throw new BoxRuntimeException( "not implemented" ); + Object value = expression.evaluate( tableLookup, i ); + for ( SQLExpression v : values ) { + if ( EqualsEquals.invoke( value, v.evaluate( tableLookup, i ), true ) ) { + return !not; + } + } + return not; } @Override diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLUnaryOperation.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLUnaryOperation.java index ddf94d696..c4bba35ff 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLUnaryOperation.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLUnaryOperation.java @@ -35,6 +35,9 @@ public class SQLUnaryOperation extends SQLExpression { // NOT, ISNULL, ISNOTNULL private static final Set booleanOperators = Set.of( SQLUnaryOperator.NOT, SQLUnaryOperator.ISNULL, SQLUnaryOperator.ISNOTNULL ); + // math operators + private static final Set mathOperators = Set.of( SQLUnaryOperator.PLUS, SQLUnaryOperator.MINUS ); + private SQLExpression expression; private SQLUnaryOperator operator; @@ -96,17 +99,81 @@ public QueryColumnType getType( Map tableLookup ) { if ( isBoolean() ) { return QueryColumnType.BIT; } - if ( operator == SQLUnaryOperator.PLUS || operator == SQLUnaryOperator.MINUS ) { + if ( mathOperators.contains( operator ) ) { return QueryColumnType.DOUBLE; } return QueryColumnType.OBJECT; } + /** + * Runtime check if the expression evaluates to a numeric value and works for columns as well + * + * @param tableLookup lookup for tables + * + * @return true if the expression evaluates to a numeric value + */ + public boolean isNumeric( Map tableLookup ) { + return getType( tableLookup ) == QueryColumnType.DOUBLE; + } + /** * Evaluate the expression */ public Object evaluate( Map tableLookup, int i ) { - throw new BoxRuntimeException( "not implemented" ); + // Implement each unary operator + switch ( operator ) { + case ISNOTNULL : + return expression.evaluate( tableLookup, i ) != null; + case ISNULL : + return expression.evaluate( tableLookup, i ) != null; + case MINUS : + ensureNumericOperand( tableLookup ); + return -evalAsNumber( expression, tableLookup, i ); + case NOT : + ensureBooleanOperand( tableLookup ); + return ! ( ( boolean ) expression.evaluate( tableLookup, i ) ); + case PLUS : + ensureNumericOperand( tableLookup ); + return expression.evaluate( tableLookup, i ); + default : + throw new BoxRuntimeException( "Unknown binary operator: " + operator ); + + } + } + + /** + * Reusable helper method to ensure that the left and right operands are boolean expressions or bit columns + * + * @return true if the left and right operands are boolean expressions or bit columns + */ + private void ensureBooleanOperand( Map tableLookup ) { + // These checks may or may not work. If we can't get away with this, then we can boolean cast the values + // but SQL doesn't really have the same concept of truthiness and mostly expects to always get booleans from boolean columns or boolean expressions + if ( !expression.isBoolean( tableLookup ) ) { + throw new BoxRuntimeException( "Unary operation [" + operator.getSymbol() + "] must be a boolean expression or bit column" ); + } + } + + /** + * Reusable helper method to ensure that the left and right operands are numeric expressions or numeric columns + */ + private void ensureNumericOperand( Map tableLookup ) { + if ( !expression.isNumeric( tableLookup ) ) { + throw new BoxRuntimeException( "Unary operation [" + operator.getSymbol() + "] must be a numeric expression or numeric column" ); + } + } + + /** + * Helper for evaluating an expression as a number + * + * @param tableLookup + * @param expression + * @param i + * + * @return + */ + private double evalAsNumber( SQLExpression expression, Map tableLookup, int i ) { + return ( ( Number ) expression.evaluate( tableLookup, i ) ).doubleValue(); } @Override diff --git a/src/main/java/ortus/boxlang/compiler/parser/ParserErrorStrategy.java b/src/main/java/ortus/boxlang/compiler/parser/ParserErrorStrategy.java index f334e1800..c2665f903 100644 --- a/src/main/java/ortus/boxlang/compiler/parser/ParserErrorStrategy.java +++ b/src/main/java/ortus/boxlang/compiler/parser/ParserErrorStrategy.java @@ -1,10 +1,15 @@ package ortus.boxlang.compiler.parser; -import org.antlr.v4.runtime.*; -import org.antlr.v4.runtime.misc.IntervalSet; - import java.util.Comparator; +import org.antlr.v4.runtime.DefaultErrorStrategy; +import org.antlr.v4.runtime.InputMismatchException; +import org.antlr.v4.runtime.NoViableAltException; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.TokenStream; +import org.antlr.v4.runtime.misc.IntervalSet; + public abstract class ParserErrorStrategy extends DefaultErrorStrategy { @Override diff --git a/src/main/java/ortus/boxlang/compiler/toolchain/SQLVisitor.java b/src/main/java/ortus/boxlang/compiler/toolchain/SQLVisitor.java index 85a6f8a36..2d447bb46 100644 --- a/src/main/java/ortus/boxlang/compiler/toolchain/SQLVisitor.java +++ b/src/main/java/ortus/boxlang/compiler/toolchain/SQLVisitor.java @@ -5,6 +5,7 @@ import org.antlr.v4.runtime.tree.TerminalNode; import ortus.boxlang.compiler.ast.BoxNode; +import ortus.boxlang.compiler.ast.Position; import ortus.boxlang.compiler.ast.sql.select.SQLJoin; import ortus.boxlang.compiler.ast.sql.select.SQLResultColumn; import ortus.boxlang.compiler.ast.sql.select.SQLSelect; @@ -13,13 +14,18 @@ import ortus.boxlang.compiler.ast.sql.select.expression.SQLColumn; import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.sql.select.expression.SQLOrderBy; +import ortus.boxlang.compiler.ast.sql.select.expression.SQLParenthesis; import ortus.boxlang.compiler.ast.sql.select.expression.SQLStarExpression; import ortus.boxlang.compiler.ast.sql.select.expression.literal.SQLBooleanLiteral; import ortus.boxlang.compiler.ast.sql.select.expression.literal.SQLNullLiteral; import ortus.boxlang.compiler.ast.sql.select.expression.literal.SQLNumberLiteral; import ortus.boxlang.compiler.ast.sql.select.expression.literal.SQLStringLiteral; +import ortus.boxlang.compiler.ast.sql.select.expression.operation.SQLBetweenOperation; import ortus.boxlang.compiler.ast.sql.select.expression.operation.SQLBinaryOperation; import ortus.boxlang.compiler.ast.sql.select.expression.operation.SQLBinaryOperator; +import ortus.boxlang.compiler.ast.sql.select.expression.operation.SQLInOperation; +import ortus.boxlang.compiler.ast.sql.select.expression.operation.SQLUnaryOperation; +import ortus.boxlang.compiler.ast.sql.select.expression.operation.SQLUnaryOperator; import ortus.boxlang.compiler.parser.SQLParser; import ortus.boxlang.parser.antlr.SQLGrammar.ExprContext; import ortus.boxlang.parser.antlr.SQLGrammar.Literal_valueContext; @@ -311,14 +317,76 @@ public SQLExpression visitExpr( ExprContext ctx, SQLTable table, List j return new SQLColumn( tableRef, ctx.column_name().getText(), pos, src ); } else if ( ctx.literal_value() != null ) { return ( SQLExpression ) visit( ctx.literal_value() ); - } else if ( ctx.EQ() != null || ctx.ASSIGN() != null ) { - return new SQLBinaryOperation( visitExpr( ctx.expr( 0 ), table, joins ), visitExpr( ctx.expr( 1 ), table, joins ), SQLBinaryOperator.EQUAL, pos, - src ); + } else if ( ctx.EQ() != null || ctx.ASSIGN() != null || ctx.IS_() != null ) { + // IS vs IS NOT + SQLBinaryOperator operator = ctx.NOT_() != null ? SQLBinaryOperator.NOTEQUAL : SQLBinaryOperator.EQUAL; + return binarySimple( ctx.expr( 0 ), ctx.expr( 1 ), operator, pos, src, table, joins ); + } else if ( ctx.BETWEEN_() != null ) { + return new SQLBetweenOperation( visitExpr( ctx.expr( 0 ), table, joins ), visitExpr( ctx.expr( 1 ), table, joins ), + visitExpr( ctx.expr( 2 ), table, joins ), ctx.NOT_() != null, pos, src ); + } else if ( ctx.AND_() != null ) { + // Needs to run AFTER between checks + return binarySimple( ctx.expr( 0 ), ctx.expr( 1 ), SQLBinaryOperator.AND, pos, src, table, joins ); + } else if ( ctx.OR_() != null ) { + return binarySimple( ctx.expr( 0 ), ctx.expr( 1 ), SQLBinaryOperator.OR, pos, src, table, joins ); + } else if ( ctx.function_name() != null ) { + throw new UnsupportedOperationException( "functions Unimplemented: " + src ); + } else if ( ctx.BIND_PARAMETER() != null ) { + throw new UnsupportedOperationException( "bind parameters unimplemented: " + src ); + } else if ( ctx.IN_() != null ) { + SQLExpression expr = visitExpr( ctx.expr( 0 ), table, joins ); + List values = ctx.expr().stream().skip( 1 ).map( e -> visitExpr( e, table, joins ) ).toList(); + return new SQLInOperation( expr, values, ctx.NOT_() != null, pos, src ); + } else if ( ctx.LIKE_() != null ) { + return binarySimple( ctx.expr( 0 ), ctx.expr( 1 ), ctx.NOT_() != null ? SQLBinaryOperator.NOTLIKE : SQLBinaryOperator.LIKE, pos, src, table, + joins ); + } else if ( ctx.PIPE2() != null ) { + return binarySimple( ctx.expr( 0 ), ctx.expr( 1 ), SQLBinaryOperator.CONCAT, pos, src, table, joins ); + } else if ( ctx.STAR() != null ) { + return binarySimple( ctx.expr( 0 ), ctx.expr( 1 ), SQLBinaryOperator.MULTIPLY, pos, src, table, joins ); + } else if ( ctx.DIV() != null ) { + return binarySimple( ctx.expr( 0 ), ctx.expr( 1 ), SQLBinaryOperator.DIVIDE, pos, src, table, joins ); + } else if ( ctx.MOD() != null ) { + return binarySimple( ctx.expr( 0 ), ctx.expr( 1 ), SQLBinaryOperator.MODULO, pos, src, table, joins ); + } else if ( ctx.PLUS() != null ) { + return binarySimple( ctx.expr( 0 ), ctx.expr( 1 ), SQLBinaryOperator.PLUS, pos, src, table, joins ); + } else if ( ctx.MINUS() != null ) { + return binarySimple( ctx.expr( 0 ), ctx.expr( 1 ), SQLBinaryOperator.MINUS, pos, src, table, joins ); + } else if ( ctx.LT() != null ) { + return binarySimple( ctx.expr( 0 ), ctx.expr( 1 ), SQLBinaryOperator.LESSTHAN, pos, src, table, joins ); + } else if ( ctx.LT_EQ() != null ) { + return binarySimple( ctx.expr( 0 ), ctx.expr( 1 ), SQLBinaryOperator.LESSTHANOREQUAL, pos, src, table, joins ); + } else if ( ctx.GT() != null ) { + return binarySimple( ctx.expr( 0 ), ctx.expr( 1 ), SQLBinaryOperator.GREATERTHAN, pos, src, table, joins ); + } else if ( ctx.GT_EQ() != null ) { + return binarySimple( ctx.expr( 0 ), ctx.expr( 1 ), SQLBinaryOperator.GREATERTHANOREQUAL, pos, src, table, joins ); + } else if ( ctx.NOT_EQ1() != null || ctx.NOT_EQ2() != null ) { + return binarySimple( ctx.expr( 0 ), ctx.expr( 1 ), SQLBinaryOperator.NOTEQUAL, pos, src, table, joins ); + } else if ( ctx.OPEN_PAR() != null ) { + // Needs to run AFTER function and IN checks + return new SQLParenthesis( visitExpr( ctx.expr( 0 ), table, joins ), pos, src ); + } else if ( ctx.unary_operator() != null ) { + SQLUnaryOperator op; + if ( ctx.unary_operator().BANG() != null ) { + op = SQLUnaryOperator.NOT; + } else if ( ctx.unary_operator().MINUS() != null ) { + op = SQLUnaryOperator.MINUS; + } else if ( ctx.unary_operator().PLUS() != null ) { + op = SQLUnaryOperator.PLUS; + } else { + throw new UnsupportedOperationException( "Unimplemented unary operator: " + ctx.unary_operator().getText() ); + } + return new SQLUnaryOperation( visitExpr( ctx.expr( 0 ), table, joins ), op, pos, src ); } else { throw new UnsupportedOperationException( "Unimplemented expression: " + src ); } } + private SQLExpression binarySimple( ExprContext left, ExprContext right, SQLBinaryOperator op, Position pos, String src, SQLTable table, + List joins ) { + return new SQLBinaryOperation( visitExpr( left, table, joins ), visitExpr( right, table, joins ), op, pos, src ); + } + /** * Visit the class or interface context to generate the AST node for the * top level node diff --git a/src/main/java/ortus/boxlang/runtime/operators/Compare.java b/src/main/java/ortus/boxlang/runtime/operators/Compare.java index 618a4f936..9679d1031 100644 --- a/src/main/java/ortus/boxlang/runtime/operators/Compare.java +++ b/src/main/java/ortus/boxlang/runtime/operators/Compare.java @@ -57,7 +57,7 @@ public static int invoke( Object left, Object right ) { * @param right The right operand * @param caseSensitive Whether to compare strings case sensitive * - * @return 1 if greater than, -1 if less than, = if equal + * @return 1 if greater than, -1 if less than, 0 if equal */ public static int invoke( Object left, Object right, Boolean caseSensitive ) { return attempt( left, right, caseSensitive, true ); @@ -71,7 +71,7 @@ public static int invoke( Object left, Object right, Boolean caseSensitive ) { * @param caseSensitive Whether to compare strings case sensitive * @param fail True to throw an exception if the left and right arguments cannot be compared * - * @return 1 if greater than, -1 if less than, = if equal + * @return 1 if greater than, -1 if less than, 0 if equal */ public static Integer attempt( Object left, Object right, Boolean caseSensitive, boolean fail ) { return attempt( left, right, caseSensitive, fail, Locale.US ); @@ -86,7 +86,7 @@ public static Integer attempt( Object left, Object right, Boolean caseSensitive, * @param fail True to throw an exception if the left and right arguments cannot be compared * @param locale The locale to use for comparison * - * @return 1 if greater than, -1 if less than, = if equal + * @return 1 if greater than, -1 if less than, 0 if equal */ @SuppressWarnings( "unchecked" ) public static Integer attempt( Object left, Object right, Boolean caseSensitive, boolean fail, Locale locale ) { From f78dedc73bd876a504665c734f2cb2e354ea6497 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Wed, 11 Dec 2024 16:17:45 +0100 Subject: [PATCH 174/193] no more logging --- .../ortus/boxlang/runtime/loader/resolvers/BoxResolver.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java b/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java index 9d1197fc8..3f8dacbbe 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java +++ b/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java @@ -318,7 +318,7 @@ private Optional findByMapping( // System.out.println( "mappings: " + mappings ); // System.out.println( "slashName: " + slashName ); - getLogger().trace( "Resolving [{}] against mappings: [{}]", slashName, mappings ); + // getLogger().trace( "Resolving [{}] against mappings: [{}]", slashName, mappings ); // Maybe if we have > 20 mappings we should use parallel streams From d86d7d5dd0b70abb5210bb8b78a3526ad76d51db Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 11 Dec 2024 10:41:30 -0600 Subject: [PATCH 175/193] BL-841 --- .../boxlang/runtime/types/exceptions/ExceptionUtil.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/types/exceptions/ExceptionUtil.java b/src/main/java/ortus/boxlang/runtime/types/exceptions/ExceptionUtil.java index e4aea84ee..3a1648844 100644 --- a/src/main/java/ortus/boxlang/runtime/types/exceptions/ExceptionUtil.java +++ b/src/main/java/ortus/boxlang/runtime/types/exceptions/ExceptionUtil.java @@ -280,7 +280,10 @@ private static String getSurroudingLinesOfCode( String fileName, int lineNo, boo StringBuilder codeSnippet = new StringBuilder(); for ( int i = startLine; i <= endLine; i++ ) { - String theLine = StringEscapeUtils.escapeHtml4( lines.get( i - 1 ) ); + String theLine = lines.get( i - 1 ); + if ( html ) { + theLine = StringEscapeUtils.escapeHtml4( theLine ); + } if ( i == lineNo && html ) { codeSnippet.append( "" ).append( i ).append( ": " ).append( theLine ).append( "" ).append( "
    " ); } else { From 4720ed90229aecfcb6e47e946ebb4a9dfa39ecbb Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 11 Dec 2024 16:29:02 -0600 Subject: [PATCH 176/193] BL-823 --- src/main/antlr/SQLGrammar.g4 | 4 +- .../compiler/ast/sql/select/SQLJoin.java | 2 +- .../compiler/ast/sql/select/SQLSelect.java | 4 +- .../ast/sql/select/expression/SQLColumn.java | 35 +- .../select/expression/SQLCountFunction.java | 7 +- .../sql/select/expression/SQLExpression.java | 28 +- .../sql/select/expression/SQLFunction.java | 53 ++- .../ast/sql/select/expression/SQLParam.java | 148 +++++++ .../sql/select/expression/SQLParenthesis.java | 30 +- .../select/expression/SQLStarExpression.java | 4 +- .../expression/literal/SQLBooleanLiteral.java | 13 +- .../expression/literal/SQLNullLiteral.java | 5 +- .../expression/literal/SQLNumberLiteral.java | 7 +- .../expression/literal/SQLStringLiteral.java | 7 +- .../operation/SQLBetweenOperation.java | 19 +- .../operation/SQLBinaryOperation.java | 160 ++++--- .../expression/operation/SQLInOperation.java | 17 +- .../operation/SQLUnaryOperation.java | 51 ++- .../compiler/toolchain/SQLVisitor.java | 36 +- .../runtime/jdbc/qoq/LikeOperation.java | 141 ++++++ .../runtime/jdbc/qoq/QoQConnection.java | 4 +- ...QService.java => QoQExecutionService.java} | 39 +- .../runtime/jdbc/qoq/QoQFunctionService.java | 71 +++ .../jdbc/qoq/QoQPreparedStatement.java | 416 ++++++++++++++++++ .../runtime/jdbc/qoq/QoQStatement.java | 12 +- .../ortus/boxlang/compiler/QoQParseTest.java | 22 +- 26 files changed, 1118 insertions(+), 217 deletions(-) create mode 100644 src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLParam.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/LikeOperation.java rename src/main/java/ortus/boxlang/runtime/jdbc/qoq/{QoQService.java => QoQExecutionService.java} (83%) create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQFunctionService.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQPreparedStatement.java diff --git a/src/main/antlr/SQLGrammar.g4 b/src/main/antlr/SQLGrammar.g4 index d91dcb7f5..32fa9c279 100644 --- a/src/main/antlr/SQLGrammar.g4 +++ b/src/main/antlr/SQLGrammar.g4 @@ -860,6 +860,6 @@ table_function_name: any_name: IDENTIFIER | keyword - | STRING_LITERAL - | OPEN_PAR any_name CLOSE_PAR + // | STRING_LITERAL + //| OPEN_PAR any_name CLOSE_PAR ; \ No newline at end of file diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLJoin.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLJoin.java index 8eed69023..243d17705 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLJoin.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLJoin.java @@ -86,7 +86,7 @@ public SQLExpression getOn() { * Set the ON expression */ public void setOn( SQLExpression on ) { - if ( !on.isBoolean() ) { + if ( !on.isBoolean( null ) ) { throw new BoxRuntimeException( "ON clause must be a boolean expression" ); } replaceChildren( this.on, on ); diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLSelect.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLSelect.java index d6728f4a6..b74dca64d 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLSelect.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLSelect.java @@ -114,7 +114,7 @@ public void setJoins( List joins ) { * Set the WHERE node */ public void setWhere( SQLExpression where ) { - if ( where != null && !where.isBoolean() ) { + if ( where != null && !where.isBoolean( null ) ) { throw new BoxRuntimeException( "WHERE clause must be a boolean expression" ); } replaceChildren( this.where, where ); @@ -139,7 +139,7 @@ public void setGroupBys( List groupBys ) { * Set the HAVING node */ public void setHaving( SQLExpression having ) { - if ( having != null && !having.isBoolean() ) { + if ( having != null && !having.isBoolean( null ) ) { throw new BoxRuntimeException( "HAVING clause must be a boolean expression" ); } replaceChildren( this.having, having ); diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLColumn.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLColumn.java index be040eb9e..d9ab3bca8 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLColumn.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLColumn.java @@ -15,14 +15,15 @@ package ortus.boxlang.compiler.ast.sql.select.expression; import java.util.Map; +import java.util.Set; import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; import ortus.boxlang.runtime.scopes.Key; -import ortus.boxlang.runtime.types.Query; import ortus.boxlang.runtime.types.QueryColumnType; /** @@ -30,9 +31,12 @@ */ public class SQLColumn extends SQLExpression { - private SQLTable table; + private final static Set numericTypes = Set.of( QueryColumnType.BIGINT, QueryColumnType.DECIMAL, QueryColumnType.DOUBLE, + QueryColumnType.INTEGER, QueryColumnType.BIT ); - private Key name; + private SQLTable table; + + private Key name; /** * Constructor @@ -82,26 +86,37 @@ public void setTable( SQLTable table ) { /** * What type does this expression evaluate to */ - public QueryColumnType getType( Map tableLookup ) { - return tableLookup.get( table ).getColumns().get( name ).getType(); + public QueryColumnType getType( QoQExecution QoQExec ) { + return QoQExec.tableLookup().get( table ).getColumns().get( name ).getType(); } /** * Evaluate the expression */ - public Object evaluate( Map tableLookup, int i ) { - return tableLookup.get( table ).getCell( name, i - 1 ); + public Object evaluate( QoQExecution QoQExec, int i ) { + return QoQExec.tableLookup().get( table ).getCell( name, i - 1 ); } /** * Runtime check if the expression evaluates to a boolean value and works for columns as well * - * @param tableLookup lookup for tables + * @param QoQExec Query execution state * * @return true if the expression evaluates to a boolean value */ - public boolean isBoolean( Map tableLookup ) { - return getType( tableLookup ) == QueryColumnType.BIT; + public boolean isBoolean( QoQExecution QoQExec ) { + return getType( QoQExec ) == QueryColumnType.BIT; + } + + /** + * Runtime check if the expression evaluates to a numeric value and works for columns as well + * + * @param QoQExec Query execution state + * + * @return true if the expression evaluates to a numeric value + */ + public boolean isNumeric( QoQExecution QoQExec ) { + return numericTypes.contains( getType( QoQExec ) ); } @Override diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLCountFunction.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLCountFunction.java index 913c335b5..fcf294bcc 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLCountFunction.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLCountFunction.java @@ -22,6 +22,7 @@ import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.Query; import ortus.boxlang.runtime.types.QueryColumnType; @@ -38,7 +39,7 @@ public class SQLCountFunction extends SQLFunction { * @param position position of the statement in the source code * @param sourceText source code of the statement */ - public SQLCountFunction( String name, List arguments, boolean distinct, Position position, String sourceText ) { + public SQLCountFunction( Key name, List arguments, boolean distinct, Position position, String sourceText ) { super( name, arguments, position, sourceText ); setDistinct( distinct ); } @@ -67,7 +68,7 @@ public QueryColumnType getType( Map tableLookup ) { /** * Runtime check if the expression evaluates to a numeric value and works for columns as well * - * @param tableLookup lookup for tables + * @param QoQExec Query execution state * * @return true if the expression evaluates to a numeric value */ @@ -92,7 +93,7 @@ public Map toMap() { Map map = super.toMap(); map.put( "distinct", isDistinct() ); - map.put( "name", getName() ); + map.put( "name", getName().getName() ); map.put( "arguments", getArguments().stream().map( BoxNode::toMap ).toList() ); return map; } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLExpression.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLExpression.java index f9e48439e..0555c868e 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLExpression.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLExpression.java @@ -14,12 +14,9 @@ */ package ortus.boxlang.compiler.ast.sql.select.expression; -import java.util.Map; - import ortus.boxlang.compiler.ast.Position; import ortus.boxlang.compiler.ast.sql.SQLNode; -import ortus.boxlang.compiler.ast.sql.select.SQLTable; -import ortus.boxlang.runtime.types.Query; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; import ortus.boxlang.runtime.types.QueryColumnType; /** @@ -44,40 +41,33 @@ public boolean isLiteral() { return false; } - /** - * Check if the expression evaluates to a boolean value - */ - public boolean isBoolean() { - return false; - } - /** * Runtime check if the expression evaluates to a boolean value and works for columns as well * - * @param tableLookup lookup for tables + * @param QoQExec Query execution state * * @return true if the expression evaluates to a boolean value */ - public boolean isBoolean( Map tableLookup ) { - return isBoolean(); + public boolean isBoolean( QoQExecution QoQExec ) { + return false; } /** * Runtime check if the expression evaluates to a numeric value and works for columns as well * - * @param tableLookup lookup for tables + * @param QoQExec Query execution state * * @return true if the expression evaluates to a numeric value */ - public boolean isNumeric( Map tableLookup ) { + public boolean isNumeric( QoQExecution QoQExec ) { return false; } /** * What type does this expression evaluate to */ - public QueryColumnType getType( Map tableLookup ) { - if ( isBoolean() ) { + public QueryColumnType getType( QoQExecution QoQExec ) { + if ( isBoolean( QoQExec ) ) { return QueryColumnType.BIT; } return QueryColumnType.OBJECT; @@ -86,6 +76,6 @@ public QueryColumnType getType( Map tableLookup ) { /** * Evaluate the expression */ - public abstract Object evaluate( Map tableLookup, int i ); + public abstract Object evaluate( QoQExecution QoQExec, int i ); } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLFunction.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLFunction.java index f06e6e1ef..64aeb4b89 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLFunction.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLFunction.java @@ -16,24 +16,28 @@ import java.util.List; import java.util.Map; +import java.util.Set; import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; -import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; -import ortus.boxlang.runtime.types.Query; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; +import ortus.boxlang.runtime.jdbc.qoq.QoQFunctionService; +import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.QueryColumnType; -import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; /** * Abstract Node class representing SQL function call */ public class SQLFunction extends SQLExpression { - private String name; + private final static Set numericTypes = Set.of( QueryColumnType.BIGINT, QueryColumnType.DECIMAL, QueryColumnType.DOUBLE, + QueryColumnType.INTEGER, QueryColumnType.BIT ); - private List arguments; + private Key name; + + private List arguments; /** * Constructor @@ -41,7 +45,7 @@ public class SQLFunction extends SQLExpression { * @param position position of the statement in the source code * @param sourceText source code of the statement */ - public SQLFunction( String name, List arguments, Position position, String sourceText ) { + public SQLFunction( Key name, List arguments, Position position, String sourceText ) { super( position, sourceText ); setName( name ); setArguments( arguments ); @@ -52,7 +56,7 @@ public SQLFunction( String name, List arguments, Position positio * * @return the name of the function */ - public String getName() { + public Key getName() { return name; } @@ -61,7 +65,7 @@ public String getName() { * * @param name the name of the function */ - public void setName( String name ) { + public void setName( Key name ) { this.name = name; } @@ -86,26 +90,39 @@ public void setArguments( List arguments ) { } /** - * Check if the expression evaluates to a boolean value + * Runtime check if the expression evaluates to a boolean value and works for columns as well + * + * @param QoQExec Query execution state + * + * @return true if the expression evaluates to a boolean value + */ + public boolean isBoolean( QoQExecution QoQExec ) { + return getType( QoQExec ) == QueryColumnType.BIT; + } + + /** + * Runtime check if the expression evaluates to a numeric value and works for columns as well + * + * @param QoQExec Query execution state + * + * @return true if the expression evaluates to a numeric value */ - public boolean isBoolean() { - // TODO implement based on name of function - return false; + public boolean isNumeric( QoQExecution QoQExec ) { + return numericTypes.contains( getType( QoQExec ) ); } /** * What type does this expression evaluate to */ - public QueryColumnType getType( Map tableLookup ) { - // TODO: actually return proper type based on the function in question - return QueryColumnType.OBJECT; + public QueryColumnType getType( QoQExecution QoQExec ) { + return QoQFunctionService.getFunction( name ).getReturnType(); } /** * Evaluate the expression */ - public Object evaluate( Map tableLookup, int i ) { - throw new BoxRuntimeException( "not implemented" ); + public Object evaluate( QoQExecution QoQExec, int i ) { + return QoQFunctionService.getFunction( name ).invoke( arguments.stream().map( a -> a.evaluate( QoQExec, i ) ).toList() ); } @Override @@ -124,7 +141,7 @@ public BoxNode accept( ReplacingBoxVisitor v ) { public Map toMap() { Map map = super.toMap(); - map.put( "name", getName() ); + map.put( "name", getName().getName() ); map.put( "arguments", getArguments().stream().map( BoxNode::toMap ).toList() ); return map; } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLParam.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLParam.java new file mode 100644 index 000000000..2b3429aad --- /dev/null +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLParam.java @@ -0,0 +1,148 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.compiler.ast.sql.select.expression; + +import java.util.Map; +import java.util.Set; + +import ortus.boxlang.compiler.ast.BoxNode; +import ortus.boxlang.compiler.ast.Position; +import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; +import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; +import ortus.boxlang.runtime.types.QueryColumnType; + +/** + * Abstract Node class representing SQL Param expression + */ +public class SQLParam extends SQLExpression { + + private final static Set numericTypes = Set.of( QueryColumnType.BIGINT, QueryColumnType.DECIMAL, QueryColumnType.DOUBLE, + QueryColumnType.INTEGER, QueryColumnType.BIT ); + + /** + * Null if positinal + */ + private String name; + + // JDBC uses 1-based indexes! + private int index = 0; + + /** + * Constructor. Index is 1-based! + * + * @param position position of the statement in the source code + * @param sourceText source code of the statement + */ + public SQLParam( String name, int index, Position position, String sourceText ) { + super( position, sourceText ); + setName( name ); + setIndex( index ); + } + + /** + * Get the name of the function + * + * @return the name of the function + */ + public String getName() { + return name; + } + + /** + * Set the name of the function + * + * @param name the name of the function + */ + public void setName( String name ) { + this.name = name; + } + + /** + * Get the index. 1-based! + */ + public int getIndex() { + return index; + } + + /** + * Set the index. 1-based! + */ + public void setIndex( int index ) { + this.index = index; + } + + /** + * Runtime check if the expression evaluates to a boolean value and works for columns as well + * + * @param QoQExec Query execution state + * + * @return true if the expression evaluates to a boolean value + */ + public boolean isBoolean( QoQExecution QoQExec ) { + return getType( QoQExec ) == QueryColumnType.BIT; + } + + /** + * Runtime check if the expression evaluates to a numeric value and works for columns as well + * + * @param QoQExec Query execution state + * + * @return true if the expression evaluates to a numeric value + */ + public boolean isNumeric( QoQExecution QoQExec ) { + return numericTypes.contains( getType( QoQExec ) ); + } + + /** + * What type does this expression evaluate to + */ + public QueryColumnType getType( QoQExecution QoQExec ) { + return QueryColumnType.fromSQLType( QoQExec.params().get( index ).type() ); + } + + /** + * Evaluate the expression + */ + public Object evaluate( QoQExecution QoQExec, int i ) { + return QoQExec.params().get( index ).value(); + } + + @Override + public void accept( VoidBoxVisitor v ) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'accept'" ); + } + + @Override + public BoxNode accept( ReplacingBoxVisitor v ) { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'accept'" ); + } + + @Override + public Map toMap() { + Map map = super.toMap(); + + if ( name != null ) { + map.put( "name", name ); + } else { + map.put( "name", null ); + } + map.put( "index", index ); + return map; + } + +} diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLParenthesis.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLParenthesis.java index 7562faee1..277d1ad92 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLParenthesis.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLParenthesis.java @@ -18,10 +18,9 @@ import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; -import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; -import ortus.boxlang.runtime.types.Query; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; import ortus.boxlang.runtime.types.QueryColumnType; /** @@ -58,47 +57,40 @@ public void setExpression( SQLExpression expression ) { this.expression.setParent( this ); } - /** - * Check if the expression evaluates to a boolean value - */ - public boolean isBoolean() { - return expression.isBoolean(); - } - /** * Runtime check if the expression evaluates to a boolean value and works for columns as well * - * @param tableLookup lookup for tables + * @param QoQExec Query execution state * * @return true if the expression evaluates to a boolean value */ - public boolean isBoolean( Map tableLookup ) { - return expression.isBoolean( tableLookup ); + public boolean isBoolean( QoQExecution QoQExec ) { + return expression.isBoolean( QoQExec ); } /** * Runtime check if the expression evaluates to a numeric value and works for columns as well * - * @param tableLookup lookup for tables + * @param QoQExec Query execution state * * @return true if the expression evaluates to a numeric value */ - public boolean isNumeric( Map tableLookup ) { - return expression.isNumeric( tableLookup ); + public boolean isNumeric( QoQExecution QoQExec ) { + return expression.isNumeric( QoQExec ); } /** * What type does this expression evaluate to */ - public QueryColumnType getType( Map tableLookup ) { - return expression.getType( tableLookup ); + public QueryColumnType getType( QoQExecution QoQExec ) { + return expression.getType( QoQExec ); } /** * Evaluate the expression */ - public Object evaluate( Map tableLookup, int i ) { - return expression.evaluate( tableLookup, i ); + public Object evaluate( QoQExecution QoQExec, int i ) { + return expression.evaluate( QoQExec, i ); } @Override diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLStarExpression.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLStarExpression.java index cde8d7f4c..6a36b52e4 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLStarExpression.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLStarExpression.java @@ -21,7 +21,7 @@ import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; -import ortus.boxlang.runtime.types.Query; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; /** @@ -60,7 +60,7 @@ public void setTable( SQLTable table ) { /** * Evaluate the expression */ - public Object evaluate( Map tableLookup, int i ) { + public Object evaluate( QoQExecution QoQExec, int i ) { throw new BoxRuntimeException( "Cannot evaluate a * expression" ); } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLBooleanLiteral.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLBooleanLiteral.java index 4d2c870bc..a9bb8b981 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLBooleanLiteral.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLBooleanLiteral.java @@ -18,11 +18,10 @@ import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; -import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; -import ortus.boxlang.runtime.types.Query; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; /** * Abstract Node class representing SQL boolean @@ -63,16 +62,20 @@ public boolean isLiteral() { } /** - * Check if the expression evaluates to a boolean value + * Runtime check if the expression evaluates to a boolean value and works for columns as well + * + * @param QoQExec Query execution state + * + * @return true if the expression evaluates to a boolean value */ - public boolean isBoolean() { + public boolean isBoolean( QoQExecution QoQExec ) { return true; } /** * Evaluate the expression */ - public Object evaluate( Map tableLookup, int i ) { + public Object evaluate( QoQExecution QoQExec, int i ) { return value; } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNullLiteral.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNullLiteral.java index 51f8f09e6..4d0ff98fa 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNullLiteral.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNullLiteral.java @@ -18,11 +18,10 @@ import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; -import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; -import ortus.boxlang.runtime.types.Query; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; /** * Abstract Node class representing SQL null @@ -49,7 +48,7 @@ public boolean isLiteral() { /** * Evaluate the expression */ - public Object evaluate( Map tableLookup, int i ) { + public Object evaluate( QoQExecution QoQExec, int i ) { return null; } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNumberLiteral.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNumberLiteral.java index d9669b5c9..6e05cbc83 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNumberLiteral.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNumberLiteral.java @@ -22,6 +22,7 @@ import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; import ortus.boxlang.runtime.types.Query; import ortus.boxlang.runtime.types.QueryColumnType; @@ -66,21 +67,21 @@ public boolean isLiteral() { /** * What type does this expression evaluate to */ - public QueryColumnType getType( Map tableLookup ) { + public QueryColumnType getType( QoQExecution QoQExec ) { return QueryColumnType.DOUBLE; } /** * Evaluate the expression */ - public Object evaluate( Map tableLookup, int i ) { + public Object evaluate( QoQExecution QoQExec, int i ) { return value; } /** * Runtime check if the expression evaluates to a numeric value and works for columns as well * - * @param tableLookup lookup for tables + * @param QoQExec Query execution state * * @return true if the expression evaluates to a numeric value */ diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLStringLiteral.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLStringLiteral.java index 0a5773a14..ff4950590 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLStringLiteral.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLStringLiteral.java @@ -18,11 +18,10 @@ import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; -import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; -import ortus.boxlang.runtime.types.Query; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; import ortus.boxlang.runtime.types.QueryColumnType; /** @@ -66,14 +65,14 @@ public boolean isLiteral() { /** * What type does this expression evaluate to */ - public QueryColumnType getType( Map tableLookup ) { + public QueryColumnType getType( QoQExecution QoQExec ) { return QueryColumnType.VARCHAR; } /** * Evaluate the expression */ - public Object evaluate( Map tableLookup, int i ) { + public Object evaluate( QoQExecution QoQExec, int i ) { return value; } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBetweenOperation.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBetweenOperation.java index ef023a6cb..5ed6efc3d 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBetweenOperation.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBetweenOperation.java @@ -18,12 +18,11 @@ import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; -import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; import ortus.boxlang.runtime.operators.Compare; -import ortus.boxlang.runtime.types.Query; /** * Abstract Node class representing SQL BETWEEN operation @@ -115,19 +114,23 @@ public void setNot( boolean not ) { } /** - * Check if the expression evaluates to a boolean value + * Runtime check if the expression evaluates to a boolean value and works for columns as well + * + * @param QoQExec Query execution state + * + * @return true if the expression evaluates to a boolean value */ - public boolean isBoolean() { + public boolean isBoolean( QoQExecution QoQExec ) { return true; } /** * Evaluate the expression */ - public Object evaluate( Map tableLookup, int i ) { - Object leftValue = left.evaluate( tableLookup, i ); - Object rightValue = right.evaluate( tableLookup, i ); - Object expressionValue = expression.evaluate( tableLookup, i ); + public Object evaluate( QoQExecution QoQExec, int i ) { + Object leftValue = left.evaluate( QoQExec, i ); + Object rightValue = right.evaluate( QoQExec, i ); + Object expressionValue = expression.evaluate( QoQExec, i ); // The ^ not inverses the result if the not flag is true return doBetween( leftValue, rightValue, expressionValue ) ^ not; } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBinaryOperation.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBinaryOperation.java index 86dac959c..6c579263f 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBinaryOperation.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBinaryOperation.java @@ -19,14 +19,15 @@ import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; -import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.dynamic.casters.StringCaster; +import ortus.boxlang.runtime.jdbc.qoq.LikeOperation; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; import ortus.boxlang.runtime.operators.Compare; import ortus.boxlang.runtime.operators.Concat; import ortus.boxlang.runtime.operators.EqualsEquals; -import ortus.boxlang.runtime.types.Query; import ortus.boxlang.runtime.types.QueryColumnType; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; @@ -37,7 +38,8 @@ public class SQLBinaryOperation extends SQLExpression { // EQUAL, NOTEQUAL, LESSTHAN, LESSTHANOREQUAL, GREATERTHAN, GREATERTHANOREQUAL, AND, OR private static final Set booleanOperators = Set.of( SQLBinaryOperator.EQUAL, SQLBinaryOperator.NOTEQUAL, SQLBinaryOperator.LESSTHAN, - SQLBinaryOperator.LESSTHANOREQUAL, SQLBinaryOperator.GREATERTHAN, SQLBinaryOperator.GREATERTHANOREQUAL, SQLBinaryOperator.AND, SQLBinaryOperator.OR ); + SQLBinaryOperator.LESSTHANOREQUAL, SQLBinaryOperator.GREATERTHAN, SQLBinaryOperator.GREATERTHANOREQUAL, SQLBinaryOperator.AND, SQLBinaryOperator.OR, + SQLBinaryOperator.LIKE, SQLBinaryOperator.NOTLIKE ); private static Set mathOperators = Set.of( SQLBinaryOperator.MINUS, SQLBinaryOperator.MULTIPLY, SQLBinaryOperator.DIVIDE, SQLBinaryOperator.MODULO ); @@ -48,6 +50,11 @@ public class SQLBinaryOperation extends SQLExpression { private SQLBinaryOperator operator; + /** + * Only used for Like + */ + private SQLExpression escape = null; + /** * Constructor * @@ -61,6 +68,21 @@ public SQLBinaryOperation( SQLExpression left, SQLExpression right, SQLBinaryOpe setOperator( operator ); } + /** + * Constructor + * + * @param position position of the statement in the source code + * @param sourceText source code of the statement + */ + public SQLBinaryOperation( SQLExpression left, SQLExpression right, SQLBinaryOperator operator, SQLExpression escape, Position position, + String sourceText ) { + super( position, sourceText ); + setLeft( left ); + setRight( right ); + setOperator( operator ); + setEscape( escape ); + } + /** * Get the left */ @@ -108,18 +130,36 @@ public void setOperator( SQLBinaryOperator operator ) { } /** - * Check if the expression evaluates to a boolean value + * Get the escape */ - public boolean isBoolean() { + public SQLExpression getEscape() { + return escape; + } + + /** + * Set the escape + */ + public void setEscape( SQLExpression escape ) { + this.escape = escape; + } + + /** + * Runtime check if the expression evaluates to a boolean value and works for columns as well + * + * @param QoQExec Query execution state + * + * @return true if the expression evaluates to a boolean value + */ + public boolean isBoolean( QoQExecution QoQExec ) { return booleanOperators.contains( operator ); } /** * What type does this expression evaluate to */ - public QueryColumnType getType( Map tableLookup ) { + public QueryColumnType getType( QoQExecution QoQExec ) { // If this is a boolean operation, then we're a bit - if ( isBoolean() ) { + if ( isBoolean( QoQExec ) ) { return QueryColumnType.BIT; } // All math operators but + return a number @@ -128,9 +168,10 @@ public QueryColumnType getType( Map tableLookup ) { } // Plus returns a string if the left and right operand were a string, otherwise it's a math operation. if ( operator == SQLBinaryOperator.PLUS ) { - return QueryColumnType.isStringType( left.getType( tableLookup ) ) && QueryColumnType.isStringType( right.getType( tableLookup ) ) - ? QueryColumnType.VARCHAR - : QueryColumnType.DOUBLE; + return QueryColumnType.isStringType( left.getType( QoQExec ) ) + && QueryColumnType.isStringType( right.getType( QoQExec ) ) + ? QueryColumnType.VARCHAR + : QueryColumnType.DOUBLE; } return QueryColumnType.OBJECT; } @@ -138,87 +179,99 @@ public QueryColumnType getType( Map tableLookup ) { /** * Runtime check if the expression evaluates to a numeric value and works for columns as well * - * @param tableLookup lookup for tables + * @param QoQExec Query execution state * * @return true if the expression evaluates to a numeric value */ - public boolean isNumeric( Map tableLookup ) { - return getType( tableLookup ) == QueryColumnType.DOUBLE; + public boolean isNumeric( QoQExecution QoQExec ) { + return getType( QoQExec ) == QueryColumnType.DOUBLE; } /** * Evaluate the expression */ - public Object evaluate( Map tableLookup, int i ) { + public Object evaluate( QoQExecution QoQExec, int i ) { Object leftValue; Object rightValue; int compareResult; // Implement each binary operator switch ( operator ) { case DIVIDE : - ensureNumericOperands( tableLookup ); - return evalAsNumber( left, tableLookup, i ) / evalAsNumber( right, tableLookup, i ); + ensureNumericOperands( QoQExec ); + return evalAsNumber( left, QoQExec, i ) / evalAsNumber( right, QoQExec, i ); case EQUAL : - leftValue = left.evaluate( tableLookup, i ); - rightValue = right.evaluate( tableLookup, i ); + leftValue = left.evaluate( QoQExec, i ); + rightValue = right.evaluate( QoQExec, i ); return EqualsEquals.invoke( leftValue, rightValue, true ); case GREATERTHAN : - return Compare.invoke( left.evaluate( tableLookup, i ), right.evaluate( tableLookup, i ), true ) == 1; + return Compare.invoke( left.evaluate( QoQExec, i ), right.evaluate( QoQExec, i ), true ) == 1; case GREATERTHANOREQUAL : - compareResult = Compare.invoke( left.evaluate( tableLookup, i ), right.evaluate( tableLookup, i ), true ); + compareResult = Compare.invoke( left.evaluate( QoQExec, i ), right.evaluate( QoQExec, i ), true ); return compareResult == 1 || compareResult == 0; case LESSTHAN : - return Compare.invoke( left.evaluate( tableLookup, i ), right.evaluate( tableLookup, i ), true ) == -1; + return Compare.invoke( left.evaluate( QoQExec, i ), right.evaluate( QoQExec, i ), true ) == -1; case LESSTHANOREQUAL : - compareResult = Compare.invoke( left.evaluate( tableLookup, i ), right.evaluate( tableLookup, i ), true ); + compareResult = Compare.invoke( left.evaluate( QoQExec, i ), right.evaluate( QoQExec, i ), true ); return compareResult == -1 || compareResult == 0; case MINUS : - ensureNumericOperands( tableLookup ); - return evalAsNumber( left, tableLookup, i ) - evalAsNumber( right, tableLookup, i ); + ensureNumericOperands( QoQExec ); + return evalAsNumber( left, QoQExec, i ) - evalAsNumber( right, QoQExec, i ); case MODULO : - ensureNumericOperands( tableLookup ); - return evalAsNumber( left, tableLookup, i ) % evalAsNumber( right, tableLookup, i ); + ensureNumericOperands( QoQExec ); + return evalAsNumber( left, QoQExec, i ) % evalAsNumber( right, QoQExec, i ); case MULTIPLY : - ensureNumericOperands( tableLookup ); - return evalAsNumber( left, tableLookup, i ) * evalAsNumber( right, tableLookup, i ); + ensureNumericOperands( QoQExec ); + return evalAsNumber( left, QoQExec, i ) * evalAsNumber( right, QoQExec, i ); case NOTEQUAL : - leftValue = left.evaluate( tableLookup, i ); - rightValue = right.evaluate( tableLookup, i ); + leftValue = left.evaluate( QoQExec, i ); + rightValue = right.evaluate( QoQExec, i ); return !EqualsEquals.invoke( leftValue, rightValue, true ); case AND : - ensureBooleanOperands( tableLookup ); - leftValue = left.evaluate( tableLookup, i ); + ensureBooleanOperands( QoQExec ); + leftValue = left.evaluate( QoQExec, i ); // Short circuit, don't eval right if left is false if ( ( Boolean ) leftValue ) { - return ( Boolean ) right.evaluate( tableLookup, i ); + return ( Boolean ) right.evaluate( QoQExec, i ); } else { return false; } case OR : - ensureBooleanOperands( tableLookup ); - if ( ( Boolean ) left.evaluate( tableLookup, i ) ) { + ensureBooleanOperands( QoQExec ); + if ( ( Boolean ) left.evaluate( QoQExec, i ) ) { return true; } - if ( ( Boolean ) right.evaluate( tableLookup, i ) ) { + if ( ( Boolean ) right.evaluate( QoQExec, i ) ) { return true; } return false; case PLUS : - if ( left.isNumeric( tableLookup ) && right.isNumeric( tableLookup ) ) { - return evalAsNumber( left, tableLookup, i ) + evalAsNumber( right, tableLookup, i ); + if ( left.isNumeric( QoQExec ) && right.isNumeric( QoQExec ) ) { + return evalAsNumber( left, QoQExec, i ) + evalAsNumber( right, QoQExec, i ); } else { - return Concat.invoke( left.evaluate( tableLookup, i ), right.evaluate( tableLookup, i ) ); + return Concat.invoke( left.evaluate( QoQExec, i ), right.evaluate( QoQExec, i ) ); } case LIKE : - break; + return doLike( QoQExec, i ); case NOTLIKE : - break; + return !doLike( QoQExec, i ); case CONCAT : - return Concat.invoke( left.evaluate( tableLookup, i ), right.evaluate( tableLookup, i ) ); + return Concat.invoke( left.evaluate( QoQExec, i ), right.evaluate( QoQExec, i ) ); default : throw new BoxRuntimeException( "Unknown binary operator: " + operator ); } - throw new UnsupportedOperationException( "Unimplemented binary operator: " + operator ); + } + + /** + * Implement LIKE so we can reuse for NOT LIKE + */ + private boolean doLike( QoQExecution QoQExec, int i ) { + String leftValueStr = StringCaster.cast( left.evaluate( QoQExec, i ) ); + String rightValueStr = StringCaster.cast( right.evaluate( QoQExec, i ) ); + String escapeValue = null; + if ( escape != null ) { + escapeValue = StringCaster.cast( escape.evaluate( QoQExec, i ) ); + } + return LikeOperation.invoke( leftValueStr, rightValueStr, escapeValue ); } /** @@ -226,13 +279,13 @@ public Object evaluate( Map tableLookup, int i ) { * * @return true if the left and right operands are boolean expressions or bit columns */ - private void ensureBooleanOperands( Map tableLookup ) { + private void ensureBooleanOperands( QoQExecution QoQExec ) { // These checks may or may not work. If we can't get away with this, then we can boolean cast the values // but SQL doesn't really have the same concept of truthiness and mostly expects to always get booleans from boolean columns or boolean expressions - if ( !left.isBoolean( tableLookup ) ) { + if ( !left.isBoolean( QoQExec ) ) { throw new BoxRuntimeException( "Left side of a boolean [" + operator.getSymbol() + "] operation must be a boolean expression or bit column" ); } - if ( !right.isBoolean( tableLookup ) ) { + if ( !right.isBoolean( QoQExec ) ) { throw new BoxRuntimeException( "Right side of a boolean [" + operator.getSymbol() + "] operation must be a boolean expression or bit column" ); } } @@ -240,11 +293,11 @@ private void ensureBooleanOperands( Map tableLookup ) { /** * Reusable helper method to ensure that the left and right operands are numeric expressions or numeric columns */ - private void ensureNumericOperands( Map tableLookup ) { - if ( !left.isNumeric( tableLookup ) ) { + private void ensureNumericOperands( QoQExecution QoQExec ) { + if ( !left.isNumeric( QoQExec ) ) { throw new BoxRuntimeException( "Left side of a math [" + operator.getSymbol() + "] operation must be a numeric expression or numeric column" ); } - if ( !right.isNumeric( tableLookup ) ) { + if ( !right.isNumeric( QoQExec ) ) { throw new BoxRuntimeException( "Right side of a math [" + operator.getSymbol() + "] operation must be a numeric expression or numeric column" ); } } @@ -258,8 +311,8 @@ private void ensureNumericOperands( Map tableLookup ) { * * @return */ - private double evalAsNumber( SQLExpression expression, Map tableLookup, int i ) { - return ( ( Number ) expression.evaluate( tableLookup, i ) ).doubleValue(); + private double evalAsNumber( SQLExpression expression, QoQExecution QoQExec, int i ) { + return ( ( Number ) expression.evaluate( QoQExec, i ) ).doubleValue(); } @Override @@ -281,6 +334,11 @@ public Map toMap() { map.put( "left", left.toMap() ); map.put( "right", right.toMap() ); map.put( "operator", enumToMap( operator ) ); + if ( escape != null ) { + map.put( "escape", escape.toMap() ); + } else { + map.put( "escape", null ); + } return map; } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLInOperation.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLInOperation.java index 18c4dc777..f287f5cd6 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLInOperation.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLInOperation.java @@ -19,12 +19,11 @@ import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; -import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; import ortus.boxlang.runtime.operators.EqualsEquals; -import ortus.boxlang.runtime.types.Query; /** * Abstract Node class representing SQL IN operation @@ -97,19 +96,23 @@ public void setNot( boolean not ) { } /** - * Check if the expression evaluates to a boolean value + * Runtime check if the expression evaluates to a boolean value and works for columns as well + * + * @param QoQExec Query execution state + * + * @return true if the expression evaluates to a boolean value */ - public boolean isBoolean() { + public boolean isBoolean( QoQExecution QoQExec ) { return true; } /** * Evaluate the expression */ - public Object evaluate( Map tableLookup, int i ) { - Object value = expression.evaluate( tableLookup, i ); + public Object evaluate( QoQExecution QoQExec, int i ) { + Object value = expression.evaluate( QoQExec, i ); for ( SQLExpression v : values ) { - if ( EqualsEquals.invoke( value, v.evaluate( tableLookup, i ), true ) ) { + if ( EqualsEquals.invoke( value, v.evaluate( QoQExec, i ), true ) ) { return !not; } } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLUnaryOperation.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLUnaryOperation.java index c4bba35ff..f8991b581 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLUnaryOperation.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLUnaryOperation.java @@ -19,11 +19,10 @@ import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; -import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; -import ortus.boxlang.runtime.types.Query; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; import ortus.boxlang.runtime.types.QueryColumnType; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; @@ -85,18 +84,22 @@ public void setOperator( SQLUnaryOperator operator ) { } /** - * Check if the expression evaluates to a boolean value + * Runtime check if the expression evaluates to a boolean value and works for columns as well + * + * @param QoQExec Query execution state + * + * @return true if the expression evaluates to a boolean value */ - public boolean isBoolean() { + public boolean isBoolean( QoQExecution QoQExec ) { return booleanOperators.contains( operator ); } /** * What type does this expression evaluate to */ - public QueryColumnType getType( Map tableLookup ) { + public QueryColumnType getType( QoQExecution QoQExec ) { // If this is a boolean operation, then we're a bit - if ( isBoolean() ) { + if ( isBoolean( QoQExec ) ) { return QueryColumnType.BIT; } if ( mathOperators.contains( operator ) ) { @@ -108,33 +111,33 @@ public QueryColumnType getType( Map tableLookup ) { /** * Runtime check if the expression evaluates to a numeric value and works for columns as well * - * @param tableLookup lookup for tables + * @param QoQExec Query execution state * * @return true if the expression evaluates to a numeric value */ - public boolean isNumeric( Map tableLookup ) { - return getType( tableLookup ) == QueryColumnType.DOUBLE; + public boolean isNumeric( QoQExecution QoQExec ) { + return getType( QoQExec ) == QueryColumnType.DOUBLE; } /** * Evaluate the expression */ - public Object evaluate( Map tableLookup, int i ) { + public Object evaluate( QoQExecution QoQExec, int i ) { // Implement each unary operator switch ( operator ) { case ISNOTNULL : - return expression.evaluate( tableLookup, i ) != null; + return expression.evaluate( QoQExec, i ) != null; case ISNULL : - return expression.evaluate( tableLookup, i ) != null; + return expression.evaluate( QoQExec, i ) != null; case MINUS : - ensureNumericOperand( tableLookup ); - return -evalAsNumber( expression, tableLookup, i ); + ensureNumericOperand( QoQExec ); + return -evalAsNumber( expression, QoQExec, i ); case NOT : - ensureBooleanOperand( tableLookup ); - return ! ( ( boolean ) expression.evaluate( tableLookup, i ) ); + ensureBooleanOperand( QoQExec ); + return ! ( ( boolean ) expression.evaluate( QoQExec, i ) ); case PLUS : - ensureNumericOperand( tableLookup ); - return expression.evaluate( tableLookup, i ); + ensureNumericOperand( QoQExec ); + return expression.evaluate( QoQExec, i ); default : throw new BoxRuntimeException( "Unknown binary operator: " + operator ); @@ -146,10 +149,10 @@ public Object evaluate( Map tableLookup, int i ) { * * @return true if the left and right operands are boolean expressions or bit columns */ - private void ensureBooleanOperand( Map tableLookup ) { + private void ensureBooleanOperand( QoQExecution QoQExec ) { // These checks may or may not work. If we can't get away with this, then we can boolean cast the values // but SQL doesn't really have the same concept of truthiness and mostly expects to always get booleans from boolean columns or boolean expressions - if ( !expression.isBoolean( tableLookup ) ) { + if ( !expression.isBoolean( QoQExec ) ) { throw new BoxRuntimeException( "Unary operation [" + operator.getSymbol() + "] must be a boolean expression or bit column" ); } } @@ -157,8 +160,8 @@ private void ensureBooleanOperand( Map tableLookup ) { /** * Reusable helper method to ensure that the left and right operands are numeric expressions or numeric columns */ - private void ensureNumericOperand( Map tableLookup ) { - if ( !expression.isNumeric( tableLookup ) ) { + private void ensureNumericOperand( QoQExecution QoQExec ) { + if ( !expression.isNumeric( QoQExec ) ) { throw new BoxRuntimeException( "Unary operation [" + operator.getSymbol() + "] must be a numeric expression or numeric column" ); } } @@ -172,8 +175,8 @@ private void ensureNumericOperand( Map tableLookup ) { * * @return */ - private double evalAsNumber( SQLExpression expression, Map tableLookup, int i ) { - return ( ( Number ) expression.evaluate( tableLookup, i ) ).doubleValue(); + private double evalAsNumber( SQLExpression expression, QoQExecution QoQExec, int i ) { + return ( ( Number ) expression.evaluate( QoQExec, i ) ).doubleValue(); } @Override diff --git a/src/main/java/ortus/boxlang/compiler/toolchain/SQLVisitor.java b/src/main/java/ortus/boxlang/compiler/toolchain/SQLVisitor.java index 2d447bb46..612a2d77f 100644 --- a/src/main/java/ortus/boxlang/compiler/toolchain/SQLVisitor.java +++ b/src/main/java/ortus/boxlang/compiler/toolchain/SQLVisitor.java @@ -1,5 +1,6 @@ package ortus.boxlang.compiler.toolchain; +import java.util.ArrayList; import java.util.List; import org.antlr.v4.runtime.tree.TerminalNode; @@ -12,8 +13,11 @@ import ortus.boxlang.compiler.ast.sql.select.SQLSelectStatement; import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.sql.select.expression.SQLColumn; +import ortus.boxlang.compiler.ast.sql.select.expression.SQLCountFunction; import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; +import ortus.boxlang.compiler.ast.sql.select.expression.SQLFunction; import ortus.boxlang.compiler.ast.sql.select.expression.SQLOrderBy; +import ortus.boxlang.compiler.ast.sql.select.expression.SQLParam; import ortus.boxlang.compiler.ast.sql.select.expression.SQLParenthesis; import ortus.boxlang.compiler.ast.sql.select.expression.SQLStarExpression; import ortus.boxlang.compiler.ast.sql.select.expression.literal.SQLBooleanLiteral; @@ -45,7 +49,8 @@ */ public class SQLVisitor extends SQLGrammarBaseVisitor { - private final SQLParser tools; + private final SQLParser tools; + private int bindCount = 0; public SQLVisitor( SQLParser tools ) { this.tools = tools; @@ -330,16 +335,37 @@ public SQLExpression visitExpr( ExprContext ctx, SQLTable table, List j } else if ( ctx.OR_() != null ) { return binarySimple( ctx.expr( 0 ), ctx.expr( 1 ), SQLBinaryOperator.OR, pos, src, table, joins ); } else if ( ctx.function_name() != null ) { - throw new UnsupportedOperationException( "functions Unimplemented: " + src ); + Key functionName = Key.of( ctx.function_name().getText() ); + boolean hasDistinct = ctx.DISTINCT_() != null; + List arguments = new ArrayList(); + if ( ctx.STAR() != null ) { + arguments.add( new SQLStarExpression( null, pos, src ) ); + } else { + arguments = ctx.expr().stream().map( e -> visitExpr( e, table, joins ) ).toList(); + } + if ( functionName.equals( Key.count ) ) { + return new SQLCountFunction( functionName, arguments, hasDistinct, pos, src ); + } else { + return new SQLFunction( functionName, arguments, pos, src ); + } } else if ( ctx.BIND_PARAMETER() != null ) { - throw new UnsupportedOperationException( "bind parameters unimplemented: " + src ); + int index = bindCount++; + String name = null; + if ( ctx.BIND_PARAMETER().getText().startsWith( ":" ) ) { + name = ctx.BIND_PARAMETER().getText().substring( 1 ); + } + return new SQLParam( name, index, pos, src ); } else if ( ctx.IN_() != null ) { SQLExpression expr = visitExpr( ctx.expr( 0 ), table, joins ); List values = ctx.expr().stream().skip( 1 ).map( e -> visitExpr( e, table, joins ) ).toList(); return new SQLInOperation( expr, values, ctx.NOT_() != null, pos, src ); } else if ( ctx.LIKE_() != null ) { - return binarySimple( ctx.expr( 0 ), ctx.expr( 1 ), ctx.NOT_() != null ? SQLBinaryOperator.NOTLIKE : SQLBinaryOperator.LIKE, pos, src, table, - joins ); + SQLBinaryOperator op = ctx.NOT_() != null ? SQLBinaryOperator.NOTLIKE : SQLBinaryOperator.LIKE; + SQLExpression escape = null; + if ( ctx.ESCAPE_() != null ) { + escape = visitExpr( ctx.expr( 2 ), table, joins ); + } + return new SQLBinaryOperation( visitExpr( ctx.expr( 0 ), table, joins ), visitExpr( ctx.expr( 1 ), table, joins ), op, escape, pos, src ); } else if ( ctx.PIPE2() != null ) { return binarySimple( ctx.expr( 0 ), ctx.expr( 1 ), SQLBinaryOperator.CONCAT, pos, src, table, joins ); } else if ( ctx.STAR() != null ) { diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/LikeOperation.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/LikeOperation.java new file mode 100644 index 000000000..69d825d39 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/LikeOperation.java @@ -0,0 +1,141 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq; + +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import ortus.boxlang.runtime.types.exceptions.DatabaseException; + +/** + * Implements the LIKE operation for SQL QoQ + */ +public class LikeOperation { + + /** + * regex metachars + */ + private static final Set specials = Set.of( '{', '}', '[', ']', '(', ')', '.', '?', '+', '\\', '^', '$', '*', '|' ); + + /** + * Cache of compiled patterns + */ + private static Map patterns = new WeakHashMap(); + + /** + * Invoke a SQL Like operation. + * + * @param stringToSearchIn The string to search in + * @param patternToSearchFor The pattern to search for + * @param escape The escape character + * + * @return true if the pattern matches the string + */ + public static boolean invoke( String stringToSearchIn, String patternToSearchFor, String escape ) { + if ( stringToSearchIn == null || patternToSearchFor == null ) { + return false; + } + + stringToSearchIn = stringToSearchIn.toLowerCase(); + patternToSearchFor = patternToSearchFor.toLowerCase(); + escape = escape == null ? null : escape.toLowerCase(); + Pattern p = createPattern( patternToSearchFor, escape == null ? null : escape ); + return p.matcher( stringToSearchIn ).matches(); + } + + /** + * Escape any actual regex metachars in the pattern + * + * @param sb The string builder to append to + * @param c The char to append + */ + private static void escapeForRegex( StringBuilder sb, char c ) { + // If we have a regex metachar, escape it + if ( specials.contains( c ) ) { + sb.append( '\\' ).append( c ); + } else { + sb.append( c ); + } + } + + /** + * Create a pattern from the patternToSearchFor + */ + private static Pattern createPattern( String patternToSearchFor, String escape ) { + var patternCacheKey = patternToSearchFor + escape == null ? "" : escape; + var pattern = patterns.get( patternCacheKey ); + if ( pattern != null ) + return pattern; + // Thread-safe compilation so only one thread compiles a pattern + synchronized ( LikeOperation.class ) { + // Double check in the lock + pattern = patterns.get( patternCacheKey ); + if ( pattern != null ) + return pattern; + + char esc = 0; + if ( escape != null && !escape.isEmpty() ) { + esc = escape.charAt( 0 ); + if ( escape.length() > 1 ) { + throw new DatabaseException( + "Invalid escape character [" + escape + "] has been specified in a LIKE conditional. Escape char must be a single character." ); + } + } + + StringBuilder sb = new StringBuilder( patternToSearchFor.length() ); + int len = patternToSearchFor.length(); + char c; + for ( int i = 0; i < len; i++ ) { + c = patternToSearchFor.charAt( i ); + if ( c == esc ) { + // If we aren't at the end of the string grab the next char + // An escape char at the end of the string gets used as a literal + if ( i + 1 < len ) { + c = patternToSearchFor.charAt( ++i ); + } + escapeForRegex( sb, c ); + } else { + if ( c == '%' ) + sb.append( ".*" ); + else if ( c == '_' ) + sb.append( '.' ); + else if ( c == '[' ) { + sb.append( c ); + // If we just opened unescaped brackets, check for a ^ + // All other ^ chars are treated like normal + if ( i + 1 < len && patternToSearchFor.charAt( i + 1 ) == '^' ) { + i++; + sb.append( '^' ); + } + } else if ( c == ']' ) + sb.append( c ); + else + escapeForRegex( sb, c ); + } + + } + try { + patterns.put( patternCacheKey, pattern = Pattern.compile( sb.toString(), Pattern.DOTALL ) ); + } catch ( PatternSyntaxException e ) { + throw new DatabaseException( "Invalid LIKE pattern [" + patternToSearchFor + "] has been specified in a LIKE conditional", e ); + } + return pattern; + } + } + +} \ No newline at end of file diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQConnection.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQConnection.java index c5da85a95..c4e6dd02a 100644 --- a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQConnection.java +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQConnection.java @@ -53,7 +53,7 @@ public Statement createStatement() throws SQLException { } public PreparedStatement prepareStatement( String sql ) throws SQLException { - throw new UnsupportedOperationException(); + return prepareStatement( sql, Statement.NO_GENERATED_KEYS ); } public CallableStatement prepareCall( String sql ) throws SQLException { @@ -174,7 +174,7 @@ public CallableStatement prepareCall( String sql, int resultSetType, int resultS } public PreparedStatement prepareStatement( String sql, int autoGeneratedKeys ) throws SQLException { - throw new UnsupportedOperationException(); + return new QoQPreparedStatement( context, this, sql, autoGeneratedKeys ); } public PreparedStatement prepareStatement( String sql, int columnIndexes[] ) throws SQLException { diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQService.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQExecutionService.java similarity index 83% rename from src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQService.java rename to src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQExecutionService.java index 0f7e0a0af..4023583be 100644 --- a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQService.java +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQExecutionService.java @@ -16,6 +16,7 @@ import java.sql.SQLException; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import ortus.boxlang.compiler.ast.sql.SQLNode; @@ -31,6 +32,7 @@ import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.dynamic.ExpressionInterpreter; import ortus.boxlang.runtime.interop.DynamicObject; +import ortus.boxlang.runtime.jdbc.qoq.QoQPreparedStatement.ParamItem; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.Query; @@ -43,7 +45,7 @@ /** * I handle executing query of queries */ -public class QoQService { +public class QoQExecutionService { /** * The transaction service used to track subtransactions @@ -85,7 +87,14 @@ public static Query executeSelect( IBoxContext context, SQLSelectStatement selec tableLookup.put( selectStatement.getSelect().getTable(), source ); } - Map resultColumns = calculateResultColumns( selectStatement, tableLookup ); + // This holds the AST and the runtime values for the query + QoQExecution QoQExec = QoQExecution.of( + selectStatement, + tableLookup, + statement instanceof QoQPreparedStatement qp ? qp.getParameters() : null + ); + + Map resultColumns = calculateResultColumns( QoQExec ); Query target = buildTargetQuery( resultColumns ); @@ -103,7 +112,7 @@ public static Query executeSelect( IBoxContext context, SQLSelectStatement selec Object[] values = new Object[ resultColumns.size() ]; for ( Key key : resultColumns.keySet() ) { SQLResultColumn resultColumn = resultColumns.get( key ).resultColumn; - Object value = resultColumn.getExpression().evaluate( tableLookup, 1 ); + Object value = resultColumn.getExpression().evaluate( QoQExec, 1 ); values[ resultColumn.getOrdinalPosition() - 1 ] = value; } target.addRow( values ); @@ -118,11 +127,11 @@ public static Query executeSelect( IBoxContext context, SQLSelectStatement selec break; } // Evaluate the where expression - if ( where == null || ( Boolean ) where.evaluate( tableLookup, i ) ) { + if ( where == null || ( Boolean ) where.evaluate( QoQExec, i ) ) { Object[] values = new Object[ resultColumns.size() ]; for ( Key key : resultColumns.keySet() ) { SQLResultColumn resultColumn = resultColumns.get( key ).resultColumn; - Object value = resultColumn.getExpression().evaluate( tableLookup, i ); + Object value = resultColumn.getExpression().evaluate( QoQExec, i ); values[ resultColumn.getOrdinalPosition() - 1 ] = value; } target.addRow( values ); @@ -160,19 +169,19 @@ private static Query buildTargetQuery( Map resultColumns return target; } - private static Map calculateResultColumns( SQLSelectStatement select, Map tableLookup ) { + private static Map calculateResultColumns( QoQExecution QoQExec ) { Map resultColumns = new LinkedHashMap(); - for ( SQLResultColumn resultColumn : select.getSelect().getResultColumns() ) { + for ( SQLResultColumn resultColumn : QoQExec.select.getSelect().getResultColumns() ) { // For *, expand all columns in the query if ( resultColumn.isStarExpression() ) { // TODO: when we add joins, handle looking up the correct table reference based on resultColumn.getTable() - var thisTable = tableLookup.get( select.getSelect().getTable() ); + var thisTable = QoQExec.tableLookup.get( QoQExec.select.getSelect().getTable() ); for ( Key key : thisTable.getColumns().keySet() ) { resultColumns.put( key, TypedResultColumn.of( thisTable.getColumns().get( key ).getType(), new SQLResultColumn( - new SQLColumn( select.getSelect().getTable(), key.getName(), null, null ), + new SQLColumn( QoQExec.select.getSelect().getTable(), key.getName(), null, null ), null, resultColumns.size() + 1, null, @@ -184,7 +193,7 @@ private static Map calculateResultColumns( SQLSelectStat // Non-star columns are named after the column, or given a column_0, column_1, etc name } else { resultColumns.put( resultColumn.getResultColumnName(), - TypedResultColumn.of( resultColumn.getExpression().getType( tableLookup ), resultColumn ) ); + TypedResultColumn.of( resultColumn.getExpression().getType( QoQExec ), resultColumn ) ); } } return resultColumns; @@ -206,4 +215,14 @@ public static TypedResultColumn of( QueryColumnType type, SQLResultColumn result } } + /** + * A wrapper class to hold together both the SQL AST being executed as well as the runtime values for a given execution of the query + */ + public record QoQExecution( SQLSelectStatement select, Map tableLookup, List params ) { + + public static QoQExecution of( SQLSelectStatement select, Map tableLookup, List params ) { + return new QoQExecution( select, tableLookup, params ); + } + } + } diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQFunctionService.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQFunctionService.java new file mode 100644 index 000000000..5f3d86808 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQFunctionService.java @@ -0,0 +1,71 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import ortus.boxlang.runtime.dynamic.casters.StringCaster; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; +import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; + +/** + * I handle executing functions in query of queries + */ +public class QoQFunctionService { + + private static Map functions = new HashMap(); + + static { + register( Key.of( "upper" ), args -> StringCaster.cast( args.get( 0 ) ).toUpperCase(), QueryColumnType.VARCHAR ); + } + + private QoQFunctionService() { + + } + + public static void register( Key name, java.util.function.Function, Object> function, QueryColumnType returnType ) { + functions.put( name, QoQFunction.of( function, returnType ) ); + } + + public static void unregister( Key name ) { + functions.remove( name ); + } + + public static QoQFunction getFunction( Key name ) { + if ( !functions.containsKey( name ) ) { + throw new BoxRuntimeException( "Function [" + name + "] not found" ); + } + return functions.get( name ); + } + + public record QoQFunction( java.util.function.Function, Object> callable, QueryColumnType returnType ) { + + static QoQFunction of( java.util.function.Function, Object> callable, QueryColumnType returnType ) { + return new QoQFunction( callable, returnType ); + } + + public Object invoke( List arguments ) { + return callable.apply( arguments ); + } + + public QueryColumnType getReturnType() { + return returnType; + } + + } +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQPreparedStatement.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQPreparedStatement.java new file mode 100644 index 000000000..c47d44285 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQPreparedStatement.java @@ -0,0 +1,416 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq; + +import java.io.InputStream; +import java.io.Reader; +import java.math.BigDecimal; +import java.net.URL; +import java.sql.Array; +import java.sql.Blob; +import java.sql.Clob; +import java.sql.Connection; +import java.sql.Date; +import java.sql.NClob; +import java.sql.ParameterMetaData; +import java.sql.Ref; +import java.sql.ResultSet; +import java.sql.ResultSetMetaData; +import java.sql.RowId; +import java.sql.SQLException; +import java.sql.SQLXML; +import java.sql.Time; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; + +import ortus.boxlang.compiler.ast.sql.select.SQLSelectStatement; +import ortus.boxlang.runtime.context.IBoxContext; + +public class QoQPreparedStatement extends QoQStatement implements java.sql.PreparedStatement { + + private List parameters = new ArrayList(); + private String sql; + private int autoGeneratedKeys; + + /** + * Constructor. + * + * @param context The BoxLang context. + * @param connection The connection. + */ + public QoQPreparedStatement( IBoxContext context, Connection connection, String sql, int autoGeneratedKeys ) { + super( context, connection ); + this.sql = sql; + this.autoGeneratedKeys = autoGeneratedKeys; + } + + @Override + public ResultSet executeQuery() throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'executeQuery'" ); + } + + @Override + public int executeUpdate() throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'executeUpdate'" ); + } + + @Override + public void setNull( int parameterIndex, int sqlType ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setNull'" ); + } + + @Override + public void setBoolean( int parameterIndex, boolean x ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setBoolean'" ); + } + + @Override + public void setByte( int parameterIndex, byte x ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setByte'" ); + } + + @Override + public void setShort( int parameterIndex, short x ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setShort'" ); + } + + @Override + public void setInt( int parameterIndex, int x ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setInt'" ); + } + + @Override + public void setLong( int parameterIndex, long x ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setLong'" ); + } + + @Override + public void setFloat( int parameterIndex, float x ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setFloat'" ); + } + + @Override + public void setDouble( int parameterIndex, double x ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setDouble'" ); + } + + @Override + public void setBigDecimal( int parameterIndex, BigDecimal x ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setBigDecimal'" ); + } + + @Override + public void setString( int parameterIndex, String x ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setString'" ); + } + + @Override + public void setBytes( int parameterIndex, byte[] x ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setBytes'" ); + } + + @Override + public void setDate( int parameterIndex, Date x ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setDate'" ); + } + + @Override + public void setTime( int parameterIndex, Time x ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setTime'" ); + } + + @Override + public void setTimestamp( int parameterIndex, Timestamp x ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setTimestamp'" ); + } + + @Override + public void setAsciiStream( int parameterIndex, InputStream x, int length ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setAsciiStream'" ); + } + + @Override + public void setUnicodeStream( int parameterIndex, InputStream x, int length ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setUnicodeStream'" ); + } + + @Override + public void setBinaryStream( int parameterIndex, InputStream x, int length ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setBinaryStream'" ); + } + + @Override + public void clearParameters() throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'clearParameters'" ); + } + + @Override + public void setObject( int parameterIndex, Object x, int targetSqlType ) throws SQLException { + // index is 1 based! + + var item = new ParamItem( parameterIndex, x, targetSqlType ); + if ( parameters.size() < parameterIndex ) { + // add nulls if there are missing slots + // I assume this never happens + while ( parameters.size() < parameterIndex - 1 ) { + parameters.add( null ); + } + parameters.add( item ); + } else { + parameters.set( parameterIndex - 1, item ); + } + + } + + /** + * Get the parameters + */ + public List getParameters() { + return parameters; + } + + @Override + public void setObject( int parameterIndex, Object x ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setObject'" ); + } + + @Override + public boolean execute() throws SQLException { + // parse the SQL string into an AST object + SQLSelectStatement select = ( SQLSelectStatement ) QoQExecutionService.parseSQL( sql ); + + // execute the query + result = QoQExecutionService.executeSelect( context, select, this ); + return true; + } + + @Override + public void addBatch() throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'addBatch'" ); + } + + @Override + public void setCharacterStream( int parameterIndex, Reader reader, int length ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setCharacterStream'" ); + } + + @Override + public void setRef( int parameterIndex, Ref x ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setRef'" ); + } + + @Override + public void setBlob( int parameterIndex, Blob x ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setBlob'" ); + } + + @Override + public void setClob( int parameterIndex, Clob x ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setClob'" ); + } + + @Override + public void setArray( int parameterIndex, Array x ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setArray'" ); + } + + @Override + public ResultSetMetaData getMetaData() throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'getMetaData'" ); + } + + @Override + public void setDate( int parameterIndex, Date x, Calendar cal ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setDate'" ); + } + + @Override + public void setTime( int parameterIndex, Time x, Calendar cal ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setTime'" ); + } + + @Override + public void setTimestamp( int parameterIndex, Timestamp x, Calendar cal ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setTimestamp'" ); + } + + @Override + public void setNull( int parameterIndex, int sqlType, String typeName ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setNull'" ); + } + + @Override + public void setURL( int parameterIndex, URL x ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setURL'" ); + } + + @Override + public ParameterMetaData getParameterMetaData() throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'getParameterMetaData'" ); + } + + @Override + public void setRowId( int parameterIndex, RowId x ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setRowId'" ); + } + + @Override + public void setNString( int parameterIndex, String value ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setNString'" ); + } + + @Override + public void setNCharacterStream( int parameterIndex, Reader value, long length ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setNCharacterStream'" ); + } + + @Override + public void setNClob( int parameterIndex, NClob value ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setNClob'" ); + } + + @Override + public void setClob( int parameterIndex, Reader reader, long length ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setClob'" ); + } + + @Override + public void setBlob( int parameterIndex, InputStream inputStream, long length ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setBlob'" ); + } + + @Override + public void setNClob( int parameterIndex, Reader reader, long length ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setNClob'" ); + } + + @Override + public void setSQLXML( int parameterIndex, SQLXML xmlObject ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setSQLXML'" ); + } + + @Override + public void setObject( int parameterIndex, Object x, int targetSqlType, int scaleOrLength ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setObject'" ); + } + + @Override + public void setAsciiStream( int parameterIndex, InputStream x, long length ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setAsciiStream'" ); + } + + @Override + public void setBinaryStream( int parameterIndex, InputStream x, long length ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setBinaryStream'" ); + } + + @Override + public void setCharacterStream( int parameterIndex, Reader reader, long length ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setCharacterStream'" ); + } + + @Override + public void setAsciiStream( int parameterIndex, InputStream x ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setAsciiStream'" ); + } + + @Override + public void setBinaryStream( int parameterIndex, InputStream x ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setBinaryStream'" ); + } + + @Override + public void setCharacterStream( int parameterIndex, Reader reader ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setCharacterStream'" ); + } + + @Override + public void setNCharacterStream( int parameterIndex, Reader value ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setNCharacterStream'" ); + } + + @Override + public void setClob( int parameterIndex, Reader reader ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setClob'" ); + } + + @Override + public void setBlob( int parameterIndex, InputStream inputStream ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setBlob'" ); + } + + @Override + public void setNClob( int parameterIndex, Reader reader ) throws SQLException { + // TODO Auto-generated method stub + throw new UnsupportedOperationException( "Unimplemented method 'setNClob'" ); + } + + public record ParamItem( int index, Object value, int type ) { + } +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQStatement.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQStatement.java index 15cf203fa..63cd8c821 100644 --- a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQStatement.java +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQStatement.java @@ -25,10 +25,10 @@ public class QoQStatement implements java.sql.Statement { - private IBoxContext context; - private long maxRows = -1; - private Connection connection; - private Query result; + protected IBoxContext context; + protected long maxRows = -1; + protected Connection connection; + protected Query result; public QoQStatement( IBoxContext context, Connection connection ) { this.context = context; @@ -175,10 +175,10 @@ public int executeUpdate( String sql, String columnNames[] ) throws SQLException public boolean execute( String sql, int autoGeneratedKeys ) throws SQLException { // parse the SQL string into an AST object - SQLSelectStatement select = ( SQLSelectStatement ) QoQService.parseSQL( sql ); + SQLSelectStatement select = ( SQLSelectStatement ) QoQExecutionService.parseSQL( sql ); // execute the query - result = QoQService.executeSelect( context, select, this ); + result = QoQExecutionService.executeSelect( context, select, this ); return true; } diff --git a/src/test/java/ortus/boxlang/compiler/QoQParseTest.java b/src/test/java/ortus/boxlang/compiler/QoQParseTest.java index 579fc76f8..7ad6ebe3a 100644 --- a/src/test/java/ortus/boxlang/compiler/QoQParseTest.java +++ b/src/test/java/ortus/boxlang/compiler/QoQParseTest.java @@ -19,7 +19,6 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import ortus.boxlang.compiler.parser.ParsingResult; @@ -80,21 +79,18 @@ public void testMetadataVisitor() { } @Test - @Disabled public void testRunQoQ() { instance.executeSource( """ - myQry = queryNew( "col,col2", "varchar,integer", [["foo",42],["bar",9001]] ) - q = queryExecute( " - select col, 5 as brad, col2 luis - from myQry - where (col = 'foo') or col2 IS 9001 - ", - [], - { dbType : "query", maxRows = 1 } - ); - println( q ) - """, + myQry = queryNew( "col,col2", "varchar,integer", [["foo",42],["bar",9001]] ) + q = queryExecute( " + select upper('test') as val + ", + [], + { dbType : "query" } + ); + println( q ) + """, context ); } From ef9d2efe56a30d8e019293e57547a2de70d384db Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 12 Dec 2024 16:21:17 +0100 Subject: [PATCH 177/193] display the full category so we can do tracking --- .../java/ortus/boxlang/runtime/logging/LoggingService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index 50caa23e0..a611e2fc0 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -89,7 +89,7 @@ public class LoggingService { * * @see https://logback.qos.ch/manual/layouts.html#conversionWord */ - public static final String LOG_FORMAT = "[%date{STRICT}] [%thread] [%-5level] [%logger{0}] %message %ex%n"; + public static final String LOG_FORMAT = "[%date{STRICT}] [%thread] [%-5level] [%logger] %message %ex%n"; /** * -------------------------------------------------------------------------- @@ -653,7 +653,7 @@ private BoxLangLogger createLogger( Key loggerKey, String loggerFilePath ) { // Check if we have the logger configuration or else build a vanilla one LoggerConfig loggerConfig = ( LoggerConfig ) this.runtime .getConfiguration().logging.loggers - .computeIfAbsent( loggerKey, key -> new LoggerConfig( key.getNameNoCase(), this.runtime.getConfiguration().logging ) ); + .computeIfAbsent( loggerKey, key -> new LoggerConfig( key.getNameNoCase(), this.runtime.getConfiguration().logging ) ); Level configLevel = Level.toLevel( LogLevel.valueOf( loggerConfig.level.getName(), false ).getName() ); // Seed the properties From 8aeebc4028a6e716e180dc80818705910cc28f61 Mon Sep 17 00:00:00 2001 From: lmajano Date: Thu, 12 Dec 2024 15:22:15 +0000 Subject: [PATCH 178/193] Apply cfformat changes --- src/main/java/ortus/boxlang/runtime/logging/LoggingService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index a611e2fc0..279dd5390 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -653,7 +653,7 @@ private BoxLangLogger createLogger( Key loggerKey, String loggerFilePath ) { // Check if we have the logger configuration or else build a vanilla one LoggerConfig loggerConfig = ( LoggerConfig ) this.runtime .getConfiguration().logging.loggers - .computeIfAbsent( loggerKey, key -> new LoggerConfig( key.getNameNoCase(), this.runtime.getConfiguration().logging ) ); + .computeIfAbsent( loggerKey, key -> new LoggerConfig( key.getNameNoCase(), this.runtime.getConfiguration().logging ) ); Level configLevel = Level.toLevel( LogLevel.valueOf( loggerConfig.level.getName(), false ).getName() ); // Seed the properties From 9c3f37282a2c7d8a673ad835efc84e56f822ce31 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 12 Dec 2024 16:52:45 +0100 Subject: [PATCH 179/193] updating logging levels --- src/main/resources/config/boxlang.json | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/resources/config/boxlang.json b/src/main/resources/config/boxlang.json index ed02cd7ca..6e6792242 100644 --- a/src/main/resources/config/boxlang.json +++ b/src/main/resources/config/boxlang.json @@ -114,13 +114,13 @@ "encoder": "text", // Additive logging: true means that this logger will inherit the appenders from the root logger // If false, it will only use the appenders defined in this logger - "additive": true + "additive": false }, // All async operations and facilities will log here. "async": { // Valid values are in order of severity: ERROR, WARN, INFO, DEBUG, TRACE, OFF // Leave out if it should inherit from the root logger - "level": "DEBUG", + "level": "INFO", // Valid values are: "file", "console", // Coming soon: "smtp", "socket", "db", "syslog" or "java class name" // Please note that we only use Rolling File Appenders @@ -131,7 +131,7 @@ "encoder": "text", // Additive logging: true means that this logger will inherit the appenders from the root logger // If false, it will only use the appenders defined in this logger - "additive": true + "additive": false }, // All cache operations and facilities will log here. "cache": { @@ -182,13 +182,13 @@ "encoder": "text", // Additive logging: true means that this logger will inherit the appenders from the root logger // If false, it will only use the appenders defined in this logger - "additive": true + "additive": false }, // All applications will use this logger for any custom logging "application": { // Valid values are in order of severity: ERROR, WARN, INFO, DEBUG, TRACE, OFF // Leave out if it should inherit from the root logger - "level": "DEBUG", + "level": "WARN", // Valid values are: "file", "console", // Coming soon: "smtp", "socket", "db", "syslog" or "java class name" // Please note that we only use Rolling File Appenders @@ -199,7 +199,7 @@ "encoder": "text", // Additive logging: true means that this logger will inherit the appenders from the root logger // If false, it will only use the appenders defined in this logger - "additive": true + "additive": false }, // All scheduled tasks logging will go here "scheduler": { @@ -216,7 +216,7 @@ "encoder": "text", // Additive logging: true means that this logger will inherit the appenders from the root logger // If false, it will only use the appenders defined in this logger - "additive": true + "additive": false } } }, From a4d089153e234996f9d3ebf3e1908695bccc1f4d Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 12 Dec 2024 22:24:07 +0100 Subject: [PATCH 180/193] ignore semvers in local development --- .../ortus/boxlang/runtime/services/ModuleService.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/services/ModuleService.java b/src/main/java/ortus/boxlang/runtime/services/ModuleService.java index 80c24c85b..a91181bd0 100644 --- a/src/main/java/ortus/boxlang/runtime/services/ModuleService.java +++ b/src/main/java/ortus/boxlang/runtime/services/ModuleService.java @@ -143,7 +143,8 @@ public void onStartup() { this.logger.info( "+ Starting up Module Service..." ); // Store the running BoxLang version - this.runtimeSemver = new Semver( getRuntime().getVersionInfo().getAsString( Key.version ) ); + String runtimeVersion = getRuntime().getVersionInfo().getAsString( Key.version ); + this.runtimeSemver = new Semver( runtimeVersion.equalsIgnoreCase( "@build.version@" ) ? "0.0.0" : runtimeVersion ); // Register external module locations from the config runtime.getConfiguration().modulesDirectory.forEach( this::addModulePath ); @@ -592,6 +593,11 @@ public ModuleService addModulePath( Path path ) { * @throws BoxRuntimeException If the module requires a different major version of BoxLang */ public void verifyModuleAndBoxLangVersion( String moduleVersion, Path directoryPath ) { + // If we are in development mode, we don't care about the version + if ( this.runtimeSemver.getMajor() == 0 ) { + return; + } + // Early exit if the module version is null or blank if ( moduleVersion == null || moduleVersion.isBlank() ) { this.logger.warn( "Module [{}] does not have a BoxLang [minimumVersion] specified in the ModuleConfig.bx file", directoryPath.getFileName() ); From 1b14005b755e65204d19d4f4f19b9233065c78ad Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 12 Dec 2024 22:31:13 +0100 Subject: [PATCH 181/193] more logging deb ugging --- .../ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java index 7c90ccb9f..186f1bd7e 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/system/WriteLogTest.java @@ -61,7 +61,7 @@ public static void setUp() { outContent = new ByteArrayOutputStream(); System.setOut( new PrintStream( outContent ) ); logFilePath = Paths.get( logsDirectory, "/writelog.log" ).normalize().toString(); - defaultLogFilePath = Paths.get( logsDirectory, "/runtime.log" ).normalize().toString(); + defaultLogFilePath = Paths.get( logsDirectory, "/application.log" ).normalize().toString(); } @AfterAll @@ -104,7 +104,6 @@ public void testPrint() { // Assert we got here assertThat( FileSystemUtil.exists( defaultLogFilePath ) ).isTrue(); - assertThat( outContent.toString() ).contains( "Hello Logger!" ); } @DisplayName( "It can write a default with a compat log argument" ) From 2375104cb8c942f5f31d77e40e974c9052991aa6 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Thu, 12 Dec 2024 23:20:28 +0100 Subject: [PATCH 182/193] adding even more debugging --- .../runtime/loader/DynamicClassLoader.java | 24 +++++++++++++++++-- .../bifs/global/conversion/ToStringTest.java | 11 +++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/loader/DynamicClassLoader.java b/src/main/java/ortus/boxlang/runtime/loader/DynamicClassLoader.java index 455618084..640714e2e 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/DynamicClassLoader.java +++ b/src/main/java/ortus/boxlang/runtime/loader/DynamicClassLoader.java @@ -36,6 +36,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.bifs.global.type.NullValue; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.Array; @@ -196,8 +197,9 @@ public Class findClass( String className, Boolean safe ) throws ClassNotFound } // 2.5. Special case for Logback/SL4j so we are guaranteed to use the same interfaces as the BoxLang Runtime. + // Any other special cases can be added here to the PARENT_CLASSES set if ( this.parent != null && PARENT_CLASSES.stream().anyMatch( className::startsWith ) ) { - // logger.trace( "[{}].[{}] : Class is a special parent class, delegating to parent", this.nameAsKey.getName(), className ); + logger.trace( "[{}].[{}] : Class is a special parent class, delegating to parent", this.nameAsKey.getName(), className ); return getDynamicParent().loadClass( className ); } @@ -213,13 +215,13 @@ public Class findClass( String className, Boolean safe ) throws ClassNotFound cachedClass = getDynamicParent().loadClass( className ); logger.trace( "[{}].[{}] : Class found in parent", this.nameAsKey.getName(), className ); } catch ( ClassNotFoundException parentException ) { + logger.trace( "[{}].[{}] : Class not found in parent, adding to unfound classes", this.nameAsKey.getName(), className ); // Add to the unfound cache this.unfoundClasses.put( className, NullValue.class ); // If not safe, throw the exception if ( !safe ) { throw new ClassNotFoundException( String.format( "Class [%s] not found in class loader [%s]", className, this.nameAsKey.getName() ) ); } - logger.trace( "[{}].[{}] : Class not found in parent", this.nameAsKey.getName(), className ); } } @@ -463,6 +465,11 @@ public static URL[] inflateClassPaths( Array paths ) { .toArray( URL[]::new ); } + /** + * Lazy init of the logger + * + * @return The logger + */ private static Logger getLogger() { if ( logger == null ) { synchronized ( DynamicClassLoader.class ) { @@ -472,7 +479,20 @@ private static Logger getLogger() { } } return logger; + } + + /** + * Get the runtime's class loader + */ + public static ClassLoader getRuntimeClassLoader() { + return BoxRuntime.getInstance().getRuntimeLoader(); + } + /** + * Get the thread's context class loader + */ + public static ClassLoader getContextClassLoader() { + return Thread.currentThread().getContextClassLoader(); } } diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/conversion/ToStringTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/conversion/ToStringTest.java index 8b35d8135..d10be6172 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/conversion/ToStringTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/conversion/ToStringTest.java @@ -180,4 +180,15 @@ public void testFormatDouble() { assertThat( variables.getAsString( result ) ).isEqualTo( "1756.8000000000002" ); } + @DisplayName( "It can do a member method on a String" ) + @Test + public void testMemberMethodOnString() { + instance.executeSource( + """ + result = "Hello World".toString() + """, + context ); + assertThat( variables.getAsString( result ) ).isEqualTo( "Hello World" ); + } + } From 55f2bc907d989f231d4a5a510422e798346d6083 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 13 Dec 2024 00:05:19 +0100 Subject: [PATCH 183/193] more tracing --- .../ortus/boxlang/runtime/application/Application.java | 4 ++++ .../runtime/application/BaseApplicationListener.java | 2 ++ .../ortus/boxlang/runtime/loader/DynamicClassLoader.java | 8 ++++---- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/application/Application.java b/src/main/java/ortus/boxlang/runtime/application/Application.java index 18bc0e35f..6af564374 100644 --- a/src/main/java/ortus/boxlang/runtime/application/Application.java +++ b/src/main/java/ortus/boxlang/runtime/application/Application.java @@ -214,6 +214,8 @@ public void startupClassLoaderPaths( RequestBoxContext requestContext ) { // if we don't have any return out if ( loadPathsUrls.length == 0 ) { + logger.trace( "===> Setting the context classLoader to the [runtime] loader during startupClassLoaderPaths via [{}]", + Thread.currentThread().getName() ); // If there are no javasettings, ensure we just use the runtime CL Thread.currentThread().setContextClassLoader( BoxRuntime.getInstance().getRuntimeLoader() ); return; @@ -228,6 +230,8 @@ public void startupClassLoaderPaths( RequestBoxContext requestContext ) { return new DynamicClassLoader( this.name, loadPathsUrls, BoxRuntime.getInstance().getRuntimeLoader(), false ); } ); // Make sure our thread is using the right class loader + logger.trace( "===> Setting the context classLoader to the [javasettings] loader during startupClassLoaderPaths via [{}]", + Thread.currentThread().getName() ); Thread.currentThread().setContextClassLoader( this.classLoaders.get( loaderCacheKey ) ); } diff --git a/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java b/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java index 8630a6a61..e7e7a6826 100644 --- a/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java +++ b/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java @@ -175,6 +175,8 @@ protected BaseApplicationListener( RequestBoxContext context ) { this.interceptorPool = new InterceptorPool( Key.appListener, BoxRuntime.getInstance() ) .registerInterceptionPoint( REQUEST_INTERCEPTION_POINTS ); + logger.trace( "===> Setting the context classLoader to the runtime loader during BaseAppListener Constructor via [{}]", + Thread.currentThread().getName() ); // Ensure our thread is at least using the runtime CL. If there is an application defined later, this may get updated to a more specific request CL. Thread.currentThread().setContextClassLoader( BoxRuntime.getInstance().getRuntimeLoader() ); } diff --git a/src/main/java/ortus/boxlang/runtime/loader/DynamicClassLoader.java b/src/main/java/ortus/boxlang/runtime/loader/DynamicClassLoader.java index 640714e2e..5e2b92091 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/DynamicClassLoader.java +++ b/src/main/java/ortus/boxlang/runtime/loader/DynamicClassLoader.java @@ -178,7 +178,7 @@ public Class findClass( String className, Boolean safe ) throws ClassNotFound safe = false; } - logger.trace( "[{}] Discovering class: [{}]", this.nameAsKey.getName(), className ); + logger.trace( "[{}] Discovering class: [{}] from thread [{}]", this.nameAsKey.getName(), className, Thread.currentThread().getName() ); // 1. Check the loaded cache first and return if found Class cachedClass = this.loadedClasses.get( className ); @@ -206,14 +206,14 @@ public Class findClass( String className, Boolean safe ) throws ClassNotFound // 3. Attempt to load from JARs/classes in the seeded URLs try { cachedClass = super.findClass( className ); - logger.trace( "[{}].[{}] : Class found locally", this.nameAsKey.getName(), className ); + logger.trace( "[{}].[{}] : Class found locally from thread [{}] ", this.nameAsKey.getName(), className, Thread.currentThread().getName() ); } catch ( ClassNotFoundException e ) { - // 3. If not found in JARs, delegate to parent class loader + // 4. If not found in JARs, delegate to parent class loader try { logger.trace( "[{}].[{}] : Class not found locally, trying the parent...", this.nameAsKey.getName(), className ); cachedClass = getDynamicParent().loadClass( className ); - logger.trace( "[{}].[{}] : Class found in parent", this.nameAsKey.getName(), className ); + logger.trace( "[{}].[{}] : Class found in parent on thread [{}]", this.nameAsKey.getName(), className, Thread.currentThread().getName() ); } catch ( ClassNotFoundException parentException ) { logger.trace( "[{}].[{}] : Class not found in parent, adding to unfound classes", this.nameAsKey.getName(), className ); // Add to the unfound cache From 04c58ac94385d15cb320bc3cca2204488ba6576f Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 13 Dec 2024 01:26:34 +0100 Subject: [PATCH 184/193] fix for empty Listener name on build --- .../runtime/application/ApplicationClassListener.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/application/ApplicationClassListener.java b/src/main/java/ortus/boxlang/runtime/application/ApplicationClassListener.java index 86b6d7921..c988ccdef 100644 --- a/src/main/java/ortus/boxlang/runtime/application/ApplicationClassListener.java +++ b/src/main/java/ortus/boxlang/runtime/application/ApplicationClassListener.java @@ -71,8 +71,11 @@ public ApplicationClassListener( IClassRunnable listener, RequestBoxContext cont // If there is no application name or if it's empty, make one up. String appName = StringCaster.cast( this.settings.get( Key._NAME ) ); if ( appName == null || appName.isBlank() ) { - this.settings.put( Key._NAME, "Autogenerated_Application_Name_" + EncryptionUtil.hash( listener.getRunnablePath().absolutePath().toString() ) ); + appName = "Autogenerated_Application_Name_" + EncryptionUtil.hash( listener.getRunnablePath().absolutePath().toString() ); + this.settings.put( Key._NAME, appName ); } + + this.appName = Key.of( this.settings.get( Key._NAME ) ); } /** From ad5e35d3124be38d780d95f98db4776e510c153e Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Wed, 11 Dec 2024 15:08:51 -0600 Subject: [PATCH 185/193] Work to switch the debugger over to use ASM --- .../compiler/asmboxpiler/AsmHelper.java | 16 +- .../BoxArgumentDeclarationTransformer.java | 5 +- .../BoxFunctionDeclarationTransformer.java | 3 +- .../boxlang/debugger/BoxLangDebugger.java | 271 ++++++++++++------ .../debugger/CachedThreadReference.java | 18 +- .../ortus/boxlang/debugger/DebugAdapter.java | 49 +--- .../boxlang/debugger/event/StoppedEvent.java | 6 +- 7 files changed, 235 insertions(+), 133 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java index c78ffedb0..208f506f3 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java @@ -87,8 +87,8 @@ public static List addLineNumberLabels( List nodes.add( 0, start ); nodes.add( 1, new LineNumberNode( node.getPosition().getStart().getLine(), start ) ); - nodes.add( end ); - nodes.add( new LineNumberNode( node.getPosition().getStart().getLine(), end ) ); + // nodes.add( end ); + // nodes.add( new LineNumberNode( node.getPosition().getEnd().getLine(), end ) ); return nodes; } @@ -880,7 +880,16 @@ public static void methodWithContextAndClassLocator( ClassNode classNode, null, null ); methodVisitor.visitCode(); - // start tacking the context + Label startContextLabel = new Label(); + Label endContextLabel = new Label(); + methodVisitor.visitLabel( startContextLabel ); + methodVisitor.visitLocalVariable( "context", Type.getDescriptor( IBoxContext.class ), null, startContextLabel, endContextLabel, isStatic ? 0 : 1 ); + + if ( !isStatic ) { + methodVisitor.visitLocalVariable( "this", Type.getObjectType( classNode.name ).getDescriptor(), null, startContextLabel, endContextLabel, 0 ); + } + + // start tracking the context methodVisitor.visitVarInsn( Opcodes.ALOAD, isStatic ? 0 : 1 ); tracker.trackNewContext().forEach( ( node ) -> node.accept( methodVisitor ) ); methodVisitor.visitMethodInsn( @@ -905,6 +914,7 @@ public static void methodWithContextAndClassLocator( ClassNode classNode, // TODO should only clear the used nodes tracker.getTryCatchStack().stream().forEach( ( tryNode ) -> tryNode.accept( methodVisitor ) ); tracker.clearTryCatchStack(); + methodVisitor.visitLabel( endContextLabel ); methodVisitor.visitEnd(); transpiler.popMethodContextTracker(); } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArgumentDeclarationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArgumentDeclarationTransformer.java index 45b846639..a66d24120 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArgumentDeclarationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArgumentDeclarationTransformer.java @@ -31,7 +31,6 @@ import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.TypeInsnNode; -import ortus.boxlang.compiler.asmboxpiler.AsmHelper; import ortus.boxlang.compiler.asmboxpiler.MethodContextTracker; import ortus.boxlang.compiler.asmboxpiler.Transpiler; import ortus.boxlang.compiler.asmboxpiler.transformer.AbstractTransformer; @@ -93,7 +92,9 @@ public List transform( BoxNode node, TransformerContext contex Type.getType( IStruct.class ), Type.getType( IStruct.class ) ), false ) ); - return AsmHelper.addLineNumberLabels( nodes, node ); + + return nodes; + // return AsmHelper.addLineNumberLabels( nodes, node ); } private List getDefaultExpression( BoxExpression body ) { diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java index 1533d6edb..5526db735 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java @@ -269,7 +269,8 @@ public List transform( BoxNode node, TransformerContext contex } if ( function.getModifiers().contains( BoxMethodDeclarationModifier.STATIC ) ) { - return AsmHelper.addLineNumberLabels( nodes, node ); + // return AsmHelper.addLineNumberLabels( nodes, node ); + return nodes; } else { return new ArrayList<>(); } diff --git a/src/main/java/ortus/boxlang/debugger/BoxLangDebugger.java b/src/main/java/ortus/boxlang/debugger/BoxLangDebugger.java index 11de5ea42..6e07833c6 100644 --- a/src/main/java/ortus/boxlang/debugger/BoxLangDebugger.java +++ b/src/main/java/ortus/boxlang/debugger/BoxLangDebugger.java @@ -21,6 +21,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -84,32 +85,35 @@ public class BoxLangDebugger { - public VirtualMachine vm; - private OutputStream debugAdapterOutput; - Map> breakpoints; - private List vmClasses; - private Status status; - private DebugAdapter debugAdapter; - private InputStream vmInput; - private InputStream vmErrorInput; - - public Map sourceMaps = new HashMap(); - public static Map sourceMapsFromFQN = new HashMap(); - - private ClassType keyClassRef = null; - private IVMInitializationStrategy initStrat; - private Map cachedThreads = new HashMap(); - private Map eventSets = new HashMap(); - private IStepStrategy stepStrategy; - private ExceptionRequest exceptionRequest; - private boolean any; - private String matcher; - private ReferenceType boxLangExceptionRef; - - private int SUSPEND_POLICY = ThreadStartRequest.SUSPEND_EVENT_THREAD; - private MethodExitRequest methodExitRequest = null; - - private static final Pattern NON_WORD_PATTERN = Pattern.compile( "\\W" ); + public VirtualMachine vm; + private OutputStream debugAdapterOutput; + Map> breakpoints; + private List vmClasses; + private Status status; + private DebugAdapter debugAdapter; + private InputStream vmInput; + private InputStream vmErrorInput; + + public Map sourceMaps = new HashMap(); + public static Map sourceMapsFromFQN = new HashMap(); + + private ClassType keyClassRef = null; + private IVMInitializationStrategy initStrat; + private Map cachedThreads = new HashMap(); + private Map eventSets = new HashMap(); + private IStepStrategy stepStrategy; + private ExceptionRequest exceptionRequest; + private boolean any; + private String matcher; + private ReferenceType boxLangExceptionRef; + + private int SUSPEND_POLICY = ThreadStartRequest.SUSPEND_EVENT_THREAD; + private MethodExitRequest methodExitRequest = null; + + private static final Pattern NON_WORD_PATTERN = Pattern.compile( "\\W" ); + + private boolean vmUsesJavaBoxpiler = false; + private Map>> locations = new HashMap(); public enum Status { NOT_STARTED, @@ -129,6 +133,10 @@ public BoxLangDebugger( IVMInitializationStrategy initStrat, OutputStream debugA this.debugAdapter = debugAdapter; } + public boolean isJavaBoxpiler() { + return vmUsesJavaBoxpiler; + } + public void initialize() { try { this.vm = this.initStrat.initialize(); @@ -384,7 +392,7 @@ public void disconnectFromVM() { BoxLangDebugger.sourceMapsFromFQN = new HashMap(); this.cachedThreads = new HashMap(); this.eventSets = new HashMap(); - + this.locations = new HashMap(); } public boolean hasSeen( long variableReference ) { @@ -450,18 +458,47 @@ public List getStackFrames( int threadId ) throws IncompatibleThread return matchingThreadRef.frames(); } - public record StackFrameTuple( StackFrame stackFrame, Location location, int id, Map values, ThreadReference thread ) { + public record StackFrameTuple( BoxLangDebugger debugger, StackFrame stackFrame, Location location, int id, Map values, + ThreadReference thread ) { + + private static Pattern isTemplate; + + static { + isTemplate = Pattern.compile( "(.cfs|.cfm|.bx|.bxs)$" ); + } public String sourceFile() { - var sourceMap = sourceMapsFromFQN.get( this.location().declaringType().name() ); + if ( debugger.vmUsesJavaBoxpiler ) { + return sourceMapsFromFQN.get( this.location().declaringType().name() ).getSource(); + } + + try { + return location.sourceName(); + } catch ( AbsentInformationException e ) { + // TODO Auto-generated catch block + e.printStackTrace(); + } - return sourceMap.getSource(); + return ""; } public int sourceLine() { - SourceMap sourceMap = sourceMapsFromFQN.get( this.location().declaringType().name() ); + if ( debugger.vmUsesJavaBoxpiler ) { + + SourceMap sourceMap = sourceMapsFromFQN.get( this.location().declaringType().name() ); + + return sourceMap.convertJavaLineToSourceLine( this.location().lineNumber() ); + } + + return location.lineNumber(); + } + + public String getFileName() { + return Path.of( sourceFile() ).getFileName().toString(); + } - return sourceMap.convertJavaLineToSourceLine( this.location().lineNumber() ); + public boolean isTemplate() { + return isTemplate.matcher( sourceFile() ).find(); } } @@ -526,7 +563,7 @@ private CachedThreadReference cacheOrGetThread( ThreadReference thread ) { int threadId = ( int ) thread.uniqueID(); if ( !this.cachedThreads.containsKey( threadId ) ) { - CachedThreadReference ref = new CachedThreadReference( thread ); + CachedThreadReference ref = new CachedThreadReference( this, thread ); this.cachedThreads.put( threadId, ref ); } @@ -642,7 +679,7 @@ private void handleStepEvent( EventSet eventSet, StepEvent stepEvent ) { return; } - this.stepStrategy.checkStepEvent( new CachedThreadReference( stepEvent.thread() ) ) + this.stepStrategy.checkStepEvent( new CachedThreadReference( this, stepEvent.thread() ) ) .ifPresentOrElse( ( sft ) -> { new StoppedEvent( "step", ( int ) stepEvent.thread().uniqueID() ).send( this.debugAdapterOutput ); @@ -666,9 +703,34 @@ private void lookForBoxLangClasses( ThreadReference threadRef ) { .forEach( ( refType ) -> { vmClasses.add( refType ); findSourceMapByFQN( threadRef, refType.name() ); + + try { + for ( var loc : refType.allLineLocations() ) { + trackLocation( loc ); + } + } catch ( AbsentInformationException e ) { + // TODO Auto-generated catch block + e.printStackTrace(); + } } ); } + private void trackLocation( Location loc ) throws AbsentInformationException { + String lcased = loc.sourceName().toLowerCase(); + + if ( !locations.containsKey( lcased ) ) { + locations.put( lcased, new HashMap>() ); + } + + var map = locations.get( lcased ); + + if ( !map.containsKey( loc.lineNumber() ) ) { + map.put( loc.lineNumber(), new ArrayList() ); + } + + map.get( loc.lineNumber() ).add( loc ); + } + private void handleClassPrepareEvent( EventSet eventSet, ClassPrepareEvent event ) { if ( event.referenceType().name().contains( "BoxLangException" ) ) { @@ -676,6 +738,17 @@ private void handleClassPrepareEvent( EventSet eventSet, ClassPrepareEvent event this.configureExceptionBreakpoints( this.any, this.matcher ); } + if ( event.referenceType().name().contains( "boxgenerated" ) ) { + try { + for ( var loc : event.referenceType().allLineLocations() ) { + trackLocation( loc ); + } + } catch ( AbsentInformationException e ) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + vmClasses.add( event.referenceType() ); findSourceMapByFQN( event.thread(), event.referenceType().name() ); @@ -829,9 +902,11 @@ public String getStackFrameName( StackFrameTuple tuple ) { .value(); return calledName + " (lambda)"; + } else if ( tuple.isTemplate() ) { + return tuple.getFileName(); } - return null; + return tuple.location().method().name(); } public WrappedValue upateVariableByReference( int variableReference, String key, String value ) { @@ -876,8 +951,17 @@ private void readVMErrorInput() { } private void handleBreakPointEvent( EventSet eventSet, BreakpointEvent bpe ) throws IncompatibleThreadStateException, AbsentInformationException { + String sourcePath = bpe.location().sourceName(); + int lineNumber = bpe.location().lineNumber(); + + if ( vmUsesJavaBoxpiler ) { + SourceMap map = findSourceMapByFQN( bpe.thread(), bpe.location().declaringType().name() ); + sourcePath = map.source.toLowerCase(); + lineNumber = -1; + } + this.status = Status.STOPPED; - this.debugAdapter.sendStoppedEventForBreakpoint( bpe ); + this.debugAdapter.sendStoppedEventForBreakpoint( ( int ) bpe.thread().uniqueID(), sourcePath, lineNumber ); clearCachedThreads(); this.cacheOrGetThread( ( int ) bpe.thread().uniqueID() ); trackThreadEvent( bpe.thread(), eventSet ); @@ -912,75 +996,100 @@ private void setAllBreakpoints() { for ( String fileName : this.breakpoints.keySet() ) { for ( Breakpoint breakpoint : this.breakpoints.get( fileName ) ) { - List matchingTypes = getMatchingReferenceTypes( fileName ); - if ( matchingTypes.size() == 0 ) { - continue; - } + List locations = findLocation( fileName, breakpoint ); + + locations.stream().forEach( location -> { + BreakpointRequest bpReq = vm.eventRequestManager().createBreakpointRequest( location ); + bpReq.setSuspendPolicy( SUSPEND_POLICY ); + bpReq.enable(); + } ); + } + } + } + + private List findLocation( String fileName, Breakpoint breakpoint ) { - for ( ReferenceType vmClass : matchingTypes ) { - try { - SourceMap sourceMap = findSourceMapByFQN( this.vm.allThreads().getFirst(), vmClass.name() ); + if ( vmUsesJavaBoxpiler ) { + List matchingTypes = getMatchingReferenceTypes( fileName ); - if ( sourceMap == null ) { - continue; - } + return matchingTypes.stream().map( referenceType -> findLocationUsingJavaBoxpiler( referenceType, breakpoint ) ).toList(); + } - SourceMapRecord foundMapRecord = sourceMap.findClosestSourceMapRecord( breakpoint.line ); + String lcased = fileName.toLowerCase(); - if ( foundMapRecord == null ) { - continue; - } + if ( !locations.containsKey( lcased ) ) { + return new ArrayList(); + } - String sourceName = normalizeName( foundMapRecord.javaSourceClassName ); + var map = locations.get( lcased ); - if ( !sourceName.equals( normalizeName( vmClass.name() ) ) ) { - continue; - } + if ( !map.containsKey( breakpoint.line ) ) { + return new ArrayList(); + } - Location foundLoc = null; - for ( Location loc : vmClass.allLineLocations() ) { - if ( loc.lineNumber() >= foundMapRecord.javaSourceLineStart && loc.lineNumber() <= foundMapRecord.javaSourceLineEnd ) { - foundLoc = loc; - break; - } - } + return map.get( breakpoint.line ); + } - if ( foundLoc == null ) { - continue; - } + private Location findLocationUsingJavaBoxpiler( ReferenceType vmClass, Breakpoint breakpoint ) { + SourceMap sourceMap = findSourceMapByFQN( this.vm.allThreads().getFirst(), vmClass.name() ); - BreakpointRequest bpReq = vm.eventRequestManager().createBreakpointRequest( foundLoc ); - bpReq.setSuspendPolicy( SUSPEND_POLICY ); - bpReq.enable(); + if ( sourceMap == null ) { + return null; + } + + SourceMapRecord foundMapRecord = sourceMap.findClosestSourceMapRecord( breakpoint.line ); - } catch ( BoxRuntimeException e ) { - e.printStackTrace(); - } catch ( AbsentInformationException e ) { - // TODO Auto-generated catch block - e.printStackTrace(); - } + if ( foundMapRecord == null ) { + return null; + } + + String sourceName = normalizeName( foundMapRecord.javaSourceClassName ); + + if ( !sourceName.equals( normalizeName( vmClass.name() ) ) ) { + return null; + } + + try { + for ( Location loc : vmClass.allLineLocations() ) { + if ( loc.lineNumber() >= foundMapRecord.javaSourceLineStart && loc.lineNumber() <= foundMapRecord.javaSourceLineEnd ) { + return loc; } } + } catch ( AbsentInformationException e ) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; } + + return null; } private List getMatchingReferenceTypes( String fileName ) { String lCaseFileName = fileName.toLowerCase(); + if ( vmUsesJavaBoxpiler ) { - if ( !this.sourceMaps.containsKey( lCaseFileName ) ) { - return new ArrayList(); - } + if ( !this.sourceMaps.containsKey( lCaseFileName ) ) { + return new ArrayList(); + } - SourceMap map = this.sourceMaps.get( lCaseFileName ); + SourceMap map = this.sourceMaps.get( lCaseFileName ); - Set referenceTypes = new HashSet(); + Set referenceTypes = new HashSet(); - for ( SourceMapRecord record : map.sourceMapRecords ) { - referenceTypes.add( normalizeName( record.javaSourceClassName ) ); + for ( SourceMapRecord record : map.sourceMapRecords ) { + referenceTypes.add( normalizeName( record.javaSourceClassName ) ); + } + + return vmClasses.stream().filter( ( rt ) -> referenceTypes.contains( normalizeName( rt.name() ) ) ).collect( Collectors.toList() ); } - return vmClasses.stream().filter( ( rt ) -> referenceTypes.contains( normalizeName( rt.name() ) ) ).collect( Collectors.toList() ); + String normalizedFileName = normalizeName( fileName ); + + var x = vmClasses.stream().filter( rt -> normalizeName( rt.name() ).contains( "appbxs" ) ).collect( Collectors.toList() ); + ; + + return vmClasses.stream().filter( rt -> normalizeName( rt.name() ).equals( normalizedFileName ) ).collect( Collectors.toList() ); } private String normalizeName( String className ) { diff --git a/src/main/java/ortus/boxlang/debugger/CachedThreadReference.java b/src/main/java/ortus/boxlang/debugger/CachedThreadReference.java index 87ef43a93..972e4339b 100644 --- a/src/main/java/ortus/boxlang/debugger/CachedThreadReference.java +++ b/src/main/java/ortus/boxlang/debugger/CachedThreadReference.java @@ -16,15 +16,17 @@ public class CachedThreadReference { + private BoxLangDebugger debugger; private Logger logger; public final ThreadReference threadReference; public final VirtualMachine vm; private List stackFrames = new ArrayList(); - public CachedThreadReference( ThreadReference threadReference ) { + public CachedThreadReference( BoxLangDebugger debugger, ThreadReference threadReference ) { this.logger = LoggerFactory.getLogger( BoxRuntime.class ); this.threadReference = threadReference; this.vm = threadReference.virtualMachine(); + this.debugger = debugger; this.cacheStackFrames(); } @@ -44,13 +46,19 @@ private void cacheStackFrames() { this.threadReference.frames() .stream() - .filter( ( stackFrame ) -> stackFrame.location().declaringType().name().contains( "boxgenerated" ) ) - .filter( ( stackFrame ) -> !stackFrame.location().method().name().contains( "dereferenceAndInvoke" ) ) + .filter( ( stackFrame ) -> { + return stackFrame.location().declaringType().name().contains( "boxgenerated" ); + } ) + .filter( ( stackFrame ) -> { + return !stackFrame.location().method().name().contains( "dereferenceAndInvoke" ); + } ) .forEach( ( sf ) -> { try { - + // sf.getValues( sf.visibleVariables() this.stackFrames - .add( new StackFrameTuple( sf, sf.location(), sf.hashCode(), sf.getValues( sf.visibleVariables() ), this.threadReference ) ); + .add( + new StackFrameTuple( debugger, sf, sf.location(), sf.hashCode(), sf.getValues( sf.visibleVariables() ), + this.threadReference ) ); } catch ( AbsentInformationException e ) { logger.info( "Unable to gather stack frames information for {}", sf.toString() ); } diff --git a/src/main/java/ortus/boxlang/debugger/DebugAdapter.java b/src/main/java/ortus/boxlang/debugger/DebugAdapter.java index 44bd10f5b..43b8f002d 100644 --- a/src/main/java/ortus/boxlang/debugger/DebugAdapter.java +++ b/src/main/java/ortus/boxlang/debugger/DebugAdapter.java @@ -25,7 +25,6 @@ import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -40,7 +39,6 @@ import com.sun.jdi.Location; import com.sun.jdi.ObjectReference; import com.sun.jdi.ThreadReference; -import com.sun.jdi.event.BreakpointEvent; import ortus.boxlang.compiler.SourceMap; import ortus.boxlang.debugger.BoxLangDebugger.StackFrameTuple; @@ -494,30 +492,15 @@ private IVMInitializationStrategy getInitStrategy( AttachRequest attachRequest ) public Function convertStackFrameTupleToDAPStackFrame( BoxLangDebugger debugger ) { return ( tuple ) -> { StackFrame sf = new StackFrame(); - sf.id = tuple.id(); - sf.column = 1; + sf.id = tuple.id(); + sf.column = 1; - SourceMap map = debugger.findSourceMapByFQN( tuple.thread(), tuple.location().declaringType().name() ); + sf.line = tuple.sourceLine(); + sf.name = debugger.getStackFrameName( tuple ); - sf.line = tuple.location().lineNumber(); - Integer sourceLine = map.convertJavaLineToSourceLine( sf.line ); - if ( sourceLine != null ) { - sf.line = sourceLine; - } - - sf.name = tuple.location().method().name(); - String stackFrameName = debugger.getStackFrameName( tuple ); - if ( stackFrameName != null ) { - sf.name = stackFrameName; - } else if ( map != null && map.isTemplate() ) { - sf.name = map.getFileName(); - } - - sf.source = new Source(); - if ( sf.source != null ) { - sf.source.path = map.source.toString(); - sf.source.name = Path.of( map.source ).getFileName().toString(); - } + sf.source = new Source(); + sf.source.path = tuple.sourceFile(); + sf.source.name = tuple.getFileName(); return sf; }; @@ -556,24 +539,16 @@ private SourceMap getSourceMapFromJavaLocation( ThreadReference thread, Location // ================= EVENTS ========================== // =================================================== - public void sendStoppedEventForBreakpoint( BreakpointEvent breakpointEvent ) { - SourceMap map = debugger.findSourceMapByFQN( breakpointEvent.thread(), breakpointEvent.location().declaringType().name() ); - String sourcePath = map.source.toLowerCase(); - - BreakpointRequest bp = null; + public void sendStoppedEventForBreakpoint( int id, String sourcePath, int lineNumber ) { for ( BreakpointRequest b : this.breakpoints ) { - if ( b.source.equalsIgnoreCase( sourcePath ) ) { - bp = b; - break; + if ( ! ( b.source.equalsIgnoreCase( sourcePath ) && b.line == lineNumber ) + && ! ( b.source.equalsIgnoreCase( sourcePath ) && lineNumber == -1 ) ) { + continue; } - } - if ( bp == null ) { - return; + StoppedEvent.breakpoint( id, b.id ).send( this.outputStream ); } - // TODO convert this file/line number to boxlang - StoppedEvent.breakpoint( breakpointEvent, bp.id ).send( this.outputStream ); } record ScopeCache( com.sun.jdi.StackFrame stackFrame, ObjectReference scope ) { diff --git a/src/main/java/ortus/boxlang/debugger/event/StoppedEvent.java b/src/main/java/ortus/boxlang/debugger/event/StoppedEvent.java index 95de1e2f8..533d09b84 100644 --- a/src/main/java/ortus/boxlang/debugger/event/StoppedEvent.java +++ b/src/main/java/ortus/boxlang/debugger/event/StoppedEvent.java @@ -20,8 +20,6 @@ import java.util.List; import java.util.stream.IntStream; -import com.sun.jdi.event.BreakpointEvent; - import ortus.boxlang.debugger.DebugAdapter; /** @@ -108,9 +106,9 @@ public static StoppedEvent breakpoint( int threadId ) { return new StoppedEvent( "breakpoint", threadId, new int[] {} ); } - public static StoppedEvent breakpoint( BreakpointEvent breakpointEvent, int breakpointId ) { + public static StoppedEvent breakpoint( int id, int breakpointId ) { - return new StoppedEvent( "breakpoint", ( int ) breakpointEvent.thread().uniqueID(), new int[] { breakpointId } ); + return new StoppedEvent( "breakpoint", id, new int[] { breakpointId } ); } @Override From 7346d7f03d6b139e39451e875fb826182b6c3f6d Mon Sep 17 00:00:00 2001 From: Jacob Beers Date: Fri, 13 Dec 2024 00:20:10 -0600 Subject: [PATCH 186/193] BL-844 convert debugger to use ASM --- .../ortus/boxlang/compiler/asmboxpiler/AsmHelper.java | 10 ++++------ .../expression/BoxArrayLiteralTransformer.java | 3 ++- .../expression/BoxStringLiteralTransformer.java | 7 +++++-- .../statement/BoxFunctionDeclarationTransformer.java | 7 +++---- .../java/ortus/boxlang/debugger/BoxLangDebugger.java | 2 +- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java index 208f506f3..036a72a0a 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java @@ -77,18 +77,16 @@ public static LineNumberIns translatePosition( BoxNode node ) { } public static List addLineNumberLabels( List nodes, BoxNode node ) { - LabelNode start = new LabelNode(); - LabelNode end = new LabelNode(); + LabelNode start = new LabelNode(); if ( node.getPosition() == null ) { return nodes; } - nodes.add( 0, start ); - nodes.add( 1, new LineNumberNode( node.getPosition().getStart().getLine(), start ) ); + int startLine = node.getPosition().getStart().getLine(); - // nodes.add( end ); - // nodes.add( new LineNumberNode( node.getPosition().getEnd().getLine(), end ) ); + nodes.add( 0, start ); + nodes.add( 1, new LineNumberNode( startLine, start ) ); return nodes; } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArrayLiteralTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArrayLiteralTransformer.java index 4ec12b35d..6b15f8259 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArrayLiteralTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxArrayLiteralTransformer.java @@ -51,6 +51,7 @@ public List transform( BoxNode node, TransformerContext contex "of", Type.getMethodDescriptor( Type.getType( Array.class ), Type.getType( Object[].class ) ), false ) ); - return AsmHelper.addLineNumberLabels( nodes, node ); + return nodes; + // return AsmHelper.addLineNumberLabels( nodes, node ); } } diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringLiteralTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringLiteralTransformer.java index 5dc5804d6..30f29cab8 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringLiteralTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/expression/BoxStringLiteralTransformer.java @@ -53,7 +53,9 @@ public List transform( BoxNode node, TransformerContext contex if ( returnContext != ReturnValueContext.VALUE && returnContext != ReturnValueContext.VALUE_OR_NULL ) { nodes.add( new InsnNode( Opcodes.POP ) ); } - return AsmHelper.addLineNumberLabels( nodes, node ); + + return nodes; + // return AsmHelper.addLineNumberLabels( nodes, node ); } List parts = splitStringIntoParts( value ); @@ -82,7 +84,8 @@ public List transform( BoxNode node, TransformerContext contex nodes.add( new InsnNode( Opcodes.POP ) ); } - return AsmHelper.addLineNumberLabels( nodes, node ); + return nodes; + // return AsmHelper.addLineNumberLabels( nodes, node ); } /** diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java index 5526db735..1592f2c6a 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxFunctionDeclarationTransformer.java @@ -169,13 +169,13 @@ public List transform( BoxNode node, TransformerContext contex () -> { if ( function.getBody() == null ) { - return AsmHelper.addLineNumberLabels( new ArrayList(), node ); + return new ArrayList(); } - return AsmHelper.addLineNumberLabels( function.getBody() + return function.getBody() .stream() .flatMap( statement -> transpiler.transform( statement, safe, ReturnValueContext.EMPTY ).stream() ) - .collect( Collectors.toList() ), node ); + .collect( Collectors.toList() ); } ); transpiler.decrementfunctionBodyCounter(); @@ -269,7 +269,6 @@ public List transform( BoxNode node, TransformerContext contex } if ( function.getModifiers().contains( BoxMethodDeclarationModifier.STATIC ) ) { - // return AsmHelper.addLineNumberLabels( nodes, node ); return nodes; } else { return new ArrayList<>(); diff --git a/src/main/java/ortus/boxlang/debugger/BoxLangDebugger.java b/src/main/java/ortus/boxlang/debugger/BoxLangDebugger.java index 6e07833c6..07b4b7c17 100644 --- a/src/main/java/ortus/boxlang/debugger/BoxLangDebugger.java +++ b/src/main/java/ortus/boxlang/debugger/BoxLangDebugger.java @@ -999,7 +999,7 @@ private void setAllBreakpoints() { List locations = findLocation( fileName, breakpoint ); - locations.stream().forEach( location -> { + locations.stream().limit( 1 ).forEach( location -> { BreakpointRequest bpReq = vm.eventRequestManager().createBreakpointRequest( location ); bpReq.setSuspendPolicy( SUSPEND_POLICY ); bpReq.enable(); From 2e8b5e5b4b815f064d6460d452d51640e7f5c4cf Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 13 Dec 2024 01:45:28 -0600 Subject: [PATCH 187/193] BL-823 --- .../compiler/ast/sql/select/SQLJoin.java | 7 + .../ast/sql/select/SQLResultColumn.java | 13 + .../compiler/ast/sql/select/SQLSelect.java | 9 + .../compiler/ast/sql/select/SQLTable.java | 22 +- .../ast/sql/select/expression/SQLColumn.java | 32 ++- .../sql/select/expression/SQLExpression.java | 4 +- .../sql/select/expression/SQLFunction.java | 25 +- .../ast/sql/select/expression/SQLParam.java | 8 +- .../sql/select/expression/SQLParenthesis.java | 6 +- .../select/expression/SQLStarExpression.java | 4 +- .../expression/literal/SQLBooleanLiteral.java | 4 +- .../expression/literal/SQLNullLiteral.java | 4 +- .../expression/literal/SQLNumberLiteral.java | 15 +- .../expression/literal/SQLStringLiteral.java | 4 +- .../operation/SQLBetweenOperation.java | 10 +- .../operation/SQLBinaryOperation.java | 88 ++++--- .../expression/operation/SQLInOperation.java | 8 +- .../operation/SQLUnaryOperation.java | 18 +- .../compiler/toolchain/SQLVisitor.java | 43 ++-- .../boxlang/runtime/bifs/global/math/Abs.java | 5 +- .../runtime/bifs/global/math/Acos.java | 5 +- .../runtime/bifs/global/math/Asin.java | 5 +- .../boxlang/runtime/bifs/global/math/Atn.java | 15 +- .../runtime/bifs/global/math/Ceiling.java | 5 +- .../boxlang/runtime/bifs/global/math/Cos.java | 14 +- .../boxlang/runtime/bifs/global/math/Exp.java | 14 +- .../runtime/bifs/global/math/Floor.java | 5 +- .../boxlang/runtime/bifs/global/math/Sin.java | 13 +- .../boxlang/runtime/bifs/global/math/Sqr.java | 5 +- .../boxlang/runtime/bifs/global/math/Tan.java | 13 +- .../runtime/jdbc/qoq/IQoQFunctionDef.java | 33 +++ .../jdbc/qoq/QoQAggregateFunctionDef.java | 30 +++ .../runtime/jdbc/qoq/QoQExecution.java | 105 ++++++++ .../runtime/jdbc/qoq/QoQExecutionService.java | 231 ++++++++++++++---- .../runtime/jdbc/qoq/QoQFunctionService.java | 94 ++++++- .../jdbc/qoq/QoQScalarFunctionDef.java | 28 +++ .../jdbc/qoq/functions/aggregate/Max.java | 52 ++++ .../jdbc/qoq/functions/scalar/Abs.java | 50 ++++ .../jdbc/qoq/functions/scalar/Acos.java | 50 ++++ .../jdbc/qoq/functions/scalar/Asin.java | 50 ++++ .../jdbc/qoq/functions/scalar/Atan.java | 50 ++++ .../jdbc/qoq/functions/scalar/Ceiling.java | 50 ++++ .../jdbc/qoq/functions/scalar/Coalesce.java | 54 ++++ .../jdbc/qoq/functions/scalar/Concat.java | 54 ++++ .../jdbc/qoq/functions/scalar/Cos.java | 50 ++++ .../jdbc/qoq/functions/scalar/Exp.java | 50 ++++ .../jdbc/qoq/functions/scalar/Floor.java | 50 ++++ .../jdbc/qoq/functions/scalar/IsNull.java | 49 ++++ .../jdbc/qoq/functions/scalar/Length.java | 50 ++++ .../jdbc/qoq/functions/scalar/Lower.java | 50 ++++ .../jdbc/qoq/functions/scalar/Ltrim.java | 55 +++++ .../jdbc/qoq/functions/scalar/Mod.java | 56 +++++ .../jdbc/qoq/functions/scalar/Power.java | 55 +++++ .../jdbc/qoq/functions/scalar/Rtrim.java | 55 +++++ .../jdbc/qoq/functions/scalar/Sin.java | 50 ++++ .../jdbc/qoq/functions/scalar/Sqrt.java | 50 ++++ .../jdbc/qoq/functions/scalar/Tan.java | 50 ++++ .../jdbc/qoq/functions/scalar/Trim.java | 50 ++++ .../jdbc/qoq/functions/scalar/Upper.java | 50 ++++ .../ortus/boxlang/compiler/QoQParseTest.java | 21 +- 60 files changed, 1960 insertions(+), 185 deletions(-) create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/IQoQFunctionDef.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQAggregateFunctionDef.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQExecution.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQScalarFunctionDef.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/aggregate/Max.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Abs.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Acos.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Asin.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Atan.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Ceiling.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Coalesce.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Concat.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Cos.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Exp.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Floor.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/IsNull.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Length.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Lower.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Ltrim.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Mod.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Power.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Rtrim.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Sin.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Sqrt.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Tan.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Trim.java create mode 100644 src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Upper.java diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLJoin.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLJoin.java index 243d17705..2b016fcb8 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLJoin.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLJoin.java @@ -43,6 +43,9 @@ public class SQLJoin extends SQLNode { */ public SQLJoin( SQLJoinType type, SQLTable table, SQLExpression on, Position position, String sourceText ) { super( position, sourceText ); + setType( type ); + setTable( table ); + setOn( on ); } /** @@ -86,6 +89,10 @@ public SQLExpression getOn() { * Set the ON expression */ public void setOn( SQLExpression on ) { + if ( on == null ) { + this.on = null; + return; + } if ( !on.isBoolean( null ) ) { throw new BoxRuntimeException( "ON clause must be a boolean expression" ); } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLResultColumn.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLResultColumn.java index 4e5e4c1b6..4b159852c 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLResultColumn.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLResultColumn.java @@ -21,6 +21,7 @@ import ortus.boxlang.compiler.ast.sql.SQLNode; import ortus.boxlang.compiler.ast.sql.select.expression.SQLColumn; import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; +import ortus.boxlang.compiler.ast.sql.select.expression.SQLFunction; import ortus.boxlang.compiler.ast.sql.select.expression.SQLStarExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; @@ -115,6 +116,18 @@ public boolean isStarExpression() { return expression instanceof SQLStarExpression; } + /** + * Does this column contain an aggregate function? + * + * @return true if the column contains an aggregate function + */ + public boolean hasAggregate() { + if ( expression instanceof SQLFunction f && f.isAggregate() ) { + return true; + } + return expression.getDescendantsOfType( SQLFunction.class, f -> f.isAggregate() ).size() > 0; + } + @Override public void accept( VoidBoxVisitor v ) { // TODO Auto-generated method stub diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLSelect.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLSelect.java index b74dca64d..f0f2d3f93 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLSelect.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLSelect.java @@ -226,6 +226,15 @@ public Long getLimitValue() { return getLimit().getValue().longValue(); } + /** + * Does this SELECT statement have an aggregate result? + * + * @return true if the result contains an aggregate function + */ + public boolean hasAggregateResult() { + return getResultColumns().stream().anyMatch( SQLResultColumn::hasAggregate ); + } + @Override public void accept( VoidBoxVisitor v ) { // TODO Auto-generated method stub diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLTable.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLTable.java index 1fa3f0acd..4bff3a9fd 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLTable.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/SQLTable.java @@ -34,17 +34,23 @@ public class SQLTable extends SQLNode { private Key alias; + /** + * Encounter order of the table in the query. This should match the position of the table in the tableLookup map later + */ + private int index; + /** * Constructor * * @param position position of the statement in the source code * @param sourceText source code of the statement */ - public SQLTable( String schema, String name, String alias, Position position, String sourceText ) { + public SQLTable( String schema, String name, String alias, int index, Position position, String sourceText ) { super( position, sourceText ); setSchema( schema ); setName( name ); setAlias( alias ); + setIndex( index ); } /** @@ -89,6 +95,20 @@ public void setAlias( String alias ) { this.alias = ( alias == null ) ? null : Key.of( alias ); } + /** + * Get the table index + */ + public int getIndex() { + return index; + } + + /** + * Set the table index + */ + public void setIndex( int index ) { + this.index = index; + } + public boolean isCalled( Key name ) { return this.name.equals( name ) || ( alias != null && alias.equals( name ) ); } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLColumn.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLColumn.java index d9ab3bca8..82b297f98 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLColumn.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLColumn.java @@ -22,9 +22,10 @@ import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; -import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecution; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.QueryColumnType; +import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; /** * Abstract Node class representing SQL column reference @@ -69,12 +70,30 @@ public void setName( String name ) { } /** - * Get the table + * Get the table (may be null if there is no alias) */ public SQLTable getTable() { return table; } + /** + * Get the table, performing runtime lookup if necessary + */ + public SQLTable getTableFinal( QoQExecution QoQExec ) { + var t = getTable(); + if ( t != null ) { + return t; + } + // Abmiguity, we need to find the table + var tables = QoQExec.getTableLookup().entrySet(); + for ( var tableSet : tables ) { + if ( tableSet.getValue().getColumns().containsKey( name ) ) { + return tableSet.getKey(); + } + } + throw new BoxRuntimeException( "Column " + name + " is ambiguous and not found in any table." ); + } + /** * Set the table */ @@ -87,14 +106,17 @@ public void setTable( SQLTable table ) { * What type does this expression evaluate to */ public QueryColumnType getType( QoQExecution QoQExec ) { - return QoQExec.tableLookup().get( table ).getColumns().get( name ).getType(); + return QoQExec.getTableLookup().get( getTableFinal( QoQExec ) ).getColumns().get( name ).getType(); } /** * Evaluate the expression */ - public Object evaluate( QoQExecution QoQExec, int i ) { - return QoQExec.tableLookup().get( table ).getCell( name, i - 1 ); + public Object evaluate( QoQExecution QoQExec, int[] intersection ) { + var tableFinal = getTableFinal( QoQExec ); + // System.out.println( "getting SQL column: " + name.getName() + " from table: " + tableFinal.getName() + " with index: " + tableFinal.getIndex() ); + // System.out.println( "intersection: " + Arrays.toString( intersection ) ); + return QoQExec.getTableLookup().get( tableFinal ).getCell( name, intersection[ tableFinal.getIndex() ] - 1 ); } /** diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLExpression.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLExpression.java index 0555c868e..c0893767a 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLExpression.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLExpression.java @@ -16,7 +16,7 @@ import ortus.boxlang.compiler.ast.Position; import ortus.boxlang.compiler.ast.sql.SQLNode; -import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecution; import ortus.boxlang.runtime.types.QueryColumnType; /** @@ -76,6 +76,6 @@ public QueryColumnType getType( QoQExecution QoQExec ) { /** * Evaluate the expression */ - public abstract Object evaluate( QoQExecution QoQExec, int i ); + public abstract Object evaluate( QoQExecution QoQExec, int[] intersection ); } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLFunction.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLFunction.java index 64aeb4b89..969fe946b 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLFunction.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLFunction.java @@ -22,8 +22,9 @@ import ortus.boxlang.compiler.ast.Position; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; -import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecution; import ortus.boxlang.runtime.jdbc.qoq.QoQFunctionService; +import ortus.boxlang.runtime.jdbc.qoq.QoQFunctionService.QoQFunction; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.QueryColumnType; @@ -111,18 +112,34 @@ public boolean isNumeric( QoQExecution QoQExec ) { return numericTypes.contains( getType( QoQExec ) ); } + /** + * Is function aggregate + */ + public boolean isAggregate() { + return QoQFunctionService.getFunction( name ).isAggregate(); + } + /** * What type does this expression evaluate to */ public QueryColumnType getType( QoQExecution QoQExec ) { - return QoQFunctionService.getFunction( name ).getReturnType(); + return QoQFunctionService.getFunction( name ).returnType(); } /** * Evaluate the expression */ - public Object evaluate( QoQExecution QoQExec, int i ) { - return QoQFunctionService.getFunction( name ).invoke( arguments.stream().map( a -> a.evaluate( QoQExec, i ) ).toList() ); + public Object evaluate( QoQExecution QoQExec, int[] intersection ) { + QoQFunction function = QoQFunctionService.getFunction( name ); + if ( function.requiredParams() > arguments.size() ) { + throw new RuntimeException( + "QoQ Function [" + name + "] expects at least" + function.requiredParams() + " arguments, but got " + arguments.size() ); + } + if ( function.isAggregate() ) { + return function.invokeAggregate( arguments, QoQExec ); + } else { + return function.invoke( arguments.stream().map( a -> a.evaluate( QoQExec, intersection ) ).toList() ); + } } @Override diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLParam.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLParam.java index 2b3429aad..a1a33b214 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLParam.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLParam.java @@ -21,7 +21,7 @@ import ortus.boxlang.compiler.ast.Position; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; -import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecution; import ortus.boxlang.runtime.types.QueryColumnType; /** @@ -110,14 +110,14 @@ public boolean isNumeric( QoQExecution QoQExec ) { * What type does this expression evaluate to */ public QueryColumnType getType( QoQExecution QoQExec ) { - return QueryColumnType.fromSQLType( QoQExec.params().get( index ).type() ); + return QueryColumnType.fromSQLType( QoQExec.getParams().get( index ).type() ); } /** * Evaluate the expression */ - public Object evaluate( QoQExecution QoQExec, int i ) { - return QoQExec.params().get( index ).value(); + public Object evaluate( QoQExecution QoQExec, int[] intersection ) { + return QoQExec.getParams().get( index ).value(); } @Override diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLParenthesis.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLParenthesis.java index 277d1ad92..be9041f3e 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLParenthesis.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLParenthesis.java @@ -20,7 +20,7 @@ import ortus.boxlang.compiler.ast.Position; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; -import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecution; import ortus.boxlang.runtime.types.QueryColumnType; /** @@ -89,8 +89,8 @@ public QueryColumnType getType( QoQExecution QoQExec ) { /** * Evaluate the expression */ - public Object evaluate( QoQExecution QoQExec, int i ) { - return expression.evaluate( QoQExec, i ); + public Object evaluate( QoQExecution QoQExec, int[] intersection ) { + return expression.evaluate( QoQExec, intersection ); } @Override diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLStarExpression.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLStarExpression.java index 6a36b52e4..b6e4e0800 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLStarExpression.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/SQLStarExpression.java @@ -21,7 +21,7 @@ import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; -import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecution; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; /** @@ -60,7 +60,7 @@ public void setTable( SQLTable table ) { /** * Evaluate the expression */ - public Object evaluate( QoQExecution QoQExec, int i ) { + public Object evaluate( QoQExecution QoQExec, int[] intersection ) { throw new BoxRuntimeException( "Cannot evaluate a * expression" ); } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLBooleanLiteral.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLBooleanLiteral.java index a9bb8b981..05ea16c52 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLBooleanLiteral.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLBooleanLiteral.java @@ -21,7 +21,7 @@ import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; -import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecution; /** * Abstract Node class representing SQL boolean @@ -75,7 +75,7 @@ public boolean isBoolean( QoQExecution QoQExec ) { /** * Evaluate the expression */ - public Object evaluate( QoQExecution QoQExec, int i ) { + public Object evaluate( QoQExecution QoQExec, int[] intersection ) { return value; } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNullLiteral.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNullLiteral.java index 4d0ff98fa..1840dcf81 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNullLiteral.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNullLiteral.java @@ -21,7 +21,7 @@ import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; -import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecution; /** * Abstract Node class representing SQL null @@ -48,7 +48,7 @@ public boolean isLiteral() { /** * Evaluate the expression */ - public Object evaluate( QoQExecution QoQExec, int i ) { + public Object evaluate( QoQExecution QoQExec, int[] intersection ) { return null; } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNumberLiteral.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNumberLiteral.java index 6e05cbc83..e10da0955 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNumberLiteral.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLNumberLiteral.java @@ -22,7 +22,7 @@ import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; -import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecution; import ortus.boxlang.runtime.types.Query; import ortus.boxlang.runtime.types.QueryColumnType; @@ -64,6 +64,17 @@ public boolean isLiteral() { return true; } + /** + * Runtime check if the expression evaluates to a numeric value and works for columns as well + * + * @param QoQExec Query execution state + * + * @return true if the expression evaluates to a numeric value + */ + public boolean isNumeric( QoQExecution QoQExec ) { + return true; + } + /** * What type does this expression evaluate to */ @@ -74,7 +85,7 @@ public QueryColumnType getType( QoQExecution QoQExec ) { /** * Evaluate the expression */ - public Object evaluate( QoQExecution QoQExec, int i ) { + public Object evaluate( QoQExecution QoQExec, int[] intersection ) { return value; } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLStringLiteral.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLStringLiteral.java index ff4950590..b1ae96ef4 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLStringLiteral.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/literal/SQLStringLiteral.java @@ -21,7 +21,7 @@ import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; -import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecution; import ortus.boxlang.runtime.types.QueryColumnType; /** @@ -72,7 +72,7 @@ public QueryColumnType getType( QoQExecution QoQExec ) { /** * Evaluate the expression */ - public Object evaluate( QoQExecution QoQExec, int i ) { + public Object evaluate( QoQExecution QoQExec, int[] intersection ) { return value; } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBetweenOperation.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBetweenOperation.java index 5ed6efc3d..5fe76c3eb 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBetweenOperation.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBetweenOperation.java @@ -21,7 +21,7 @@ import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; -import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecution; import ortus.boxlang.runtime.operators.Compare; /** @@ -127,10 +127,10 @@ public boolean isBoolean( QoQExecution QoQExec ) { /** * Evaluate the expression */ - public Object evaluate( QoQExecution QoQExec, int i ) { - Object leftValue = left.evaluate( QoQExec, i ); - Object rightValue = right.evaluate( QoQExec, i ); - Object expressionValue = expression.evaluate( QoQExec, i ); + public Object evaluate( QoQExecution QoQExec, int[] intersection ) { + Object leftValue = left.evaluate( QoQExec, intersection ); + Object rightValue = right.evaluate( QoQExec, intersection ); + Object expressionValue = expression.evaluate( QoQExec, intersection ); // The ^ not inverses the result if the not flag is true return doBetween( leftValue, rightValue, expressionValue ) ^ not; } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBinaryOperation.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBinaryOperation.java index 6c579263f..8467248c7 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBinaryOperation.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLBinaryOperation.java @@ -24,7 +24,7 @@ import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; import ortus.boxlang.runtime.dynamic.casters.StringCaster; import ortus.boxlang.runtime.jdbc.qoq.LikeOperation; -import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecution; import ortus.boxlang.runtime.operators.Compare; import ortus.boxlang.runtime.operators.Concat; import ortus.boxlang.runtime.operators.EqualsEquals; @@ -190,72 +190,102 @@ public boolean isNumeric( QoQExecution QoQExec ) { /** * Evaluate the expression */ - public Object evaluate( QoQExecution QoQExec, int i ) { + public Object evaluate( QoQExecution QoQExec, int[] intersection ) { Object leftValue; Object rightValue; + Double leftNum; + Double rightNum; int compareResult; // Implement each binary operator switch ( operator ) { case DIVIDE : ensureNumericOperands( QoQExec ); - return evalAsNumber( left, QoQExec, i ) / evalAsNumber( right, QoQExec, i ); + leftNum = evalAsNumber( left, QoQExec, intersection ); + rightNum = evalAsNumber( right, QoQExec, intersection ); + if ( leftNum == null || rightNum == null ) { + return null; + } + if ( rightNum.doubleValue() == 0 ) { + throw new BoxRuntimeException( "Division by zero" ); + } + return leftNum / rightNum; case EQUAL : - leftValue = left.evaluate( QoQExec, i ); - rightValue = right.evaluate( QoQExec, i ); + leftValue = left.evaluate( QoQExec, intersection ); + rightValue = right.evaluate( QoQExec, intersection ); return EqualsEquals.invoke( leftValue, rightValue, true ); case GREATERTHAN : - return Compare.invoke( left.evaluate( QoQExec, i ), right.evaluate( QoQExec, i ), true ) == 1; + return Compare.invoke( left.evaluate( QoQExec, intersection ), right.evaluate( QoQExec, intersection ), true ) == 1; case GREATERTHANOREQUAL : - compareResult = Compare.invoke( left.evaluate( QoQExec, i ), right.evaluate( QoQExec, i ), true ); + compareResult = Compare.invoke( left.evaluate( QoQExec, intersection ), right.evaluate( QoQExec, intersection ), true ); return compareResult == 1 || compareResult == 0; case LESSTHAN : - return Compare.invoke( left.evaluate( QoQExec, i ), right.evaluate( QoQExec, i ), true ) == -1; + return Compare.invoke( left.evaluate( QoQExec, intersection ), right.evaluate( QoQExec, intersection ), true ) == -1; case LESSTHANOREQUAL : - compareResult = Compare.invoke( left.evaluate( QoQExec, i ), right.evaluate( QoQExec, i ), true ); + compareResult = Compare.invoke( left.evaluate( QoQExec, intersection ), right.evaluate( QoQExec, intersection ), true ); return compareResult == -1 || compareResult == 0; case MINUS : ensureNumericOperands( QoQExec ); - return evalAsNumber( left, QoQExec, i ) - evalAsNumber( right, QoQExec, i ); + leftNum = evalAsNumber( left, QoQExec, intersection ); + rightNum = evalAsNumber( right, QoQExec, intersection ); + if ( leftNum == null || rightNum == null ) { + return null; + } + return leftNum - rightNum; case MODULO : ensureNumericOperands( QoQExec ); - return evalAsNumber( left, QoQExec, i ) % evalAsNumber( right, QoQExec, i ); + leftNum = evalAsNumber( left, QoQExec, intersection ); + rightNum = evalAsNumber( right, QoQExec, intersection ); + if ( leftNum == null || rightNum == null ) { + return null; + } + return leftNum % rightNum; case MULTIPLY : ensureNumericOperands( QoQExec ); - return evalAsNumber( left, QoQExec, i ) * evalAsNumber( right, QoQExec, i ); + leftNum = evalAsNumber( left, QoQExec, intersection ); + rightNum = evalAsNumber( right, QoQExec, intersection ); + if ( leftNum == null || rightNum == null ) { + return null; + } + return leftNum * rightNum; case NOTEQUAL : - leftValue = left.evaluate( QoQExec, i ); - rightValue = right.evaluate( QoQExec, i ); + leftValue = left.evaluate( QoQExec, intersection ); + rightValue = right.evaluate( QoQExec, intersection ); return !EqualsEquals.invoke( leftValue, rightValue, true ); case AND : ensureBooleanOperands( QoQExec ); - leftValue = left.evaluate( QoQExec, i ); + leftValue = left.evaluate( QoQExec, intersection ); // Short circuit, don't eval right if left is false if ( ( Boolean ) leftValue ) { - return ( Boolean ) right.evaluate( QoQExec, i ); + return ( Boolean ) right.evaluate( QoQExec, intersection ); } else { return false; } case OR : ensureBooleanOperands( QoQExec ); - if ( ( Boolean ) left.evaluate( QoQExec, i ) ) { + if ( ( Boolean ) left.evaluate( QoQExec, intersection ) ) { return true; } - if ( ( Boolean ) right.evaluate( QoQExec, i ) ) { + if ( ( Boolean ) right.evaluate( QoQExec, intersection ) ) { return true; } return false; case PLUS : if ( left.isNumeric( QoQExec ) && right.isNumeric( QoQExec ) ) { - return evalAsNumber( left, QoQExec, i ) + evalAsNumber( right, QoQExec, i ); + leftNum = evalAsNumber( left, QoQExec, intersection ); + rightNum = evalAsNumber( right, QoQExec, intersection ); + if ( leftNum == null || rightNum == null ) { + return null; + } + return leftNum + rightNum; } else { - return Concat.invoke( left.evaluate( QoQExec, i ), right.evaluate( QoQExec, i ) ); + return Concat.invoke( left.evaluate( QoQExec, intersection ), right.evaluate( QoQExec, intersection ) ); } case LIKE : - return doLike( QoQExec, i ); + return doLike( QoQExec, intersection ); case NOTLIKE : - return !doLike( QoQExec, i ); + return !doLike( QoQExec, intersection ); case CONCAT : - return Concat.invoke( left.evaluate( QoQExec, i ), right.evaluate( QoQExec, i ) ); + return Concat.invoke( left.evaluate( QoQExec, intersection ), right.evaluate( QoQExec, intersection ) ); default : throw new BoxRuntimeException( "Unknown binary operator: " + operator ); } @@ -264,12 +294,12 @@ public Object evaluate( QoQExecution QoQExec, int i ) { /** * Implement LIKE so we can reuse for NOT LIKE */ - private boolean doLike( QoQExecution QoQExec, int i ) { - String leftValueStr = StringCaster.cast( left.evaluate( QoQExec, i ) ); - String rightValueStr = StringCaster.cast( right.evaluate( QoQExec, i ) ); + private boolean doLike( QoQExecution QoQExec, int[] intersection ) { + String leftValueStr = StringCaster.cast( left.evaluate( QoQExec, intersection ) ); + String rightValueStr = StringCaster.cast( right.evaluate( QoQExec, intersection ) ); String escapeValue = null; if ( escape != null ) { - escapeValue = StringCaster.cast( escape.evaluate( QoQExec, i ) ); + escapeValue = StringCaster.cast( escape.evaluate( QoQExec, intersection ) ); } return LikeOperation.invoke( leftValueStr, rightValueStr, escapeValue ); } @@ -311,8 +341,8 @@ private void ensureNumericOperands( QoQExecution QoQExec ) { * * @return */ - private double evalAsNumber( SQLExpression expression, QoQExecution QoQExec, int i ) { - return ( ( Number ) expression.evaluate( QoQExec, i ) ).doubleValue(); + private double evalAsNumber( SQLExpression expression, QoQExecution QoQExec, int[] intersection ) { + return ( ( Number ) expression.evaluate( QoQExec, intersection ) ).doubleValue(); } @Override diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLInOperation.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLInOperation.java index f287f5cd6..03197ad18 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLInOperation.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLInOperation.java @@ -22,7 +22,7 @@ import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; -import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecution; import ortus.boxlang.runtime.operators.EqualsEquals; /** @@ -109,10 +109,10 @@ public boolean isBoolean( QoQExecution QoQExec ) { /** * Evaluate the expression */ - public Object evaluate( QoQExecution QoQExec, int i ) { - Object value = expression.evaluate( QoQExec, i ); + public Object evaluate( QoQExecution QoQExec, int[] intersection ) { + Object value = expression.evaluate( QoQExec, intersection ); for ( SQLExpression v : values ) { - if ( EqualsEquals.invoke( value, v.evaluate( QoQExec, i ), true ) ) { + if ( EqualsEquals.invoke( value, v.evaluate( QoQExec, intersection ), true ) ) { return !not; } } diff --git a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLUnaryOperation.java b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLUnaryOperation.java index f8991b581..35295e08d 100644 --- a/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLUnaryOperation.java +++ b/src/main/java/ortus/boxlang/compiler/ast/sql/select/expression/operation/SQLUnaryOperation.java @@ -22,7 +22,7 @@ import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; import ortus.boxlang.compiler.ast.visitor.ReplacingBoxVisitor; import ortus.boxlang.compiler.ast.visitor.VoidBoxVisitor; -import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.QoQExecution; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecution; import ortus.boxlang.runtime.types.QueryColumnType; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; @@ -122,22 +122,22 @@ public boolean isNumeric( QoQExecution QoQExec ) { /** * Evaluate the expression */ - public Object evaluate( QoQExecution QoQExec, int i ) { + public Object evaluate( QoQExecution QoQExec, int[] intersection ) { // Implement each unary operator switch ( operator ) { case ISNOTNULL : - return expression.evaluate( QoQExec, i ) != null; + return expression.evaluate( QoQExec, intersection ) != null; case ISNULL : - return expression.evaluate( QoQExec, i ) != null; + return expression.evaluate( QoQExec, intersection ) != null; case MINUS : ensureNumericOperand( QoQExec ); - return -evalAsNumber( expression, QoQExec, i ); + return -evalAsNumber( expression, QoQExec, intersection ); case NOT : ensureBooleanOperand( QoQExec ); - return ! ( ( boolean ) expression.evaluate( QoQExec, i ) ); + return ! ( ( boolean ) expression.evaluate( QoQExec, intersection ) ); case PLUS : ensureNumericOperand( QoQExec ); - return expression.evaluate( QoQExec, i ); + return expression.evaluate( QoQExec, intersection ); default : throw new BoxRuntimeException( "Unknown binary operator: " + operator ); @@ -175,8 +175,8 @@ private void ensureNumericOperand( QoQExecution QoQExec ) { * * @return */ - private double evalAsNumber( SQLExpression expression, QoQExecution QoQExec, int i ) { - return ( ( Number ) expression.evaluate( QoQExec, i ) ).doubleValue(); + private double evalAsNumber( SQLExpression expression, QoQExecution QoQExec, int[] intersection ) { + return ( ( Number ) expression.evaluate( QoQExec, intersection ) ).doubleValue(); } @Override diff --git a/src/main/java/ortus/boxlang/compiler/toolchain/SQLVisitor.java b/src/main/java/ortus/boxlang/compiler/toolchain/SQLVisitor.java index 612a2d77f..f4536ad62 100644 --- a/src/main/java/ortus/boxlang/compiler/toolchain/SQLVisitor.java +++ b/src/main/java/ortus/boxlang/compiler/toolchain/SQLVisitor.java @@ -8,6 +8,7 @@ import ortus.boxlang.compiler.ast.BoxNode; import ortus.boxlang.compiler.ast.Position; import ortus.boxlang.compiler.ast.sql.select.SQLJoin; +import ortus.boxlang.compiler.ast.sql.select.SQLJoinType; import ortus.boxlang.compiler.ast.sql.select.SQLResultColumn; import ortus.boxlang.compiler.ast.sql.select.SQLSelect; import ortus.boxlang.compiler.ast.sql.select.SQLSelectStatement; @@ -51,6 +52,7 @@ public class SQLVisitor extends SQLGrammarBaseVisitor { private final SQLParser tools; private int bindCount = 0; + private int tableIndex = 0; public SQLVisitor( SQLParser tools ) { this.tools = tools; @@ -165,6 +167,22 @@ public SQLSelect visitSelect_core( Select_coreContext ctx ) { List groupBys = null; SQLExpression having = null; + if ( !ctx.table().isEmpty() ) { + var firstTable = ctx.table().get( 0 ); + table = ( SQLTable ) visit( firstTable ); + } + + // TODO: handle joins + // Treat "FROM table1, table2" as a join + if ( ctx.table() != null && ctx.table().size() > 1 ) { + joins = new ArrayList(); + for ( int i = 1; i < ctx.table().size(); i++ ) { + var tableCtx = ctx.table().get( i ); + SQLTable joinTable = ( SQLTable ) visit( tableCtx ); + joins.add( new SQLJoin( SQLJoinType.INNER, joinTable, null, tools.getPosition( tableCtx ), tools.getSourceText( tableCtx ) ) ); + } + } + // limit before order by, can have one per unioned table if ( ctx.limit_stmt() != null ) { limit = NUMERIC_LITERAL( ctx.limit_stmt().NUMERIC_LITERAL() ); @@ -174,11 +192,6 @@ public SQLSelect visitSelect_core( Select_coreContext ctx ) { limit = NUMERIC_LITERAL( ctx.top().NUMERIC_LITERAL() ); } - if ( !ctx.table().isEmpty() ) { - var firstTable = ctx.table().get( 0 ); - table = ( SQLTable ) visit( firstTable ); - } - if ( ctx.whereExpr != null ) { where = visitExpr( ctx.whereExpr, table, joins ); } @@ -189,8 +202,6 @@ public SQLSelect visitSelect_core( Select_coreContext ctx ) { // TODO: handle additional tables as joins - // TODO: handle joins - // Do this after all joins above so we know the tables available to us final SQLTable finalTable = table; final List finalJoins = joins; @@ -199,16 +210,14 @@ public SQLSelect visitSelect_core( Select_coreContext ctx ) { var result = new SQLSelect( distinct, resultColumns, table, joins, where, groupBys, having, limit, pos, src ); var cols = result.getDescendantsOfType( SQLColumn.class, c -> c.getTable() == null ); if ( cols.size() > 0 ) { - if ( joins != null && !joins.isEmpty() ) { - tools.reportError( "Column reference must have table prefix to disambiguate.", pos ); - } else { - if ( table == null ) { - tools.reportError( "This QoQ has column references, but there is no table!", pos ); - } else { - cols.forEach( c -> c.setTable( finalTable ) ); - } + if ( table == null && ( joins == null || joins.isEmpty() ) ) { + tools.reportError( "This QoQ has column references, but there is no table!", pos ); + } else if ( joins == null || joins.isEmpty() ) { + // If there is only one table, we know what it is now + cols.forEach( c -> c.setTable( finalTable ) ); } } + return result; } @@ -236,7 +245,7 @@ public SQLTable visitTable( TableContext ctx ) { alias = ctx.table_alias().getText(); } - return new SQLTable( schema, name, alias, pos, src ); + return new SQLTable( schema, name, alias, tableIndex++, pos, src ); } /** @@ -290,7 +299,7 @@ public SQLOrderBy visitOrdering_term( Ordering_termContext ctx, SQLTable table, boolean ascending = true; if ( ctx.asc_desc() != null ) { - ascending = ctx.asc_desc().DESC_() != null; + ascending = ctx.asc_desc().DESC_() == null; } return new SQLOrderBy( visitExpr( ctx.expr(), table, joins ), ascending, pos, src ); diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/math/Abs.java b/src/main/java/ortus/boxlang/runtime/bifs/global/math/Abs.java index 02fe8511c..a740eea3f 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/math/Abs.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/math/Abs.java @@ -49,7 +49,10 @@ public Abs() { * @argument.value The number to return the absolute value of */ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { - Number num = arguments.getAsNumber( Key.value ); + return _invoke( arguments.getAsNumber( Key.value ) ); + } + + public static Number _invoke( Number num ) { if ( num instanceof BigDecimal bd ) { return bd.abs( MathUtil.getMathContext() ); } diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/math/Acos.java b/src/main/java/ortus/boxlang/runtime/bifs/global/math/Acos.java index 382ee1e5d..14d63eae3 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/math/Acos.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/math/Acos.java @@ -47,7 +47,10 @@ public Acos() { * @argument.number The number to calculate the arccosine of */ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { - double value = arguments.getAsDouble( Key.number ); + return _invoke( arguments.getAsDouble( Key.number ) ); + } + + public static double _invoke( double value ) { if ( value < -1.0 || value > 1.0 ) { throw new BoxRuntimeException( "Input value must be in the range [-1, 1] for ACos function." ); } diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/math/Asin.java b/src/main/java/ortus/boxlang/runtime/bifs/global/math/Asin.java index 8f14cbc63..8c8570aac 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/math/Asin.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/math/Asin.java @@ -47,7 +47,10 @@ public Asin() { * @argument.number The number to calculate the arcsine of */ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { - double value = arguments.getAsDouble( Key.number ); + return _invoke( arguments.getAsDouble( Key.number ) ); + } + + public static double _invoke( double value ) { if ( value < -1.0 || value > 1.0 ) { throw new BoxRuntimeException( "Input value must be in the range [-1, 1] for ASin function." ); } diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/math/Atn.java b/src/main/java/ortus/boxlang/runtime/bifs/global/math/Atn.java index caf42ec5a..b52f58d5f 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/math/Atn.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/math/Atn.java @@ -53,11 +53,7 @@ public Atn() { * @argument.number The number to calculate the arc tangent of */ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { - Number num = arguments.getAsNumber( Key.number ); - if ( num instanceof BigDecimal bd ) { - return atan( bd, MathUtil.getMathContext() ); - } - return StrictMath.atan( num.doubleValue() ); + return _invoke( arguments.getAsNumber( Key.number ) ); } /** @@ -68,7 +64,7 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { * * @return The arc tangent of x. */ - private BigDecimal atan( BigDecimal x, MathContext mc ) { + private static BigDecimal atan( BigDecimal x, MathContext mc ) { BigDecimal result = BigDecimal.ZERO; BigDecimal term = x; BigDecimal xSquared = x.multiply( x, mc ); @@ -87,4 +83,11 @@ private BigDecimal atan( BigDecimal x, MathContext mc ) { return result; } + + public static Number _invoke( Number num ) { + if ( num instanceof BigDecimal bd ) { + return atan( bd, MathUtil.getMathContext() ); + } + return StrictMath.atan( num.doubleValue() ); + } } diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/math/Ceiling.java b/src/main/java/ortus/boxlang/runtime/bifs/global/math/Ceiling.java index a40d0c107..50ecd9fcf 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/math/Ceiling.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/math/Ceiling.java @@ -50,7 +50,10 @@ public Ceiling() { * @argument.number The number for which to find the ceiling value. */ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { - Number num = arguments.getAsNumber( Key.number ); + return _invoke( arguments.getAsNumber( Key.number ) ); + } + + public static Number _invoke( Number num ) { if ( num instanceof BigDecimal bd ) { return bd.setScale( 0, RoundingMode.CEILING ); } diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/math/Cos.java b/src/main/java/ortus/boxlang/runtime/bifs/global/math/Cos.java index b207d8653..8666a7517 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/math/Cos.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/math/Cos.java @@ -54,11 +54,7 @@ public Cos() { * @argument.number The number to calculate the cosine of (in radians). */ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { - Number num = arguments.getAsNumber( Key.number ); - if ( num instanceof BigDecimal bd ) { - return cos( bd, MathUtil.getMathContext() ); - } - return StrictMath.cos( num.doubleValue() ); + return _invoke( arguments.getAsNumber( Key.number ) ); } /** @@ -73,4 +69,12 @@ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { public static BigDecimal cos( BigDecimal x, MathContext mc ) { return new BigDecimal( StrictMath.cos( x.doubleValue() ) ); } + + public static Number _invoke( Number num ) { + if ( num instanceof BigDecimal bd ) { + return cos( bd, MathUtil.getMathContext() ); + } + return StrictMath.cos( num.doubleValue() ); + } + } diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/math/Exp.java b/src/main/java/ortus/boxlang/runtime/bifs/global/math/Exp.java index 3893599d5..b0939abd3 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/math/Exp.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/math/Exp.java @@ -51,11 +51,7 @@ public Exp() { * @argument.number The number to calculate the exponent for. */ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { - Number value = arguments.getAsNumber( Key.number ); - if ( value instanceof BigDecimal bd ) { - return exp( bd, MathUtil.getMathContext() ); - } - return StrictMath.exp( value.doubleValue() ); + return _invoke( arguments.getAsNumber( Key.number ) ); } /** @@ -80,4 +76,12 @@ public static BigDecimal exp( BigDecimal x, MathContext mc ) { return result; } + + public static Number _invoke( Number num ) { + if ( num instanceof BigDecimal bd ) { + return exp( bd, MathUtil.getMathContext() ); + } + return StrictMath.exp( num.doubleValue() ); + } + } diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/math/Floor.java b/src/main/java/ortus/boxlang/runtime/bifs/global/math/Floor.java index 1375d8a00..a536dbdef 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/math/Floor.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/math/Floor.java @@ -49,7 +49,10 @@ public Floor() { * @argument.value The number to return the absolute value of */ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { - Number number = arguments.getAsNumber( Key.number ); + return _invoke( arguments.getAsNumber( Key.number ) ); + } + + public static Number _invoke( Number number ) { if ( number instanceof BigDecimal bd ) { return bd.setScale( 0, RoundingMode.FLOOR ); } diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/math/Sin.java b/src/main/java/ortus/boxlang/runtime/bifs/global/math/Sin.java index 9e2f8e730..f8be29454 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/math/Sin.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/math/Sin.java @@ -50,11 +50,7 @@ public Sin() { * @argument.number The number to calculate the sine of, entered in radians. */ public Number _invoke( IBoxContext context, ArgumentsScope arguments ) { - Number value = arguments.getAsNumber( Key.number ); - if ( value instanceof BigDecimal bd ) { - return sin( bd, MathUtil.getMathContext() ); - } - return StrictMath.sin( value.doubleValue() ); + return _invoke( arguments.getAsNumber( Key.number ) ); } /** @@ -71,4 +67,11 @@ public static BigDecimal sin( BigDecimal x, MathContext mc ) { return new BigDecimal( StrictMath.sin( x.doubleValue() ) ); } + public static Number _invoke( Number value ) { + if ( value instanceof BigDecimal bd ) { + return sin( bd, MathUtil.getMathContext() ); + } + return StrictMath.sin( value.doubleValue() ); + } + } diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/math/Sqr.java b/src/main/java/ortus/boxlang/runtime/bifs/global/math/Sqr.java index bef3c1347..d3f2a9b32 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/math/Sqr.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/math/Sqr.java @@ -49,7 +49,10 @@ public Sqr() { * @argument.value The number to return the square root of */ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { - Number number = arguments.getAsNumber( Key.value ); + return _invoke( arguments.getAsNumber( Key.value ) ); + } + + public static Number _invoke( Number number ) { if ( number instanceof BigDecimal bd ) { return bd.sqrt( MathUtil.getMathContext() ); } diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/math/Tan.java b/src/main/java/ortus/boxlang/runtime/bifs/global/math/Tan.java index adbbb3f4e..83b421685 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/math/Tan.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/math/Tan.java @@ -51,11 +51,7 @@ public Tan() { * @argument.number The angle in radians to calculate the tangent of */ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { - Number value = arguments.getAsNumber( Key.number ); - if ( value instanceof BigDecimal bd ) { - return tan( bd, MathUtil.getMathContext() ); - } - return StrictMath.tan( value.doubleValue() ); + return _invoke( arguments.getAsNumber( Key.number ) ); } public static BigDecimal tan( BigDecimal x, MathContext mc ) { @@ -63,4 +59,11 @@ public static BigDecimal tan( BigDecimal x, MathContext mc ) { BigDecimal cosX = Cos.cos( x, mc ); return sinX.divide( cosX, mc ); } + + public static Number _invoke( Number value ) { + if ( value instanceof BigDecimal bd ) { + return tan( bd, MathUtil.getMathContext() ); + } + return StrictMath.tan( value.doubleValue() ); + } } diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/IQoQFunctionDef.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/IQoQFunctionDef.java new file mode 100644 index 000000000..7a6fb8534 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/IQoQFunctionDef.java @@ -0,0 +1,33 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq; + +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; + +/** + * I am the interface for QoQ function definitions + */ +public interface IQoQFunctionDef { + + abstract public Key getName(); + + abstract public QueryColumnType getReturnType(); + + abstract public int getMinArgs(); + + abstract public boolean isAggregate(); + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQAggregateFunctionDef.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQAggregateFunctionDef.java new file mode 100644 index 000000000..6d5b60a5d --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQAggregateFunctionDef.java @@ -0,0 +1,30 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq; + +import java.util.List; + +import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; + +/** + * I am the abstract class for QoQ function definitions + */ +public abstract class QoQAggregateFunctionDef implements IQoQFunctionDef, java.util.function.BiFunction, QoQExecution, Object> { + + public boolean isAggregate() { + return true; + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQExecution.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQExecution.java new file mode 100644 index 000000000..335ea32bf --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQExecution.java @@ -0,0 +1,105 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +import ortus.boxlang.compiler.ast.sql.select.SQLSelectStatement; +import ortus.boxlang.compiler.ast.sql.select.SQLTable; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.NameAndDirection; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecutionService.TypedResultColumn; +import ortus.boxlang.runtime.jdbc.qoq.QoQPreparedStatement.ParamItem; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.Query; + +/** + * A wrapper class to hold together both the SQL AST being executed as well as the runtime values for a given execution of the query + */ +public class QoQExecution { + + public SQLSelectStatement select; + public Map resultColumns = null; + public Map tableLookup; + public List params; + public int totalCombinations = 0; + public List orderByColumns = null; + public Set additionalColumns = null; + + /** + * Constructor + * + * @param select + * @param tableLookup + * @param params + * + * @return + */ + private QoQExecution( SQLSelectStatement select, Map tableLookup, List params ) { + this.select = select; + this.tableLookup = tableLookup; + this.params = params; + } + + public static QoQExecution of( SQLSelectStatement select, Map tableLookup, List params ) { + return new QoQExecution( select, tableLookup, params ); + } + + public void setTotalCombinations( int totalCombinations ) { + this.totalCombinations = totalCombinations; + } + + public int getTotalCombinations() { + return totalCombinations; + } + + public SQLSelectStatement getSelect() { + return select; + } + + public Map getTableLookup() { + return tableLookup; + } + + public List getParams() { + return params; + } + + public Map getResultColumns() { + return resultColumns; + } + + public void setResultColumns( Map resultColumns ) { + this.resultColumns = resultColumns; + } + + public List getOrderByColumns() { + return orderByColumns; + } + + public void setOrderByColumns( List orderByColumns ) { + this.orderByColumns = orderByColumns; + } + + public Set getAdditionalColumns() { + return additionalColumns; + } + + public void setAdditionalColumns( Set additionalColumns ) { + this.additionalColumns = additionalColumns; + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQExecutionService.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQExecutionService.java index 4023583be..0dcec90a4 100644 --- a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQExecutionService.java +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQExecutionService.java @@ -15,24 +15,33 @@ package ortus.boxlang.runtime.jdbc.qoq; import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Stream; import ortus.boxlang.compiler.ast.sql.SQLNode; +import ortus.boxlang.compiler.ast.sql.select.SQLJoin; import ortus.boxlang.compiler.ast.sql.select.SQLResultColumn; import ortus.boxlang.compiler.ast.sql.select.SQLSelect; import ortus.boxlang.compiler.ast.sql.select.SQLSelectStatement; import ortus.boxlang.compiler.ast.sql.select.SQLTable; import ortus.boxlang.compiler.ast.sql.select.expression.SQLColumn; import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; +import ortus.boxlang.compiler.ast.sql.select.expression.SQLStarExpression; +import ortus.boxlang.compiler.ast.sql.select.expression.literal.SQLNumberLiteral; import ortus.boxlang.compiler.parser.ParsingResult; import ortus.boxlang.compiler.parser.SQLParser; import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.context.IBoxContext; import ortus.boxlang.runtime.dynamic.ExpressionInterpreter; import ortus.boxlang.runtime.interop.DynamicObject; -import ortus.boxlang.runtime.jdbc.qoq.QoQPreparedStatement.ParamItem; +import ortus.boxlang.runtime.operators.Compare; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.IStruct; import ortus.boxlang.runtime.types.Query; @@ -84,9 +93,19 @@ public static Query executeSelect( IBoxContext context, SQLSelectStatement selec if ( hasTable ) { String tableVarName = selectStatement.getSelect().getTable().getVariableName(); source = getSourceQuery( context, tableVarName ); + // TODO: ensure tables are added in the order of their index, which represents the encounter-order in the SQL tableLookup.put( selectStatement.getSelect().getTable(), source ); } + // Register joins + if ( selectStatement.getSelect().getJoins() != null ) { + for ( SQLJoin thisJoin : selectStatement.getSelect().getJoins() ) { + SQLTable table = thisJoin.getTable(); + String tableVarName = table.getVariableName(); + tableLookup.put( table, getSourceQuery( context, tableVarName ) ); + } + } + // This holds the AST and the runtime values for the query QoQExecution QoQExec = QoQExecution.of( selectStatement, @@ -94,51 +113,80 @@ public static Query executeSelect( IBoxContext context, SQLSelectStatement selec statement instanceof QoQPreparedStatement qp ? qp.getParameters() : null ); + var intersections = createCartesianStream( QoQExec ); + Map resultColumns = calculateResultColumns( QoQExec ); + calculateOrderBys( QoQExec ); - Query target = buildTargetQuery( resultColumns ); + Query target = buildTargetQuery( QoQExec ); // Process one select // TODO: refactor this out - SQLSelect select = selectStatement.getSelect(); + SQLSelect select = selectStatement.getSelect(); - Long thisSelectLimit = select.getLimitValue(); + Long thisSelectLimit = select.getLimitValue(); // This boolean expression will be used to filter the records we keep - SQLExpression where = select.getWhere(); - boolean canEarlyLimit = selectStatement.getOrderBys() == null; + SQLExpression where = select.getWhere(); + boolean canEarlyLimit = selectStatement.getOrderBys() == null; // Just selecting out literal values if ( !hasTable ) { Object[] values = new Object[ resultColumns.size() ]; for ( Key key : resultColumns.keySet() ) { SQLResultColumn resultColumn = resultColumns.get( key ).resultColumn; - Object value = resultColumn.getExpression().evaluate( QoQExec, 1 ); + Object value = resultColumn.getExpression().evaluate( QoQExec, null ); values[ resultColumn.getOrdinalPosition() - 1 ] = value; } target.addRow( values ); return target; } + if ( select.hasAggregateResult() ) { + + } + + // enforce top/limit for this select. This would be a "top N" clause in the select or a "limit N" clause BEFORE the order by, which + // could exist or all selects in a union. + if ( canEarlyLimit && thisSelectLimit > -1 ) { + intersections = intersections.limit( Math.min( thisSelectLimit, QoQExec.getTotalCombinations() ) ); + + } + // 1-based index! - for ( int i = 1; i <= source.size(); i++ ) { - // enforce top/limit for this select. This would be a "top N" clause in the select or a "limit N" clause BEFORE the order by, which - // could exist or all selects in a union. - if ( canEarlyLimit && thisSelectLimit > -1 && i > thisSelectLimit ) { - break; - } + intersections.forEach( intersection -> { // Evaluate the where expression - if ( where == null || ( Boolean ) where.evaluate( QoQExec, i ) ) { - Object[] values = new Object[ resultColumns.size() ]; + if ( where == null || ( Boolean ) where.evaluate( QoQExec, intersection ) ) { + Object[] values = new Object[ resultColumns.size() ]; + int colPos = 0; for ( Key key : resultColumns.keySet() ) { SQLResultColumn resultColumn = resultColumns.get( key ).resultColumn; - Object value = resultColumn.getExpression().evaluate( QoQExec, i ); - values[ resultColumn.getOrdinalPosition() - 1 ] = value; + Object value = resultColumn.getExpression().evaluate( QoQExec, intersection ); + values[ colPos++ ] = value; } target.addRow( values ); } - } + } ); - // TODO: Sort the query + // TODO: Implement a sort that doesn't turn the query into a list of structs and back again + if ( QoQExec.getOrderByColumns() != null ) { + target.sort( ( row1, row2 ) -> { + var orderBys = QoQExec.getOrderByColumns(); + for ( var orderBy : orderBys ) { + var name = orderBy.name; + int result = Compare.invoke( row1.get( name ), row2.get( name ) ); + if ( result != 0 ) { + return orderBy.ascending ? result : -result; + } + } + return 0; + } ); + // These were just here for sorting. Nuke them now. + if ( QoQExec.getAdditionalColumns() != null ) { + for ( Key key : QoQExec.getAdditionalColumns() ) { + target.deleteColumn( key ); + } + } + } // This is the maxRows in the query options. It takes priority. Long overallSelectLimit = statement.getLargeMaxRows(); @@ -154,6 +202,45 @@ public static Query executeSelect( IBoxContext context, SQLSelectStatement selec return target; } + private static void calculateOrderBys( QoQExecution qoQExec ) { + SQLSelectStatement selectStatement = qoQExec.select; + if ( selectStatement.getOrderBys() == null ) { + return; + } + Set additionalColumns = new LinkedHashSet(); + Map resultColumns = qoQExec.getResultColumns(); + List orderByColumns = new ArrayList(); + int additionalCounter = 1; + for ( var orderBy : selectStatement.getOrderBys() ) { + SQLExpression expr = orderBy.getExpression(); + if ( expr instanceof SQLColumn column ) { + var match = resultColumns.entrySet().stream().filter( rc -> column.getName().equals( rc.getKey() ) ).findFirst(); + if ( match.isPresent() ) { + orderByColumns.add( NameAndDirection.of( match.get().getKey(), orderBy.isAscending() ) ); + continue; + } + } else if ( expr instanceof SQLNumberLiteral num ) { + // This is a number literal, which is a 1-based index into the result set + int index = num.getValue().intValue(); + if ( index < 1 || index > resultColumns.size() ) { + throw new DatabaseException( "The column index [" + index + "] in the order by clause is out of range." ); + } + orderByColumns.add( NameAndDirection.of( resultColumns.keySet().toArray( new Key[ 0 ] )[ index - 1 ], orderBy.isAscending() ) ); + continue; + } + // TODO: Figure out if this exact expression is already in the result set and use that + // To do this, we need something like toString() implemented to compare two expressions for equivalence + Key newName = Key.of( "__order_by_column_" + additionalCounter++ ); + resultColumns.put( newName, + TypedResultColumn.of( QueryColumnType.OBJECT, new SQLResultColumn( expr, newName.getName(), resultColumns.size() + 1, null, null ) ) ); + orderByColumns.add( NameAndDirection.of( newName, orderBy.isAscending() ) ); + additionalColumns.add( newName ); + + } + qoQExec.setOrderByColumns( orderByColumns ); + qoQExec.setAdditionalColumns( additionalColumns ); + } + /** * Build the target query based on the result columns * @@ -161,8 +248,9 @@ public static Query executeSelect( IBoxContext context, SQLSelectStatement selec * * @return the target query */ - private static Query buildTargetQuery( Map resultColumns ) { - Query target = new Query(); + private static Query buildTargetQuery( QoQExecution QoQExec ) { + Map resultColumns = QoQExec.getResultColumns(); + Query target = new Query(); for ( Key key : resultColumns.keySet() ) { target.addColumn( key, resultColumns.get( key ).type ); } @@ -174,28 +262,39 @@ private static Map calculateResultColumns( QoQExecution for ( SQLResultColumn resultColumn : QoQExec.select.getSelect().getResultColumns() ) { // For *, expand all columns in the query if ( resultColumn.isStarExpression() ) { - // TODO: when we add joins, handle looking up the correct table reference based on resultColumn.getTable() - var thisTable = QoQExec.tableLookup.get( QoQExec.select.getSelect().getTable() ); - for ( Key key : thisTable.getColumns().keySet() ) { - resultColumns.put( key, - TypedResultColumn.of( - thisTable.getColumns().get( key ).getType(), - new SQLResultColumn( - new SQLColumn( QoQExec.select.getSelect().getTable(), key.getName(), null, null ), - null, - resultColumns.size() + 1, - null, - null - ) - ) - ); + SQLTable starTable = ( ( SQLStarExpression ) resultColumn.getExpression() ).getTable(); + Key tableName = starTable == null ? null : starTable.getName(); + var matchingTables = QoQExec.tableLookup.keySet().stream().filter( t -> tableName == null || tableName.equals( t.getName() ) ) + .toList(); + if ( matchingTables.isEmpty() ) { + throw new DatabaseException( + "The table alias [" + tableName + "] in the result column [" + resultColumn.getSourceText() + "] is does not match a table." ); } + matchingTables.stream().forEach( t -> { + System.out.println( "Expanding * for table: " + t.getName() ); + var thisTable = QoQExec.tableLookup.get( t ); + for ( Key key : thisTable.getColumns().keySet() ) { + resultColumns.put( key, + TypedResultColumn.of( + thisTable.getColumns().get( key ).getType(), + new SQLResultColumn( + new SQLColumn( t, key.getName(), null, null ), + null, + resultColumns.size() + 1, + null, + null + ) + ) + ); + } + } ); // Non-star columns are named after the column, or given a column_0, column_1, etc name } else { resultColumns.put( resultColumn.getResultColumnName(), TypedResultColumn.of( resultColumn.getExpression().getType( QoQExec ), resultColumn ) ); } } + QoQExec.setResultColumns( resultColumns ); return resultColumns; } @@ -208,6 +307,57 @@ private static Query getSourceQuery( IBoxContext context, String tableVarName ) } } + /** + * Create all possible intercections of the tables as a stream of int arrays + */ + public static Stream createCartesianStream( QoQExecution QoQExec ) { + Map tableLookup = QoQExec.tableLookup; + int numTables = tableLookup.size(); + // I hold the number of rows in each corresponding query. table lookup is a linked array, so it contains the tables in encounter order from the query + int[] rowCounts = new int[ numTables ]; + // Tracks what row we're on for each table + int[] current = new int[ numTables ]; + Arrays.fill( current, 1 ); + int i = 0; + for ( Query table : tableLookup.values() ) { + rowCounts[ i++ ] = table.size(); + } + + // Total number of combinations, which is all recordcounts multiplied together + int totalCombinations = Arrays.stream( rowCounts ).reduce( 1, ( a, b ) -> a * b ); + QoQExec.setTotalCombinations( totalCombinations ); + Supplier supplier = () -> { + int[] indices = Arrays.copyOf( current, numTables ); + // FWIW, the last time this supplier runs, we will increment nothing + for ( int j = 0; j < numTables; j++ ) { + // Stop incrementing each table when we reach the end of the table + if ( current[ j ] < rowCounts[ j ] ) { + current[ j ]++; + // As soon as we increment any table, we're done. + // This ensure we hit every possible combo by only changing one index at a time. + break; + } else { + // The first column always resets, all other colmns reset if their previous column just reset + if ( j == 0 || current[ j - 1 ] == 1 ) { + current[ j ] = 1; + } + } + } + return indices; + }; + + // Limit the stream to the total number of combos + Stream theStream = Stream.generate( supplier ).limit( totalCombinations ); + + // Tweak this based on size of intersections to process + if ( totalCombinations > 50 ) { + // The supplier above MUST be called in order, so we can't parallelize it due to stupid Java behaviors of pre-loading supplier calls. + // Collect the values and then create a new stream from the list + theStream = theStream.toList().stream().parallel(); + } + return theStream; + } + public record TypedResultColumn( QueryColumnType type, SQLResultColumn resultColumn ) { public static TypedResultColumn of( QueryColumnType type, SQLResultColumn resultColumn ) { @@ -215,13 +365,10 @@ public static TypedResultColumn of( QueryColumnType type, SQLResultColumn result } } - /** - * A wrapper class to hold together both the SQL AST being executed as well as the runtime values for a given execution of the query - */ - public record QoQExecution( SQLSelectStatement select, Map tableLookup, List params ) { + public record NameAndDirection( Key name, boolean ascending ) { - public static QoQExecution of( SQLSelectStatement select, Map tableLookup, List params ) { - return new QoQExecution( select, tableLookup, params ); + public static NameAndDirection of( Key name, boolean ascending ) { + return new NameAndDirection( name, ascending ); } } diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQFunctionService.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQFunctionService.java index 5f3d86808..fcbcd0c01 100644 --- a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQFunctionService.java +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQFunctionService.java @@ -18,7 +18,30 @@ import java.util.List; import java.util.Map; -import ortus.boxlang.runtime.dynamic.casters.StringCaster; +import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; +import ortus.boxlang.runtime.jdbc.qoq.functions.aggregate.Max; +import ortus.boxlang.runtime.jdbc.qoq.functions.scalar.Abs; +import ortus.boxlang.runtime.jdbc.qoq.functions.scalar.Acos; +import ortus.boxlang.runtime.jdbc.qoq.functions.scalar.Asin; +import ortus.boxlang.runtime.jdbc.qoq.functions.scalar.Atan; +import ortus.boxlang.runtime.jdbc.qoq.functions.scalar.Ceiling; +import ortus.boxlang.runtime.jdbc.qoq.functions.scalar.Coalesce; +import ortus.boxlang.runtime.jdbc.qoq.functions.scalar.Concat; +import ortus.boxlang.runtime.jdbc.qoq.functions.scalar.Cos; +import ortus.boxlang.runtime.jdbc.qoq.functions.scalar.Exp; +import ortus.boxlang.runtime.jdbc.qoq.functions.scalar.Floor; +import ortus.boxlang.runtime.jdbc.qoq.functions.scalar.IsNull; +import ortus.boxlang.runtime.jdbc.qoq.functions.scalar.Length; +import ortus.boxlang.runtime.jdbc.qoq.functions.scalar.Lower; +import ortus.boxlang.runtime.jdbc.qoq.functions.scalar.Ltrim; +import ortus.boxlang.runtime.jdbc.qoq.functions.scalar.Mod; +import ortus.boxlang.runtime.jdbc.qoq.functions.scalar.Power; +import ortus.boxlang.runtime.jdbc.qoq.functions.scalar.Rtrim; +import ortus.boxlang.runtime.jdbc.qoq.functions.scalar.Sin; +import ortus.boxlang.runtime.jdbc.qoq.functions.scalar.Sqrt; +import ortus.boxlang.runtime.jdbc.qoq.functions.scalar.Tan; +import ortus.boxlang.runtime.jdbc.qoq.functions.scalar.Trim; +import ortus.boxlang.runtime.jdbc.qoq.functions.scalar.Upper; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.QueryColumnType; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; @@ -31,15 +54,56 @@ public class QoQFunctionService { private static Map functions = new HashMap(); static { - register( Key.of( "upper" ), args -> StringCaster.cast( args.get( 0 ) ).toUpperCase(), QueryColumnType.VARCHAR ); + + // Scalar + register( Upper.INSTANCE ); + register( Lower.INSTANCE ); + register( Abs.INSTANCE ); + register( Acos.INSTANCE ); + register( Asin.INSTANCE ); + register( Atan.INSTANCE ); + register( Ceiling.INSTANCE ); + register( Coalesce.INSTANCE ); + register( Concat.INSTANCE ); + register( Cos.INSTANCE ); + register( Exp.INSTANCE ); + register( Floor.INSTANCE ); + register( IsNull.INSTANCE ); + register( Length.INSTANCE ); + register( Lower.INSTANCE ); + register( Ltrim.INSTANCE ); + register( Mod.INSTANCE ); + register( Power.INSTANCE ); + register( Rtrim.INSTANCE ); + register( Sin.INSTANCE ); + register( Sqrt.INSTANCE ); + register( Tan.INSTANCE ); + register( Trim.INSTANCE ); + register( Upper.INSTANCE ); + + // Aggregate + register( Max.INSTANCE ); } private QoQFunctionService() { + } + + public static void register( Key name, java.util.function.Function, Object> function, QueryColumnType returnType, int requiredParams ) { + functions.put( name, QoQFunction.of( function, returnType, requiredParams ) ); + } + public static void registerAggregate( Key name, java.util.function.BiFunction, QoQExecution, Object> function, + QueryColumnType returnType, + int requiredParams ) { + functions.put( name, QoQFunction.ofAggregate( function, returnType, requiredParams ) ); } - public static void register( Key name, java.util.function.Function, Object> function, QueryColumnType returnType ) { - functions.put( name, QoQFunction.of( function, returnType ) ); + public static void register( QoQScalarFunctionDef functionDef ) { + register( functionDef.getName(), functionDef, functionDef.getReturnType(), functionDef.getMinArgs() ); + } + + public static void register( QoQAggregateFunctionDef functionDef ) { + registerAggregate( functionDef.getName(), functionDef, functionDef.getReturnType(), functionDef.getMinArgs() ); } public static void unregister( Key name ) { @@ -53,19 +117,31 @@ public static QoQFunction getFunction( Key name ) { return functions.get( name ); } - public record QoQFunction( java.util.function.Function, Object> callable, QueryColumnType returnType ) { + public record QoQFunction( + java.util.function.Function, Object> callable, + java.util.function.BiFunction, QoQExecution, Object> aggregateCallable, + QueryColumnType returnType, + int requiredParams ) { - static QoQFunction of( java.util.function.Function, Object> callable, QueryColumnType returnType ) { - return new QoQFunction( callable, returnType ); + static QoQFunction of( java.util.function.Function, Object> callable, QueryColumnType returnType, int requiredParams ) { + return new QoQFunction( callable, null, returnType, requiredParams ); + } + + static QoQFunction ofAggregate( java.util.function.BiFunction, QoQExecution, Object> callable, QueryColumnType returnType, + int requiredParams ) { + return new QoQFunction( null, callable, returnType, requiredParams ); } public Object invoke( List arguments ) { return callable.apply( arguments ); } - public QueryColumnType getReturnType() { - return returnType; + public Object invokeAggregate( List arguments, QoQExecution QoQExec ) { + return aggregateCallable.apply( arguments, QoQExec ); } + public boolean isAggregate() { + return aggregateCallable != null; + } } } diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQScalarFunctionDef.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQScalarFunctionDef.java new file mode 100644 index 000000000..3846e907f --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/QoQScalarFunctionDef.java @@ -0,0 +1,28 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq; + +import java.util.List; + +/** + * I am the abstract class for QoQ function definitions + */ +public abstract class QoQScalarFunctionDef implements IQoQFunctionDef, java.util.function.Function, Object> { + + public boolean isAggregate() { + return false; + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/aggregate/Max.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/aggregate/Max.java new file mode 100644 index 000000000..afb06df79 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/aggregate/Max.java @@ -0,0 +1,52 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq.functions.aggregate; + +import java.util.List; + +import ortus.boxlang.compiler.ast.sql.select.expression.SQLExpression; +import ortus.boxlang.runtime.dynamic.casters.NumberCaster; +import ortus.boxlang.runtime.jdbc.qoq.QoQAggregateFunctionDef; +import ortus.boxlang.runtime.jdbc.qoq.QoQExecution; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; + +public class Max extends QoQAggregateFunctionDef { + + private static final Key name = Key.of( "atan" ); + + public static final QoQAggregateFunctionDef INSTANCE = new Max(); + + @Override + public Key getName() { + return name; + } + + @Override + public QueryColumnType getReturnType() { + return QueryColumnType.DOUBLE; + } + + @Override + public int getMinArgs() { + return 1; + } + + @Override + public Object apply( List args, QoQExecution QoQExec ) { + return ortus.boxlang.runtime.bifs.global.math.Atn._invoke( NumberCaster.cast( args.get( 0 ) ) ); + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Abs.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Abs.java new file mode 100644 index 000000000..3e110a5f6 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Abs.java @@ -0,0 +1,50 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq.functions.scalar; + +import java.util.List; + +import ortus.boxlang.runtime.dynamic.casters.NumberCaster; +import ortus.boxlang.runtime.jdbc.qoq.QoQScalarFunctionDef; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; + +public class Abs extends QoQScalarFunctionDef { + + private static final Key name = Key.of( "abs" ); + + public static final QoQScalarFunctionDef INSTANCE = new Abs(); + + @Override + public Key getName() { + return name; + } + + @Override + public QueryColumnType getReturnType() { + return QueryColumnType.DOUBLE; + } + + @Override + public int getMinArgs() { + return 1; + } + + @Override + public Object apply( List args ) { + return ortus.boxlang.runtime.bifs.global.math.Abs._invoke( NumberCaster.cast( args.get( 0 ) ) ); + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Acos.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Acos.java new file mode 100644 index 000000000..1d11b03ec --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Acos.java @@ -0,0 +1,50 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq.functions.scalar; + +import java.util.List; + +import ortus.boxlang.runtime.dynamic.casters.DoubleCaster; +import ortus.boxlang.runtime.jdbc.qoq.QoQScalarFunctionDef; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; + +public class Acos extends QoQScalarFunctionDef { + + private static final Key name = Key.of( "acos" ); + + public static final QoQScalarFunctionDef INSTANCE = new Acos(); + + @Override + public Key getName() { + return name; + } + + @Override + public QueryColumnType getReturnType() { + return QueryColumnType.DOUBLE; + } + + @Override + public int getMinArgs() { + return 1; + } + + @Override + public Object apply( List args ) { + return ortus.boxlang.runtime.bifs.global.math.Acos._invoke( DoubleCaster.cast( args.get( 0 ) ) ); + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Asin.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Asin.java new file mode 100644 index 000000000..5fd251df3 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Asin.java @@ -0,0 +1,50 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq.functions.scalar; + +import java.util.List; + +import ortus.boxlang.runtime.dynamic.casters.DoubleCaster; +import ortus.boxlang.runtime.jdbc.qoq.QoQScalarFunctionDef; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; + +public class Asin extends QoQScalarFunctionDef { + + private static final Key name = Key.of( "asin" ); + + public static final QoQScalarFunctionDef INSTANCE = new Asin(); + + @Override + public Key getName() { + return name; + } + + @Override + public QueryColumnType getReturnType() { + return QueryColumnType.DOUBLE; + } + + @Override + public int getMinArgs() { + return 1; + } + + @Override + public Object apply( List args ) { + return ortus.boxlang.runtime.bifs.global.math.Asin._invoke( DoubleCaster.cast( args.get( 0 ) ) ); + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Atan.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Atan.java new file mode 100644 index 000000000..1c6c3abcc --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Atan.java @@ -0,0 +1,50 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq.functions.scalar; + +import java.util.List; + +import ortus.boxlang.runtime.dynamic.casters.NumberCaster; +import ortus.boxlang.runtime.jdbc.qoq.QoQScalarFunctionDef; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; + +public class Atan extends QoQScalarFunctionDef { + + private static final Key name = Key.of( "atan" ); + + public static final QoQScalarFunctionDef INSTANCE = new Atan(); + + @Override + public Key getName() { + return name; + } + + @Override + public QueryColumnType getReturnType() { + return QueryColumnType.DOUBLE; + } + + @Override + public int getMinArgs() { + return 1; + } + + @Override + public Object apply( List args ) { + return ortus.boxlang.runtime.bifs.global.math.Atn._invoke( NumberCaster.cast( args.get( 0 ) ) ); + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Ceiling.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Ceiling.java new file mode 100644 index 000000000..92e182cac --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Ceiling.java @@ -0,0 +1,50 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq.functions.scalar; + +import java.util.List; + +import ortus.boxlang.runtime.dynamic.casters.NumberCaster; +import ortus.boxlang.runtime.jdbc.qoq.QoQScalarFunctionDef; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; + +public class Ceiling extends QoQScalarFunctionDef { + + private static final Key name = Key.of( "ceiling" ); + + public static final QoQScalarFunctionDef INSTANCE = new Ceiling(); + + @Override + public Key getName() { + return name; + } + + @Override + public QueryColumnType getReturnType() { + return QueryColumnType.DOUBLE; + } + + @Override + public int getMinArgs() { + return 1; + } + + @Override + public Object apply( List args ) { + return ortus.boxlang.runtime.bifs.global.math.Ceiling._invoke( NumberCaster.cast( args.get( 0 ) ) ); + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Coalesce.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Coalesce.java new file mode 100644 index 000000000..a19260909 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Coalesce.java @@ -0,0 +1,54 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq.functions.scalar; + +import java.util.List; + +import ortus.boxlang.runtime.jdbc.qoq.QoQScalarFunctionDef; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; + +public class Coalesce extends QoQScalarFunctionDef { + + private static final Key name = Key.of( "coalesce" ); + + public static final QoQScalarFunctionDef INSTANCE = new Coalesce(); + + @Override + public Key getName() { + return name; + } + + @Override + public QueryColumnType getReturnType() { + return QueryColumnType.OBJECT; + } + + @Override + public int getMinArgs() { + return 1; + } + + @Override + public Object apply( List args ) { + for ( Object arg : args ) { + if ( arg != null ) { + return arg; + } + } + return null; + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Concat.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Concat.java new file mode 100644 index 000000000..1bffe6b3e --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Concat.java @@ -0,0 +1,54 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq.functions.scalar; + +import java.util.List; + +import ortus.boxlang.runtime.dynamic.casters.StringCaster; +import ortus.boxlang.runtime.jdbc.qoq.QoQScalarFunctionDef; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; + +public class Concat extends QoQScalarFunctionDef { + + private static final Key name = Key.of( "concat" ); + + public static final QoQScalarFunctionDef INSTANCE = new Concat(); + + @Override + public Key getName() { + return name; + } + + @Override + public QueryColumnType getReturnType() { + return QueryColumnType.VARCHAR; + } + + @Override + public int getMinArgs() { + return 2; + } + + @Override + public Object apply( List args ) { + StringBuilder sb = new StringBuilder(); + for ( Object arg : args ) { + sb.append( StringCaster.cast( arg ) ); + } + return sb.toString(); + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Cos.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Cos.java new file mode 100644 index 000000000..022c22893 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Cos.java @@ -0,0 +1,50 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq.functions.scalar; + +import java.util.List; + +import ortus.boxlang.runtime.dynamic.casters.NumberCaster; +import ortus.boxlang.runtime.jdbc.qoq.QoQScalarFunctionDef; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; + +public class Cos extends QoQScalarFunctionDef { + + private static final Key name = Key.of( "cos" ); + + public static final QoQScalarFunctionDef INSTANCE = new Cos(); + + @Override + public Key getName() { + return name; + } + + @Override + public QueryColumnType getReturnType() { + return QueryColumnType.DOUBLE; + } + + @Override + public int getMinArgs() { + return 1; + } + + @Override + public Object apply( List args ) { + return ortus.boxlang.runtime.bifs.global.math.Cos._invoke( NumberCaster.cast( args.get( 0 ) ) ); + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Exp.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Exp.java new file mode 100644 index 000000000..4e42a75f3 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Exp.java @@ -0,0 +1,50 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq.functions.scalar; + +import java.util.List; + +import ortus.boxlang.runtime.dynamic.casters.NumberCaster; +import ortus.boxlang.runtime.jdbc.qoq.QoQScalarFunctionDef; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; + +public class Exp extends QoQScalarFunctionDef { + + private static final Key name = Key.of( "exp" ); + + public static final QoQScalarFunctionDef INSTANCE = new Exp(); + + @Override + public Key getName() { + return name; + } + + @Override + public QueryColumnType getReturnType() { + return QueryColumnType.DOUBLE; + } + + @Override + public int getMinArgs() { + return 1; + } + + @Override + public Object apply( List args ) { + return ortus.boxlang.runtime.bifs.global.math.Exp._invoke( NumberCaster.cast( args.get( 0 ) ) ); + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Floor.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Floor.java new file mode 100644 index 000000000..099ea4733 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Floor.java @@ -0,0 +1,50 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq.functions.scalar; + +import java.util.List; + +import ortus.boxlang.runtime.dynamic.casters.NumberCaster; +import ortus.boxlang.runtime.jdbc.qoq.QoQScalarFunctionDef; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; + +public class Floor extends QoQScalarFunctionDef { + + private static final Key name = Key.of( "floor" ); + + public static final QoQScalarFunctionDef INSTANCE = new Floor(); + + @Override + public Key getName() { + return name; + } + + @Override + public QueryColumnType getReturnType() { + return QueryColumnType.DOUBLE; + } + + @Override + public int getMinArgs() { + return 1; + } + + @Override + public Object apply( List args ) { + return ortus.boxlang.runtime.bifs.global.math.Floor._invoke( NumberCaster.cast( args.get( 0 ) ) ); + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/IsNull.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/IsNull.java new file mode 100644 index 000000000..8ff5370c7 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/IsNull.java @@ -0,0 +1,49 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq.functions.scalar; + +import java.util.List; + +import ortus.boxlang.runtime.jdbc.qoq.QoQScalarFunctionDef; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; + +public class IsNull extends QoQScalarFunctionDef { + + private static final Key name = Key.of( "isnull" ); + + public static final QoQScalarFunctionDef INSTANCE = new IsNull(); + + @Override + public Key getName() { + return name; + } + + @Override + public QueryColumnType getReturnType() { + return QueryColumnType.OBJECT; + } + + @Override + public int getMinArgs() { + return 1; + } + + @Override + public Object apply( List args ) { + return args.get( 0 ) == null; + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Length.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Length.java new file mode 100644 index 000000000..94ec35635 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Length.java @@ -0,0 +1,50 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq.functions.scalar; + +import java.util.List; + +import ortus.boxlang.runtime.dynamic.casters.StringCaster; +import ortus.boxlang.runtime.jdbc.qoq.QoQScalarFunctionDef; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; + +public class Length extends QoQScalarFunctionDef { + + private static final Key name = Key.of( "length" ); + + public static final QoQScalarFunctionDef INSTANCE = new Length(); + + @Override + public Key getName() { + return name; + } + + @Override + public QueryColumnType getReturnType() { + return QueryColumnType.INTEGER; + } + + @Override + public int getMinArgs() { + return 1; + } + + @Override + public Object apply( List args ) { + return StringCaster.cast( args.get( 0 ) ).length(); + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Lower.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Lower.java new file mode 100644 index 000000000..e03438d2e --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Lower.java @@ -0,0 +1,50 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq.functions.scalar; + +import java.util.List; + +import ortus.boxlang.runtime.dynamic.casters.StringCaster; +import ortus.boxlang.runtime.jdbc.qoq.QoQScalarFunctionDef; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; + +public class Lower extends QoQScalarFunctionDef { + + private static final Key name = Key.of( "lower" ); + + public static final QoQScalarFunctionDef INSTANCE = new Lower(); + + @Override + public Key getName() { + return name; + } + + @Override + public QueryColumnType getReturnType() { + return QueryColumnType.VARCHAR; + } + + @Override + public int getMinArgs() { + return 1; + } + + @Override + public Object apply( List args ) { + return StringCaster.cast( args.get( 0 ) ).toLowerCase(); + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Ltrim.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Ltrim.java new file mode 100644 index 000000000..5c347942a --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Ltrim.java @@ -0,0 +1,55 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq.functions.scalar; + +import java.util.List; + +import ortus.boxlang.runtime.dynamic.casters.StringCaster; +import ortus.boxlang.runtime.jdbc.qoq.QoQScalarFunctionDef; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; + +public class Ltrim extends QoQScalarFunctionDef { + + private static final Key name = Key.of( "ltrim" ); + + public static final QoQScalarFunctionDef INSTANCE = new Ltrim(); + + @Override + public Key getName() { + return name; + } + + @Override + public QueryColumnType getReturnType() { + return QueryColumnType.VARCHAR; + } + + @Override + public int getMinArgs() { + return 1; + } + + @Override + public Object apply( List args ) { + String str = StringCaster.cast( args.get( 0 ) ); + int start = 0; + while ( start < str.length() && Character.isWhitespace( str.charAt( start ) ) ) { + start++; + } + return str.substring( start ); + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Mod.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Mod.java new file mode 100644 index 000000000..18fa4553c --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Mod.java @@ -0,0 +1,56 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq.functions.scalar; + +import java.util.List; + +import ortus.boxlang.runtime.jdbc.qoq.QoQScalarFunctionDef; +import ortus.boxlang.runtime.operators.Modulus; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; + +public class Mod extends QoQScalarFunctionDef { + + private static final Key name = Key.of( "mod" ); + + public static final QoQScalarFunctionDef INSTANCE = new Mod(); + + @Override + public Key getName() { + return name; + } + + @Override + public QueryColumnType getReturnType() { + return QueryColumnType.DOUBLE; + } + + @Override + public int getMinArgs() { + return 2; + } + + @Override + public Object apply( List args ) { + Object left = args.get( 0 ); + Object right = args.get( 1 ); + if ( left == null || right == null ) { + return null; + } + // Modulus does its own casting + return Modulus.invoke( left, right ); + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Power.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Power.java new file mode 100644 index 000000000..ba2a335df --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Power.java @@ -0,0 +1,55 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq.functions.scalar; + +import java.util.List; + +import ortus.boxlang.runtime.dynamic.casters.NumberCaster; +import ortus.boxlang.runtime.jdbc.qoq.QoQScalarFunctionDef; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; + +public class Power extends QoQScalarFunctionDef { + + private static final Key name = Key.of( "power" ); + + public static final QoQScalarFunctionDef INSTANCE = new Power(); + + @Override + public Key getName() { + return name; + } + + @Override + public QueryColumnType getReturnType() { + return QueryColumnType.DOUBLE; + } + + @Override + public int getMinArgs() { + return 2; + } + + @Override + public Object apply( List args ) { + Object left = args.get( 0 ); + Object right = args.get( 1 ); + if ( left == null || right == null ) { + return null; + } + return Math.pow( NumberCaster.cast( left ).doubleValue(), NumberCaster.cast( right ).doubleValue() ); + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Rtrim.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Rtrim.java new file mode 100644 index 000000000..596dd2987 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Rtrim.java @@ -0,0 +1,55 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq.functions.scalar; + +import java.util.List; + +import ortus.boxlang.runtime.dynamic.casters.StringCaster; +import ortus.boxlang.runtime.jdbc.qoq.QoQScalarFunctionDef; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; + +public class Rtrim extends QoQScalarFunctionDef { + + private static final Key name = Key.of( "rtrim" ); + + public static final QoQScalarFunctionDef INSTANCE = new Rtrim(); + + @Override + public Key getName() { + return name; + } + + @Override + public QueryColumnType getReturnType() { + return QueryColumnType.VARCHAR; + } + + @Override + public int getMinArgs() { + return 1; + } + + @Override + public Object apply( List args ) { + String str = StringCaster.cast( args.get( 0 ) ); + int end = str.length(); + while ( end > 0 && Character.isWhitespace( str.charAt( end - 1 ) ) ) { + end--; + } + return str.substring( 0, end ); + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Sin.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Sin.java new file mode 100644 index 000000000..f507eec43 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Sin.java @@ -0,0 +1,50 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq.functions.scalar; + +import java.util.List; + +import ortus.boxlang.runtime.dynamic.casters.NumberCaster; +import ortus.boxlang.runtime.jdbc.qoq.QoQScalarFunctionDef; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; + +public class Sin extends QoQScalarFunctionDef { + + private static final Key name = Key.of( "sin" ); + + public static final QoQScalarFunctionDef INSTANCE = new Sin(); + + @Override + public Key getName() { + return name; + } + + @Override + public QueryColumnType getReturnType() { + return QueryColumnType.DOUBLE; + } + + @Override + public int getMinArgs() { + return 1; + } + + @Override + public Object apply( List args ) { + return ortus.boxlang.runtime.bifs.global.math.Sin._invoke( NumberCaster.cast( args.get( 0 ) ) ); + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Sqrt.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Sqrt.java new file mode 100644 index 000000000..7cb81235a --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Sqrt.java @@ -0,0 +1,50 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq.functions.scalar; + +import java.util.List; + +import ortus.boxlang.runtime.dynamic.casters.NumberCaster; +import ortus.boxlang.runtime.jdbc.qoq.QoQScalarFunctionDef; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; + +public class Sqrt extends QoQScalarFunctionDef { + + private static final Key name = Key.of( "sqrt" ); + + public static final QoQScalarFunctionDef INSTANCE = new Sqrt(); + + @Override + public Key getName() { + return name; + } + + @Override + public QueryColumnType getReturnType() { + return QueryColumnType.DOUBLE; + } + + @Override + public int getMinArgs() { + return 1; + } + + @Override + public Object apply( List args ) { + return ortus.boxlang.runtime.bifs.global.math.Sqr._invoke( NumberCaster.cast( args.get( 0 ) ) ); + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Tan.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Tan.java new file mode 100644 index 000000000..5f58f3c42 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Tan.java @@ -0,0 +1,50 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq.functions.scalar; + +import java.util.List; + +import ortus.boxlang.runtime.dynamic.casters.NumberCaster; +import ortus.boxlang.runtime.jdbc.qoq.QoQScalarFunctionDef; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; + +public class Tan extends QoQScalarFunctionDef { + + private static final Key name = Key.of( "tan" ); + + public static final QoQScalarFunctionDef INSTANCE = new Tan(); + + @Override + public Key getName() { + return name; + } + + @Override + public QueryColumnType getReturnType() { + return QueryColumnType.DOUBLE; + } + + @Override + public int getMinArgs() { + return 1; + } + + @Override + public Object apply( List args ) { + return ortus.boxlang.runtime.bifs.global.math.Tan._invoke( NumberCaster.cast( args.get( 0 ) ) ); + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Trim.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Trim.java new file mode 100644 index 000000000..81bc38e17 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Trim.java @@ -0,0 +1,50 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq.functions.scalar; + +import java.util.List; + +import ortus.boxlang.runtime.dynamic.casters.StringCaster; +import ortus.boxlang.runtime.jdbc.qoq.QoQScalarFunctionDef; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; + +public class Trim extends QoQScalarFunctionDef { + + private static final Key name = Key.of( "Trim" ); + + public static final QoQScalarFunctionDef INSTANCE = new Trim(); + + @Override + public Key getName() { + return name; + } + + @Override + public QueryColumnType getReturnType() { + return QueryColumnType.VARCHAR; + } + + @Override + public int getMinArgs() { + return 1; + } + + @Override + public Object apply( List args ) { + return StringCaster.cast( args.get( 0 ) ).trim(); + } + +} diff --git a/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Upper.java b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Upper.java new file mode 100644 index 000000000..a2742afa6 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/jdbc/qoq/functions/scalar/Upper.java @@ -0,0 +1,50 @@ +/** + * [BoxLang] + * + * Copyright [2023] [Ortus Solutions, Corp] + * + * 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 + * + * http://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. + */ +package ortus.boxlang.runtime.jdbc.qoq.functions.scalar; + +import java.util.List; + +import ortus.boxlang.runtime.dynamic.casters.StringCaster; +import ortus.boxlang.runtime.jdbc.qoq.QoQScalarFunctionDef; +import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.types.QueryColumnType; + +public class Upper extends QoQScalarFunctionDef { + + private static final Key name = Key.of( "upper" ); + + public static final QoQScalarFunctionDef INSTANCE = new Upper(); + + @Override + public Key getName() { + return name; + } + + @Override + public QueryColumnType getReturnType() { + return QueryColumnType.VARCHAR; + } + + @Override + public int getMinArgs() { + return 1; + } + + @Override + public Object apply( List args ) { + return StringCaster.cast( args.get( 0 ) ).toUpperCase(); + } + +} diff --git a/src/test/java/ortus/boxlang/compiler/QoQParseTest.java b/src/test/java/ortus/boxlang/compiler/QoQParseTest.java index 7ad6ebe3a..4f8c428ba 100644 --- a/src/test/java/ortus/boxlang/compiler/QoQParseTest.java +++ b/src/test/java/ortus/boxlang/compiler/QoQParseTest.java @@ -82,15 +82,18 @@ public void testMetadataVisitor() { public void testRunQoQ() { instance.executeSource( """ - myQry = queryNew( "col,col2", "varchar,integer", [["foo",42],["bar",9001]] ) - q = queryExecute( " - select upper('test') as val - ", - [], - { dbType : "query" } - ); - println( q ) - """, + qryEmployees = queryNew( "name,age,dept", "varchar,integer,varchar", [["brad",44,"IT"],["luis",43,"Exec"],["Jon",45,"IT"]] ) + qryDept = queryNew( "name,code", "varchar,integer", [["IT",404],["Exec",200]] ) + q = queryExecute( " + select e.*, d.code + from qryEmployees e, qryDept d + where e.dept = d.name + ", + [], + { dbType : "query" } + ); + println( q ) + """, context ); } From a949648d4a049af196bfaea8fb3ed288abafb6f1 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 13 Dec 2024 12:22:23 +0100 Subject: [PATCH 188/193] BL-840 #resolve GetSystemSetting BIF is overriding Coldbox Env function and producing different results --- .../bifs/global/system/GetSystemSetting.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/system/GetSystemSetting.java b/src/main/java/ortus/boxlang/runtime/bifs/global/system/GetSystemSetting.java index 7340b4c61..ee37bfe82 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/system/GetSystemSetting.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/system/GetSystemSetting.java @@ -24,6 +24,7 @@ import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.Argument; import ortus.boxlang.runtime.types.IStruct; +import ortus.boxlang.runtime.types.Struct; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; @BoxBIF @@ -42,8 +43,11 @@ public GetSystemSetting() { /** * Retrieve a Java System property or environment value by name. + *

    * It looks at properties first then environment variables second. - * + *

    + * Please note that the property or environment variable name is case-sensitive. + *

    * You can also pass a default value to return if the property or environment variable is not found. * * @param context The context in which the BIF is being invoked. @@ -56,27 +60,25 @@ public GetSystemSetting() { public String _invoke( IBoxContext context, ArgumentsScope arguments ) { Key key = Key.of( arguments.getAsString( Key.key ) ); String defaultValue = arguments.getAsString( Key.defaultValue ); - IStruct system = context.getScope( Key.server ).getAsStruct( Key.system ); - IStruct environment = system.getAsStruct( Key.environment ); - IStruct properties = system.getAsStruct( Key.properties ); - // Get from properties first + IStruct properties = context.computeAttachmentIfAbsent( Key.properties, attachmentKey -> new Struct( System.getProperties() ) ); + IStruct env = context.computeAttachmentIfAbsent( Key.environment, attachmentKey -> new Struct( System.getenv() ) ); + + // Properties take precedence over environment variables String value = properties.getAsString( key ); - // If not null, return it if ( value != null ) { return value; } - // If null, try the environment - value = environment.getAsString( key ); - // If not null, return it + // If the property was not found, try to get the environment variable + value = env.getAsString( key ); if ( value != null ) { return value; } // If still null, return the default value if it was provided else throw an exception if ( defaultValue == null ) { - throw new BoxRuntimeException( "System property or environment variable not found: " + key ); + throw new BoxRuntimeException( "System property or environment variable not found: " + key.getName() ); } return defaultValue; From b3146e7570abb41a844491cf9e94681c8c8888fa Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 13 Dec 2024 15:16:40 +0100 Subject: [PATCH 189/193] BL-845 #resolve Transformers unecessary creating new script contexts for doing interface implementations, this was old code still left but broke all orm and context class loaders --- .../transformer/statement/BoxClassTransformer.java | 7 +++++-- .../javaboxpiler/transformer/BoxClassTransformer.java | 7 +++++-- .../runtime/application/BaseApplicationListener.java | 4 +--- .../ortus/boxlang/runtime/loader/DynamicClassLoader.java | 2 ++ 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java index ecf0ff2d3..8b47181c7 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/transformer/statement/BoxClassTransformer.java @@ -57,8 +57,8 @@ import ortus.boxlang.compiler.ast.statement.BoxReturnType; import ortus.boxlang.compiler.ast.statement.BoxType; import ortus.boxlang.compiler.parser.BoxSourceType; +import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.context.IBoxContext; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; import ortus.boxlang.runtime.dynamic.IReferenceable; import ortus.boxlang.runtime.dynamic.javaproxy.InterfaceProxyService; import ortus.boxlang.runtime.loader.ImportDefinition; @@ -125,7 +125,10 @@ public static ClassNode transpile( Transpiler transpiler, BoxClass boxClass ) th .filter( it -> it.toLowerCase().startsWith( "java:" ) ) .map( it -> it.substring( 5 ) ) .collect( BLCollector.toArray() ); - var interfaceProxyDefinition = InterfaceProxyService.generateDefinition( new ScriptingRequestBoxContext(), implementsArray ); + + // var interfaceProxyDefinition = InterfaceProxyService.generateDefinition( new ScriptingRequestBoxContext(), implementsArray ); + var interfaceProxyDefinition = InterfaceProxyService.generateDefinition( BoxRuntime.getInstance().getRuntimeContext(), implementsArray ); + // TODO: Remove methods that already have a @overrideJava UDF definition to avoid duplicates interfaces.addAll( interfaceProxyDefinition.interfaces().stream().map( iface -> Type.getType( "L" + iface.replace( '.', '/' ) + ";" ) ).toList() ); interfaceMethods = interfaceProxyDefinition.methods().stream() diff --git a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/BoxClassTransformer.java b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/BoxClassTransformer.java index 9e3614949..1d1dd56bf 100644 --- a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/BoxClassTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/BoxClassTransformer.java @@ -61,8 +61,8 @@ import ortus.boxlang.compiler.ast.statement.BoxType; import ortus.boxlang.compiler.javaboxpiler.JavaTranspiler; import ortus.boxlang.compiler.javaboxpiler.transformer.expression.BoxStringLiteralTransformer; +import ortus.boxlang.runtime.BoxRuntime; import ortus.boxlang.runtime.config.util.PlaceholderHelper; -import ortus.boxlang.runtime.context.ScriptingRequestBoxContext; import ortus.boxlang.runtime.dynamic.casters.BooleanCaster; import ortus.boxlang.runtime.dynamic.javaproxy.InterfaceProxyService; import ortus.boxlang.runtime.types.Array; @@ -478,7 +478,10 @@ public Node transform( BoxNode node, TransformerContext context ) throws Illegal .filter( it -> it.toLowerCase().startsWith( "java:" ) ) .map( it -> it.substring( 5 ) ) .collect( BLCollector.toArray() ); - var interfaceProxyDefinition = InterfaceProxyService.generateDefinition( new ScriptingRequestBoxContext(), implementsArray ); + + // var interfaceProxyDefinition = InterfaceProxyService.generateDefinition( new ScriptingRequestBoxContext(), implementsArray ); + var interfaceProxyDefinition = InterfaceProxyService.generateDefinition( BoxRuntime.getInstance().getRuntimeContext(), implementsArray ); + // TODO: Remove methods that already have a @overrideJava UDF definition to avoid duplicates interfaces.addAll( interfaceProxyDefinition.interfaces() ); interfaceMethods = ProxyTransformer.generateInterfaceMethods( interfaceProxyDefinition.methods(), "this" ); diff --git a/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java b/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java index e7e7a6826..701c9780a 100644 --- a/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java +++ b/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java @@ -156,7 +156,7 @@ public abstract class BaseApplicationListener { /** * Logger */ - private static final Logger logger = LoggerFactory.getLogger( BaseApplicationListener.class ); + protected static final Logger logger = LoggerFactory.getLogger( BaseApplicationListener.class ); /** * -------------------------------------------------------------------------- @@ -175,8 +175,6 @@ protected BaseApplicationListener( RequestBoxContext context ) { this.interceptorPool = new InterceptorPool( Key.appListener, BoxRuntime.getInstance() ) .registerInterceptionPoint( REQUEST_INTERCEPTION_POINTS ); - logger.trace( "===> Setting the context classLoader to the runtime loader during BaseAppListener Constructor via [{}]", - Thread.currentThread().getName() ); // Ensure our thread is at least using the runtime CL. If there is an application defined later, this may get updated to a more specific request CL. Thread.currentThread().setContextClassLoader( BoxRuntime.getInstance().getRuntimeLoader() ); } diff --git a/src/main/java/ortus/boxlang/runtime/loader/DynamicClassLoader.java b/src/main/java/ortus/boxlang/runtime/loader/DynamicClassLoader.java index 5e2b92091..e7eac15fc 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/DynamicClassLoader.java +++ b/src/main/java/ortus/boxlang/runtime/loader/DynamicClassLoader.java @@ -179,6 +179,7 @@ public Class findClass( String className, Boolean safe ) throws ClassNotFound } logger.trace( "[{}] Discovering class: [{}] from thread [{}]", this.nameAsKey.getName(), className, Thread.currentThread().getName() ); + logger.trace( "The context class loader is [{}]", Thread.currentThread().getContextClassLoader().getName() ); // 1. Check the loaded cache first and return if found Class cachedClass = this.loadedClasses.get( className ); @@ -212,6 +213,7 @@ public Class findClass( String className, Boolean safe ) throws ClassNotFound // 4. If not found in JARs, delegate to parent class loader try { logger.trace( "[{}].[{}] : Class not found locally, trying the parent...", this.nameAsKey.getName(), className ); + logger.trace( "The context class loader is [{}]", Thread.currentThread().getContextClassLoader().getName() ); cachedClass = getDynamicParent().loadClass( className ); logger.trace( "[{}].[{}] : Class found in parent on thread [{}]", this.nameAsKey.getName(), className, Thread.currentThread().getName() ); } catch ( ClassNotFoundException parentException ) { From 227d21fce9ff997c8770ef9d25a0d91fe2a548cc Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 13 Dec 2024 20:02:12 +0100 Subject: [PATCH 190/193] new property boolean processor --- .../runtime/config/util/PropertyHelper.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/ortus/boxlang/runtime/config/util/PropertyHelper.java b/src/main/java/ortus/boxlang/runtime/config/util/PropertyHelper.java index b6c5d371f..9e086b5c8 100644 --- a/src/main/java/ortus/boxlang/runtime/config/util/PropertyHelper.java +++ b/src/main/java/ortus/boxlang/runtime/config/util/PropertyHelper.java @@ -25,6 +25,7 @@ import org.slf4j.LoggerFactory; import ortus.boxlang.runtime.dynamic.casters.ArrayCaster; +import ortus.boxlang.runtime.dynamic.casters.BooleanCaster; import ortus.boxlang.runtime.dynamic.casters.IntegerCaster; import ortus.boxlang.runtime.dynamic.casters.StringCaster; import ortus.boxlang.runtime.scopes.Key; @@ -151,6 +152,22 @@ public static Integer processInteger( IStruct config, Key key, Integer defaultVa return defaultValue; } + /** + * Process an incoming string, and do replacements and return the value as a boolean + * + * @param config The configuration object + * @param key The target key to look and process + * @param defaultValue The default value to return if the key is not found + * + * @return The integer value + */ + public static boolean processBoolean( IStruct config, Key key, boolean defaultValue ) { + if ( config.containsKey( key ) ) { + return BooleanCaster.cast( PlaceholderHelper.resolve( config.get( key ) ) ); + } + return defaultValue; + } + /** * Process a key that's supposed to be a IStruct and return it as a struct, * else, return a new struct. From 62d8685a7f3cf9a2d47180684e630e13b0d15ab2 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 13 Dec 2024 20:10:12 +0100 Subject: [PATCH 191/193] BL-846 #resolve New logging setting: statusPrinterOnLoad for much needed debuging of the logging configs --- .../config/segments/LoggingConfig.java | 20 +++++++++++++------ .../runtime/logging/LoggingService.java | 6 +++--- .../ortus/boxlang/runtime/scopes/Key.java | 1 + src/main/resources/config/boxlang.json | 7 +++++-- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/config/segments/LoggingConfig.java b/src/main/java/ortus/boxlang/runtime/config/segments/LoggingConfig.java index 45e5519fd..cbcab4e85 100644 --- a/src/main/java/ortus/boxlang/runtime/config/segments/LoggingConfig.java +++ b/src/main/java/ortus/boxlang/runtime/config/segments/LoggingConfig.java @@ -80,6 +80,11 @@ public class LoggingConfig implements IConfigSegment { */ public Key defaultEncoder = DEFAULT_ENCODER; + /** + * Status printer on load + */ + public boolean statusPrinterOnLoad = false; + /** * -------------------------------------------------------------------------- * Methods @@ -102,12 +107,14 @@ public LoggingConfig() { */ @Override public IConfigSegment process( IStruct config ) { - this.logsDirectory = PropertyHelper.processString( config, Key.logsDirectory, this.logsDirectory ); - this.maxLogDays = PropertyHelper.processInteger( config, Key.maxLogDays, this.maxLogDays ); - this.maxFileSize = PropertyHelper.processString( config, Key.maxFileSize, this.maxFileSize ); - this.totalCapSize = PropertyHelper.processString( config, Key.totalCapSize, this.totalCapSize ); - this.rootLevel = LogLevel.valueOf( PropertyHelper.processString( config, Key.rootLevel, this.rootLevel.getName(), VALID_LOG_LEVELS ), false ); - this.defaultEncoder = Key.of( PropertyHelper.processString( config, Key.defaultEncoder, DEFAULT_ENCODER.getName(), VALID_ENCODERS ) ); + this.logsDirectory = PropertyHelper.processString( config, Key.logsDirectory, this.logsDirectory ); + this.maxLogDays = PropertyHelper.processInteger( config, Key.maxLogDays, this.maxLogDays ); + this.maxFileSize = PropertyHelper.processString( config, Key.maxFileSize, this.maxFileSize ); + this.totalCapSize = PropertyHelper.processString( config, Key.totalCapSize, this.totalCapSize ); + this.statusPrinterOnLoad = PropertyHelper.processBoolean( config, Key.statusPrinterOnLoad, this.statusPrinterOnLoad ); + this.rootLevel = LogLevel.valueOf( PropertyHelper.processString( config, Key.rootLevel, this.rootLevel.getName(), VALID_LOG_LEVELS ), + false ); + this.defaultEncoder = Key.of( PropertyHelper.processString( config, Key.defaultEncoder, DEFAULT_ENCODER.getName(), VALID_ENCODERS ) ); // process loggers now PropertyHelper .processToStruct( config, Key.loggers ) @@ -138,6 +145,7 @@ public IStruct asStruct() { Key.maxLogDays, this.maxLogDays, Key.maxFileSize, this.maxFileSize, Key.rootLevel, this.rootLevel.getName(), + Key.statusPrinterOnLoad, this.statusPrinterOnLoad, Key.totalCapSize, this.totalCapSize ); } diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index 279dd5390..bf281fd89 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -324,7 +324,7 @@ public LoggingService reconfigure() { } // Debugging - if ( this.runtime.inDebugMode() ) { + if ( this.runtime.getConfiguration().logging.statusPrinterOnLoad ) { StatusPrinter.print( this.loggerContext ); } @@ -567,7 +567,7 @@ public FileAppender getOrBuildAppender( String filePath, LoggerCo appender.start(); // Uncomment to verify issues - if ( this.runtime.inDebugMode() ) { + if ( this.runtime.getConfiguration().logging.statusPrinterOnLoad ) { StatusPrinter.print( logContext ); } @@ -653,7 +653,7 @@ private BoxLangLogger createLogger( Key loggerKey, String loggerFilePath ) { // Check if we have the logger configuration or else build a vanilla one LoggerConfig loggerConfig = ( LoggerConfig ) this.runtime .getConfiguration().logging.loggers - .computeIfAbsent( loggerKey, key -> new LoggerConfig( key.getNameNoCase(), this.runtime.getConfiguration().logging ) ); + .computeIfAbsent( loggerKey, key -> new LoggerConfig( key.getNameNoCase(), this.runtime.getConfiguration().logging ) ); Level configLevel = Level.toLevel( LogLevel.valueOf( loggerConfig.level.getName(), false ).getName() ); // Seed the properties diff --git a/src/main/java/ortus/boxlang/runtime/scopes/Key.java b/src/main/java/ortus/boxlang/runtime/scopes/Key.java index 732d6d0e5..073b1bdce 100644 --- a/src/main/java/ortus/boxlang/runtime/scopes/Key.java +++ b/src/main/java/ortus/boxlang/runtime/scopes/Key.java @@ -660,6 +660,7 @@ public class Key implements Comparable, Serializable { public static final Key sqltype = Key.of( "sqltype" ); public static final Key stackTrace = Key.of( "stackTrace" ); public static final Key start = Key.of( "start" ); + public static final Key statusPrinterOnLoad = Key.of( "statusPrinterOnLoad" ); public static final Key startRow = Key.of( "startRow" ); public static final Key startTicks = Key.of( "startTicks" ); public static final Key startTime = Key.of( "startTime" ); diff --git a/src/main/resources/config/boxlang.json b/src/main/resources/config/boxlang.json index 6e6792242..02ec06a2a 100644 --- a/src/main/resources/config/boxlang.json +++ b/src/main/resources/config/boxlang.json @@ -97,6 +97,9 @@ // Default Encoder for file appenders. // The available options are "text" and "json" "defaultEncoder": "text", + // Activate the status printer on load to print out the logging configuration + // Turn on to debug LogBack and BoxLang logging configurations + "statusPrinterOnLoad": false, // A collection of pre-defined loggers and their configurations "loggers": { // The runtime main and default log @@ -148,7 +151,7 @@ "encoder": "text", // Additive logging: true means that this logger will inherit the appenders from the root logger // If false, it will only use the appenders defined in this logger - "additive": true + "additive": false }, // The datasource log is used by the creation, debugging, and management of datasources "datasource": { @@ -165,7 +168,7 @@ "encoder": "text", // Additive logging: true means that this logger will inherit the appenders from the root logger // If false, it will only use the appenders defined in this logger - "additive": true + "additive": false }, // The modules log is used by the module service and records all module activity "modules": { From 5c7317c6d703f4459c222cd7d07846fa3a3763d4 Mon Sep 17 00:00:00 2001 From: lmajano Date: Fri, 13 Dec 2024 19:11:11 +0000 Subject: [PATCH 192/193] Apply cfformat changes --- src/main/java/ortus/boxlang/runtime/logging/LoggingService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java index bf281fd89..7c615082c 100644 --- a/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java +++ b/src/main/java/ortus/boxlang/runtime/logging/LoggingService.java @@ -653,7 +653,7 @@ private BoxLangLogger createLogger( Key loggerKey, String loggerFilePath ) { // Check if we have the logger configuration or else build a vanilla one LoggerConfig loggerConfig = ( LoggerConfig ) this.runtime .getConfiguration().logging.loggers - .computeIfAbsent( loggerKey, key -> new LoggerConfig( key.getNameNoCase(), this.runtime.getConfiguration().logging ) ); + .computeIfAbsent( loggerKey, key -> new LoggerConfig( key.getNameNoCase(), this.runtime.getConfiguration().logging ) ); Level configLevel = Level.toLevel( LogLevel.valueOf( loggerConfig.level.getName(), false ).getName() ); // Seed the properties From e1c6c59cc08dfa8d247715d3cdf547cccc0c0249 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 13 Dec 2024 14:19:37 -0600 Subject: [PATCH 193/193] BL-847 --- .../runtime/interop/proxies/BaseProxy.java | 75 +++++++++--- .../ortus/boxlang/runtime/scopes/Key.java | 114 +++++++++--------- .../system/java/CreateDynamicProxyTest.java | 24 ++++ .../dynamic/javaproxy/BoxClassRunnable.cfc | 4 + 4 files changed, 149 insertions(+), 68 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/interop/proxies/BaseProxy.java b/src/main/java/ortus/boxlang/runtime/interop/proxies/BaseProxy.java index 7a9948c89..15eea50f8 100644 --- a/src/main/java/ortus/boxlang/runtime/interop/proxies/BaseProxy.java +++ b/src/main/java/ortus/boxlang/runtime/interop/proxies/BaseProxy.java @@ -17,6 +17,8 @@ */ package ortus.boxlang.runtime.interop.proxies; +import java.util.Set; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,6 +27,7 @@ import ortus.boxlang.runtime.runnables.IClassRunnable; import ortus.boxlang.runtime.scopes.Key; import ortus.boxlang.runtime.types.Function; +import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.util.RequestThreadManager; /** @@ -63,6 +66,16 @@ public abstract class BaseProxy { */ protected RequestThreadManager threadManager; + private static final Set javaLangObjectPublicMethods = Set.of( + Key.getClass, + Key._hashCode, + Key.equals, + Key.toString, + Key.notify, + Key.notifyAll, + Key.wait + ); + /** * Constructor for the proxy. * @@ -120,14 +133,51 @@ protected BaseProxy( Object target, IBoxContext context ) { * @param args The arguments to pass to the function * * @return The result of the function + * + * @throws InterruptedException */ - protected Object invoke( Key method, Object... args ) { - return getDynamicTarget().dereferenceAndInvoke( - this.context, - method, - args, - false - ); + protected Object invoke( Key method, Object... args ) throws InterruptedException { + IClassRunnable target = getDynamicTarget(); + // We need to handle the case where a proxy being passed around in the JDK code could have methods like .toString() call on them, with the expectation that + // the method will exist, since that is true of all Java objects extending `java.lang.Object`. + if ( javaLangObjectPublicMethods.contains( method ) && !target.getThisScope().containsKey( method ) ) { + if ( method.equals( Key.getClass ) ) { + return target.getClass(); + } else if ( method.equals( Key._hashCode ) ) { + return target.hashCode(); + } else if ( method.equals( Key.equals ) ) { + return target.equals( args[ 0 ] ); + } else if ( method.equals( Key.toString ) ) { + return target.toString(); + } else if ( method.equals( Key.notify ) ) { + target.notify(); + return null; + } else if ( method.equals( Key.notifyAll ) ) { + target.notifyAll(); + return null; + } else if ( method.equals( Key.wait ) ) { + if ( args.length == 0 ) { + target.wait(); + } else if ( args.length == 1 ) { + target.wait( ( Long ) args[ 0 ] ); + } else if ( args.length == 2 ) { + target.wait( ( Long ) args[ 0 ], ( Integer ) args[ 1 ] ); + } else { + throw new BoxRuntimeException( "Unknown method: " + method ); + } + return null; + } else { + throw new BoxRuntimeException( "Unknown method: " + method ); + } + + } else { + return target.dereferenceAndInvoke( + this.context, + method, + args, + false + ); + } } /** @@ -136,20 +186,17 @@ protected Object invoke( Key method, Object... args ) { * @param args The arguments to pass to the function * * @return The result of the function + * + * @throws InterruptedException */ - protected Object invoke( Object... args ) { + protected Object invoke( Object... args ) throws InterruptedException { if ( isFunctionTarget() ) { return this.context.invokeFunction( this.target, args ); } else { - return getDynamicTarget().dereferenceAndInvoke( - this.context, - this.defaultMethod, - args, - false - ); + return invoke( this.defaultMethod, args ); } } diff --git a/src/main/java/ortus/boxlang/runtime/scopes/Key.java b/src/main/java/ortus/boxlang/runtime/scopes/Key.java index 073b1bdce..7e51bfaa3 100644 --- a/src/main/java/ortus/boxlang/runtime/scopes/Key.java +++ b/src/main/java/ortus/boxlang/runtime/scopes/Key.java @@ -48,18 +48,19 @@ public class Key implements Comparable, Serializable { public static final Key __functionName = Key.of( "__functionName" ); public static final Key __isMemberExecution = Key.of( "__isMemberExecution" ); public static final Key _ABSTRACT = Key.of( "abstract" ); - public static final Key ancestorLevels = Key.of( "ancestorLevels" ); public static final Key _ANY = Key.of( "any" ); public static final Key _ARRAY = Key.of( "array" ); public static final Key _BOOLEAN = Key.of( "boolean" ); public static final Key _CLASS = Key.of( "class" ); public static final Key _DATE = Key.of( "date" ); public static final Key _DATETIME = Key.of( "datetime" ); - public static final Key _DOUBLE = Key.of( "double" ); public static final Key _DEFAULT = Key.of( "default" ); + public static final Key _DOUBLE = Key.of( "double" ); public static final Key _EMPTY = Key.of( "" ); public static final Key _EXTENDS = Key.of( "extends" ); public static final Key _FILE = Key.of( "file" ); + public static final Key _final = Key.of( "final" ); + public static final Key _hashCode = Key.of( "hashCode" ); public static final Key _HASHCODE = Key.of( "hashcode" ); public static final Key _IMPLEMENTS = Key.of( "implements" ); public static final Key _INTEGER = Key.of( "integer" ); @@ -81,15 +82,16 @@ public class Key implements Comparable, Serializable { public static final Key access = Key.of( "access" ); public static final Key accessors = Key.of( "accessors" ); public static final Key action = Key.of( "action" ); + public static final Key additive = Key.of( "additive" ); public static final Key addnewline = Key.of( "addnewline" ); public static final Key addToken = Key.of( "addToken" ); - public static final Key additive = Key.of( "additive" ); public static final Key algorithm = Key.of( "algorithm" ); public static final Key allow = Key.of( "allow" ); public static final Key allowedFileOperationExtensions = Key.of( "allowedFileOperationExtensions" ); - public static final Key disallowedFileOperationExtensions = Key.of( "disallowedFileOperationExtensions" ); public static final Key allowRealPath = Key.of( "allowRealPath" ); + public static final Key ancestorLevels = Key.of( "ancestorLevels" ); public static final Key annotations = Key.of( "annotations" ); + public static final Key announce = Key.of( "announce" ); public static final Key ANONYMOUSCLOSURE = Key.of( "ANONYMOUSCLOSURE" ); public static final Key ANONYMOUSLAMBDA = Key.of( "ANONYMOUSLAMBDA" ); public static final Key append = Key.of( "append" ); @@ -111,7 +113,6 @@ public class Key implements Comparable, Serializable { public static final Key asOptional = Key.of( "asOptional" ); public static final Key assocAttribs = Key.of( "assocAttribs" ); public static final Key asyncService = Key.of( "asyncService" ); - public static final Key announce = Key.of( "announce" ); public static final Key attribute = Key.of( "attribute" ); public static final Key attributeCollection = Key.of( "attributeCollection" ); public static final Key attributes = Key.of( "attributes" ); @@ -125,17 +126,17 @@ public class Key implements Comparable, Serializable { public static final Key binary = Key.of( "binary" ); public static final Key body = Key.of( "body" ); public static final Key boxBif = Key.of( "BoxBif" ); - public static final Key boxComponent = Key.of( "BoxComponent" ); public static final Key boxCacheProvider = Key.of( "BoxCacheProvider" ); + public static final Key boxComponent = Key.of( "BoxComponent" ); public static final Key boxlang = Key.of( "boxlang" ); - public static final Key bxSessions = Key.of( "bxSessions" ); - public static final Key bxRegex = Key.of( "bxRegex" ); public static final Key boxMember = Key.of( "BoxMember" ); public static final Key boxRuntime = Key.of( "boxRuntime" ); public static final Key buffer = Key.of( "buffer" ); public static final Key buffersize = Key.of( "buffersize" ); public static final Key bxDefaultDatasource = Key.of( "bxDefaultDatasource" ); public static final Key bxRandomSeed = Key.of( "bxRandomSeed" ); + public static final Key bxRegex = Key.of( "bxRegex" ); + public static final Key bxSessions = Key.of( "bxSessions" ); public static final Key bxTracers = Key.of( "bxTracers" ); public static final Key cached = Key.of( "cached" ); public static final Key cachedwithin = Key.of( "cachedwithin" ); @@ -146,8 +147,8 @@ public class Key implements Comparable, Serializable { public static final Key caller = Key.of( "caller" ); public static final Key canonicalize = Key.of( "canonicalize" ); public static final Key caseSensitive = Key.of( "caseSensitive" ); - public static final Key cause = Key.of( "cause" ); public static final Key category = Key.of( "category" ); + public static final Key cause = Key.of( "cause" ); public static final Key cert_cookie = Key.of( "cert_cookie" ); public static final Key cert_flags = Key.of( "cert_flags" ); public static final Key cert_issuer = Key.of( "cert_issuer" ); @@ -167,8 +168,8 @@ public class Key implements Comparable, Serializable { public static final Key classGenerationDirectory = Key.of( "classGenerationDirectory" ); public static final Key className = Key.of( "className" ); public static final Key classPaths = Key.of( "classPaths" ); - public static final Key cli = Key.of( "cli" ); public static final Key clazz = Key.of( "clazz" ); + public static final Key cli = Key.of( "cli" ); public static final Key clientCert = Key.of( "clientCert" ); public static final Key clientCertPassword = Key.of( "clientCertPassword" ); public static final Key closure = Key.of( "closure" ); @@ -209,8 +210,8 @@ public class Key implements Comparable, Serializable { public static final Key customInterceptionPoints = Key.of( "customInterceptionPoints" ); public static final Key customTagName = Key.of( "customTagName" ); public static final Key customTagPath = Key.of( "customTagPath" ); - public static final Key customTagsDirectory = Key.of( "customTagsDirectory" ); public static final Key customTagPaths = Key.of( "customTagPaths" ); + public static final Key customTagsDirectory = Key.of( "customTagsDirectory" ); public static final Key data = Key.of( "data" ); public static final Key dataCollection = Key.of( "dataCollection" ); public static final Key datasource = Key.of( "datasource" ); @@ -229,19 +230,19 @@ public class Key implements Comparable, Serializable { public static final Key debugMode = Key.of( "debugMode" ); public static final Key deep = Key.of( "deep" ); public static final Key defaultCache = Key.of( "defaultCache" ); - public static final Key defaultEncoder = Key.of( "defaultEncoder" ); public static final Key defaultDatasource = Key.of( "defaultDatasource" ); + public static final Key defaultEncoder = Key.of( "defaultEncoder" ); public static final Key defaultFunctions = Key.of( "defaultFunctions" ); public static final Key defaultLastAccessTimeout = Key.of( "defaultLastAccessTimeout" ); + public static final Key defaultRemoteMethodReturnFormat = Key.of( "defaultRemoteMethodReturnFormat" ); public static final Key defaultTimeout = Key.of( "defaultTimeout" ); public static final Key defaultValue = Key.of( "defaultValue" ); - public static final Key delete = Key.of( "delete" ); public static final Key delay = Key.of( "delay" ); + public static final Key delete = Key.of( "delete" ); public static final Key deleteFile = Key.of( "deleteFile" ); public static final Key delimiter = Key.of( "delimiter" ); public static final Key delimiters = Key.of( "delimiters" ); public static final Key dependsOn = Key.of( "dependsOn" ); - public static final Key defaultRemoteMethodReturnFormat = Key.of( "defaultRemoteMethodReturnFormat" ); public static final Key depth = Key.of( "depth" ); public static final Key description = Key.of( "description" ); public static final Key descriptor = Key.of( "descriptor" ); @@ -255,10 +256,11 @@ public class Key implements Comparable, Serializable { public static final Key directoryList = Key.of( "directoryList" ); public static final Key directoryMove = Key.of( "directoryMove" ); public static final Key disabled = Key.of( "disabled" ); - public static final Key display = Key.of( "display" ); - public static final Key disallowedImports = Key.of( "disallowedImports" ); public static final Key disallowedBIFs = Key.of( "disallowedBIFs" ); public static final Key disallowedComponents = Key.of( "disallowedComponents" ); + public static final Key disallowedFileOperationExtensions = Key.of( "disallowedFileOperationExtensions" ); + public static final Key disallowedImports = Key.of( "disallowedImports" ); + public static final Key display = Key.of( "display" ); public static final Key doAll = Key.of( "doAll" ); public static final Key documentation = Key.of( "documentation" ); public static final Key dollarFormat = Key.of( "dollarFormat" ); @@ -278,6 +280,7 @@ public class Key implements Comparable, Serializable { public static final Key encoded = Key.of( "encoded" ); public static final Key encoded_binary = Key.of( "encoded_binary" ); public static final Key encodefor = Key.of( "encodefor" ); + public static final Key encoder = Key.of( "encoder" ); public static final Key encodeUrl = Key.of( "encodeUrl" ); public static final Key encoding = Key.of( "encoding" ); public static final Key encodingBase64 = Key.of( "Base64" ); @@ -285,10 +288,12 @@ public class Key implements Comparable, Serializable { public static final Key encodingHex = Key.of( "Hex" ); public static final Key encodingUU = Key.of( "UU" ); public static final Key end = Key.of( "end" ); - public static final Key executor = Key.of( "executor" ); public static final Key endRow = Key.of( "endRow" ); public static final Key enforceExplicitOutput = Key.of( "enforceExplicitOutput" ); + public static final Key entryPath = Key.of( "entryPath" ); + public static final Key entryPaths = Key.of( "entryPaths" ); public static final Key environment = Key.of( "environment" ); + public static final Key equals = Key.of( "equals" ); public static final Key error = Key.of( "error" ); public static final Key errorcode = Key.of( "errorcode" ); public static final Key errorDetail = Key.of( "errorDetail" ); @@ -296,17 +301,14 @@ public class Key implements Comparable, Serializable { public static final Key escapeChars = Key.of( "escapeChars" ); public static final Key evictCount = Key.of( "evictCount" ); public static final Key evictionPolicy = Key.of( "evictionPolicy" ); - public static final Key experimental = Key.of( "experimental" ); public static final Key execute = Key.of( "execute" ); - public static final Key encoder = Key.of( "encoder" ); - public static final Key entryPath = Key.of( "entryPath" ); - public static final Key entryPaths = Key.of( "entryPaths" ); public static final Key executionMode = Key.of( "executionMode" ); public static final Key executionState = Key.of( "executionState" ); public static final Key executionTime = Key.of( "executionTime" ); - public static final Key generatedKey = Key.of( "generatedKey" ); - public static final Key expand = Key.of( "expand" ); + public static final Key executor = Key.of( "executor" ); public static final Key executors = Key.of( "executors" ); + public static final Key expand = Key.of( "expand" ); + public static final Key experimental = Key.of( "experimental" ); public static final Key expires = Key.of( "expires" ); public static final Key expireURL = Key.of( "expireURL" ); public static final Key explanation = Key.of( "explanation" ); @@ -322,15 +324,14 @@ public class Key implements Comparable, Serializable { public static final Key filefield = Key.of( "filefield" ); public static final Key filepath = Key.of( "filepath" ); public static final Key filter = Key.of( "filter" ); - public static final Key _final = Key.of( "final" ); public static final Key find = Key.of( "find" ); public static final Key findAll = Key.of( "findAll" ); public static final Key findNoCase = Key.of( "findNoCase" ); public static final Key firstRowAsHeaders = Key.of( "firstRowAsHeaders" ); public static final Key fixnewline = Key.of( "fixnewline" ); public static final Key flatList = Key.of( "flatList" ); - public static final Key format = Key.of( "format" ); public static final Key force = Key.of( "force" ); + public static final Key format = Key.of( "format" ); public static final Key freeMemoryPercentageThreshold = Key.of( "freeMemoryPercentageThreshold" ); public static final Key from = Key.of( "from" ); public static final Key fromKey = Key.of( "fromKey" ); @@ -340,6 +341,7 @@ public class Key implements Comparable, Serializable { public static final Key functionService = Key.of( "functionService" ); public static final Key gateway_interface = Key.of( "gateway_interface" ); public static final Key generatedContent = Key.of( "generatedContent" ); + public static final Key generatedKey = Key.of( "generatedKey" ); public static final Key generic = Key.of( "generic" ); public static final Key getAsBinary = Key.of( "getAsBinary" ); public static final Key getClass = Key.of( "getClass" ); @@ -373,10 +375,10 @@ public class Key implements Comparable, Serializable { public static final Key https_server_issuer = Key.of( "https_server_issuer" ); public static final Key https_server_subject = Key.of( "https_server_subject" ); public static final Key id = Key.of( "id" ); - public static final Key includeBaseFolder = Key.of( "includeBaseFolder" ); public static final Key idleTime = Key.of( "idleTime" ); public static final Key ignoreCase = Key.of( "ignoreCase" ); public static final Key ignoreExists = Key.of( "ignoreExists" ); + public static final Key includeBaseFolder = Key.of( "includeBaseFolder" ); public static final Key includeBody = Key.of( "includeBody" ); public static final Key includeEmptyFields = Key.of( "includeEmptyFields" ); public static final Key index = Key.of( "index" ); @@ -393,11 +395,11 @@ public class Key implements Comparable, Serializable { public static final Key interceptorService = Key.of( "interceptorService" ); public static final Key interfaces = Key.of( "interfaces" ); public static final Key interrupted = Key.of( "interrupted" ); + public static final Key interval = Key.of( "interval" ); public static final Key invoke = Key.of( "invoke" ); public static final Key invokeArgs = Key.of( "invokeArgs" ); public static final Key invokeImplicitAccessor = Key.of( "invokeImplicitAccessor" ); public static final Key ip = Key.of( "ip" ); - public static final Key interval = Key.of( "interval" ); public static final Key iso = Key.of( "iso" ); public static final Key isValid = Key.of( "isValid" ); public static final Key item = Key.of( "item" ); @@ -408,12 +410,12 @@ public class Key implements Comparable, Serializable { public static final Key javascriptvar = Key.of( "javascriptvar" ); public static final Key javaSettings = Key.of( "javaSettings" ); public static final Key join = Key.of( "join" ); - public static final Key json = Key.of( "json" ); - public static final Key JSONSerialize = Key.of( "JSONSerialize" ); public static final Key jsessionID = Key.of( "jsessionID" ); + public static final Key json = Key.of( "json" ); public static final Key jsonExclude = Key.of( "jsonExclude" ); public static final Key jsonInclude = Key.of( "jsonInclude" ); public static final Key jsonNeverInclude = Key.of( "jsonNeverInclude" ); + public static final Key JSONSerialize = Key.of( "JSONSerialize" ); public static final Key key = Key.of( "key" ); public static final Key keyMap = Key.of( "keyMap" ); public static final Key keySize = Key.of( "keySize" ); @@ -434,16 +436,16 @@ public class Key implements Comparable, Serializable { public static final Key listToJSON = Key.of( "listToJSON" ); public static final Key lJustify = Key.of( "lJustify" ); public static final Key loadPaths = Key.of( "loadPaths" ); - public static final Key logging = Key.of( "logging" ); - public static final Key loggers = Key.of( "loggers" ); - public static final Key logsDirectory = Key.of( "logsDirectory" ); - public static final Key loggingService = Key.of( "loggingService" ); public static final Key local_addr = Key.of( "local_addr" ); public static final Key local_host = Key.of( "local_host" ); public static final Key locale = Key.of( "locale" ); public static final Key localeSensitive = Key.of( "localeSensitive" ); public static final Key log = Key.of( "log" ); public static final Key logger = Key.of( "logger" ); + public static final Key loggers = Key.of( "loggers" ); + public static final Key logging = Key.of( "logging" ); + public static final Key loggingService = Key.of( "loggingService" ); + public static final Key logsDirectory = Key.of( "logsDirectory" ); public static final Key lucee = Key.of( "lucee" ); public static final Key main = Key.of( "main" ); public static final Key mapping = Key.of( "mapping" ); @@ -451,10 +453,10 @@ public class Key implements Comparable, Serializable { public static final Key mask = Key.of( "mask" ); public static final Key match = Key.of( "match" ); public static final Key max = Key.of( "max" ); - public static final Key maxFrames = Key.of( "maxFrames" ); public static final Key maxFileSize = Key.of( "maxFileSize" ); - public static final Key maxLogDays = Key.of( "maxLogDays" ); + public static final Key maxFrames = Key.of( "maxFrames" ); public static final Key maxLength = Key.of( "maxLength" ); + public static final Key maxLogDays = Key.of( "maxLogDays" ); public static final Key maxObjects = Key.of( "maxObjects" ); public static final Key maxRows = Key.of( "maxRows" ); public static final Key maxThreads = Key.of( "maxThreads" ); @@ -474,8 +476,8 @@ public class Key implements Comparable, Serializable { public static final Key missingTemplate = Key.of( "missingTemplate" ); public static final Key mode = Key.of( "mode" ); public static final Key modifiableArray = Key.of( "modifiableArray" ); - public static final Key modifiableStruct = Key.of( "modifiableStruct" ); public static final Key modifiableQuery = Key.of( "modifiableQuery" ); + public static final Key modifiableStruct = Key.of( "modifiableStruct" ); public static final Key module = Key.of( "module" ); public static final Key moduleMapping = Key.of( "moduleMapping" ); public static final Key moduleName = Key.of( "moduleName" ); @@ -492,12 +494,14 @@ public class Key implements Comparable, Serializable { public static final Key nameAsKey = Key.of( "nameAsKey" ); public static final Key nameconflict = Key.of( "nameconflict" ); public static final Key namespace = Key.of( "namespace" ); + public static final Key newBuffer = Key.of( "newBuffer" ); + public static final Key newBuilder = Key.of( "newBuilder" ); public static final Key newDelimiter = Key.of( "newDelimiter" ); public static final Key newDirectory = Key.of( "newDirectory" ); public static final Key newPath = Key.of( "newPath" ); - public static final Key newBuffer = Key.of( "newBuffer" ); - public static final Key newBuilder = Key.of( "newBuilder" ); public static final Key noInit = Key.of( "noInit" ); + public static final Key notify = Key.of( "notify" ); + public static final Key notifyAll = Key.of( "notifyAll" ); public static final Key nulls = Key.of( "null" ); public static final Key number = Key.of( "number" ); public static final Key number1 = Key.of( "number1" ); @@ -548,11 +552,11 @@ public class Key implements Comparable, Serializable { public static final Key position = Key.of( "position" ); public static final Key position1 = Key.of( "position1" ); public static final Key position2 = Key.of( "position2" ); + public static final Key positionals = Key.of( "positionals" ); public static final Key precise = Key.of( "precise" ); public static final Key prefix = Key.of( "prefix" ); public static final Key priority = Key.of( "priority" ); public static final Key processBody = Key.of( "processBody" ); - public static final Key positionals = Key.of( "positionals" ); public static final Key properties = Key.of( "properties" ); public static final Key protocol = Key.of( "protocol" ); public static final Key proxyPassword = Key.of( "proxyPassword" ); @@ -569,17 +573,15 @@ public class Key implements Comparable, Serializable { public static final Key queryParams = Key.of( "queryParams" ); public static final Key queryTimeout = Key.of( "queryTimeout" ); public static final Key radix = Key.of( "radix" ); - public static final Key runnable = Key.of( "runnable" ); public static final Key Raw_Trace = Key.of( "Raw_Trace" ); public static final Key read = Key.of( "read" ); public static final Key readBinary = Key.of( "readBinary" ); public static final Key reapFrequency = Key.of( "reapFrequency" ); public static final Key recordCount = Key.of( "recordCount" ); - public static final Key reFindNoCase = Key.of( "reFindNoCase" ); public static final Key recurse = Key.of( "recurse" ); - public static final Key rootLevel = Key.of( "rootLevel" ); public static final Key recursive = Key.of( "recursive" ); public static final Key redirect = Key.of( "redirect" ); + public static final Key reFindNoCase = Key.of( "reFindNoCase" ); public static final Key reg_expression = Key.of( "reg_expression" ); public static final Key regex = Key.of( "regex" ); public static final Key region = Key.of( "region" ); @@ -606,25 +608,27 @@ public class Key implements Comparable, Serializable { public static final Key returnType = Key.of( "returnType" ); public static final Key returnVariable = Key.of( "returnVariable" ); public static final Key rJustify = Key.of( "rJustify" ); + public static final Key rootLevel = Key.of( "rootLevel" ); public static final Key row = Key.of( "row" ); public static final Key row_number = Key.of( "row_number" ); public static final Key rowData = Key.of( "rowData" ); public static final Key rowNumber = Key.of( "rowNumber" ); public static final Key run = Key.of( "run" ); + public static final Key runnable = Key.of( "runnable" ); public static final Key runtime = Key.of( "runtime" ); public static final Key samesite = Key.of( "samesite" ); public static final Key scale = Key.of( "scale" ); public static final Key schedulerService = Key.of( "schedulerService" ); public static final Key scope = Key.of( "scope" ); public static final Key script_name = Key.of( "script_name" ); - public static final Key security = Key.of( "security" ); public static final Key second = Key.of( "second" ); public static final Key seconds = Key.of( "seconds" ); public static final Key secure = Key.of( "secure" ); - public static final Key serializable = Key.of( "serializable" ); + public static final Key security = Key.of( "security" ); public static final Key seed = Key.of( "seed" ); public static final Key seekable = Key.of( "seekable" ); public static final Key separator = Key.of( "separator" ); + public static final Key serializable = Key.of( "serializable" ); public static final Key serializeQueryByColumns = Key.of( "serializeQueryByColumns" ); public static final Key server = Key.of( "server" ); public static final Key server_name = Key.of( "server_name" ); @@ -634,11 +638,11 @@ public class Key implements Comparable, Serializable { public static final Key server_software = Key.of( "server_software" ); public static final Key servlet = Key.of( "servlet" ); public static final Key session = Key.of( "session" ); - public static final Key sessionScope = Key.of( "sessionScope" ); public static final Key sessionCluster = Key.of( "sessionCluster" ); public static final Key sessionId = Key.of( "sessionId" ); public static final Key sessionManagement = Key.of( "sessionManagement" ); public static final Key sessions = Key.of( "sessions" ); + public static final Key sessionScope = Key.of( "sessionScope" ); public static final Key sessionStorage = Key.of( "sessionStorage" ); public static final Key sessionTimeout = Key.of( "sessionTimeout" ); public static final Key set = Key.of( "set" ); @@ -682,9 +686,9 @@ public class Key implements Comparable, Serializable { public static final Key strip = Key.of( "strip" ); public static final Key stripWhitespace = Key.of( "stripWhitespace" ); public static final Key struct = Key.of( "struct" ); - public static final Key structLoose = Key.of( "structLoose" ); public static final Key struct1 = Key.of( "struct1" ); public static final Key struct2 = Key.of( "struct2" ); + public static final Key structLoose = Key.of( "structLoose" ); public static final Key structure = Key.of( "structure" ); public static final Key substring = Key.of( "substring" ); public static final Key substring1 = Key.of( "substring1" ); @@ -700,38 +704,39 @@ public class Key implements Comparable, Serializable { public static final Key terminate = Key.of( "terminate" ); public static final Key terminated = Key.of( "terminated" ); public static final Key terminateOnTimeout = Key.of( "terminateOnTimeout" ); - public static final Key trace = Key.of( "trace" ); public static final Key text = Key.of( "text" ); public static final Key textQualifier = Key.of( "textQualifier" ); public static final Key thisTag = Key.of( "thisTag" ); public static final Key thread = Key.of( "thread" ); + public static final Key threadName = Key.of( "threadName" ); public static final Key throwOnError = Key.of( "throwOnError" ); public static final Key throwOnTimeout = Key.of( "throwOnTimeout" ); public static final Key time = Key.of( "time" ); - public static final Key times = Key.of( "times" ); public static final Key timeCreated = Key.of( "timeCreated" ); public static final Key timeFormat = Key.of( "timeFormat" ); public static final Key timeout = Key.of( "timeout" ); + public static final Key times = Key.of( "times" ); public static final Key timespan = Key.of( "timespan" ); public static final Key timezone = Key.of( "timezone" ); - public static final Key threadName = Key.of( "threadName" ); public static final Key to = Key.of( "to" ); public static final Key toJSON = Key.of( "toJSON" ); public static final Key token = Key.of( "token" ); public static final Key toKey = Key.of( "toKey" ); public static final Key top = Key.of( "top" ); + public static final Key toString = Key.of( "toString" ); public static final Key totalCapSize = Key.of( "totalCapSize" ); + public static final Key trace = Key.of( "trace" ); public static final Key trim = Key.of( "trim" ); public static final Key type = Key.of( "type" ); public static final Key typename = Key.of( "typename" ); public static final Key unit = Key.of( "unit" ); - public static final Key useHighPrecisionMath = Key.of( "useHighPrecisionMath" ); public static final Key upload = Key.of( "upload" ); public static final Key uploadAll = Key.of( "uploadAll" ); public static final Key URL = Key.of( "URL" ); public static final Key urlToken = Key.of( "urlToken" ); public static final Key useCache = Key.of( "useCache" ); public static final Key useCustomSerializer = Key.of( "useCustomSerializer" ); + public static final Key useHighPrecisionMath = Key.of( "useHighPrecisionMath" ); public static final Key useLastAccessTimeouts = Key.of( "useLastAccessTimeouts" ); public static final Key useQueryString = Key.of( "useQueryString" ); public static final Key userAgent = Key.of( "userAgent" ); @@ -739,22 +744,23 @@ public class Key implements Comparable, Serializable { public static final Key useSecureJSONPrefix = Key.of( "useSecureJSONPrefix" ); public static final Key validator = Key.of( "validator" ); public static final Key validators = Key.of( "validators" ); + public static final Key validClassExtensions = Key.of( "validClassExtensions" ); + public static final Key validExtensions = Key.of( "validExtensions" ); + public static final Key validTemplateExtensions = Key.of( "validTemplateExtensions" ); public static final Key value = Key.of( "value" ); public static final Key var = Key.of( "var" ); public static final Key variable = Key.of( "variable" ); - public static final Key validExtensions = Key.of( "validExtensions" ); - public static final Key validClassExtensions = Key.of( "validClassExtensions" ); - public static final Key validTemplateExtensions = Key.of( "validTemplateExtensions" ); public static final Key variables = Key.of( "variables" ); public static final Key variant = Key.of( "variant" ); public static final Key version = Key.of( "version" ); + public static final Key wait = Key.of( "wait" ); public static final Key warning = Key.of( "warning" ); + public static final Key wddx = Key.of( "wddx" ); public static final Key web_server_api = Key.of( "web_server_api" ); public static final Key webURL = Key.of( "webURL" ); public static final Key whitespaceCompressionEnabled = Key.of( "whitespaceCompressionEnabled" ); public static final Key workstation = Key.of( "workstation" ); public static final Key write = Key.of( "write" ); - public static final Key wddx = Key.of( "wddx" ); public static final Key XML = Key.of( "XML" ); public static final Key XMLAttributes = Key.of( "XMLAttributes" ); public static final Key XMLCdata = Key.of( "XMLCdata" ); diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/system/java/CreateDynamicProxyTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/system/java/CreateDynamicProxyTest.java index c24e70ebc..8d6fc546e 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/system/java/CreateDynamicProxyTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/system/java/CreateDynamicProxyTest.java @@ -75,6 +75,30 @@ public void testCreatesAProxy() { assertThat( context.getScope( ServerScope.name ).get( "runnableProxyFired" ) ).isEqualTo( true ); } + @DisplayName( "It allows java.lang.Object methods" ) + @Test + public void testAllowsObjectMethods() { + // @formatter:off + instance.executeSource( + """ + import java:java.lang.Thread; + + jRunnable = CreateDynamicProxy( + "src.test.java.ortus.boxlang.runtime.dynamic.javaproxy.BoxClassRunnable", + "java.lang.Runnable" + ); + + // Should defer to the Object class + result = jRunnable.toString(); + // Actually exists in the CFC + result2 = jRunnable.hashCode(); + """, + context ); + // @formatter:on + assertThat( variables.getAsString( result ) ).contains( "Boxclassrunnable$cfc@" ); + assertThat( variables.get( Key.of( "result2" ) ) ).isEqualTo( 42 ); + } + @DisplayName( "It creates a proxy with multiple interfaces" ) @Test public void testCreatesMultipleProxies() { diff --git a/src/test/java/ortus/boxlang/runtime/dynamic/javaproxy/BoxClassRunnable.cfc b/src/test/java/ortus/boxlang/runtime/dynamic/javaproxy/BoxClassRunnable.cfc index 8ba6b66f2..717313e1c 100644 --- a/src/test/java/ortus/boxlang/runtime/dynamic/javaproxy/BoxClassRunnable.cfc +++ b/src/test/java/ortus/boxlang/runtime/dynamic/javaproxy/BoxClassRunnable.cfc @@ -9,4 +9,8 @@ component { return "I was called!"; } + function hashCode(){ + return 42; + } + }