From 849175f6b5a5a61931eb753c43634b506f2ecae9 Mon Sep 17 00:00:00 2001 From: Github Actions Date: Fri, 27 Sep 2024 20:09:07 +0000 Subject: [PATCH 01/19] Version bump --- changelog.md | 6 +++++- gradle.properties | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/changelog.md b/changelog.md index b766998a9..8113f1c57 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-beta16] - 2024-09-27 + ## [1.0.0-beta15] - 2024-09-20 ## [1.0.0-beta14] - 2024-09-13 @@ -43,7 +45,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-beta15...HEAD +[Unreleased]: https://github.com/ortus-boxlang/BoxLang/compare/v1.0.0-beta16...HEAD + +[1.0.0-beta16]: https://github.com/ortus-boxlang/BoxLang/compare/v1.0.0-beta15...v1.0.0-beta16 [1.0.0-beta15]: https://github.com/ortus-boxlang/BoxLang/compare/v1.0.0-beta14...v1.0.0-beta15 diff --git a/gradle.properties b/gradle.properties index e95c151af..a1d246936 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ -#Fri Sep 20 12:58:58 UTC 2024 +#Fri Sep 27 20:09:04 UTC 2024 antlrVersion=4.13.1 jdkVersion=21 -version=1.0.0-beta16 +version=1.0.0-beta17 From 66cd974f6bd89576c2fbca2f065fc930de962864 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 27 Sep 2024 17:07:04 -0600 Subject: [PATCH 02/19] fixing publish for next beta --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 812c4d533..25dbd2fc0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -158,7 +158,7 @@ jobs: if: env.SNAPSHOT == 'false' run: | gradle publish --no-daemon --no-parallel - gradle publishToSonatype closeAndReleaseSonatypeStagingRepository + gradle publishAllPublicationsToCentralPortal env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GPG_KEY: ${{ secrets.GPG_KEY }} From c078771abe566097c5384da9e85e72fca170fd27 Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Sun, 29 Sep 2024 11:03:25 -0700 Subject: [PATCH 03/19] BL-608 #resolve Timeouts (connection, idle) for datasources needs to be in seconds and not in milliseconds to adhere to cfconfig --- .../config/segments/DatasourceConfig.java | 47 ++++++++++++------- .../config/segments/DatasourceConfigTest.java | 16 +++---- 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/config/segments/DatasourceConfig.java b/src/main/java/ortus/boxlang/runtime/config/segments/DatasourceConfig.java index ef73e7146..18179f2bf 100644 --- a/src/main/java/ortus/boxlang/runtime/config/segments/DatasourceConfig.java +++ b/src/main/java/ortus/boxlang/runtime/config/segments/DatasourceConfig.java @@ -110,7 +110,7 @@ public class DatasourceConfig implements Comparable, IConfigSe /** * BoxLang Datasource Default configuration values * These are applied to ALL datasources - * Please note that most of them rely on Hikari defaults + * Please note that most of them rely on Hikari defaults but we use seconds in BoxLang to match CFConfig standards *

