Thursday 31 July 2014

CFML switches: how they're implemented in the underlying Java

G'day:
This is just a quick follow-up tot he earlier article "Cool (I think?) Railo can have dynamic case values". Michael was wondering out loud what the CFML gets compiled down to in Java (well: he was talking bytecode, but samesame).

I knocked-out a quick generic switch in CFML:

// simpleSwitch.cfm
switch(URL.option){
    case "a": 
        result = 1;
    break;
    case "b": 
        result = 2;
    break;
    case "c": 
        result = 3;
    break;
}

I ran this through both Railo and CF, and this is the relevant part of the decompiled code:

// simpleSwitchRailo.java
public final void call(PageContext paramPageContext) throws Throwable {
    if (1 != 0)    {
        ArrayImpl localArrayImpl = new ArrayImpl();
        localArrayImpl.append("a");
        localArrayImpl.append("b");
        localArrayImpl.append("c");

        int i = ArrayUtil.find(localArrayImpl, paramPageContext.urlScope().get(this.keys[0]));

        switch (i) {
            case 1:
                paramPageContext.us().set(KeyConstants._RESULT, ConstantsDouble._1);
            break;
            case 2:
                paramPageContext.us().set(KeyConstants._RESULT, ConstantsDouble._2);
            break;
            case 3:
                paramPageContext.us().set(KeyConstants._RESULT, ConstantsDouble._3);
            break;
        }
    }
}

Here Railo sticks the case values into an array, finds the selected on, and then does a switch on that value. Simple!

// simpleSwitchCf11.java
public final class cfsimpleSwitch2ecfm1789124596 extends CFPage {
    private Variable RESULT;
    private static final FastHashtable __HTSWT_0;
    public static final Object metaData;

    static {
        __HTSWT_0 = new SwitchTable()
            .addStringCase("A", 0)
            .addStringCase("C", 2)
            .addStringCase("B", 1)
        ;
        // etc
    }

    // etc

    protected final Object runPage() {
        Object value;
        JspWriter out = this.pageContext.getOut();
        Tag parent = this.parent;
        bindImportPath("com.adobe.coldfusion.*");
        switch (CfJspPage.__caseValue(__HTSWT_0, _resolveAndAutoscalarize("URL", new String[] { "OPTION" }))) {
            case 0:
                this.RESULT.set("1");
            break;
            case 1:
                this.RESULT.set("2");
            break;
            case 2:
                this.RESULT.set("3");
            break;
            }
            return null;
        }
}
Compared to the Railo version, ColdFusion seems a bit over-engineered: they've got a special SwitchTable class which they populate, then they do some mostly impenetrable jiggery pokery in the switch statement, which I presume is the equivalent of the Railo approach, and it's back to an integer-based switch after that.

I wonder if the (seemingly) heavy-handed Adobe approach is why CF tends to be slower than Railo... it seems to be doing an awful lot of work for something that Railo have demonstrated as being quite simple.

Anyway, that's all I have to say here, I just wanted to follow-up Michael's comment.


Update:

As per Michaels' coment below, I did not demonstrate a coupla <cfswitch> options: multiple case values, and different delimiters for same. For the sake of completeness, I knocked-together this code:

<!--- simpleSwitchWithMultipleCaseValues.cfm --->
<cfswitch expression="#URL.option#">
    <cfcase value="a">
        <cfset result = 1>
    </cfcase>
    <cfcase value="b,c">
        <cfset result = 2>
    </cfcase>
    <cfcase value="d;e" delimiters=";">
        <cfset result = 3>
    </cfcase>
    <cfdefaultcase>
        <cfset result = 4>
    </cfdefaultcase>
</cfswitch>

And the relevant bits of the decompile is as follows. Railo:

// simpleSwitchWithMultipleCaseValues_Railo.java
public final void call(PageContext paramPageContext) throws Throwable {
    String str = Caster.toString(paramPageContext.urlScope().get(this.keys[0]));
    
    if ((ListUtil.listFindForSwitch("a", str, ",") == -1 ? 0 : 1) != 0) {
        paramPageContext.us().set(KeyConstants._RESULT, ConstantsDouble._1);
    }
    else if ((ListUtil.listFindForSwitch("b,c", str, ",") == -1 ? 0 : 1) != 0) {
        paramPageContext.us().set(KeyConstants._RESULT, ConstantsDouble._2);
    }
    else if ((ListUtil.listFindForSwitch("d;e", str, ";") == -1 ? 0 : 1) != 0) {
        paramPageContext.us().set(KeyConstants._RESULT, ConstantsDouble._3);
    } else {
        paramPageContext.us().set(KeyConstants._RESULT, ConstantsDouble._4);
    }
}

Interesting. So Railo, in this case, switches to using an else/if construct.

And CF:

// simpleSwitchWithMultipleCaseValues_CF11.java

// etc

    static {
        __HTSWT_0 = new SwitchTable()
            .addStringCase("A", 0)
            .addStringCase("E", 4)
            .addStringCase("D", 3)
            .addStringCase("C", 2)
            .addStringCase("B", 1)
        ;
        metaData = new AttributeCollection(new Object[0]);
    }


// etc

    switch (CfJspPage.__caseValue(__HTSWT_0, _resolveAndAutoscalarize("URL", new String[] { "OPTION" }))) {
            case 0:
                this.RESULT.set("1");
            break;
            case 1:
            case 2:
                this.RESULT.set("2");
            break;
            case 3:
            case 4:
                this.RESULT.set("3");
            break;
            default:
                this.RESULT.set("4");
            break;
        }
        return null;
    }
}

So CF the CF treatment is the same as before. The multiple cases are not reflected in the generated Java... they're parsed out before hand it seems. In this case, the CF approach makes more sense to me (the earlier comment about a cluttered approach notwithstanding).

Anyway, this topic is well and truly now beyond my levels of "giving a shit", so whatever other twists anyone comes up with... research it yerself.

Righto.

--
Adam