Groovy Bitwise Operations Monday, June 3, 2013

long bits = 1L

for (pos in 0..63){
    print pos + ": \t"
    // To set a bit
    bits |= (1L << pos)

    // To unset a bit
    // In this case, unset the one behind the one we just set
    bits &= ~(1L << pos - 1)
    println Long.toBinaryString(bits)
}

Output:

0: 1
1: 10
2: 100
3: 1000
4: 10000
5: 100000
6: 1000000
7: 10000000
8: 100000000
9: 1000000000
10: 10000000000
11: 100000000000
12: 1000000000000
13: 10000000000000
14: 100000000000000
15: 1000000000000000
16: 10000000000000000
17: 100000000000000000
18: 1000000000000000000
19: 10000000000000000000
20: 100000000000000000000
21: 1000000000000000000000
22: 10000000000000000000000
23: 100000000000000000000000
24: 1000000000000000000000000
25: 10000000000000000000000000
26: 100000000000000000000000000
27: 1000000000000000000000000000
28: 10000000000000000000000000000
29: 100000000000000000000000000000
30: 1000000000000000000000000000000
31: 10000000000000000000000000000000
32: 100000000000000000000000000000000
33: 1000000000000000000000000000000000
34: 10000000000000000000000000000000000
35: 100000000000000000000000000000000000
36: 1000000000000000000000000000000000000
37: 10000000000000000000000000000000000000
38: 100000000000000000000000000000000000000
39: 1000000000000000000000000000000000000000
40: 10000000000000000000000000000000000000000
41: 100000000000000000000000000000000000000000
42: 1000000000000000000000000000000000000000000
43: 10000000000000000000000000000000000000000000
44: 100000000000000000000000000000000000000000000
45: 1000000000000000000000000000000000000000000000
46: 10000000000000000000000000000000000000000000000
47: 100000000000000000000000000000000000000000000000
48: 1000000000000000000000000000000000000000000000000
49: 10000000000000000000000000000000000000000000000000
50: 100000000000000000000000000000000000000000000000000
51: 1000000000000000000000000000000000000000000000000000
52: 10000000000000000000000000000000000000000000000000000
53: 100000000000000000000000000000000000000000000000000000
54: 1000000000000000000000000000000000000000000000000000000
55: 10000000000000000000000000000000000000000000000000000000
56: 100000000000000000000000000000000000000000000000000000000
57: 1000000000000000000000000000000000000000000000000000000000
58: 10000000000000000000000000000000000000000000000000000000000
59: 100000000000000000000000000000000000000000000000000000000000
60: 1000000000000000000000000000000000000000000000000000000000000
61: 10000000000000000000000000000000000000000000000000000000000000
62: 100000000000000000000000000000000000000000000000000000000000000
63: 1000000000000000000000000000000000000000000000000000000000000000

Full ZKGrails App: Calling Data Breakout tabs Wednesday, May 29, 2013

This .zul page is dramatically simpler, despite the complexity of the page. The simplicity reflects my improved understanding of how to programmaticly build the ZKGrails component within the Grails composer for this .zul page.



Note: disregard the extra closing tags displayed in the ZUML. Blogger automatically, adds those.

The front-end ZUML code for the complex breakouts tab has been reduced to a single tabbox and tabpanel tag.

See the Grails composer "IntlBillAuditToolComposer.groovy" below.

Note: this .zul page is called from the main .zul page, rendered inside it via an "include" component like this:

    
  
Including the page significantly simplified the design, allowing the content to be easily divided across .zul pages and grails composers. This avoided having one huge grails composer.


    
        

            

                   
               

            
             
              
    


Composer Details


Here is the start of our main "afterCompose()" method, and our call for the data we will need for our data models.