* References * https://github.com/brettwooldridge/HikariCP/wiki/MySQL-Configuration @@ -122,28 +122,32 @@ public class DatasourceConfig implements Comparable, IConfigSe // The minimum number of connections // Hikari: minimumIdle "minConnections", 10, - // The maximum number of idle time in milliseconds ( 1 Minute ) - "maxIdleTime", 60000, - // Maximum time to wait for a successful connection, in milliseconds ( 1 Second ) - "connectionTimeout", 1000, - // The maximum number of idle time in milliseconds ( 10 Minutes ) - "idleTimeout", 60000, + // Maximum time to wait for a successful connection, in seconds ( 1 Second ) + "connectionTimeout", 1, + // The maximum number of idle time in seconds ( 10 Minutes = 600 ) + // • Refers to the maximum amount of time a connection can remain idle in the pool before it is eligible for eviction. + // •If a connection is idle for longer than this time, it can be closed and removed from the pool, helping to free up resources. + // • This setting only affects connections that are not in use and have exceeded the idle duration specified. + // In Seconds + "idleTimeout", 600, // This property controls the maximum lifetime of a connection in the pool. // An in-use connection will never be retired, only when it is closed will it then be removed // We strongly recommend setting this value, and it should be several seconds shorter than any database // or infrastructure imposed connection time limit - // 30 minutes by default - "maxLifetime", 1800000, + // 30 minutes by default = 1800000ms = 1800 seconds + // In Seconds + "maxLifetime", 1800, // This property controls how frequently HikariCP will attempt to keep a connection alive, in order to prevent it from being timed out by the database // or network infrastructure // This value must be less than the maxLifetime value. A "keepalive" will only occur on an idle connectionThis value must be less than the maxLifetime - // value. A "keepalive" will only occur on an idle connection ( 10 Minutes ) - "keepaliveTime", 600000, + // value. A "keepalive" will only occur on an idle connection ( 10 Minutes = 600 seconds = 600,000 ms ) + // In Seconds + "keepaliveTime", 600, // The default auto-commit state of connections created by this pool "autoCommit", true, - // Register mbeans or not. By default, this is false + // Register mbeans or not. By default, this is true // However, if you are using JMX, you can set this to true to get some additional monitoring information - "registerMbeans", false, + "registerMbeans", true, // Prep the custom properties "custom", new Struct() ); @@ -509,8 +513,11 @@ public HikariConfig toHikariConfig() { if ( properties.containsKey( Key.password ) ) { result.setPassword( properties.getAsString( Key.password ) ); } + // Connection timeouts in seconds, but Hikari uses milliseconds if ( properties.containsKey( Key.connectionTimeout ) ) { - result.setConnectionTimeout( LongCaster.cast( properties.get( Key.connectionTimeout ), false ) ); + result.setConnectionTimeout( + LongCaster.cast( properties.get( Key.connectionTimeout ), false ) * 1000 + ); } if ( properties.containsKey( Key.minConnections ) ) { result.setMinimumIdle( IntegerCaster.cast( properties.get( Key.minConnections ), false ) ); @@ -530,13 +537,19 @@ public HikariConfig toHikariConfig() { result.setAutoCommit( properties.getAsBoolean( Key.autoCommit ) ); } if ( properties.containsKey( Key.idleTimeout ) ) { - result.setIdleTimeout( LongCaster.cast( properties.get( Key.idleTimeout ), false ) ); + result.setIdleTimeout( + LongCaster.cast( properties.get( Key.idleTimeout ), false ) * 1000 + ); } if ( properties.containsKey( Key.keepaliveTime ) ) { - result.setKeepaliveTime( LongCaster.cast( properties.get( Key.keepaliveTime ), false ) ); + result.setKeepaliveTime( + LongCaster.cast( properties.get( Key.keepaliveTime ), false ) * 1000 + ); } if ( properties.containsKey( Key.maxLifetime ) ) { - result.setMaxLifetime( LongCaster.cast( properties.get( Key.maxLifetime ), false ) ); + result.setMaxLifetime( + LongCaster.cast( properties.get( Key.maxLifetime ), false ) * 1000 + ); } if ( properties.containsKey( Key.connectionTestQuery ) ) { result.setConnectionTestQuery( properties.getAsString( Key.connectionTestQuery ) ); diff --git a/src/test/java/ortus/boxlang/runtime/config/segments/DatasourceConfigTest.java b/src/test/java/ortus/boxlang/runtime/config/segments/DatasourceConfigTest.java index 865118f1a..a86df5bfb 100644 --- a/src/test/java/ortus/boxlang/runtime/config/segments/DatasourceConfigTest.java +++ b/src/test/java/ortus/boxlang/runtime/config/segments/DatasourceConfigTest.java @@ -262,17 +262,17 @@ void testNumericCasting() { "database", "integerTest", "minConnections", "1", "maxConnections", "11", - "connectionTimeout", "30000", - "idleTimeout", "600000", - "maxLifetime", "180000" + "connectionTimeout", "3", + "idleTimeout", "600", + "maxLifetime", "1800" ) ).setOnTheFly(); HikariConfig hikariConfig = datasource.toHikariConfig(); - assertEquals( hikariConfig.getMinimumIdle(), 1 ); - assertEquals( hikariConfig.getMaximumPoolSize(), 11 ); - assertEquals( hikariConfig.getConnectionTimeout(), 30000 ); - assertEquals( hikariConfig.getIdleTimeout(), 600000 ); - assertEquals( hikariConfig.getMaxLifetime(), 180000 ); + assertEquals( 1, hikariConfig.getMinimumIdle() ); + assertEquals( 11, hikariConfig.getMaximumPoolSize() ); + assertEquals( 3000, hikariConfig.getConnectionTimeout() ); + assertEquals( 600000, hikariConfig.getIdleTimeout() ); + assertEquals( 1800000, hikariConfig.getMaxLifetime() ); } @DisplayName( "It can skip driver in place of jdbc url" ) From 274b4925919e03f3491a3dd6cc00093abea00894 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 15:16:29 +0000 Subject: [PATCH 04/19] Bump com.zaxxer:HikariCP from 5.1.0 to 6.0.0 Bumps [com.zaxxer:HikariCP](https://github.com/brettwooldridge/HikariCP) from 5.1.0 to 6.0.0. - [Changelog](https://github.com/brettwooldridge/HikariCP/blob/dev/CHANGES) - [Commits](https://github.com/brettwooldridge/HikariCP/compare/HikariCP-5.1.0...HikariCP-6.0.0) --- updated-dependencies: - dependency-name: com.zaxxer:HikariCP dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 47e04f391..121b2bd10 100644 --- a/build.gradle +++ b/build.gradle @@ -130,7 +130,7 @@ dependencies { // https://mvnrepository.com/artifact/ch.qos.logback/logback-classic implementation 'ch.qos.logback:logback-classic:1.5.8' // https://mvnrepository.com/artifact/com.zaxxer/HikariCP - implementation 'com.zaxxer:HikariCP:5.1.0' + implementation 'com.zaxxer:HikariCP:6.0.0' // https://mvnrepository.com/artifact/org.ow2.asm/asm-tree implementation 'org.ow2.asm:asm-tree:9.7' // https://mvnrepository.com/artifact/org.ow2.asm/asm-util From 62de953ae598cb112bca6b11f12665acbd2d6712 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 15:16:31 +0000 Subject: [PATCH 05/19] Bump com.fasterxml.jackson.jr:jackson-jr-objects from 2.17.2 to 2.18.0 Bumps [com.fasterxml.jackson.jr:jackson-jr-objects](https://github.com/FasterXML/jackson-jr) from 2.17.2 to 2.18.0. - [Commits](https://github.com/FasterXML/jackson-jr/compare/jackson-jr-parent-2.17.2...jackson-jr-parent-2.18.0) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.jr:jackson-jr-objects 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 47e04f391..80bbf4b61 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.17.2' + implementation 'com.fasterxml.jackson.jr:jackson-jr-objects:2.18.0' // https://mvnrepository.com/artifact/com.fasterxml.jackson.jr/jackson-jr-extension-javatime implementation 'com.fasterxml.jackson.jr:jackson-jr-extension-javatime:2.17.2' // https://mvnrepository.com/artifact/com.fasterxml.jackson.jr/jackson-jr-stree From 40b4964761afa1fcefd0bfd62a258c49b726c523 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 15:16:35 +0000 Subject: [PATCH 06/19] 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.17.2 to 2.18.0. - [Commits](https://github.com/FasterXML/jackson-jr/compare/jackson-jr-parent-2.17.2...jackson-jr-parent-2.18.0) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.jr:jackson-jr-extension-javatime 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 47e04f391..76704ac95 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.17.2' // https://mvnrepository.com/artifact/com.fasterxml.jackson.jr/jackson-jr-extension-javatime - implementation 'com.fasterxml.jackson.jr:jackson-jr-extension-javatime:2.17.2' + implementation 'com.fasterxml.jackson.jr:jackson-jr-extension-javatime:2.18.0' // https://mvnrepository.com/artifact/com.fasterxml.jackson.jr/jackson-jr-stree implementation 'com.fasterxml.jackson.jr:jackson-jr-stree:2.17.2' // https://mvnrepository.com/artifact/com.fasterxml.jackson.jr/jackson-jr-annotation-support From 9e21c703f1c2b813d0a94e1d1fb9868bf3a7f7d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 30 Sep 2024 15:16:37 +0000 Subject: [PATCH 07/19] 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.17.2 to 2.18.0. - [Commits](https://github.com/FasterXML/jackson-jr/compare/jackson-jr-parent-2.17.2...jackson-jr-parent-2.18.0) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.jr:jackson-jr-annotation-support 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 47e04f391..6f62a523e 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.17.2' // https://mvnrepository.com/artifact/com.fasterxml.jackson.jr/jackson-jr-annotation-support - implementation 'com.fasterxml.jackson.jr:jackson-jr-annotation-support:2.17.2' + implementation 'com.fasterxml.jackson.jr:jackson-jr-annotation-support:2.18.0' // 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 e12e1e45203838878a17e66f4ddbd788d4393157 Mon Sep 17 00:00:00 2001 From: Michael Born Date: Mon, 30 Sep 2024 14:05:52 -0400 Subject: [PATCH 08/19] JDBC - Drop custom class support until we can resolve the class loader issues Due to class loading issues, setting a custom 'class' on a datasource will just not work. We can take another pass at this, but for now, "removing" support fixes existing datasource configs with a 'class' property already specified. We already use the driver and JDBC url to connect, and setting a custom class will only lock you to a specific DB/vendor version. --- .../boxlang/runtime/config/segments/DatasourceConfig.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/config/segments/DatasourceConfig.java b/src/main/java/ortus/boxlang/runtime/config/segments/DatasourceConfig.java index 18179f2bf..2675d8b9b 100644 --- a/src/main/java/ortus/boxlang/runtime/config/segments/DatasourceConfig.java +++ b/src/main/java/ortus/boxlang/runtime/config/segments/DatasourceConfig.java @@ -526,12 +526,6 @@ public HikariConfig toHikariConfig() { result.setMaximumPoolSize( IntegerCaster.cast( properties.get( Key.maxConnections ), false ) ); } - // Hikari doesn't use a driver, but if present use it - // This is mostly for legacy support - if ( properties.containsKey( Key._CLASS ) ) { - result.setDriverClassName( properties.getAsString( Key._CLASS ) ); - } - // We also support these HikariConfig-specific properties if ( properties.containsKey( Key.autoCommit ) ) { result.setAutoCommit( properties.getAsBoolean( Key.autoCommit ) ); From 35041d3aee743fe5298c665a700c16a94af88b93 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 30 Sep 2024 22:52:58 -0500 Subject: [PATCH 09/19] BL-610 --- .../application/BaseApplicationListener.java | 6 ++++- .../boxlang/runtime/application/Session.java | 11 ++++++++- .../context/ApplicationBoxContext.java | 11 ++++++++- .../context/ScriptingRequestBoxContext.java | 14 ++++++++++- .../global/system/SessionInvalidateTest.java | 21 +++++++++++++++++ .../bifs/global/system/testApp/Application.bx | 23 +++++++++++++++++++ .../bifs/global/system/testApp/MyClass.bx | 3 +++ 7 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 src/test/java/ortus/boxlang/runtime/bifs/global/system/testApp/Application.bx create mode 100644 src/test/java/ortus/boxlang/runtime/bifs/global/system/testApp/MyClass.bx diff --git a/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java b/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java index 194478459..b4ba3c10b 100644 --- a/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java +++ b/src/main/java/ortus/boxlang/runtime/application/BaseApplicationListener.java @@ -404,7 +404,11 @@ public void rotateSession() { * @param newID The new session identifier */ public void invalidateSession( Key newID ) { - Session terminalSession = this.context.getParentOfType( SessionBoxContext.class ).getSession(); + SessionBoxContext sessionContext = context.getParentOfType( SessionBoxContext.class ); + if ( sessionContext == null ) { + throw new BoxRuntimeException( "No session to invalidate. Is session management enabled?" ); + } + Session terminalSession = sessionContext.getSession(); context.getParentOfType( ApplicationBoxContext.class ).getApplication().getSessionsCache().clear( terminalSession.getID().getName() ); terminalSession.shutdown( this ); initializeSession( newID ); diff --git a/src/main/java/ortus/boxlang/runtime/application/Session.java b/src/main/java/ortus/boxlang/runtime/application/Session.java index 704122777..572ba5930 100644 --- a/src/main/java/ortus/boxlang/runtime/application/Session.java +++ b/src/main/java/ortus/boxlang/runtime/application/Session.java @@ -21,6 +21,7 @@ import java.util.concurrent.atomic.AtomicBoolean; 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; @@ -214,8 +215,16 @@ public void shutdown( BaseApplicationListener listener ) { ) ); // Any buffer output in this context will be discarded + // Create a temp request context with an application context with our application listener. + // This will allow the application scope to be available as well as all settings from the original Application.bx listener.onSessionEnd( - new ScriptingRequestBoxContext( BoxRuntime.getInstance().getRuntimeContext() ), + new ApplicationBoxContext( + new ScriptingRequestBoxContext( + BoxRuntime.getInstance().getRuntimeContext(), + listener + ), + listener.getApplication() + ), new Object[] { sessionScope != null ? sessionScope : Struct.of(), listener.getApplication().getApplicationScope() } ); diff --git a/src/main/java/ortus/boxlang/runtime/context/ApplicationBoxContext.java b/src/main/java/ortus/boxlang/runtime/context/ApplicationBoxContext.java index 16036cdaa..4f7fe84ee 100644 --- a/src/main/java/ortus/boxlang/runtime/context/ApplicationBoxContext.java +++ b/src/main/java/ortus/boxlang/runtime/context/ApplicationBoxContext.java @@ -57,7 +57,16 @@ public class ApplicationBoxContext extends BaseBoxContext { * @param application The application to bind to this context */ public ApplicationBoxContext( Application application ) { - super( null ); + this( null, application ); + } + + /** + * Creates a new execution context with a bounded execution template and parent context + * + * @param application The application to bind to this context + */ + public ApplicationBoxContext( IBoxContext parent, Application application ) { + super( parent ); updateApplication( application ); } diff --git a/src/main/java/ortus/boxlang/runtime/context/ScriptingRequestBoxContext.java b/src/main/java/ortus/boxlang/runtime/context/ScriptingRequestBoxContext.java index 0612910c1..66e2ebb6b 100644 --- a/src/main/java/ortus/boxlang/runtime/context/ScriptingRequestBoxContext.java +++ b/src/main/java/ortus/boxlang/runtime/context/ScriptingRequestBoxContext.java @@ -21,6 +21,7 @@ import java.util.UUID; import ortus.boxlang.runtime.BoxRuntime; +import ortus.boxlang.runtime.application.BaseApplicationListener; import ortus.boxlang.runtime.events.BoxEvent; import ortus.boxlang.runtime.scopes.IScope; import ortus.boxlang.runtime.scopes.Key; @@ -83,6 +84,17 @@ public ScriptingRequestBoxContext( IBoxContext parent ) { loadApplicationDescriptor( null ); } + /** + * Creates a new execution context with a parent context and a specific application listener. + * + * @param parent The parent context + * @param listener The application listener + */ + public ScriptingRequestBoxContext( IBoxContext parent, BaseApplicationListener listener ) { + super( parent ); + setApplicationListener( listener ); + } + /** * Creates a new execution context with a parent context, and template * @@ -107,7 +119,7 @@ public ScriptingRequestBoxContext( URI template ) { * Creates a new execution context */ public ScriptingRequestBoxContext() { - this( runtime.getRuntimeContext(), null ); + this( runtime.getRuntimeContext(), ( URI ) null ); } /** diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/system/SessionInvalidateTest.java b/src/test/java/ortus/boxlang/runtime/bifs/global/system/SessionInvalidateTest.java index d5f8f9362..126dc0ff1 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/system/SessionInvalidateTest.java +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/system/SessionInvalidateTest.java @@ -22,6 +22,9 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import java.net.URI; +import java.net.URISyntaxException; + import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; @@ -77,4 +80,22 @@ public void testBif() { assertNotEquals( initialSession.getAsString( Key.of( "sessionid" ) ), variables.getAsStruct( result ).getAsString( Key.of( "sessionid" ) ) ); } + @DisplayName( "It tests onSessionEnd" ) + @Test + public void testOnSessionEnd() { + try { + context = new ScriptingRequestBoxContext( instance.getRuntimeContext(), + new URI( "src/test/java/ortus/boxlang/runtime/bifs/global/system/testApp/index.bxm" ) ); + } catch ( URISyntaxException e ) { + throw new RuntimeException( e ); + } + instance.executeSource( + """ + application.brad = "wood"; + println( "in test: " & expandPath( "/foobar" ) ) + sessionInvalidate(); + """, + context ); + } + } diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/system/testApp/Application.bx b/src/test/java/ortus/boxlang/runtime/bifs/global/system/testApp/Application.bx new file mode 100644 index 000000000..08a760f51 --- /dev/null +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/system/testApp/Application.bx @@ -0,0 +1,23 @@ +class { + this.sessionManagement = true; + this.mappings["/foobar"] = getDirectoryFromPath( getCurrentTemplatePath() ); + + function onApplicationStart() { + println( "onApplicationStart fired!" ) + } + + function onSessionStart() { + session.luis = "majano"; + } + + function onSessionEnd( sessionScope, appScope ) { + println( "onSessionEnd fired!" ) + println( "in onSessionEnd: " & expandPath( "/foobar" ) ) + // This will only be found if our mapping above is present + new foobar.myClass(); + var tmp = application.brad + tmp = appScope.brad + tmp = sessionScope.luis + } + +} \ No newline at end of file diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/system/testApp/MyClass.bx b/src/test/java/ortus/boxlang/runtime/bifs/global/system/testApp/MyClass.bx new file mode 100644 index 000000000..4a20930ab --- /dev/null +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/system/testApp/MyClass.bx @@ -0,0 +1,3 @@ +class { + +} \ No newline at end of file From c59ccb6fc2883ef31ddd163da2ab67738a3d79ed Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Mon, 30 Sep 2024 23:51:36 -0500 Subject: [PATCH 10/19] BL-610 --- .../bifs/global/struct/StructDelete.java | 4 +- .../ortus/boxlang/runtime/scopes/Key.java | 2 + .../bifs/global/system/testApp/Application.bx | 1 + .../boxlang/runtime/types/StructTest.java | 65 +++++++++++++++++++ 4 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/bifs/global/struct/StructDelete.java b/src/main/java/ortus/boxlang/runtime/bifs/global/struct/StructDelete.java index 5a1767a22..2cde08350 100644 --- a/src/main/java/ortus/boxlang/runtime/bifs/global/struct/StructDelete.java +++ b/src/main/java/ortus/boxlang/runtime/bifs/global/struct/StructDelete.java @@ -39,7 +39,7 @@ public StructDelete() { super(); declaredArguments = new Argument[] { new Argument( true, "modifiableStruct", Key.struct ), - new Argument( true, "string", Key.key ) + new Argument( true, "any", Key.key ) }; } @@ -57,7 +57,7 @@ public StructDelete() { */ public Object _invoke( IBoxContext context, ArgumentsScope arguments ) { IStruct target = arguments.getAsStruct( Key.struct ); - Key key = Key.of( arguments.getAsString( Key.key ) ); + Key key = Key.of( arguments.get( Key.key ) ); target.remove( key ); return target; } diff --git a/src/main/java/ortus/boxlang/runtime/scopes/Key.java b/src/main/java/ortus/boxlang/runtime/scopes/Key.java index e231115c7..cca2fdb55 100644 --- a/src/main/java/ortus/boxlang/runtime/scopes/Key.java +++ b/src/main/java/ortus/boxlang/runtime/scopes/Key.java @@ -20,6 +20,7 @@ import java.io.Serializable; import java.util.Arrays; +import ortus.boxlang.runtime.interop.DynamicObject; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; /** @@ -1006,6 +1007,7 @@ public static Key of( Object obj ) { if ( obj == null ) { throw new BoxRuntimeException( "Cannot create a key from a null object" ); } + obj = DynamicObject.unWrap( obj ); return new Key( obj.toString(), obj ); } diff --git a/src/test/java/ortus/boxlang/runtime/bifs/global/system/testApp/Application.bx b/src/test/java/ortus/boxlang/runtime/bifs/global/system/testApp/Application.bx index 08a760f51..e1ff12167 100644 --- a/src/test/java/ortus/boxlang/runtime/bifs/global/system/testApp/Application.bx +++ b/src/test/java/ortus/boxlang/runtime/bifs/global/system/testApp/Application.bx @@ -1,4 +1,5 @@ class { + this.name = "testApp"; this.sessionManagement = true; this.mappings["/foobar"] = getDirectoryFromPath( getCurrentTemplatePath() ); diff --git a/src/test/java/ortus/boxlang/runtime/types/StructTest.java b/src/test/java/ortus/boxlang/runtime/types/StructTest.java index 5a8420b9a..d8d89f896 100644 --- a/src/test/java/ortus/boxlang/runtime/types/StructTest.java +++ b/src/test/java/ortus/boxlang/runtime/types/StructTest.java @@ -24,15 +24,45 @@ import java.util.Comparator; import java.util.HashMap; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +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.interop.DynamicObject; +import ortus.boxlang.runtime.scopes.IScope; import ortus.boxlang.runtime.scopes.Key; +import ortus.boxlang.runtime.scopes.VariablesScope; class StructTest { + 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 equals and hash code with no data" ) @Test void testEqualsAndHashCode() { @@ -232,4 +262,39 @@ void testCanCreateCaseSensitiveStruct() { assertThat( struct.getWrapped() ).isInstanceOf( ConcurrentHashMap.class ); } + @DisplayName( "Can use Java objects as struct keys" ) + @Test + void testCanUseJavaObjectsAsKeys() { + instance.executeSource( + """ + import java.lang.Object; + import java.util.HashMap; + + key = new Object(); + key2 = new Object(); + myStr = new HashMap(); + myStr.put(key, "test"); + myStr[key2] = "test2"; + value = myStr[key]; + value2 = myStr[key2]; + // myStr.delete(key); + values = []; + for( theKey in myStr ) { + assert theKey.getClass().getName() == 'java.lang.Object' + values.append( myStr[ theKey ] ); + } + """, + context ); + Object key = DynamicObject.unWrap( variables.get( "key" ) ); + Object key2 = DynamicObject.unWrap( variables.get( "key2" ) ); + Map myStr = ( Map ) DynamicObject.unWrap( variables.get( Key.of( "myStr" ) ) ); + assertThat( myStr ).containsKey( key ); + assertThat( myStr ).containsKey( key2 ); + assertThat( variables.get( "value" ) ).isEqualTo( "test" ); + assertThat( variables.get( "value2" ) ).isEqualTo( "test2" ); + // assertThat( myStr ).doesNotContainKey( key ); + // assertThat( myStr ).hasSize( 1 ); + assertThat( variables.getAsArray( Key.of( "values" ) ) ).containsExactly( "test", "test2" ); + } + } From e82b21f3624c465d7e3893076def1acc625eb1bb Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 1 Oct 2024 00:08:40 -0500 Subject: [PATCH 11/19] BL-610 Try to fix CI race condition --- .../java/ortus/boxlang/runtime/application/Session.java | 8 ++++---- .../boxlang/runtime/context/ApplicationBoxContext.java | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/application/Session.java b/src/main/java/ortus/boxlang/runtime/application/Session.java index 572ba5930..880346835 100644 --- a/src/main/java/ortus/boxlang/runtime/application/Session.java +++ b/src/main/java/ortus/boxlang/runtime/application/Session.java @@ -218,12 +218,12 @@ public void shutdown( BaseApplicationListener listener ) { // Create a temp request context with an application context with our application listener. // This will allow the application scope to be available as well as all settings from the original Application.bx listener.onSessionEnd( - new ApplicationBoxContext( - new ScriptingRequestBoxContext( + new ScriptingRequestBoxContext( + new ApplicationBoxContext( BoxRuntime.getInstance().getRuntimeContext(), - listener + listener.getApplication() ), - listener.getApplication() + listener ), new Object[] { sessionScope != null ? sessionScope : Struct.of(), listener.getApplication().getApplicationScope() } ); diff --git a/src/main/java/ortus/boxlang/runtime/context/ApplicationBoxContext.java b/src/main/java/ortus/boxlang/runtime/context/ApplicationBoxContext.java index 4f7fe84ee..bc91e7297 100644 --- a/src/main/java/ortus/boxlang/runtime/context/ApplicationBoxContext.java +++ b/src/main/java/ortus/boxlang/runtime/context/ApplicationBoxContext.java @@ -63,6 +63,7 @@ public ApplicationBoxContext( Application application ) { /** * Creates a new execution context with a bounded execution template and parent context * + * @param parent The parent context * @param application The application to bind to this context */ public ApplicationBoxContext( IBoxContext parent, Application application ) { From 65de0f8b5701b7e6dc72be9c998b121b0b2ff812 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Tue, 1 Oct 2024 00:29:23 -0500 Subject: [PATCH 12/19] BL-610 Try again --- src/main/java/ortus/boxlang/runtime/testing/Phase1.java | 2 +- src/main/java/ortus/boxlang/runtime/testing/Phase1Switch.java | 2 +- src/main/java/ortus/boxlang/runtime/testing/Phase1TryCatch.java | 2 +- src/main/java/ortus/boxlang/runtime/testing/Phase2Closure.java | 2 +- src/main/java/ortus/boxlang/runtime/testing/Phase2Lambda.java | 2 +- src/main/java/ortus/boxlang/runtime/testing/Phase2UDF.java | 2 +- .../ortus/boxlang/runtime/services/SchedulerServiceTest.java | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/ortus/boxlang/runtime/testing/Phase1.java b/src/main/java/ortus/boxlang/runtime/testing/Phase1.java index 18dd2881a..9a6e70f3b 100644 --- a/src/main/java/ortus/boxlang/runtime/testing/Phase1.java +++ b/src/main/java/ortus/boxlang/runtime/testing/Phase1.java @@ -209,6 +209,6 @@ public static void main( String[] args ) { } // Bye bye! Ciao Bella! - boxRuntime.shutdown(); + // boxRuntime.shutdown(); } } diff --git a/src/main/java/ortus/boxlang/runtime/testing/Phase1Switch.java b/src/main/java/ortus/boxlang/runtime/testing/Phase1Switch.java index 731aa1805..e70d901f7 100644 --- a/src/main/java/ortus/boxlang/runtime/testing/Phase1Switch.java +++ b/src/main/java/ortus/boxlang/runtime/testing/Phase1Switch.java @@ -227,6 +227,6 @@ public static void main( String[] args ) { } // Bye bye! Ciao Bella! - boxRuntime.shutdown(); + // boxRuntime.shutdown(); } } diff --git a/src/main/java/ortus/boxlang/runtime/testing/Phase1TryCatch.java b/src/main/java/ortus/boxlang/runtime/testing/Phase1TryCatch.java index 216580f2f..3e94c5f73 100644 --- a/src/main/java/ortus/boxlang/runtime/testing/Phase1TryCatch.java +++ b/src/main/java/ortus/boxlang/runtime/testing/Phase1TryCatch.java @@ -281,6 +281,6 @@ public static void main( String[] args ) { } // Bye bye! Ciao Bella! - boxRuntime.shutdown(); + // boxRuntime.shutdown(); } } diff --git a/src/main/java/ortus/boxlang/runtime/testing/Phase2Closure.java b/src/main/java/ortus/boxlang/runtime/testing/Phase2Closure.java index c86c91f61..1a0b0af0c 100644 --- a/src/main/java/ortus/boxlang/runtime/testing/Phase2Closure.java +++ b/src/main/java/ortus/boxlang/runtime/testing/Phase2Closure.java @@ -203,6 +203,6 @@ public static void main( String[] args ) { } // Bye bye! Ciao Bella! - boxRuntime.shutdown(); + // boxRuntime.shutdown(); } } diff --git a/src/main/java/ortus/boxlang/runtime/testing/Phase2Lambda.java b/src/main/java/ortus/boxlang/runtime/testing/Phase2Lambda.java index 70e4f2c36..ce0fab319 100644 --- a/src/main/java/ortus/boxlang/runtime/testing/Phase2Lambda.java +++ b/src/main/java/ortus/boxlang/runtime/testing/Phase2Lambda.java @@ -193,6 +193,6 @@ public static void main( String[] args ) { } // Bye bye! Ciao Bella! - boxRuntime.shutdown(); + // boxRuntime.shutdown(); } } diff --git a/src/main/java/ortus/boxlang/runtime/testing/Phase2UDF.java b/src/main/java/ortus/boxlang/runtime/testing/Phase2UDF.java index ae699cb8c..279dca6c8 100644 --- a/src/main/java/ortus/boxlang/runtime/testing/Phase2UDF.java +++ b/src/main/java/ortus/boxlang/runtime/testing/Phase2UDF.java @@ -196,6 +196,6 @@ public static void main( String[] args ) { } // Bye bye! Ciao Bella! - boxRuntime.shutdown(); + // boxRuntime.shutdown(); } } diff --git a/src/test/java/ortus/boxlang/runtime/services/SchedulerServiceTest.java b/src/test/java/ortus/boxlang/runtime/services/SchedulerServiceTest.java index 0ef875094..23eca8d45 100644 --- a/src/test/java/ortus/boxlang/runtime/services/SchedulerServiceTest.java +++ b/src/test/java/ortus/boxlang/runtime/services/SchedulerServiceTest.java @@ -43,7 +43,7 @@ public static void setUp() { @AfterAll public static void tearDownAfterAll() { - runtime.shutdown( true ); + // runtime.shutdown( true ); } @DisplayName( "Test it can get an instance of the service" ) From a831b718bae7f95b41e6cd60f2d796919a0d0640 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Wed, 2 Oct 2024 01:49:46 -0500 Subject: [PATCH 13/19] BL-609 --- .../java/ortus/boxlang/compiler/Boxpiler.java | 40 ++-- .../ortus/boxlang/compiler/ClassInfo.java | 77 ++++---- .../ortus/boxlang/compiler/IBoxpiler.java | 92 --------- .../compiler/asmboxpiler/ASMBoxpiler.java | 4 +- .../compiler/asmboxpiler/AsmTranspiler.java | 16 +- .../compiler/javaboxpiler/JavaBoxpiler.java | 16 +- .../transformer/BoxClassTransformer.java | 24 +-- .../transformer/BoxInterfaceTransformer.java | 40 ++-- .../runtime/loader/resolvers/BoxResolver.java | 4 +- .../ortus/boxlang/runtime/util/BoxFQN.java | 186 ++++++++++++++++++ .../java/ortus/boxlang/runtime/util/FQN.java | 155 ++++++++++----- .../runtime/util/ResolvedFilePath.java | 30 ++- src/test/java/TestCases/phase3/ClassTest.java | 29 ++- .../phase3/sub-folder/Funky-Class.bx | 3 + .../boxlang/compiler/CFTranspilerTest.java | 2 +- .../boxlang/runtime/util/BoxFQNTest.java | 56 ++++++ .../ortus/boxlang/runtime/util/FQNTest.java | 34 +++- 17 files changed, 525 insertions(+), 283 deletions(-) create mode 100644 src/main/java/ortus/boxlang/runtime/util/BoxFQN.java create mode 100644 src/test/java/TestCases/phase3/sub-folder/Funky-Class.bx create mode 100644 src/test/java/ortus/boxlang/runtime/util/BoxFQNTest.java diff --git a/src/main/java/ortus/boxlang/compiler/Boxpiler.java b/src/main/java/ortus/boxlang/compiler/Boxpiler.java index 0413993c1..75d67de9e 100644 --- a/src/main/java/ortus/boxlang/compiler/Boxpiler.java +++ b/src/main/java/ortus/boxlang/compiler/Boxpiler.java @@ -193,8 +193,8 @@ public ParsingResult validateParse( ParsingResult result, String source ) { public Class compileStatement( String source, BoxSourceType type ) { ClassInfo classInfo = ClassInfo.forStatement( source, type, this ); var classPool = getClassPool( classInfo.classPoolName() ); - classPool.putIfAbsent( classInfo.FQN(), classInfo ); - classInfo = classPool.get( classInfo.FQN() ); + classPool.putIfAbsent( classInfo.fqn().toString(), classInfo ); + classInfo = classPool.get( classInfo.fqn().toString() ); return classInfo.getDiskClass(); @@ -212,8 +212,8 @@ public Class compileStatement( String source, BoxSourceType type ) public Class compileScript( String source, BoxSourceType type ) { ClassInfo classInfo = ClassInfo.forScript( source, type, this ); var classPool = getClassPool( classInfo.classPoolName() ); - classPool.putIfAbsent( classInfo.FQN(), classInfo ); - classInfo = classPool.get( classInfo.FQN() ); + classPool.putIfAbsent( classInfo.fqn().toString(), classInfo ); + classInfo = classPool.get( classInfo.fqn().toString() ); return classInfo.getDiskClass(); } @@ -229,19 +229,19 @@ public Class compileScript( String source, BoxSourceType type ) { public Class compileTemplate( ResolvedFilePath resolvedFilePath ) { ClassInfo classInfo = ClassInfo.forTemplate( resolvedFilePath, Parser.detectFile( resolvedFilePath.absolutePath().toFile() ), this ); var classPool = getClassPool( classInfo.classPoolName() ); - classPool.putIfAbsent( classInfo.FQN(), classInfo ); + classPool.putIfAbsent( classInfo.fqn().toString(), classInfo ); // If the new class is newer than the one on disk, recompile it - if ( classPool.get( classInfo.FQN() ).lastModified() < classInfo.lastModified() ) { + if ( classPool.get( classInfo.fqn().toString() ).lastModified() < classInfo.lastModified() ) { try { // Don't know if this does anything, but calling it for good measure - classPool.get( classInfo.FQN() ).getClassLoader().close(); + classPool.get( classInfo.fqn().toString() ).getClassLoader().close(); } catch ( IOException e ) { e.printStackTrace(); } - classPool.put( classInfo.FQN(), classInfo ); - compileClassInfo( classInfo.classPoolName(), classInfo.FQN() ); + classPool.put( classInfo.fqn().toString(), classInfo ); + compileClassInfo( classInfo.classPoolName(), classInfo.fqn().toString() ); } else { - classInfo = classPool.get( classInfo.FQN() ); + classInfo = classPool.get( classInfo.fqn().toString() ); } return classInfo.getDiskClass(); } @@ -257,8 +257,8 @@ public Class compileTemplate( ResolvedFilePath resolvedFilePath ) public Class compileClass( String source, BoxSourceType type ) { ClassInfo classInfo = ClassInfo.forClass( source, type, this ); var classPool = getClassPool( classInfo.classPoolName() ); - classPool.putIfAbsent( classInfo.FQN(), classInfo ); - classInfo = classPool.get( classInfo.FQN() ); + classPool.putIfAbsent( classInfo.fqn().toString(), classInfo ); + classInfo = classPool.get( classInfo.fqn().toString() ); return classInfo.getDiskClass(); } @@ -274,19 +274,19 @@ public Class compileClass( String source, BoxSourceType type ) { public Class compileClass( ResolvedFilePath resolvedFilePath ) { ClassInfo classInfo = ClassInfo.forClass( resolvedFilePath, Parser.detectFile( resolvedFilePath.absolutePath().toFile() ), this ); var classPool = getClassPool( classInfo.classPoolName() ); - classPool.putIfAbsent( classInfo.FQN(), classInfo ); + classPool.putIfAbsent( classInfo.fqn().toString(), classInfo ); // If the new class is newer than the one on disk, recompile it - if ( classPool.get( classInfo.FQN() ).lastModified() < classInfo.lastModified() ) { + if ( classPool.get( classInfo.fqn().toString() ).lastModified() < classInfo.lastModified() ) { try { // Don't know if this does anything, but calling it for good measure - classPool.get( classInfo.FQN() ).getClassLoader().close(); + classPool.get( classInfo.fqn().toString() ).getClassLoader().close(); } catch ( IOException e ) { e.printStackTrace(); } - classPool.put( classInfo.FQN(), classInfo ); - compileClassInfo( classInfo.classPoolName(), classInfo.FQN() ); + classPool.put( classInfo.fqn().toString(), classInfo ); + compileClassInfo( classInfo.classPoolName(), classInfo.fqn().toString() ); } else { - classInfo = classPool.get( classInfo.FQN() ); + classInfo = classPool.get( classInfo.fqn().toString() ); } return classInfo.getDiskClass(); } @@ -295,8 +295,8 @@ public Class compileClass( ResolvedFilePath resolvedFilePath ) { public Class compileInterfaceProxy( IBoxContext context, InterfaceProxyDefinition definition ) { ClassInfo classInfo = ClassInfo.forInterfaceProxy( definition.name(), definition, this ); var classPool = getClassPool( classInfo.classPoolName() ); - classPool.putIfAbsent( classInfo.FQN(), classInfo ); - classInfo = classPool.get( classInfo.FQN() ); + classPool.putIfAbsent( classInfo.fqn().toString(), classInfo ); + classInfo = classPool.get( classInfo.fqn().toString() ); return classInfo.getDiskClassProxy(); diff --git a/src/main/java/ortus/boxlang/compiler/ClassInfo.java b/src/main/java/ortus/boxlang/compiler/ClassInfo.java index a12031a26..9a4bc6294 100644 --- a/src/main/java/ortus/boxlang/compiler/ClassInfo.java +++ b/src/main/java/ortus/boxlang/compiler/ClassInfo.java @@ -1,6 +1,5 @@ package ortus.boxlang.compiler; -import java.io.File; import java.net.URL; import java.nio.file.Paths; @@ -11,6 +10,7 @@ import ortus.boxlang.runtime.runnables.IBoxRunnable; import ortus.boxlang.runtime.runnables.IProxyRunnable; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; +import ortus.boxlang.runtime.util.BoxFQN; import ortus.boxlang.runtime.util.FQN; import ortus.boxlang.runtime.util.ResolvedFilePath; @@ -18,9 +18,8 @@ * A Record that represents the information about a class to be compiled */ public record ClassInfo( - FQN packageName, - String className, - FQN boxPackageName, + FQN fqn, + BoxFQN boxFqn, String baseclass, String returnType, BoxSourceType sourceType, @@ -35,7 +34,7 @@ public record ClassInfo( * Hash Code */ public int hashCode() { - return FQN().hashCode(); + return fqn().toString().hashCode(); } /** @@ -46,16 +45,15 @@ public boolean equals( Object obj ) { return false; } if ( obj instanceof ClassInfo ) { - return FQN().equals( ( ( ClassInfo ) obj ).FQN() ); + return fqn().toString().equals( ( ( ClassInfo ) obj ).fqn().toString() ); } return false; } public static ClassInfo forScript( String source, BoxSourceType sourceType, IBoxpiler boxpiler ) { return new ClassInfo( - FQN.of( "boxgenerated.scripts" ), - "Script_" + IBoxpiler.MD5( sourceType.toString() + source ), - FQN.of( "" ), + FQN.of( "boxgenerated.scripts", "Script_" + IBoxpiler.MD5( sourceType.toString() + source ) ), + BoxFQN.of( "" ), "BoxScript", "Object", sourceType, @@ -70,9 +68,8 @@ public static ClassInfo forScript( String source, BoxSourceType sourceType, IBox public static ClassInfo forStatement( String source, BoxSourceType sourceType, IBoxpiler boxpiler ) { return new ClassInfo( - FQN.of( "boxgenerated.scripts" ), - "Statement_" + IBoxpiler.MD5( sourceType.toString() + source ), - FQN.of( "" ), + FQN.of( "boxgenerated.scripts", "Statement_" + IBoxpiler.MD5( sourceType.toString() + source ) ), + BoxFQN.of( "" ), "BoxScript", "Object", sourceType, @@ -86,13 +83,9 @@ public static ClassInfo forStatement( String source, BoxSourceType sourceType, I } public static ClassInfo forTemplate( ResolvedFilePath resolvedFilePath, BoxSourceType sourceType, IBoxpiler boxpiler ) { - File lcaseFile = new File( resolvedFilePath.absolutePath().toString().toLowerCase() ); - String className = IBoxpiler.getClassName( lcaseFile ); - FQN packageName = resolvedFilePath.getPackage( "boxgenerated.templates" ); return new ClassInfo( - packageName, - className, - packageName, + resolvedFilePath.getFQN( "boxgenerated.templates" ), + resolvedFilePath.getBoxFQN( "boxgenerated.templates" ), "BoxTemplate", "void", sourceType, @@ -106,11 +99,9 @@ public static ClassInfo forTemplate( ResolvedFilePath resolvedFilePath, BoxSourc } public static ClassInfo forClass( ResolvedFilePath resolvedFilePath, BoxSourceType sourceType, IBoxpiler boxpiler ) { - String className = IBoxpiler.getClassName( resolvedFilePath.absolutePath().toFile() ); return new ClassInfo( - resolvedFilePath.getPackage( "boxgenerated.boxclass" ), - className, - resolvedFilePath.getPackage(), + resolvedFilePath.getFQN( "boxgenerated.boxclass" ), + resolvedFilePath.getBoxFQN(), null, null, sourceType, @@ -125,9 +116,8 @@ public static ClassInfo forClass( ResolvedFilePath resolvedFilePath, BoxSourceTy public static ClassInfo forClass( String source, BoxSourceType sourceType, IBoxpiler boxpiler ) { return new ClassInfo( - FQN.of( "boxgenerated.boxclass" ), - "Class_" + IBoxpiler.MD5( source ), - FQN.of( "" ), + FQN.of( "boxgenerated.boxclass", "Class_" + IBoxpiler.MD5( source ) ), + BoxFQN.of( "" ), null, null, sourceType, @@ -142,9 +132,8 @@ public static ClassInfo forClass( String source, BoxSourceType sourceType, IBoxp public static ClassInfo forInterfaceProxy( String name, InterfaceProxyDefinition interfaceProxyDefinition, IBoxpiler boxpiler ) { return new ClassInfo( - FQN.of( "boxgenerated.dynamicProxy" ), - "InterfaceProxy_" + name, - FQN.of( "" ), + FQN.of( "boxgenerated.dynamicProxy", "InterfaceProxy_" + name ), + BoxFQN.of( "" ), null, null, null, @@ -157,21 +146,16 @@ public static ClassInfo forInterfaceProxy( String name, InterfaceProxyDefinition ); } - public String FQN() { - return packageName.toString() + "." + className(); - } - public String toString() { if ( resolvedFilePath != null ) - return "Class Info-- sourcePath: [" + resolvedFilePath.absolutePath().toString() + "], packageName: [" + packageName + "], className: [" + className - + "]"; + return "Class Info-- sourcePath: [" + resolvedFilePath.absolutePath().toString() + "], fqn: [" + fqn().toString() + "]"; else if ( sourceType != null ) - return "Class Info-- type: [" + sourceType + "], packageName: [" + packageName + "], className: [" + className + "]"; + return "Class Info-- type: [" + sourceType + "], fqn: [" + fqn().toString() + "]"; else if ( interfaceProxyDefinition != null ) - return "Class Info-- interface proxy: [" + interfaceProxyDefinition.interfaces().toString() + "], packageName: [" + packageName - + "], className: [" + className + "]"; + return "Class Info-- interface proxy: [" + interfaceProxyDefinition.interfaces().toString() + "], fqn: [" + fqn().toString() + + "]"; else - return "Class Info-- packageName: [" + packageName + "], className: [" + className + "]"; + return "Class Info-- fqn: [" + fqn().toString() + "]"; } public DiskClassLoader getClassLoader() { @@ -201,9 +185,9 @@ public DiskClassLoader getClassLoader() { @SuppressWarnings( "unchecked" ) public Class getDiskClass() { try { - return ( Class ) getClassLoader().loadClass( FQN() ); + return ( Class ) getClassLoader().loadClass( fqn().toString() ); } catch ( ClassNotFoundException e ) { - throw new BoxRuntimeException( "Error compiling source " + FQN(), e ); + throw new BoxRuntimeException( "Error compiling source " + fqn().toString(), e ); } } @@ -215,13 +199,22 @@ public Class getDiskClass() { @SuppressWarnings( "unchecked" ) public Class getDiskClassProxy() { try { - return ( Class ) getClassLoader().loadClass( FQN() ); + return ( Class ) getClassLoader().loadClass( fqn().toString() ); } catch ( ClassNotFoundException e ) { - throw new BoxRuntimeException( "Error compiling source " + FQN(), e ); + throw new BoxRuntimeException( "Error compiling source " + fqn().toString(), e ); } } + public String packageName() { + return fqn().getPackageString(); + } + + public String className() { + return fqn().getClassName(); + } + public Boolean isClass() { + System.out.println( "ClassInfo.isClass() " + packageName().toString() ); return packageName().toString().startsWith( "boxgenerated.boxclass" ); } diff --git a/src/main/java/ortus/boxlang/compiler/IBoxpiler.java b/src/main/java/ortus/boxlang/compiler/IBoxpiler.java index 0f1b14466..d5d3afde3 100644 --- a/src/main/java/ortus/boxlang/compiler/IBoxpiler.java +++ b/src/main/java/ortus/boxlang/compiler/IBoxpiler.java @@ -9,7 +9,6 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Collectors; import ortus.boxlang.compiler.parser.BoxSourceType; import ortus.boxlang.compiler.parser.ParsingResult; @@ -49,97 +48,6 @@ static String MD5( String md5 ) { } } - /** - * Transforms the path into the package name - * - * @param file File object to grab the package name for. - * - * @return returns the class name according the name conventions Test.ext - - * Test$ext - */ - static String getPackageName( File file ) { - String packg = file.toString().replace( File.separatorChar + file.getName(), "" ); - if ( packg.startsWith( "/" ) ) { - packg = packg.substring( 1 ); - } - // trim trailing \ or / - if ( packg.endsWith( "\\" ) || packg.endsWith( "/" ) ) { - packg = packg.substring( 0, packg.length() - 1 ); - } - - // Take out periods in folder names - packg = packg.replaceAll( "\\.", "" ); - // Replace / with . - packg = packg.replaceAll( "/", "." ); - // Remove any : from Windows drives - packg = packg.replaceAll( ":", "" ); - // Replace \ with . - packg = packg.replaceAll( "\\\\", "." ); - - return cleanPackageName( packg ); - - } - - /** - * Transforms the path into the package name - * - * @param packg String to grab the package name for. - * - * @return returns the class name according the name conventions Test.ext - - * Test$ext - */ - static String cleanPackageName( String packg ) { - // Replace .. with . - packg = packg.replaceAll( "\\.\\.", "." ); - // trim trailing period - if ( packg.endsWith( "." ) ) { - packg = packg.substring( 0, packg.length() - 1 ); - } - // trim leading period - if ( packg.startsWith( "." ) ) { - packg = packg.substring( 1 ); - } - // Remove any non alpha-numeric chars. - packg = packg.replaceAll( "[^a-zA-Z0-9\\.]", "" ); - - // parse fqn into list, loop over list and remove any empty strings and turn back into fqn - packg = Arrays.stream( packg.split( "\\." ) ) - .map( s -> s.toLowerCase() ) - // if starts with number, prefix with _ - .map( s -> s.matches( "^\\d.*" ) ? "_" + s : s ) - .map( s -> { - if ( RESERVED_WORDS.contains( s ) ) { - return "_" + s; - } - return s; - } ) - .collect( Collectors.joining( "." ) ); - - return packg; - - } - - /** - * Transforms the filename into the class name - * - * @param file File object to grab the class name for. - * - * @return returns the class name according the name conventions Test.ext - - * Test$ext - */ - static String getClassName( File file ) { - String name = file.getName().replace( ".", "$" ).replace( "-", "_" ); - // Can't start with a number - name = name.matches( "^\\d.*" ) ? "_" + name : name; - // handle reserved words - if ( RESERVED_WORDS.contains( name.toLowerCase() ) ) { - name = "_" + name; - } - // Title case the name - name = name.substring( 0, 1 ).toUpperCase() + name.substring( 1 ); - return name; - } - Map getClassPool( String classPoolName ); Class compileStatement( String source, BoxSourceType type ); diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java index a8c5fd6d6..20085a6e1 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/ASMBoxpiler.java @@ -114,7 +114,7 @@ private static Transpiler transpiler( ClassInfo classInfo ) { Transpiler transpiler = Transpiler.getTranspiler(); transpiler.setProperty( "classname", classInfo.className() ); transpiler.setProperty( "packageName", classInfo.packageName().toString() ); - transpiler.setProperty( "boxPackageName", classInfo.boxPackageName().toString() ); + transpiler.setProperty( "boxFQN", classInfo.boxFqn().toString() ); transpiler.setProperty( "baseclass", classInfo.baseclass() ); transpiler.setProperty( "returnType", classInfo.returnType() ); transpiler.setProperty( "sourceType", classInfo.sourceType().name() ); @@ -134,7 +134,7 @@ private void doCompileClassInfo( Transpiler transpiler, ClassInfo classInfo, Box throw new IllegalStateException( "Unexpected root type: " + node ); } transpiler.getAuxiliary().forEach( consumer ); - consumer.accept( classInfo.FQN(), classNode ); + consumer.accept( classInfo.fqn().toString(), classNode ); } private ParsingResult parseClassInfo( ClassInfo info ) { diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index ad8aa22b5..d7d548cda 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -328,20 +328,12 @@ public ClassNode transpile( BoxScript boxScript ) throws BoxRuntimeException { @Override public ClassNode transpile( BoxClass boxClass ) throws BoxRuntimeException { - Source source = boxClass.getPosition().getSource(); - String sourceType = getProperty( "sourceType" ); + Source source = boxClass.getPosition().getSource(); + String sourceType = getProperty( "sourceType" ); - String filePath = source instanceof SourceFile file && file.getFile() != null ? file.getFile().getAbsolutePath() + String filePath = source instanceof SourceFile file && file.getFile() != null ? file.getFile().getAbsolutePath() : "unknown"; - String fileName = source instanceof SourceFile file && file.getFile() != null ? file.getFile().getName() : "unknown"; - String boxPackageName = getProperty( "boxPackageName" ); - String rawBoxClassName = boxPackageName + "." + fileName.replace( ".bx", "" ).replace( ".cfc", "" ), boxClassName; - // trim leading . if exists - if ( rawBoxClassName.startsWith( "." ) ) { - boxClassName = rawBoxClassName.substring( 1 ); - } else { - boxClassName = rawBoxClassName; - } + String boxClassName = getProperty( "boxFQN" ); String mappingName = getProperty( "mappingName" ); String mappingPath = getProperty( "mappingPath" ); String relativePath = getProperty( "relativePath" ); diff --git a/src/main/java/ortus/boxlang/compiler/javaboxpiler/JavaBoxpiler.java b/src/main/java/ortus/boxlang/compiler/javaboxpiler/JavaBoxpiler.java index b2dfcfa63..11900b398 100644 --- a/src/main/java/ortus/boxlang/compiler/javaboxpiler/JavaBoxpiler.java +++ b/src/main/java/ortus/boxlang/compiler/javaboxpiler/JavaBoxpiler.java @@ -126,7 +126,7 @@ public String generateJavaSource( BoxNode node, ClassInfo classInfo ) { Transpiler transpiler = Transpiler.getTranspiler(); transpiler.setProperty( "classname", classInfo.className() ); transpiler.setProperty( "packageName", classInfo.packageName().toString() ); - transpiler.setProperty( "boxPackageName", classInfo.boxPackageName().toString() ); + transpiler.setProperty( "boxFQN", classInfo.boxFqn().toString() ); transpiler.setProperty( "baseclass", classInfo.baseclass() ); transpiler.setProperty( "returnType", classInfo.returnType() ); transpiler.setProperty( "sourceType", classInfo.sourceType().name() ); @@ -162,7 +162,7 @@ public String generateJavaSource( BoxNode node, ClassInfo classInfo ) { throw new BoxRuntimeException( javaSource ); // Capture the line numbers of each Java AST node from printing the Java source - diskClassUtil.writeLineNumbers( classInfo.classPoolName(), classInfo.FQN(), + diskClassUtil.writeLineNumbers( classInfo.classPoolName(), classInfo.fqn().toString(), generateLineNumberJSON( classInfo, prettyPrinter.getVisitor().getLineNumbers() ) ); return javaSource; } @@ -186,12 +186,12 @@ public void compileClassInfo( String classPoolName, String FQN ) { return; } ParsingResult result = parseOrFail( sourceFile ); - compileSource( generateJavaSource( result.getRoot(), classInfo ), classInfo.FQN(), classPoolName ); + compileSource( generateJavaSource( result.getRoot(), classInfo ), classInfo.fqn().toString(), classPoolName ); } else if ( classInfo.source() != null ) { ParsingResult result = parseOrFail( classInfo.source(), classInfo.sourceType(), classInfo.isClass() ); - compileSource( generateJavaSource( result.getRoot(), classInfo ), classInfo.FQN(), classPoolName ); + compileSource( generateJavaSource( result.getRoot(), classInfo ), classInfo.fqn().toString(), classPoolName ); } else if ( classInfo.interfaceProxyDefinition() != null ) { - compileSource( generateProxyJavaSource( classInfo ), classInfo.FQN(), classPoolName ); + compileSource( generateProxyJavaSource( classInfo ), classInfo.fqn().toString(), classPoolName ); } else { throw new BoxRuntimeException( "Unknown class info type: " + classInfo.toString() ); } @@ -321,9 +321,9 @@ public List compileTemplateBytes( ResolvedFilePath resolvedFilePath ) { classInfo = ClassInfo.forTemplate( resolvedFilePath, Parser.detectFile( path.toFile() ), this ); } var classPool = getClassPool( classInfo.classPoolName() ); - classPool.putIfAbsent( classInfo.FQN(), classInfo ); - compileClassInfo( classInfo.classPoolName(), classInfo.FQN() ); - return diskClassUtil.readClassBytes( classInfo.classPoolName(), classInfo.FQN() ); + classPool.putIfAbsent( classInfo.fqn().toString(), classInfo ); + compileClassInfo( classInfo.classPoolName(), classInfo.fqn().toString() ); + return diskClassUtil.readClassBytes( classInfo.classPoolName(), classInfo.fqn().toString() ); } } 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 d9f8bcb91..ae7ae2141 100644 --- a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/BoxClassTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/BoxClassTransformer.java @@ -158,7 +158,7 @@ public class ${className} ${extendsTemplate} implements ${interfaceList} { // Private instance fields private VariablesScope variablesScope = new ClassVariablesScope(this); private ThisScope thisScope = new ThisScope(); - private Key name = ${boxClassName}; + private Key name = ${boxFQN}; private IClassRunnable _super = null; private IClassRunnable child = null; private Boolean canOutput = null; @@ -443,7 +443,7 @@ public Node transform( BoxNode node, TransformerContext context ) throws Illegal BoxClass boxClass = ( BoxClass ) node; Source source = boxClass.getPosition().getSource(); String packageName = transpiler.getProperty( "packageName" ); - String boxPackageName = transpiler.getProperty( "boxPackageName" ); + String boxFQN = transpiler.getProperty( "boxFQN" ); String className = transpiler.getProperty( "classname" ); String mappingName = transpiler.getProperty( "mappingName" ); String mappingPath = transpiler.getProperty( "mappingPath" ); @@ -514,20 +514,14 @@ public Node transform( BoxNode node, TransformerContext context ) throws Illegal * Prep the class template properties * -------------------------------------------------------------------------- */ - 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"; - String boxClassName = boxPackageName + "." + fileName.replace( ".bx", "" ).replace( ".cfc", "" ); - String sourceType = transpiler.getProperty( "sourceType" ); - - // trim leading . if exists - if ( boxClassName.startsWith( "." ) ) { - boxClassName = boxClassName.substring( 1 ); - } + 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"; + String sourceType = transpiler.getProperty( "sourceType" ); // This map replaces the string template - Map values = Map.ofEntries( + Map values = Map.ofEntries( Map.entry( "packagename", packageName ), - Map.entry( "boxPackageName", boxPackageName ), Map.entry( "className", className ), Map.entry( "fileName", fileName ), Map.entry( "interfaceMethods", interfaceMethods ), @@ -539,10 +533,10 @@ public Node transform( BoxNode node, TransformerContext context ) throws Illegal Map.entry( "resolvedFilePath", transpiler.getResolvedFilePath( mappingName, mappingPath, relativePath, filePath ) ), Map.entry( "compiledOnTimestamp", transpiler.getDateTime( LocalDateTime.now() ) ), Map.entry( "compileVersion", "1L" ), - Map.entry( "boxClassName", createKey( boxClassName ).toString() ), + Map.entry( "boxFQN", createKey( boxFQN ).toString() ), Map.entry( "compileTimeMethodNames", generateCompileTimeMethodNames( boxClass ) ) ); - String code = PlaceholderHelper.resolve( CLASS_TEMPLATE, values ); + String code = PlaceholderHelper.resolve( CLASS_TEMPLATE, values ); ParseResult result; try { 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 814673e38..bf913cdfb 100644 --- a/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/BoxInterfaceTransformer.java +++ b/src/main/java/ortus/boxlang/compiler/javaboxpiler/transformer/BoxInterfaceTransformer.java @@ -114,7 +114,7 @@ public class ${classname} extends BoxInterface { private static Map abstractMethods = new LinkedHashMap<>(); private static Map defaultMethods = new LinkedHashMap<>(); private static ${classname} instance; - private static Key name = ${boxInterfacename}; + private static Key name = ${boxFQN}; private static List _supers = new ArrayList(); private static StaticScope staticScope; @@ -258,36 +258,30 @@ public BoxInterfaceTransformer( JavaTranspiler transpiler ) { @Override public Node transform( BoxNode node, TransformerContext context ) throws IllegalStateException { - BoxInterface boxInterface = ( BoxInterface ) node; - Source source = boxInterface.getPosition().getSource(); - String packageName = transpiler.getProperty( "packageName" ); - String boxPackageName = transpiler.getProperty( "boxPackageName" ); - String classname = transpiler.getProperty( "classname" ); - 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"; - String boxInterfacename = boxPackageName + "." + fileName.replace( ".bx", "" ).replace( ".cfc", "" ); - String sourceType = transpiler.getProperty( "sourceType" ); - - // trim leading . if exists - if ( boxInterfacename.startsWith( "." ) ) { - boxInterfacename = boxInterfacename.substring( 1 ); - } - - Map values = Map.ofEntries( + BoxInterface boxInterface = ( BoxInterface ) node; + Source source = boxInterface.getPosition().getSource(); + String packageName = transpiler.getProperty( "packageName" ); + String boxFQN = transpiler.getProperty( "boxFQN" ); + String classname = transpiler.getProperty( "classname" ); + 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"; + String sourceType = transpiler.getProperty( "sourceType" ); + + Map values = Map.ofEntries( Map.entry( "packagename", packageName ), - Map.entry( "boxPackageName", boxPackageName ), Map.entry( "classname", classname ), Map.entry( "fileName", fileName ), Map.entry( "resolvedFilePath", transpiler.getResolvedFilePath( mappingName, mappingPath, relativePath, filePath ) ), Map.entry( "sourceType", sourceType ), Map.entry( "compiledOnTimestamp", transpiler.getDateTime( LocalDateTime.now() ) ), Map.entry( "compileVersion", "1L" ), - Map.entry( "boxInterfacename", createKey( boxInterfacename ).toString() ) + Map.entry( "boxFQN", createKey( boxFQN ).toString() ) ); - String code = PlaceholderHelper.resolve( template, values ); + String code = PlaceholderHelper.resolve( template, values ); ParseResult result; try { 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 20a5c7788..ccd1d82c0 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java +++ b/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java @@ -312,7 +312,7 @@ private Optional findByMapping( return new ClassLocation( className, possibleMatch.absolutePath().toAbsolutePath().toString(), - possibleMatch.getPackage().toString(), + possibleMatch.getFQN().getPackageString(), ClassLocator.TYPE_BX, loadClass ? RunnableLoader.getInstance().loadClass( possibleMatch, context ) : null, "", @@ -361,7 +361,7 @@ private Optional findByRelativeLocation( return Optional.of( new ClassLocation( FilenameUtils.getBaseName( newResolvedFilePath.absolutePath().toString() ), targetPath.toAbsolutePath().toString(), - newResolvedFilePath.getPackage().toString(), + newResolvedFilePath.getFQN().getPackageString(), ClassLocator.TYPE_BX, loadClass ? RunnableLoader.getInstance().loadClass( newResolvedFilePath, context ) : null, "", diff --git a/src/main/java/ortus/boxlang/runtime/util/BoxFQN.java b/src/main/java/ortus/boxlang/runtime/util/BoxFQN.java new file mode 100644 index 000000000..8cd9889a8 --- /dev/null +++ b/src/main/java/ortus/boxlang/runtime/util/BoxFQN.java @@ -0,0 +1,186 @@ +/** + * [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.nio.file.Path; +import java.util.Arrays; + +/** + * This class represents a BoxLang fully qualified name (FQN) for a class or package. + * It handles all the edge cases of dealing with file paths and package names. + */ +public class BoxFQN extends FQN { + + /** + * Construct an FQN that uses the root path to generate a relative path based on filePath. + * + * For example, given root = "c:\foo\bar" and filePath = "c:\foo\bar\test\Car.bx" this will create an FQN + * like "test.Car". + * + * @param root The root path to use to generate the relative path. + * @param filePath The file path to generate the FQN from. + */ + public BoxFQN( Path root, Path filePath ) { + super( root, filePath ); + } + + private BoxFQN( String[] parts ) { + super( parts ); + } + + /** + * Construct an FQN from a Path. + * + * @param path The path to generate the FQN from. + */ + public BoxFQN( Path path ) { + super( path ); + } + + /** + * Construct an FQN from a string. + * + * @param path The string to generate the FQN from. + */ + public BoxFQN( String path ) { + super( path ); + } + + /** + * Construct an FQN from a prefix and an existing FQN. + * + * @param prefix The prefix to add to the FQN. + * @param fqn The existing FQN to add the prefix to. + */ + public BoxFQN( String prefix, FQN fqn ) { + super( prefix, fqn ); + } + + /** + * Construct an FQN from a prefix and a string. + * + * @param prefix The prefix to add to the FQN. + * @param path The string to generate the FQN from. + */ + public BoxFQN( String prefix, String path ) { + super( prefix, path ); + } + + /** + * Construct an FQN from a prefix and a Path. + * + * @param prefix The prefix to add to the FQN. + * @param path The path to generate the FQN from. + */ + public BoxFQN( String prefix, Path path ) { + super( prefix, path ); + } + + /** + * Get only the package as an FQN. + * + * @return String + */ + public BoxFQN getPackage() { + if ( parts.length > 1 ) { + return new BoxFQN( Arrays.copyOfRange( parts, 0, parts.length - 1 ) ); + } else { + return new BoxFQN( new String[] {} ); + } + } + + /** + * Transforms the path into the package name following Java rules. + * + * @param fqn String to grab the package name for. + * + * @return returns the class name according the name conventions Test.ext - + * Test$ext + */ + protected String[] parseParts( String fqn ) { + fqn = normalizeDots( fqn ); + + if ( fqn.isEmpty() ) { + return new String[] {}; + } + + // parse fqn into array of parts + return Arrays.stream( fqn.split( "\\." ) ) + .toArray( String[]::new ); + } + + /** + * Clean the file name by removing the extension. + * + * @param fileName The file name to clean. + * + * @return The cleaned file name. + */ + protected String cleanFileName( String fileName ) { + int dotIndex = fileName.lastIndexOf( '.' ); + if ( dotIndex > 0 ) { + fileName = fileName.substring( 0, dotIndex ); + } + return fileName; + } + + /** + * Factory method to create a new BoxFQN instance from a prefix and a string. + * + * @return A new BoxFQN instance. + */ + public static BoxFQN of( String prefix, String path ) { + return new BoxFQN( prefix, path ); + } + + /** + * Factory method to create a new BoxFQN instance from a prefix and a path. + * + * @return A new BoxFQN instance. + */ + public static BoxFQN of( String prefix, Path path ) { + return new BoxFQN( prefix, path ); + } + + /** + * Factory method to create a new BoxFQN instance from a string. + * + * @return A new BoxFQN instance. + */ + public static BoxFQN of( String path ) { + return new BoxFQN( path ); + } + + /** + * Factory method to create a new BoxFQN instance from a path. + * + * @return A new BoxFQN instance. + */ + public static BoxFQN of( Path path ) { + return new BoxFQN( path ); + } + + /** + * Create a new BoxFQN instance from the current BoxFQN with the prefix appended. + * + * @return A new BoxFQN instance. + */ + public BoxFQN appendPrefix( String prefix ) { + return new BoxFQN( prefix, this ); + } +} diff --git a/src/main/java/ortus/boxlang/runtime/util/FQN.java b/src/main/java/ortus/boxlang/runtime/util/FQN.java index 7b534dd23..1b7307f7b 100644 --- a/src/main/java/ortus/boxlang/runtime/util/FQN.java +++ b/src/main/java/ortus/boxlang/runtime/util/FQN.java @@ -23,7 +23,7 @@ import java.util.Set; /** - * This class represents a fully qualified name (FQN) for a class or package. + * 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. */ public class FQN { @@ -39,7 +39,7 @@ public class FQN { /** * An array of strings representing all the pieces of the FQN. */ - private String[] parts; + protected String[] parts; /** * Construct an FQN that uses the root path to generate a relative path based on filePath. @@ -59,7 +59,7 @@ public FQN( Path root, Path filePath ) { this.parts = parseParts( parseFromFile( root.relativize( filePath ) ) ); } - private FQN( String[] parts ) { + protected FQN( String[] parts ) { this.parts = parts; } @@ -88,9 +88,7 @@ public FQN( String path ) { * @param fqn The existing FQN to add the prefix to. */ public FQN( String prefix, FQN fqn ) { - this.parts = new String[ fqn.parts.length + 1 ]; - this.parts[ 0 ] = prefix; - System.arraycopy( fqn.parts, 0, this.parts, 1, fqn.parts.length ); + combineParts( parseParts( prefix, true ), fqn.parts ); } /** @@ -100,9 +98,7 @@ public FQN( String prefix, FQN fqn ) { * @param path The string to generate the FQN from. */ public FQN( String prefix, String path ) { - this.parts = new String[ parseParts( path ).length + 1 ]; - this.parts[ 0 ] = prefix; - System.arraycopy( parseParts( path ), 0, this.parts, 1, parseParts( path ).length ); + combineParts( parseParts( prefix, true ), parseParts( path ) ); } /** @@ -112,8 +108,16 @@ public FQN( String prefix, String path ) { * @param path The path to generate the FQN from. */ public FQN( String prefix, Path path ) { - var prefixParts = parseParts( prefix ); - var pathParts = parseParts( parseFromFile( path ) ); + combineParts( parseParts( prefix, true ), parseParts( parseFromFile( path ) ) ); + } + + /** + * Construct an FQN from a prefix and a Path. + * + * @param prefix The prefix to add to the FQN. + * @param path The path to generate the FQN from. + */ + protected void combineParts( String[] prefixParts, String[] pathParts ) { this.parts = new String[ prefixParts.length + pathParts.length ]; System.arraycopy( prefixParts, 0, this.parts, 0, prefixParts.length ); System.arraycopy( pathParts, 0, this.parts, prefixParts.length, pathParts.length ); @@ -137,6 +141,13 @@ public String getPackageString() { return getPackage().toString(); } + public String getClassName() { + if ( parts.length == 0 ) { + return ""; + } + return parts[ parts.length - 1 ]; + } + /** * Get only the package as an FQN. * @@ -151,41 +162,53 @@ public FQN getPackage() { } /** - * Transforms the path into the package name + * Transforms the path into the package name following Java rules. * * @param fqn String to grab the package name for. * * @return returns the class name according the name conventions Test.ext - * Test$ext */ - static String[] parseParts( String fqn ) { - // Replace .. with . - fqn = fqn.replaceAll( "\\.\\.", "." ); - // trim trailing period - if ( fqn.endsWith( "." ) ) { - fqn = fqn.substring( 0, fqn.length() - 1 ); - } - // trim leading period - if ( fqn.startsWith( "." ) ) { - fqn = fqn.substring( 1 ); - } + protected String[] parseParts( String fqn ) { + return parseParts( fqn, false ); + } + + /** + * Transforms the path into the package name following Java rules. + * + * @param fqn String to grab the package name for. + * @param allPackage True to return all parts as package names. + * + * @return returns the class name according the name conventions Test.ext - + * Test$ext + */ + protected String[] parseParts( String fqn, boolean allPackage ) { + fqn = normalizeDots( fqn ); + // Remove any non alpha-numeric chars. - fqn = fqn.replaceAll( "[^a-zA-Z0-9\\.]", "" ); + fqn = fqn.replaceAll( "[^a-zA-Z0-9$\\.]", "" ); if ( fqn.isEmpty() ) { return new String[] {}; } - // Find the last period in the string - int lastPeriodIndex = fqn.lastIndexOf( '.' ); - if ( lastPeriodIndex != -1 ) { - // Lowercase everything up to the last period - String beforeLastPeriod = fqn.substring( 0, lastPeriodIndex ).toLowerCase(); - String afterLastPeriod = fqn.substring( lastPeriodIndex + 1 ); - fqn = beforeLastPeriod + "." + afterLastPeriod; + if ( allPackage ) { + // Lowercase everything + fqn = fqn.toLowerCase(); + } else { + // Find the last period in the string + int lastPeriodIndex = fqn.lastIndexOf( '.' ); + if ( lastPeriodIndex != -1 ) { + // Lowercase everything up to the last period + String beforeLastPeriod = fqn.substring( 0, lastPeriodIndex ).toLowerCase(); + String afterLastPeriod = fqn.substring( lastPeriodIndex + 1 ); + // upper case first char of afterLastPeriod + afterLastPeriod = afterLastPeriod.substring( 0, 1 ).toUpperCase() + afterLastPeriod.substring( 1 ); + fqn = beforeLastPeriod + "." + afterLastPeriod; + } } - // parse fqn into list, loop over list and remove any empty strings and turn back into fqn + // 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 ) @@ -196,7 +219,30 @@ static String[] parseParts( String fqn ) { return s; } ) .toArray( String[]::new ); + } + /** + * Normalize the dots in a string. + * - 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( "\\.\\.", "." ); + // trim trailing period + if ( fqn.endsWith( "." ) ) { + fqn = fqn.substring( 0, fqn.length() - 1 ); + } + // trim leading period + if ( fqn.startsWith( "." ) ) { + fqn = fqn.substring( 1 ); + } + return fqn; } /** @@ -206,42 +252,51 @@ static String[] parseParts( String fqn ) { * * @return The package name. */ - static String parseFromFile( Path file ) { + protected String parseFromFile( Path file ) { // Strip extension from file name, if exists - String fileName = file.getFileName().toString(); - int dotIndex = fileName.lastIndexOf( '.' ); - if ( dotIndex > 0 ) { - fileName = fileName.substring( 0, dotIndex ); - } - String packg; + String fileName = file.getFileName().toString(); + fileName = cleanFileName( fileName ); + + String fqn; Path parent = file.getParent(); if ( parent != null ) { - packg = parent.resolve( fileName ).toString(); + fqn = parent.resolve( fileName ).toString(); } else { - packg = fileName; + fqn = fileName; } - if ( packg.startsWith( "/" ) || packg.startsWith( "\\" ) ) { - packg = packg.substring( 1 ); + if ( fqn.startsWith( "/" ) || fqn.startsWith( "\\" ) ) { + fqn = fqn.substring( 1 ); } // trim trailing \ or / - if ( packg.endsWith( "\\" ) || packg.endsWith( "/" ) ) { - packg = packg.substring( 0, packg.length() - 1 ); + if ( fqn.endsWith( "\\" ) || fqn.endsWith( "/" ) ) { + fqn = fqn.substring( 0, fqn.length() - 1 ); } // Take out periods in folder names - packg = packg.replaceAll( "\\.", "" ); + fqn = fqn.replaceAll( "\\.", "" ); // Replace / with . - packg = packg.replaceAll( "/", "." ); + fqn = fqn.replaceAll( "/", "." ); // Remove any : from Windows drives - packg = packg.replaceAll( ":", "" ); + fqn = fqn.replaceAll( ":", "" ); // Replace \ with . - packg = packg.replaceAll( "\\\\", "." ); + fqn = fqn.replaceAll( "\\\\", "." ); - return packg; + return fqn; } + /** + * Clean the file name by replacing periods with $. + * + * @param fileName The file name to clean. + * + * @return The cleaned file name. + */ + protected String cleanFileName( String fileName ) { + return fileName.replace( ".", "$" ); + } + /** * Factory method to create a new FQN instance from a prefix and a string. * diff --git a/src/main/java/ortus/boxlang/runtime/util/ResolvedFilePath.java b/src/main/java/ortus/boxlang/runtime/util/ResolvedFilePath.java index 5306a9ec9..8b311de24 100644 --- a/src/main/java/ortus/boxlang/runtime/util/ResolvedFilePath.java +++ b/src/main/java/ortus/boxlang/runtime/util/ResolvedFilePath.java @@ -101,24 +101,44 @@ public boolean resovledViaMapping() { return mappingName != null; } + /** + * Get the package of the resolved path, but with a prefix appended in front + * + * @param prefix The prefix to append to the package. + * + * @return The package of the resolved path with the prefix appended. + */ + public FQN getFQN( String prefix ) { + return FQN.of( prefix, relativePath != null ? Path.of( relativePath ) : absolutePath ); + } + /** * Get the package of the resolved path. * * @return The package of the resolved path. */ - public FQN getPackage() { - return FQN.of( relativePath != null ? Path.of( relativePath ) : absolutePath ).getPackage(); + public FQN getFQN() { + return FQN.of( relativePath != null ? Path.of( relativePath ) : absolutePath ); } /** - * Get the package of the resolved path, but with a prefix appended in front + * Get the Box package of the resolved path. + * + * @return The package of the resolved path. + */ + public BoxFQN getBoxFQN() { + return BoxFQN.of( relativePath != null ? Path.of( relativePath ) : absolutePath ); + } + + /** + * Get the Box package of the resolved path, but with a prefix appended in front * * @param prefix The prefix to append to the package. * * @return The package of the resolved path with the prefix appended. */ - public FQN getPackage( String prefix ) { - return FQN.of( prefix, relativePath != null ? Path.of( relativePath ) : absolutePath ).getPackage(); + public BoxFQN getBoxFQN( String prefix ) { + return BoxFQN.of( prefix, relativePath != null ? Path.of( relativePath ) : absolutePath ); } /** diff --git a/src/test/java/TestCases/phase3/ClassTest.java b/src/test/java/TestCases/phase3/ClassTest.java index b9033d3c9..c5ed44f73 100644 --- a/src/test/java/TestCases/phase3/ClassTest.java +++ b/src/test/java/TestCases/phase3/ClassTest.java @@ -401,9 +401,9 @@ public void testlegacyMeta() { 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( "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.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 ); @@ -430,9 +430,9 @@ public void testlegacyMetaCF() { 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( "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.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 ); @@ -497,7 +497,7 @@ public void testBoxMeta() { 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.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(); @@ -819,11 +819,11 @@ public void testCanExtend() { // 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", + "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", + "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", } ); @@ -832,7 +832,7 @@ public void testCanExtend() { var boxMeta = ( ClassMeta ) cfc.getBoxMeta(); var meta = boxMeta.meta; - assertThat( meta.get( Key.of( "name" ) ) ).isEqualTo( "src.test.java.testcases.phase3.Chihuahua" ); + 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" ); @@ -1287,6 +1287,7 @@ public void testPseduoConstructorError() { """, context ); } catch ( BoxRuntimeException e ) { tagContext = ExceptionUtil.buildTagContext( e ); + System.out.println( tagContext ); } assertThat( tagContext ).isNotNull(); assertThat( tagContext.size() ).isEqualTo( 1 ); @@ -1336,4 +1337,16 @@ public void testBinderProperties() { assertThat( variables.get( "currentMapping" ) ).isInstanceOf( Array.class ); } + @Test + public void testHypenInPath() { + instance.executeSource( + """ + cfc = new "src.test.java.TestCases.phase3.sub-folder.Funky-Class"(); + meta = cfc.$bx.meta; + """, context ); + assertThat( variables.get( "meta" ) ).isInstanceOf( IStruct.class ); + assertThat( variables.getAsStruct( Key.of( "meta" ) ).getAsString( Key.fullname ) ) + .isEqualTo( "src.test.java.TestCases.phase3.sub-folder.Funky-Class" ); + } + } diff --git a/src/test/java/TestCases/phase3/sub-folder/Funky-Class.bx b/src/test/java/TestCases/phase3/sub-folder/Funky-Class.bx new file mode 100644 index 000000000..4a20930ab --- /dev/null +++ b/src/test/java/TestCases/phase3/sub-folder/Funky-Class.bx @@ -0,0 +1,3 @@ +class { + +} \ No newline at end of file diff --git a/src/test/java/ortus/boxlang/compiler/CFTranspilerTest.java b/src/test/java/ortus/boxlang/compiler/CFTranspilerTest.java index f4384a0cb..34e44637d 100644 --- a/src/test/java/ortus/boxlang/compiler/CFTranspilerTest.java +++ b/src/test/java/ortus/boxlang/compiler/CFTranspilerTest.java @@ -175,7 +175,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" ) ) ).getName().getName() ).isEqualTo( "src.test.java.TestCases.phase3.MyClassCF" ); } @DisplayName( "Test BIF return value" ) diff --git a/src/test/java/ortus/boxlang/runtime/util/BoxFQNTest.java b/src/test/java/ortus/boxlang/runtime/util/BoxFQNTest.java new file mode 100644 index 000000000..d260f1d8e --- /dev/null +++ b/src/test/java/ortus/boxlang/runtime/util/BoxFQNTest.java @@ -0,0 +1,56 @@ +package ortus.boxlang.runtime.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.nio.file.Paths; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class BoxFQNTest { + + @DisplayName( "Can convert an absolute path to a full package/class path" ) + @Test + void testItCanParseAbsolutePathToString() { + BoxFQN fqn = new BoxFQN( Paths.get( "/src/test/java/ortus/boxlang/runtime/util/BoxFQNTest.bx" ) ); + assertEquals( "src.test.java.ortus.boxlang.runtime.util.BoxFQNTest", fqn.toString() ); + } + + @DisplayName( "Can convert an absolute path to just a package path" ) + @Test + void testItCanParseAbsolutePathToPackage() { + BoxFQN fqn = new BoxFQN( Paths.get( "/src/test/java/ortus/boxlang/runtime/util/BoxFQNTest.bx" ) ); + assertEquals( "src.test.java.ortus.boxlang.runtime.util", fqn.getPackageString() ); + } + + @DisplayName( "Can convert a file path to a relative package name" ) + @Test + void testItCanParseRelativePathToString() { + BoxFQN fqn = new BoxFQN( Paths.get( "src/test/java/" ), Paths.get( "src/test/java/ortus/boxlang/runtime/util/BoxFQNTest.bx" ) ); + assertEquals( "ortus.boxlang.runtime.util.BoxFQNTest", fqn.toString() ); + } + + @DisplayName( "Can convert a file path with prefix to a relative package name" ) + @Test + void testItCanParseRelativePrefixedPathToPackagePath() { + BoxFQN fqn = new BoxFQN( Paths.get( "src/test/java/" ), Paths.get( "src/test/java/ortus/boxlang/runtime/util/BoxFQNTest.bx" ) ); + assertEquals( "ortus.boxlang.runtime.util", fqn.getPackageString() ); + } + + @DisplayName( "Can parse with no package" ) + @Test + void testItCanParseWithNoPackage() { + BoxFQN fqn = new BoxFQN( Paths.get( "BoxFQNTest.java" ) ); + assertEquals( "BoxFQNTest", fqn.toString() ); + assertEquals( "", fqn.getPackageString() ); + // Ensure we don't get array out of bounds exceptions + assertEquals( "", fqn.getPackage().getPackage().getPackageString() ); + } + + @DisplayName( "Can clean invalid parts" ) + @Test + void testItCanCleanInvalidParts() { + BoxFQN fqn = new BoxFQN( Paths.get( "/src\\\\test\\class/ORTUS//switch/45/2run-time/util/fqntest.bx" ) ); + assertEquals( "src.test.class.ORTUS.switch.45.2run-time.util.fqntest", fqn.toString() ); + } +} diff --git a/src/test/java/ortus/boxlang/runtime/util/FQNTest.java b/src/test/java/ortus/boxlang/runtime/util/FQNTest.java index ef260dca0..78447faa7 100644 --- a/src/test/java/ortus/boxlang/runtime/util/FQNTest.java +++ b/src/test/java/ortus/boxlang/runtime/util/FQNTest.java @@ -13,7 +13,7 @@ public class FQNTest { @Test void testItCanParseAbsolutePathToString() { FQN fqn = new FQN( Paths.get( "/src/test/java/ortus/boxlang/runtime/util/FQNTest.java" ) ); - assertEquals( "src.test.java.ortus.boxlang.runtime.util.FQNTest", fqn.toString() ); + assertEquals( "src.test.java.ortus.boxlang.runtime.util.FQNTest$java", fqn.toString() ); } @DisplayName( "Can convert an absolute path to just a package path" ) @@ -27,7 +27,7 @@ void testItCanParseAbsolutePathToPackage() { @Test void testItCanParseRelativePathToString() { FQN fqn = new FQN( Paths.get( "src/test/java/" ), Paths.get( "src/test/java/ortus/boxlang/runtime/util/FQNTest.java" ) ); - assertEquals( "ortus.boxlang.runtime.util.FQNTest", fqn.toString() ); + assertEquals( "ortus.boxlang.runtime.util.FQNTest$java", fqn.toString() ); } @DisplayName( "Can convert a file path with prefix to a relative package name" ) @@ -41,9 +41,37 @@ void testItCanParseRelativePrefixedPathToPackagePath() { @Test void testItCanParseWithNoPackage() { FQN fqn = new FQN( Paths.get( "FQNTest.java" ) ); - assertEquals( "FQNTest", fqn.toString() ); + assertEquals( "FQNTest$java", fqn.toString() ); assertEquals( "", fqn.getPackageString() ); // Ensure we don't get array out of bounds exceptions assertEquals( "", fqn.getPackage().getPackage().getPackageString() ); } + + @DisplayName( "Can parse string" ) + @Test + void testItCanParseString() { + FQN fqn = new FQN( "foo.Bar.baz" ); + assertEquals( "foo.bar.Baz", fqn.toString() ); + } + + @DisplayName( "Can parse string with prefix" ) + @Test + void testItCanParseStringWithPrefix() { + FQN fqn = new FQN( "brad.wood", "foo.Bar.baz" ); + assertEquals( "brad.wood.foo.bar.Baz", fqn.toString() ); + } + + @DisplayName( "Can parse fqn with prefix" ) + @Test + void testItCanParseFQNWithPrefix() { + FQN fqn = new FQN( "brad.wood", FQN.of( "foo.Bar.baz" ) ); + assertEquals( "brad.wood.foo.bar.Baz", fqn.toString() ); + } + + @DisplayName( "Can clean invalid parts" ) + @Test + void testItCanCleanInvalidParts() { + FQN fqn = new FQN( Paths.get( "/src\\\\test\\class/ORTUS//switch/45/2run-time/util/fqntest.java" ) ); + assertEquals( "src.test._class.ortus._switch._45._2runtime.util.Fqntest$java", fqn.toString() ); + } } From 0ee660b5fa18c0d89bb9a60c9cd995cab83edbd8 Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Thu, 3 Oct 2024 11:56:47 -0500 Subject: [PATCH 14/19] BL-609 --- .../boxlang/runtime/loader/resolvers/BoxResolver.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 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 ccd1d82c0..c2cf5a1ef 100644 --- a/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java +++ b/src/main/java/ortus/boxlang/runtime/loader/resolvers/BoxResolver.java @@ -24,7 +24,6 @@ import java.util.List; import java.util.Optional; -import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import ortus.boxlang.runtime.context.IBoxContext; @@ -308,11 +307,10 @@ private Optional findByMapping( // System.out.println( "found: " + possibleMatch.absolutePath().toAbsolutePath().toString() ); // System.out.println( "found package: " + possibleMatch.getPackage().toString() ); - var className = FilenameUtils.getBaseName( possibleMatch.absolutePath().toString() ); return new ClassLocation( - className, + possibleMatch.getBoxFQN().getClassName(), possibleMatch.absolutePath().toAbsolutePath().toString(), - possibleMatch.getFQN().getPackageString(), + possibleMatch.getBoxFQN().getPackageString(), ClassLocator.TYPE_BX, loadClass ? RunnableLoader.getInstance().loadClass( possibleMatch, context ) : null, "", @@ -359,9 +357,9 @@ private Optional findByRelativeLocation( .newFromRelative( parentPath.relativize( Paths.get( targetPath.toString() ) ).toString() ); return Optional.of( new ClassLocation( - FilenameUtils.getBaseName( newResolvedFilePath.absolutePath().toString() ), + newResolvedFilePath.getBoxFQN().getClassName(), targetPath.toAbsolutePath().toString(), - newResolvedFilePath.getFQN().getPackageString(), + newResolvedFilePath.getBoxFQN().getPackageString(), ClassLocator.TYPE_BX, loadClass ? RunnableLoader.getInstance().loadClass( newResolvedFilePath, context ) : null, "", From ae8cf0a883c448efc655793d5c6a7aaee298aa18 Mon Sep 17 00:00:00 2001 From: Rafael Winterhalter Date: Fri, 4 Oct 2024 01:15:29 +0200 Subject: [PATCH 15/19] Fix constructor invocation. --- .../boxlang/compiler/asmboxpiler/AsmTranspiler.java | 10 ++++------ .../java/TestCases/asm/integration/ControllerTest.java | 2 -- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index d7d548cda..d6bd69669 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -136,11 +136,7 @@ import ortus.boxlang.runtime.scopes.StaticScope; import ortus.boxlang.runtime.scopes.ThisScope; import ortus.boxlang.runtime.scopes.VariablesScope; -import ortus.boxlang.runtime.types.Array; -import ortus.boxlang.runtime.types.IStruct; -import ortus.boxlang.runtime.types.IType; -import ortus.boxlang.runtime.types.Property; -import ortus.boxlang.runtime.types.Struct; +import ortus.boxlang.runtime.types.*; import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException; import ortus.boxlang.runtime.types.exceptions.ExpressionException; import ortus.boxlang.runtime.types.meta.BoxMeta; @@ -878,6 +874,7 @@ private List> transformProperties( Type declaringType, Li javaExpr.addAll( jNameKey ); javaExpr.add( new LdcInsnNode( type ) ); javaExpr.addAll( init ); + javaExpr.add( new InsnNode( Opcodes.ACONST_NULL ) ); // TODO: might be lambda expression javaExpr.addAll( annotationStruct ); javaExpr.addAll( documentationStruct ); @@ -890,7 +887,8 @@ private List> transformProperties( Type declaringType, Li Type.getInternalName( Property.class ), "", Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( Key.class ), Type.getType( String.class ), Type.getType( Object.class ), - Type.getType( IStruct.class ), Type.getType( IStruct.class ), Type.getType( BoxSourceType.class ) ), + Type.getType( DefaultExpression.class ), Type.getType( IStruct.class ), Type.getType( IStruct.class ), + Type.getType( BoxSourceType.class ) ), false ) ); members.add( jNameKey ); diff --git a/src/test/java/TestCases/asm/integration/ControllerTest.java b/src/test/java/TestCases/asm/integration/ControllerTest.java index f1d0cbe41..dbc536db8 100644 --- a/src/test/java/TestCases/asm/integration/ControllerTest.java +++ b/src/test/java/TestCases/asm/integration/ControllerTest.java @@ -21,7 +21,6 @@ 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.runtime.BoxRuntime; @@ -61,7 +60,6 @@ public void teardownEach() { } @Test - @Disabled( "Needs update for new constructor argument in Property record" ) public void testControllerCompilation() { instance.executeStatement( """ From f2db035c1dd20df5e9b6f89c89776d2a2eef2b2d Mon Sep 17 00:00:00 2001 From: Brad Wood Date: Fri, 4 Oct 2024 00:47:40 -0500 Subject: [PATCH 16/19] BL-609 --- src/main/java/ortus/boxlang/compiler/ClassInfo.java | 1 - src/main/java/ortus/boxlang/runtime/util/FQN.java | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/ortus/boxlang/compiler/ClassInfo.java b/src/main/java/ortus/boxlang/compiler/ClassInfo.java index 9a4bc6294..4e7fe4708 100644 --- a/src/main/java/ortus/boxlang/compiler/ClassInfo.java +++ b/src/main/java/ortus/boxlang/compiler/ClassInfo.java @@ -214,7 +214,6 @@ public String className() { } public Boolean isClass() { - System.out.println( "ClassInfo.isClass() " + packageName().toString() ); return packageName().toString().startsWith( "boxgenerated.boxclass" ); } diff --git a/src/main/java/ortus/boxlang/runtime/util/FQN.java b/src/main/java/ortus/boxlang/runtime/util/FQN.java index 1b7307f7b..19687608b 100644 --- a/src/main/java/ortus/boxlang/runtime/util/FQN.java +++ b/src/main/java/ortus/boxlang/runtime/util/FQN.java @@ -201,10 +201,13 @@ protected String[] parseParts( String fqn, boolean allPackage ) { if ( lastPeriodIndex != -1 ) { // Lowercase everything up to the last period String beforeLastPeriod = fqn.substring( 0, lastPeriodIndex ).toLowerCase(); - String afterLastPeriod = fqn.substring( lastPeriodIndex + 1 ); + String afterLastPeriod = fqn.substring( lastPeriodIndex + 1 ).toLowerCase(); // upper case first char of afterLastPeriod afterLastPeriod = afterLastPeriod.substring( 0, 1 ).toUpperCase() + afterLastPeriod.substring( 1 ); fqn = beforeLastPeriod + "." + afterLastPeriod; + } else { + // There is no package, just a class, so upper case first char of fqn + fqn = fqn.substring( 0, 1 ).toUpperCase() + fqn.substring( 1 ).toLowerCase(); } } From 2e61e6e3dd291b3fb0ec1b1c1aa72ce7da2388bd Mon Sep 17 00:00:00 2001 From: Rafael Winterhalter Date: Fri, 4 Oct 2024 10:03:49 +0200 Subject: [PATCH 17/19] Add lambda branch for property constructor. --- .../compiler/asmboxpiler/AsmHelper.java | 4 +-- .../compiler/asmboxpiler/AsmTranspiler.java | 33 ++++++++++++++++--- 2 files changed, 31 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 58d79c764..2a1ea7f8d 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmHelper.java @@ -258,8 +258,8 @@ public static void complete( ClassVisitor classVisitor, Type type, Consumer transformBodyExpressions( Transpiler transpiler, List statements, TransformerContext context, ReturnValueContext finalReturnValueContext ) { - if ( statements.size() == 0 ) { - return new ArrayList(); + if ( statements.isEmpty() ) { + return new ArrayList<>(); } ReturnValueContext bodyContext = finalReturnValueContext == ReturnValueContext.VALUE_OR_NULL ? ReturnValueContext.EMPTY_UNLESS_JUMPING diff --git a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java index d6bd69669..b5aaef0e6 100644 --- a/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java +++ b/src/main/java/ortus/boxlang/compiler/asmboxpiler/AsmTranspiler.java @@ -841,11 +841,36 @@ private List> transformProperties( Type declaringType, Li List annotationStruct = transformAnnotations( finalAnnotations ); /* Process default value */ - List init; + List init, initLambda; if ( defaultAnnotation.getValue() != null ) { - init = transform( defaultAnnotation.getValue(), TransformerContext.NONE, ReturnValueContext.EMPTY ); + + if ( defaultAnnotation.getValue().isLiteral() ) { + init = transform( defaultAnnotation.getValue(), TransformerContext.NONE, ReturnValueContext.EMPTY ); + initLambda = List.of( new InsnNode( Opcodes.ACONST_NULL ) ); + } else { + init = List.of( new InsnNode( Opcodes.ACONST_NULL ) ); + + Type type = Type.getType( "L" + getProperty( "packageName" ).replace( '.', '/' ) + + "/" + getProperty( "classname" ) + + "$Lambda_" + incrementAndGetLambdaCounter() + ";" ); + + List body = transform( defaultAnnotation.getValue(), TransformerContext.NONE, ReturnValueContext.EMPTY ); + ClassNode classNode = new ClassNode(); + AsmHelper.init( classNode, false, type, Type.getType( Object.class ), methodVisitor -> { + }, Type.getType( DefaultExpression.class ) ); + AsmHelper.methodWithContextAndClassLocator( classNode, "evaluate", Type.getType( IBoxContext.class ), Type.getType( Object.class ), false, + this, false, + () -> body ); + setAuxiliary( type.getClassName(), classNode ); + + initLambda = List.of( + new TypeInsnNode( Opcodes.NEW, type.getInternalName() ), + new InsnNode( Opcodes.DUP ), + new MethodInsnNode( Opcodes.INVOKESPECIAL, type.getInternalName(), "", Type.getMethodDescriptor( Type.VOID_TYPE ), false ) ); + } } else { - init = List.of( new InsnNode( Opcodes.ACONST_NULL ) ); + init = List.of( new InsnNode( Opcodes.ACONST_NULL ) ); + initLambda = List.of( new InsnNode( Opcodes.ACONST_NULL ) ); } // name and type must be simple values String name; @@ -874,7 +899,7 @@ private List> transformProperties( Type declaringType, Li javaExpr.addAll( jNameKey ); javaExpr.add( new LdcInsnNode( type ) ); javaExpr.addAll( init ); - javaExpr.add( new InsnNode( Opcodes.ACONST_NULL ) ); // TODO: might be lambda expression + javaExpr.addAll( initLambda ); javaExpr.addAll( annotationStruct ); javaExpr.addAll( documentationStruct ); From 2359b0b4fea61faa2345caf3b1ef5886a00a21ec Mon Sep 17 00:00:00 2001 From: Luis Majano Date: Fri, 4 Oct 2024 19:50:40 +0200 Subject: [PATCH 18/19] fix tests to match new code paths --- src/test/java/ortus/boxlang/runtime/util/FQNTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/ortus/boxlang/runtime/util/FQNTest.java b/src/test/java/ortus/boxlang/runtime/util/FQNTest.java index 78447faa7..55c586e07 100644 --- a/src/test/java/ortus/boxlang/runtime/util/FQNTest.java +++ b/src/test/java/ortus/boxlang/runtime/util/FQNTest.java @@ -13,7 +13,7 @@ public class FQNTest { @Test void testItCanParseAbsolutePathToString() { FQN fqn = new FQN( Paths.get( "/src/test/java/ortus/boxlang/runtime/util/FQNTest.java" ) ); - assertEquals( "src.test.java.ortus.boxlang.runtime.util.FQNTest$java", fqn.toString() ); + assertEquals( "src.test.java.ortus.boxlang.runtime.util.Fqntest$java", fqn.toString() ); } @DisplayName( "Can convert an absolute path to just a package path" ) @@ -27,7 +27,7 @@ void testItCanParseAbsolutePathToPackage() { @Test void testItCanParseRelativePathToString() { FQN fqn = new FQN( Paths.get( "src/test/java/" ), Paths.get( "src/test/java/ortus/boxlang/runtime/util/FQNTest.java" ) ); - assertEquals( "ortus.boxlang.runtime.util.FQNTest$java", fqn.toString() ); + assertEquals( "ortus.boxlang.runtime.util.Fqntest$java", fqn.toString() ); } @DisplayName( "Can convert a file path with prefix to a relative package name" ) @@ -41,7 +41,7 @@ void testItCanParseRelativePrefixedPathToPackagePath() { @Test void testItCanParseWithNoPackage() { FQN fqn = new FQN( Paths.get( "FQNTest.java" ) ); - assertEquals( "FQNTest$java", fqn.toString() ); + assertEquals( "Fqntest$java", fqn.toString() ); assertEquals( "", fqn.getPackageString() ); // Ensure we don't get array out of bounds exceptions assertEquals( "", fqn.getPackage().getPackage().getPackageString() ); From c81e98b87cd5bc5ff23f21c3dd2b9e28b00577f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 4 Oct 2024 17:52:28 +0000 Subject: [PATCH 19/19] Bump com.fasterxml.jackson.jr:jackson-jr-stree from 2.17.2 to 2.18.0 Bumps [com.fasterxml.jackson.jr:jackson-jr-stree](https://github.com/FasterXML/jackson-jr) from 2.17.2 to 2.18.0. - [Commits](https://github.com/FasterXML/jackson-jr/compare/jackson-jr-parent-2.17.2...jackson-jr-parent-2.18.0) --- updated-dependencies: - dependency-name: com.fasterxml.jackson.jr:jackson-jr-stree 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 d0a093975..dd6849dca 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.0' // https://mvnrepository.com/artifact/com.fasterxml.jackson.jr/jackson-jr-stree - implementation 'com.fasterxml.jackson.jr:jackson-jr-stree:2.17.2' + implementation 'com.fasterxml.jackson.jr:jackson-jr-stree:2.18.0' // https://mvnrepository.com/artifact/com.fasterxml.jackson.jr/jackson-jr-annotation-support implementation 'com.fasterxml.jackson.jr:jackson-jr-annotation-support:2.18.0' // https://mvnrepository.com/artifact/org.slf4j/slf4j-api