Skip to content

Commit

Permalink
BL-187 #resolve
Browse files Browse the repository at this point in the history
Accessors should default to true in BoxLang and false in CFML
  • Loading branch information
lmajano committed May 30, 2024
1 parent 2b464d6 commit 1d767b1
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@
*/
public class CFTranspilerVisitor extends ReplacingBoxVisitor {

private static Map<String, String> BIFMap = new HashMap<String, String>();
private static Map<String, String> identifierMap = new HashMap<String, String>();
private static Map<String, Map<String, String>> componentAttrMap = new HashMap<String, Map<String, String>>();
private static Map<String, String> BIFMap = new HashMap<>();
private static Map<String, String> identifierMap = new HashMap<>();
private static Map<String, Map<String, String>> componentAttrMap = new HashMap<>();
private boolean isClass = false;

static {
Expand Down Expand Up @@ -85,21 +85,39 @@ public class CFTranspilerVisitor extends ReplacingBoxVisitor {
componentAttrMap.put( "invoke", Map.of( "component", "class" ) );
componentAttrMap.put( "procparam", Map.of( "cfsqltype", "sqltype" ) );
componentAttrMap.put( "queryparam", Map.of( "cfsqltype", "sqltype" ) );

}

/**
* Constructor
*/
public CFTranspilerVisitor() {
// Simple Constructor
}

/**
* Transpile Box Classes
* - Merge documentation into annotations
* - enable output
*/
@Override
public BoxNode visit( BoxClass node ) {
isClass = true;
mergeDocsIntoAnnotations( node.getAnnotations(), node.getDocumentation() );
enableOutput( node.getAnnotations() );
var annotations = node.getAnnotations();
this.isClass = true;
mergeDocsIntoAnnotations( annotations, node.getDocumentation() );

// Disable Accessors by default in CFML
if ( annotations.stream().noneMatch( a -> a.getKey().getValue().equalsIgnoreCase( "accessors" ) ) ) {
// @output true
annotations.add(
new BoxAnnotation(
new BoxFQN( "accessors", null, null ),
new BoxBooleanLiteral( false, null, null ),
null,
null )
);
}

enableOutput( annotations );
return super.visit( node );
}

Expand All @@ -108,6 +126,7 @@ public BoxNode visit( BoxClass node ) {
* - Merge documentation into annotations
* - enable output
*/
@Override
public BoxNode visit( BoxFunctionDeclaration node ) {
mergeDocsIntoAnnotations( node.getAnnotations(), node.getDocumentation() );
// Don't touch UDFs in a class, otherwise they won't inherit from the class's output annotation.
Expand All @@ -121,6 +140,7 @@ public BoxNode visit( BoxFunctionDeclaration node ) {
* Transpile Box Class properties
* - Merge documentation into annotations
*/
@Override
public BoxNode visit( BoxProperty node ) {
mergeDocsIntoAnnotations( node.getAnnotations(), node.getDocumentation() );
return super.visit( node );
Expand All @@ -129,6 +149,7 @@ public BoxNode visit( BoxProperty node ) {
/**
* Rename top level CF variables
*/
@Override
public BoxNode visit( BoxArrayAccess node ) {
renameTopLevelVars( node );
return super.visit( node );
Expand All @@ -138,6 +159,7 @@ public BoxNode visit( BoxArrayAccess node ) {
* Rename top level CF variables
* change foo.bar to foo.BAR
*/
@Override
public BoxNode visit( BoxDotAccess node ) {
renameTopLevelVars( node );
upperCaseDotAceessKeys( node );
Expand Down Expand Up @@ -180,12 +202,13 @@ private void upperCaseStructLiteralKeys( BoxStructLiteral node ) {

/**
* Rename some common CF built-in functions like chr() to char()
*
*
* Replace
* structKeyExists( struct, key )
* with
* !isNull( struct[ key ] )
*/
@Override
public BoxNode visit( BoxFunctionInvocation node ) {
String name = node.getName().toLowerCase();
if ( BIFMap.containsKey( name ) ) {
Expand Down Expand Up @@ -334,6 +357,7 @@ private BoxNode transpileStructKeyExists( BoxFunctionInvocation node ) {
/**
* Rename enablecfoutputonly attribute on cfsetting tag
*/
@Override
public BoxNode visit( BoxComponent node ) {
if ( componentAttrMap.containsKey( node.getName().toLowerCase() ) ) {
var attrs = node.getAttributes();
Expand All @@ -350,14 +374,14 @@ public BoxNode visit( BoxComponent node ) {

/**
* CF reads documentation comment lines such as
*
*
* @foo bar
* as an actual "annotation" for classes and functions and properties.
* We'll need to merge these in manually as BL keeps them separate.
*
*
* @param annotations The annotations for the node
* @param documentation The documentation for the node
*
*
*/
private void mergeDocsIntoAnnotations( List<BoxAnnotation> annotations, List<BoxDocumentationAnnotation> documentation ) {
Set<String> existingAnnotations = annotations.stream().map( BoxAnnotation::getKey ).map( BoxFQN::getValue ).map( k -> k.toLowerCase() )
Expand Down Expand Up @@ -387,6 +411,7 @@ private void mergeDocsIntoAnnotations( List<BoxAnnotation> annotations, List<Box
/**
* Remove empty output nodes from script (because in BoxLang, classes are only script, so the original CF may have been tags)
*/
@Override
public BoxNode visit( BoxBufferOutput node ) {
if ( isClass ) {
BoxExpression expr = node.getExpression();
Expand All @@ -400,11 +425,11 @@ public BoxNode visit( BoxBufferOutput node ) {

/**
* Add output annotation and set to true if it doesn't exist
*
*
* @param annotations The annotations for the node
*/
private void enableOutput( List<BoxAnnotation> annotations ) {
if ( !annotations.stream().anyMatch( a -> a.getKey().getValue().equalsIgnoreCase( "output" ) ) ) {
if ( annotations.stream().noneMatch( a -> a.getKey().getValue().equalsIgnoreCase( "output" ) ) ) {
// @output true
annotations.add(
new BoxAnnotation(
Expand All @@ -419,7 +444,7 @@ private void enableOutput( List<BoxAnnotation> annotations ) {
/**
* Rename some common CF variables like cfthread.foo to bxthread.foo
* or cfthread[ "foo" ] to bxthread[ "foo" ]
*
*
* @param boxAccess The access node to rename
*/
private void renameTopLevelVars( BoxAccess boxAccess ) {
Expand All @@ -431,4 +456,4 @@ private void renameTopLevelVars( BoxAccess boxAccess ) {
}
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ protected Expression transformDocumentation( List<BoxDocumentationAnnotation> do
protected Expression transformAnnotations( List<BoxAnnotation> annotations, Boolean defaultTrue, boolean onlyLiteralValues ) {
List<Expression> members = new ArrayList<>();
annotations.forEach( annotation -> {
Expression annotationKey = ( Expression ) createKey( annotation.getKey().getValue() );
Expression annotationKey = createKey( annotation.getKey().getValue() );
members.add( annotationKey );
BoxExpression thisValue = annotation.getValue();
Expression value;
Expand All @@ -266,8 +266,10 @@ protected Expression transformAnnotations( List<BoxAnnotation> annotations, Bool
}
members.add( value );
} );

// If we are empty then return a struct
if ( annotations.isEmpty() ) {
return ( Expression ) parseExpression( "new Struct()", new HashMap<>() );
return parseExpression( "new Struct()", new HashMap<>() );
} else {
MethodCallExpr annotationStruct = ( MethodCallExpr ) parseExpression( "Struct.linkedOf()", new HashMap<>() );
annotationStruct.getArguments().addAll( members );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@
import ortus.boxlang.runtime.context.ScriptingRequestBoxContext;
import ortus.boxlang.runtime.dynamic.casters.BooleanCaster;
import ortus.boxlang.runtime.dynamic.javaproxy.InterfaceProxyService;
import ortus.boxlang.runtime.scopes.Key;
import ortus.boxlang.runtime.types.Array;
import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException;
import ortus.boxlang.runtime.types.exceptions.ExpressionException;
Expand Down Expand Up @@ -387,10 +388,28 @@ public MethodHandle lookupPrivateField( Field field ) {
""";
// @formatter:on

/**
* Default annotations that are added to all BoxLang classes
*/
private static final Map<Key, BoxAnnotation> DEFAULT_ANNOTATIONS = Map.of(
Key.of( "accessors" ), new BoxAnnotation(
new BoxFQN( "accessors", null, null ),
new BoxBooleanLiteral( true, null, null ),
null,
null
),
Key.of( "output" ), new BoxAnnotation(
new BoxFQN( "output", null, null ),
new BoxBooleanLiteral( false, null, null ),
null,
null
)
);

/**
* The marker used to indicate that a method should be overridden in the Java class
*/
private static final String EXTENDS_ANNOTATION_MARKER = "overrideJava";
private static final String EXTENDS_ANNOTATION_MARKER = "overrideJava";

/**
* Constructor
Expand All @@ -403,7 +422,6 @@ public BoxClassTransformer( JavaTranspiler transpiler ) {

@Override
public Node transform( BoxNode node, TransformerContext context ) throws IllegalStateException {

BoxClass boxClass = ( BoxClass ) node;
Source source = boxClass.getPosition().getSource();
String packageName = transpiler.getProperty( "packageName" );
Expand All @@ -416,6 +434,7 @@ public Node transform( BoxNode node, TransformerContext context ) throws Illegal
String extendsTemplate = "";
String extendsMethods = "";
String isJavaExtends = "false";
// The list of automatically implemented interfaces
List<String> interfaces = new ArrayList<>();
interfaces.add( "IClassRunnable" );
interfaces.add( "IReferenceable" );
Expand Down Expand Up @@ -537,8 +556,21 @@ public Node transform( BoxNode node, TransformerContext context ) throws Illegal
.getClassByName( className ).orElseThrow()
.getFieldByName( "keys" ).orElseThrow();

/* Transform the annotations creating the initialization value */
Expression annotationStruct = transformAnnotations( boxClass.getAnnotations() );
/**
* Transform the annotations creating the initialization value
* It also includes the default annotations that are added to all BoxLang classes
* if they are not already present
*/
List<Key> foundAnnotationKeys = boxClass.getAnnotations()
.stream()
.map( it -> Key.of( it.getKey().getValue() ) )
.collect( java.util.stream.Collectors.toList() );
DEFAULT_ANNOTATIONS.forEach( ( key, value ) -> {
if ( !foundAnnotationKeys.contains( key ) ) {
boxClass.getAnnotations().add( value );
}
} );
Expression annotationStruct = transformAnnotations( boxClass.getAnnotations() );
result.getResult().orElseThrow().getType( 0 ).getFieldByName( "annotations" ).orElseThrow().getVariable( 0 ).setInitializer( annotationStruct );

/* Transform the documentation creating the initialization value */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ public static Object dereferenceAndInvoke( IClassRunnable thisClass, IBoxContext

// Check for generated accessors
Object hasAccessors = thisClass.getAnnotations().get( Key.accessors );

if ( hasAccessors != null && BooleanCaster.cast( hasAccessors ) ) {
Property getterProperty = thisClass.getGetterLookup().get( name );
if ( getterProperty != null ) {
Expand Down Expand Up @@ -457,8 +458,9 @@ public static IStruct getMetaData( IClassRunnable thisClass ) {
}
}
meta.put( "name", thisClass.getName().getName() );
meta.put( "accessors", false );
meta.put( "accessors", thisClass.getAnnotations().getOrDefault( Key.accessors, false ) );
meta.put( "functions", Array.fromList( functions ) );

// meta.put( "hashCode", hashCode() );
var properties = new Array();
// loop over properties list and add struct for each property
Expand Down
2 changes: 1 addition & 1 deletion src/test/bx/Task.bx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class accessors="true"{
class inject hello="word"{

property foo;
property firstName;
Expand Down
9 changes: 3 additions & 6 deletions src/test/java/TestCases/ScratchPad.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -56,15 +55,13 @@ public void setupEach() {

@DisplayName( "Test it" )
@Test
@Disabled
void testIt() {
// @formatter:off
instance.executeSource(
"""
task = new src.test.bx.Task();
invoke( task, "setFoo", { foo : "bar" } );
result = invoke( task, "getFoo" );
println( result );
task = new src.test.java.TestCases.phase3.MyClassCF();
printLn( getMetadata( task ).accessors )
printLn( getMetadata( task ).functions.asString() )
""", context);
// @formatter:on

Expand Down
2 changes: 1 addition & 1 deletion src/test/java/TestCases/phase3/ClassTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ public void testlegacyMeta() {
assertThat( meta.get( Key.of( "extends" ) ) ).isNull();
assertThat( meta.get( Key.of( "output" ) ) ).isEqualTo( false );
assertThat( meta.get( Key.of( "persisent" ) ) ).isEqualTo( false );
assertThat( meta.get( Key.of( "accessors" ) ) ).isEqualTo( false );
assertThat( meta.get( Key.of( "accessors" ) ) ).isEqualTo( true );
}

@DisplayName( "legacy meta CF" )
Expand Down

0 comments on commit 1d767b1

Please sign in to comment.