Note: it is feasible to optimize the code to avoid a second call to the DB to retrieve the same data.
def afterCompose = { window ->
        
        /**
         * Prepare date range for 1 year of data
         */
        Calendar cal = Calendar.getInstance()
        cal.add(Calendar.HOUR, +8)
        Calendar cal2 = Calendar.getInstance()
        cal2.add(Calendar.YEAR, -1)
        Date today = cal.getTime()
        Date lastYear = cal2.getTime()

        /**
         * Build Query and get results
         */
        def query = VendorStats.where{
            period in (lastYear .. today)
            vendor.intl == true // Just Domestic
        }
        def results = query.list()
Here we prepare for our later percentage calculations by calling a subroutine which totals our data.
/**
         * Get the totals calculated for all the calls and minutes
         * for
         * L = Lcr (Data from the LCR)
         * A = Actual (Data from the Vendor's bill)
         */
        def totals = getTotals( results )
        def total_L = totals[0]
        def total_A = totals[1]
Here are the details of the "getTotals()" subroutine.
/**
     * Total all the calls and minutes for later percent calculations
     * It takes a collection of "VendorStats"
     * L = LCR value
     * A = Actual bill value
     */
    def getTotals( data ){
        def L = [:]
        def A = [:]
        
        data.each { vs ->
            def month = new SimpleDateFormat("yyyy-MM").format(vs.period)
            def mon = new SimpleDateFormat("MMM").format(vs.period)
            def L_label = "LCR"
            def A_label = "Actual"

            if ( ! L[month] ){ L[month] =   [ "label":L_label, "month":mon, "minutes":0, "cost":0 ] }
            if ( ! A[month] ){ A[month] =   [ "label":A_label, "month":mon, "minutes":0, "cost":0 ] }
         
            if (vs.lcr){ //LCR
                def m = vs.minutes
                def c = vs.cost
                L[month]["minutes"] = L[month]["minutes"] + m
                L[month]["cost"] = L[month]["cost"] + c
                
            } else { //Actual
                def m = vs.minutes
                def c = vs.cost
                A[month]["minutes"] = A[month]["minutes"] + m
                A[month]["cost"] = A[month]["cost"] + c     
            }            
        }
        return [L, A]
    }  
}
Here we compile our main data model in so we can build our various ZK components.
/**
         * Intialize the 4 classes of "Vendor Data" (VD)
         * 
         * I = Int'l value
         * D = Domestic value
         * L = LCR value
         * A = Actual bill value
         */
        def VD = [:]
        def VD_label = "Vendor Data"
        results.each { vs ->
            def vendorName = vs.vendor.name
            def month = new SimpleDateFormat("yyyy-MM").format(vs.period)
            def mon = new SimpleDateFormat("MMM").format(vs.period)
            def L_label = "LCR"
            def A_label = "Actual"
            def L = [:]
            def A = [:]
            
            try {
                if ( ! VD."${month}" ){
                    VD."${month}" = [:]
                }
                if ( ! VD."${month}"."${vendorName}" ){
                    VD."${month}"."${vendorName}" = [:]
                }

                if ( vs.lcr ){ //LCR
                    def lcr = true
                    def cst = vs.cost
                    def min = vs.minutes
                    def sumCost = total_L[month]["cost"]                       
                    def sumMin = total_L[month]["minutes"]
                    def percentCost = cst / sumCost
                    def percentMinutes = min / sumMin
                    L = ["month":month, "mon":mon, "lcr":lcr, 
                         "label2":L_label, "vendorName":vendorName, 
                         "label":VD_label, "cost":cst, "minutes":min, 
                         "percentCost":percentCost, "percentMinutes":percentMinutes, 
                         "sumCost":sumCost, "sumMin":sumMin ]

                    if ( ! VD."${month}"."${vendorName}"."L" ){
                        VD."${month}"."${vendorName}"."L" = [:]
                    }                

                    VD."${month}"."${vendorName}"."L" = L

                } else { //Actual
                    def lcr = false              
                    def cst = vs.cost
                    def min = vs.minutes
                    def sumCost = total_A[month]["cost"]                       
                    def sumMin = total_A[month]["minutes"]
                    def percentCost = cst / sumCost
                    def percentMinutes = min / sumMin                           
                    A = ["month":month, "mon":mon, "lcr":lcr,
                         "label2":A_label, "vendorName":vendorName, 
                         "label":VD_label, "cost":cst, "minutes":min, 
                         "percentCost":percentCost, "percentMinutes":percentMinutes, 
                         "sumCost":sumCost, "sumMin":sumMin ]

                    if ( ! VD."${month}"."${vendorName}"."A" ){
                        VD."${month}"."${vendorName}"."A" = [:]
                    }                     

                    VD."${month}"."${vendorName}"."A" = A
                }
            } catch( NullPointerException e ){
                println "caught: " + e
            }
        }
Here we dynamically build and append our tabbox component with all the needed tabs.
$('#tabbox01').append {
            tabs {
                total_L.each{ m, v ->
                    tab {
                        label = m
                    }
                }
            }    
        }
Here we dynamically build and append our our tabpanels for each month if the data exists. We also fill each of those tabpanels with the rows of data for the appropriate month.
$('#tabpanels01').append {
    total_L.each { m, v ->
        tabpanel {
            grid {
            vflex = 1
            auxhead {
                auxheader {
                    colspan=1
                }
                auxheader {
                    label="LCR"; colspan=2
                }
                auxheader {
                    label="Percentages of Totals"; colspan=2                            
                }                             
                auxheader {
                    label="Actual"; colspan=2
                }
                auxheader {
                    label="Percentages of Totals"; colspan=2                             
                } 
            }

            def list = ["Vendors","Minutes","Cost","% Minutes",
                        "% Cost","Minutes","Cost","% Minutes","% Cost"]
                        
            columns {
                for (i in list){
                    column {
                        label=i; sort="auto"; align="center"
                    }
                }                           
            }

            String moneyPat = "\$##,###.##"
            String minutesPat = "##,###"
            String percentPat = "##.##%"

            def red = "color:red"
. . .(con't) $('#tabpanels01'.append {} call . . .

We build the rows components.
rows {
    VD[m].each { i, j ->
        def vendorName =  i
        def minutes =  j."L"."minutes"
        def cost =  j."L"."cost"
        def percentCost =  j."L"."percentCost"
        def percentMinutes  =  j."L"."percentMinutes"
        def minutes2 =  j."A"."minutes"
        def cost2 =  j."A"."cost"
        def percentCost2 =  j."A"."percentCost"
        def percentMinutes2 =  j."A"."percentMinutes"
                                        
        /**
         * Format values for display
         */                           
        minutes = textFormatService.numberFormatter( minutes, minutesPat )
        cost = textFormatService.numberFormatter( cost, moneyPat )
        
        def form_percentCost = textFormatService.numberFormatter( percentCost, percentPat )
        
        def form_percentMinutes = 
            textFormatService.numberFormatter( percentMinutes, percentPat )

        minutes2 = textFormatService.numberFormatter( minutes2, minutesPat )
        
        cost2 = textFormatService.numberFormatter( cost2, moneyPat )
        
        def form_percentCost2 = 
            textFormatService.numberFormatter( percentCost2, percentPat )
        
        def form_percentMinutes2 = 
            textFormatService.numberFormatter( percentMinutes2, percentPat )
                                       
        row {
            label( value:vendorName )
            label( value:minutes )
            label( value:cost )

            /**
             * Color values red if it is > 100%
             */
            if( percentCost > 1 ){
                label( value:form_percentCost, style:red )
            } else {
                label( value:form_percentCost )
            }
            if( percentMinutes > 1 ){
                label( value:form_percentMinutes, style:red )
            } else {
                label( value:form_percentMinutes )  
            }                                           

            label( value:minutes2 )
            label( value:cost2 )

            /**
             * Color values red if it is > 100%
             */                                    
            if( percentCost2 > 1 ){
                label( value:form_percentCost2, style:red )
            } else {
                label( value:form_percentCost2 )
            }
            if( percentMinutes > 1 ){
                label( value:form_percentMinutes2, style:red )
            } else {
                label( value:form_percentMinutes2 )  
            }                                    

        } //row

Full ZKGrails App: Main Page Zul & Composer Detail

We start by looking at the .zul ZUML component definitions, then following we will see the detail grails composer code found in "BillAuditToolComposer.groovy".




Note: disregard the extra closing tags displayed in the ZUML. Blogger automatically, adds those.

The main page starts with a standard .zul header and "window" component tab.

    
    
        
        
            
        
To wrap all the various kinds of data and charts into tabs, we first define a "tabbox" component, which creates just the visual "tabs" and the label displayed on each. Once those are defined a "tabpanel" is defined, which is rendered under each tab respectively.


    
        
        
        
        
        
        
        
    
We define our first "tabpanels" container component for each of the following "tabpanel" components.

Note: much of the following detail could have been defined programatically inside the grails composer for this zul page. However, this was the first page I started with and was still learning the ins and outs of the ZKGrails builder code. I use more of the programatic logic in the Int'l and Dom 'breakout' pages, which detail the data by month.


    
        
            
            
            
            
                      
        
            
            
            
            
            
            
            
        
        
        
    

The definition of our next tabpanel. It is just a chart of the previous data, and uses the ZK img tag and composer methods show far below. See the ZKGrails builder jQuery-like call $('#intlChart01').

    
                                                    
        
                                
    

The details of this tabpanel are rendered via an "include" component, which calls an entirely different .zul page; a .zul page that has its own grails composer and methods.

    

This begins the definition of our summarized domestic calling data tabpanel.

    
        
            
            
            
            
                      
        
            
            
            
            
            
            
                            
        
        
        
                                                    

We define our domestic data chart tabpanel.

    
                                                    
                                
                            

Again, rather than coding all the complexity of our monthly breakouts, we are just including an entirely separate .zul page that has its own grails composer backing it.

    

We include an entirely separate .zul page, which wraps our Grails scaffolding created CRUD data entry and editing pages.

    

We now close our tabpanels contain component.

Composer Details


The composer contains two major methods for processing and displaying the Domestic and International calling data pulled from the DB. It also, provides several methods for building and displaying the related charts shown in the 2 chart tabs.

Start by injecting a few components into the composer. "Col_month" correspond to ZK components in the corresponding .zul page.
class BillAuditToolComposer extends GrailsComposer {
    def chartService
    def col_month
    def col_month2
    def addDataLabel01 = false
    def addDataLabel02 = false

Most of the backend of the app is defined in the obligatory afterCompose method.

First, get the data we need and build the mail data structure. The app tries to pull a year of data if it exits and uses it to dynamically build the needed ZK components. Since, each row of data on the main page is a monthly summary of information, and because there's "Least Cost Router" (LCR) reported billing information and VoIP vendor reported information, this step is pretty complex.
def afterCompose = { window ->
        
        /**
         * Prepare date range for 1 year of data
         *
         */
        Calendar cal = Calendar.getInstance()
        cal.add(Calendar.HOUR, +8)
        Calendar cal2 = Calendar.getInstance()
        cal2.add(Calendar.YEAR, -1)              
        Date today = cal.getTime()
        Date lastYear = cal2.getTime()

        /**
         * Build Query and get results
         *
         */
        def query = VendorStats.where{
            period in (lastYear .. today)
        }
        def results = query.list()
The data is divided into 4 main structures.
/**
         * Gather data into the models
         * 
         * I = Int'l value
         * D = Domestic value
         * L = LCR value
         * A = Actual bill value
         */ 
        def IL = [:]
        def IA = [:]   
        def DL = [:]
        def DA = [:]
Since the main tabs summarize the International and Domestic calling data, logic is needed to sum the data for each of the the given months.
results.each{ vs ->                
            def month = new SimpleDateFormat("yyyy-MM").format(vs.period)
            def month1 = new SimpleDateFormat("MMM").format(vs.period)
            def IL_label = "Int'l LCR"
            def IA_label = "Int'l Actual"
            def DL_label = "Domestic LCR"
            def DA_label = "Domestic Actual"
            
            if (! IL[month]){ IL[month] = ["label":IL_label, "month":month1, "minutes":0, "cost":0]}
            if (! IA[month]){ IA[month] = ["label":IA_label, "month":month1, "minutes":0, "cost":0]}
            if (! DL[month]){ DL[month] = ["label":DL_label, "month":month1, "minutes":0, "cost":0]}
            if (! DA[month]){ DA[month] = ["label":DA_label, "month":month1, "minutes":0, "cost":0]}

            if (vs.vendor.intl){ //Intl
                if (vs.lcr){ //LCR
                    def m = vs.minutes
                    def c = vs.cost
                    IL[month]["minutes"] = IL[month]["minutes"] + m
                    IL[month]["cost"] = IL[month]["cost"] + c

                } else { //Actual
                    def m = vs.minutes
                    def c = vs.cost
                    IA[month]["minutes"] = IA[month]["minutes"] + m
                    IA[month]["cost"] = IA[month]["cost"] + c 

                }
            } else { //Domestic
                if (vs.lcr){ //LCR
                    def m = vs.minutes
                    def c = vs.cost
                    DL[month]["minutes"] = DL[month]["minutes"] + m
                    DL[month]["cost"] = DL[month]["cost"] + c 

                } else { //Actual
                    def m = vs.minutes
                    def c = vs.cost
                    DA[month]["minutes"] = DA[month]["minutes"] + m
                    DA[month]["cost"] = DA[month]["cost"] + c                    

                }
            }
        }
        

Display the International Call Data


We now process and display Int'l calling data into ZK rows using ZKGrails "rows" & "row" components:
/**
         * Build the Int'l data tab
         *
         */
        $('#rows01').append{
            IL.each{ m, v ->
                def red = "color:red"
                def I_min_diff = 1
                def I_cost_diff = 1
           
                try {
                    if ( IA[m]["minutes"] ){
                        I_min_diff = IL[m]["minutes"] / ( IA[m]["minutes"] )
                    } 
                    if ( IA[m]["cost"] ){
                        I_cost_diff = IL[m]["cost"] / ( IA[m]["cost"] )
                    }

                    String moneyPat = "\$##,###.##"
                    String minutesPat = "##,###"
                    String percentPat = "##.##%"

                    def IL_minutes = numFormer( IL[m]["minutes"], minutesPat )
                    def IL_cost = numFormer( IL[m]["cost"], moneyPat )
                    def IA_minutes = numFormer( IA[m]["minutes"], minutesPat )
                    def IA_cost = numFormer( IA[m]["cost"], moneyPat )
                    def string_I_min_diff = numFormer( I_min_diff, percentPat )
                    def string_I_cost_diff = numFormer( I_cost_diff, percentPat )
The data has just been formatted and now the first row component is built. Note: there is a conditional to color the firgures red if it exceeds a 100%.
row {
                        label(value:m)
                        label(value:IL_minutes)
                        label(value:IL_cost)
                        label(value:IA_minutes)
                        label(value:IA_cost)
                        
                        if( I_min_diff > 1 ){
                            label(value:string_I_min_diff, style:red)
                        } else {
                            label(value:string_I_min_diff)
                        }
                        if( I_cost_diff > 1 ){
                            label(value:string_I_cost_diff, style:red)
                        } else {
                            label(value:string_I_cost_diff)  
                        }

                    }

                } catch( NullPointerException e ){
                    println "caught: " + e
                }                    
            }
            col_month.sort(false)

        }

Display the Domestic Call Data


Next, we process the "Domestic" data, building the row components to be displayed under the domestic tab in the main page.
/**
         * Build the Domestic data tab
         *
         */
        $('#rows02').append{
            DL.each{ m, v ->
                def red = "color:red"                
                def D_min_diff = 1
                def D_cost_diff = 1
                
                try {
                    if ( DA[m]["minutes"] ){
                        D_min_diff = DL[m]["minutes"] / ( DA[m]["minutes"] )
                    }
                    if ( DA[m]["cost"] ){
                        D_cost_diff = DL[m]["cost"] / ( DA[m]["cost"] )
                    }                

                    def moneyPat = "\$##,###.##"
                    def minutesPat = "##,###"
                    def percentPat = "##.##%"

                    def DL_minutes = numFormer( DL[m]["minutes"], minutesPat )
                    def DL_cost = numFormer( DL[m]["cost"], moneyPat )
                    def DA_minutes = numFormer( DA[m]["minutes"], minutesPat )
                    def DA_cost = numFormer( DA[m]["cost"], moneyPat )
                    def string_D_min_diff = numFormer( D_min_diff, percentPat )
                    def string_D_cost_diff = numFormer( D_cost_diff, percentPat )                

                    row {
                        label(value:m)
                        label(value:DL_minutes)
                        label(value:DL_cost)
                        label(value:DA_minutes)
                        label(value:DA_cost)
                        
                        if( D_min_diff > 1 ){
                            label(value:string_D_min_diff, style:red)
                        } else {
                            label(value:string_D_min_diff)
                        }
                        if( D_cost_diff > 1 ){
                            label(value:string_D_cost_diff, style:red)
                        } else {
                            label(value:string_D_cost_diff)  
                        }
                                          
                    }
                
                } catch( NullPointerException e ){
                    println "caught: " + e
                }     
            }
            col_month2.sort(false)
            
        }               

Chart related processes


Here we define the logic for displaying the data labels using the check box component under the Int'l Chart. This features was added because we found the data labels sometimes overlapped, and removing the labels sometimes made it easier to read the chart.
$('#intlDataLabel01').onCheck{
            def id = "#intlChart01"
            
            if ( it.isChecked() ){
                def params = [title:"Int'l: LCR vs Actual Cost", dataLabel:true]
                getChart( IL, IA, id, params )
            } else {
                def params = [title:"Int'l: LCR vs Actual Cost", dataLabel:false]
                getChart( IL, IA, id, params )
            }
        }
        
        $('#intlChart01').append{
            def params = [title:"Int'l: LCR vs Actual Cost", dataLabel:false]
            def chart = chartService.getChart( IL, IA, params )
            def bufferImg = chart.createBufferedImage( 700, 400 )            
            def chartId = $('#intlChart01')
            chartId.setContent( bufferImg )
        }
Now, we compose the charts for the domestic data.
$('#domDataLabel01').onCheck{
            def id = "#domChart01"
            
            if ( it.isChecked() ){
                def params = [title:"Domestic: LCR vs Actual Cost", dataLabel:true]
                getChart( DL, DA, id, params )
            } else {
                def params = [title:"Domestic: LCR vs Actual Cost", dataLabel:false]
                getChart( DL, DA, id, params )
            }
        }
        
        $('#domChart01').append{
            def params = [title:"Domestic: LCR vs Actual Cost", dataLabel:false]
            def chart = chartService.getChart( DL, DA, params )
            def bufferImg = chart.createBufferedImage( 700, 400 )            
            def chartId = $('#domChart01')
            chartId.setContent( bufferImg )
        }                
        
    } //afterCompose()
We have finished our main "afterCompose()" method, and define a couple of utility methods used above.
def getChart( L, A, id, params ){
        def chart = chartService.getChart( L, A, params )
        def bufferImg = chart.createBufferedImage( 700, 400 )            
        def chartId = $( id )
        chartId.setContent( bufferImg )
    }
    
def numFormer( value, pattern ){
        DecimalFormat form = new DecimalFormat(pattern)
        String output = form.format(value)
        return output
    }
    
}

Full ZKGrails App

I work for a company providing a Voice over IP service. We needed a tool to track and audit our billing, i.e., compare what we believe out costs for a given period were, verse what our various service providers say those costs were.

The app uses:
  • Grails 2.2.0
  • ZK 2.1.0
  • jfreechart 1.0.14
First, we will see the layout of the application; then delve into the some of the more interesting ZKGrails, Groovy and Grails elements.

The total data is divided into International and Domestic Calling data, and each of these categories is divided into (Internal) LCR (Least Cost Router) billing data and (external) "Actual" data, which is the data coming from our VoIP calling vendor monthly bills.

The LCR data is basically our internal accounting of the calls made in a given month, i.e., the number of minutes and their cost. The actual data is basically what it cost us to route calls to various phone companies (long distance and international calling providers/vendors)

The data from both internal and external sources is entered by hand into a MySQL database.

This application tries to pull 12 months of data if it exists, categorizes, formats and displays the data in the various tabs.

As stated the 2 main divisions are Int'l and Dom calling data. This data is summarized into monthly totals compiled for all the various domestic and international vendors our company may have used in a given month. Then it is charted across time.

In addition, the data is broken down for each month into the various vendors and displayed for each month.

Finally, there is a data entry and editing tab.


 


Find the implementation details here
Full ZKGrails App: Main Page Zul & Composer Detail

The second tab displays a dynamically drawn chart based on the summarized data.




Here is how the monthly call data is broken down by vendor and displayed.




Find the implementation details here
Full ZKGrails App: Calling Data Breakout tabs

Here is how the Grails scaffolding CRUD interface is wrapped inside the ZKGrails tab. We can enter new vendors.




Here we can enter detailed data for a given vendor.




Again: Find the implementation details in two parts here:
Full ZKGrails App: Main Page Zul & Composer Detail

Full ZKGrails App: Calling Data Breakout tabs

Groovy: Building Nested Maps Friday, May 10, 2013

See Below: Groovy: More Nested Maps

def e = [:]

println "---\n" + e
e."m1" = [:]
e."m2" = [:]
e."m3" = [:]
println "---\n" + e
e."m1"."v1" = [:]
e."m1"."v2" = [:]
e."m1"."v3" = [:]
println "---\n" + e
e."m2"."v1" = [:]
e."m2"."v2" = [:]
e."m2"."v3" = [:]
println "---\n" + e
e."m3"."v1" = [:]
e."m3"."v2" = [:]
e."m3"."v3" = [:]
println "---\n" + e
e."m1"."v1"."L" = [:]
e."m1"."v1"."A" = [:]
e."m1"."v2"."L" = [:]
e."m1"."v2"."A" = [:]
e."m1"."v3"."L" = [:]
e."m1"."v3"."A" = [:]
println "---\n" + e
e."m2"."v1"."L" = [:]
e."m2"."v1"."A" = [:]
e."m2"."v2"."L" = [:]
e."m2"."v2"."A" = [:]
e."m2"."v3"."L" = [:]
e."m2"."v3"."A" = [:]
println "---\n" + e
e."m3"."v1"."L" = [:]
e."m3"."v1"."A" = [:]
e."m3"."v2"."L" = [:]
e."m3"."v2"."A" = [:]
e."m3"."v3"."L" = [:]
e."m3"."v3"."A" = [:]
println "---\n" + e
e."m1"."v1"."L" = [a:1, b:2, c:3]
e."m1"."v1"."A" = [a:4, b:5, c:6]
e."m1"."v2"."L" = [a:7, b:8, c:9]
e."m1"."v2"."A" = [a:10, b:11, c:12]
e."m1"."v3"."L" = [a:13, b:14, c:15]
e."m1"."v3"."A" = [a:16, b:17, c:18]
println "---\n" + e

Output:

[m1:[:]]
---
[m1:[:], m2:[:], m3:[:]]
---
[m1:[v1:[:], v2:[:], v3:[:]], m2:[:], m3:[:]]
---
[m1:[v1:[:], v2:[:], v3:[:]], m2:[v1:[:], v2:[:], v3:[:]], m3:[:]]
---
[m1:[v1:[:], v2:[:], v3:[:]], m2:[v1:[:], v2:[:], v3:[:]], m3:[v1:[:], v2:[:], v3:[:]]]
---
[m1:[v1:[L:[:], A:[:]], v2:[L:[:], A:[:]], v3:[L:[:], A:[:]]], m2:[v1:[:], v2:[:], v3:[:]], m3:[v1:[:], v2:[:], v3:[:]]]
---
[m1:[v1:[L:[:], A:[:]], v2:[L:[:], A:[:]], v3:[L:[:], A:[:]]], m2:[v1:[L:[:], A:[:]], v2:[L:[:], A:[:]], v3:[L:[:], A:[:]]], m3:[v1:[:], v2:[:], v3:[:]]]
---
[m1:[v1:[L:[:], A:[:]], v2:[L:[:], A:[:]], v3:[L:[:], A:[:]]], m2:[v1:[L:[:], A:[:]], v2:[L:[:], A:[:]], v3:[L:[:], A:[:]]], m3:[v1:[L:[:], A:[:]], v2:[L:[:], A:[:]], v3:[L:[:], A:[:]]]]
---
[m1:[v1:[L:[a:1, b:2, c:3], A:[a:4, b:5, c:6]], v2:[L:[a:7, b:8, c:9], A:[a:10, b:11, c:12]], v3:[L:[a:13, b:14, c:15], A:[a:16, b:17, c:18]]], m2:[v1:[L:[:], A:[:]], v2:[L:[:], A:[:]], v3:[L:[:], A:[:]]], m3:[v1:[L:[:], A:[:]], v2:[L:[:], A:[:]], v3:[L:[:], A:[:]]]]

Groovy: More Nested Maps

def d = [
            m1:[ 
                    v1:[ 
                            L:[ a:1, b:2, c:3 ], 
                            A:[ a:4, b:5, c:6 ] 
                       ], 
                    v2:[ 
                            L:[ a:7, b:8, c:9 ], 
                            A:[ a:10, b:11, c:12 ] 
                       ], 
                    v3:[ 
                            L:[ a:13, b:14, c:15 ], 
                            A:[ a:16, b:17, c:18 ] 
                       ] 
               ], 
            m2:[ 
                    v1:[ 
                            L:[ a:19, b:20, c:21 ], 
                            A:[ a:22, b:23, c:24 ] 
                       ], 
                    v2:[ 
                            L:[ a:25, b:26, c:27 ], 
                            A:[ a:28, b:29, c:30 ] 
                       ], 
                    v3:[ 
                            L:[ a:31, b:32, c:33 ], 
                            A:[ a:34, b:35, c:36 ] 
                       ] 
               ], 
            m3:[ 
                    v1:[ 
                            L:[ a:37, b:38, c:39 ], 
                            A:[ a:40, b:41, c:42 ] 
                       ], 
                    v2:[ 
                            L:[ a:43, b:44, c:45 ], 
                            A:[ a:46, b:47, c:48 ] 
                       ], 
                    v3:[ 
                            L:[ a:49, b:50, c:51 ], 
                            A:[ a:52, b:53, c:54 ] 
                       ] 
               ]
        ]

println d

println "---"
println d."m1"."v2"."L"."a"
println d."m2"."v3"."A"."c"
println d."m3"."v1"."A"."b"

Output:

[m1:[v1:[L:[a:1, b:2, c:3], A:[a:4, b:5, c:6]], v2:[L:[a:7, b:8, c:9], A:[a:10, b:11, c:12]], v3:[L:[a:13, b:14, c:15], A:[a:16, b:17, c:18]]], m2:[v1:[L:[a:19, b:20, c:21], A:[a:22, b:23, c:24]], v2:[L:[a:25, b:26, c:27], A:[a:28, b:29, c:30]], v3:[L:[a:31, b:32, c:33], A:[a:34, b:35, c:36]]], m3:[v1:[L:[a:37, b:38, c:39], A:[a:40, b:41, c:42]], v2:[L:[a:43, b:44, c:45], A:[a:46, b:47, c:48]], v3:[L:[a:49, b:50, c:51], A:[a:52, b:53, c:54]]]]
---
7
36
41

Groovy: Nested Maps Thursday, May 9, 2013

def map = [:]

map["a"] = ["b":1]

println map["a"]["b"]
println map."a"."b"

// Can't assign using dot notation
//map."a"."b" = 1  // Cannot set property 'b' on null object

map = ["a":["b":["c":1]]]
map."a"."b"."c"

// Nested lists
map = ["a":["b":["c":[1,2,3],"d":[4,5,6] ] ] ]

println map."a"."b"."c"
println map."a"."b"."d"[2] // Calling index of array

// Nested maps
map = ["a":["b":["c":["c1":1,"c2":2],"d":["d1":4,"d2":5] ] ] ]

println map."a"."b"."c"."c2"
println map."a"."b"."d"."d1"