Ngôn ngữ lập trình Groovy - phần 1

Groovy là ngôn ngữ lập trình chạy theo mô hình lập trình chức năng (functional programming) thực thi các câu lệnh trên JVM (Java Virtual Machine - máy ảo Java). Để tự động hóa các nghiệp vụ kinh doanh trong phần mềm ERP, như trong Apache Ofbiz, có rất nhiều đoạn chương trình viết bằng *.groovy để thực hiện các nghiệp vụ một cách tự động theo nhiều bước. Cú pháp viết file build.gradle là sử dụng cú pháp của Apache Groovy.

Môi trường phát triển

  • IntelliJ IDEA 2020.2.3 (Ultimate Edition)
  • Windows 10 x64 version 2004 - Enterprise edition
  • Groovy 3.0.6
  • JDK 1.8

Ví dụ: File \ofbiz-framework\applications\order\groovyScripts\allocationplan\CreateAllocationPlan.groovy (Apache Ofbiz version 17.12.04, bản quyền Apache Software Foundation)

import org.apache.ofbiz.entity.condition.EntityOperator
import org.apache.ofbiz.entity.condition.EntityCondition
import org.apache.ofbiz.order.order.OrderReadHelper
import org.apache.ofbiz.party.party.PartyHelper

allocationPlanInfo = [:]
itemList = []
isPlanAlreadyExists = false
productId = parameters.productId
planName = parameters.planName

if (productId) {
    orderedQuantityTotal = 0.0
    orderedValueTotal = 0.0
    reservedQuantityTotal = 0.0

    ecl = EntityCondition.makeCondition([
                            EntityCondition.makeCondition("productId", EntityOperator.EQUALS, productId),
                            EntityCondition.makeCondition("statusId", EntityOperator.IN, ["ALLOC_PLAN_CREATED", "ALLOC_PLAN_APPROVED"]),
                            EntityCondition.makeCondition("planTypeId", EntityOperator.EQUALS, "SALES_ORD_ALLOCATION")],
                        EntityOperator.AND)
    allocationPlanHeader = from("AllocationPlanHeader").where(ecl).queryFirst()
    if (allocationPlanHeader == null) {
        ecl = EntityCondition.makeCondition([
                                EntityCondition.makeCondition("productId", EntityOperator.EQUALS, productId),
                                EntityCondition.makeCondition("orderStatusId", EntityOperator.EQUALS, "ORDER_APPROVED"),
                                EntityCondition.makeCondition("orderTypeId", EntityOperator.EQUALS, "SALES_ORDER")],
                            EntityOperator.AND)
        orderAndItemList = from("OrderHeaderAndItems").where(ecl).queryList()
        orderAndItemList.each { orderAndItem ->
            itemMap = [:]
            salesChannelEnumId = orderAndItem.salesChannelEnumId
            itemMap.salesChannelEnumId = salesChannelEnumId
            salesChannel = from("Enumeration").where("enumId", salesChannelEnumId).queryOne()
            if (salesChannel) {
                itemMap.salesChannel = salesChannel.description
            }

            orh = new OrderReadHelper(delegator, orderAndItem.orderId)
            placingParty = orh.getPlacingParty()
            if (placingParty != null) {
                itemMap.partyId = placingParty.partyId
                itemMap.partyName = PartyHelper.getPartyName(placingParty)
            }

            itemMap.orderId = orderAndItem.orderId
            itemMap.orderItemSeqId = orderAndItem.orderItemSeqId
            itemMap.estimatedShipDate = orderAndItem.estimatedShipDate

            unitPrice = orderAndItem.unitPrice
            cancelQuantity = orderAndItem.cancelQuantity
            quantity = orderAndItem.quantity
            if (cancelQuantity != null) {
                orderedQuantity = quantity.subtract(cancelQuantity)
            } else {
                orderedQuantity = quantity
            }
            orderedValue = orderedQuantity.multiply(unitPrice)
            orderedQuantityTotal = orderedQuantityTotal.add(orderedQuantity)
            orderedValueTotal = orderedValueTotal.add(orderedValue)
            itemMap.orderedQuantity = orderedQuantity
            itemMap.orderedValue = orderedValue

            // Reserved quantity
            reservedQuantity = 0.0
            reservations = from("OrderItemShipGrpInvRes").where("orderId", orderAndItem.orderId, "orderItemSeqId", orderAndItem.orderItemSeqId).queryList()
            reservations.each { reservation ->
                if (reservation.quantity) {
                    reservedQuantity += reservation.quantity
                }
            }
            reservedQuantityTotal = reservedQuantityTotal.add(reservedQuantity)
            itemMap.reservedQuantity = reservedQuantity
            itemList.add(itemMap)
        }
    } else {
        isPlanAlreadyExists = true
    }
    allocationPlanInfo.orderedQuantityTotal = orderedQuantityTotal
    allocationPlanInfo.orderedValueTotal = orderedValueTotal
    allocationPlanInfo.reservedQuantityTotal = reservedQuantityTotal
}
allocationPlanInfo.isPlanAlreadyExists = isPlanAlreadyExists
allocationPlanInfo.itemList = itemList
context.allocationPlanInfo = allocationPlanInfo

Đoạn code trên tạo kế hoạch phan bổ dựa trên Mã sản phẩm, Mã trạng thái, Mã kế hoạch; phân bổ đơn hàng vào các kênh bán hàng với số lượng cụ thể. Liên kết tải về https://dl.bintray.com/groovy/maven/apache-groovy-sdk-3.0.6.zip (71,2 MB), giải nén ra (thành khoảng 242 MB). Source code: https://github.com/apache/groovy Groovy 3.0.6 yêu cầu phải có JDK phiên bản từ 1.8 trở lên. Trong thư mục sau giải nén, tìm thư mục bin , giả sử D:\tools\groovy-3.0.6\bin , khai báo đường dẫn này trong biến môi trường của Windows.

Chạy lệnh kiểm tra phiên bản của Groovy

groovy --version

Kết quả là

Picked up _JAVA_OPTIONS: -Xmx512M
Groovy Version: 3.0.6 JVM: 1.8.0_261 Vendor: Oracle Corporation OS: Windows 10

Khai báo Groovy SDK trong IntelliJ IDEA 2020

Viết chương trình Groovy đầu tiên, file Hello.groovy

println("Hello, Hanoi!");

Kết quả:

Hello, Hanoi!

Xin chúc mừng, vậy là bạn đã viết chương trình Groovy đầu tiên.

Các từ khóa (keyword) trong Groovy là as, assert, break, case, catch, class, const, continue, def, default, do, else, enum, extends, false, finally, for, goto, if, implements, import, in, instanceof, interface, new, null, package, return, super, switch, this, throws, throw, trait, true, try, var, while .

Các định danh hợp lệ và không hợp lệ

# Hợp lệ
def name
def item3
def with_under_score
def $startByDollarSign

// Không hợp lệ
def 3abc
def a+b
def a#b

// Hợp lệ
foo.as2
foo.asertAA
foo.breakXYZ
foo.case99
foo.catch42

Kiểu dữ liệu map

def aha = [:]
aha.'xx' = "XXX many XX";
aha.'girl' = "BIG GIRL";

assert aha.'xx' == "XXX many XX";
assert aha.'girl' == 'BIG GIRL'

Quy định đặt tên định danh: Bắt đầu bởi chữ cái, ký hiệu dollar sign, hoặc dấu gạch dưới. Không được phép bắt đầu bởi số.

Chữ cái là các ký tự nằm trong khoảng

  • từ a đến z (ký tự ASCII viết thường)
  • từ A đến Z (ký tự ASCIII viết hoa)
  • từ \u00c0 đến \u00d6
  • từ \u00d8 đến \u00f6
  • từ \u00f8 đến \u00ff
  • từ \u0100 đến \ufffe

Một số định danh đặc biệt

// https://www.fileformat.info/info/unicode/char/00c0/index.htm 
def À = 1                                                       
assert À == 1                                                   

Cách sử dụng map

File Hello.groovy có nội dung

map = [:]

map.'''triple abc'''
map."""triple xyz"""
map./love string/
map.$/doll love string/$

def something = [:]
def firstName = "Van"
something."NguyenBich-${firstName}" = "Nguoi yeu cu"
assert something."NguyenBich-Van" == "Nguoi yeu cu"
println(something.'NguyenBich-Van')
println(something."NguyenBich-Van")

Lưu ý với cách đặt tên key trong kiểu dữ liệu map, những thứ dưới đây là cho phép

foo = [:]
foo./yeu em/ = "Yêu em Vân"
foo.$/bo em trang/$ = "Bỏ em Trang"

Chạy lệnh

groovy Hello.groovy

Kết quả là

Picked up _JAVA_OPTIONS: -Xmx512M
Nguoi yeu cu
Nguoi yeu cu

Cái này gọi là Groovy GString, khả năng nội suy xâu ký tự của Groovy. Javadoc https://docs.groovy-lang.org/latest/html/api/groovy/lang/GString.html

Các ký tự đặc biệt cho phép trong string là

  • \b
  • \f
  • \n
  • \r
  • \s
  • \\
  • \'
  • \"

Ví dụ liên quan đến GString

def sum = "The sum of 2 and 3 equals ${2 + 3}"
assert sum.toString() == 'The sum of 2 and 3 equals 5'

def sum2 = "The sum of 2 and 3 equals ${def a = 1; def b = 6; a + b}"
assert sum2.toString() == 'The sum of 2 and 3 equals 7'

def nguoi_nao_do = [ten: 'Van', tuoi: 33]
assert "$nguoi_nao_do.ten hien tai $nguoi_nao_do.tuoi tuoi" == "Van hien tai 33 tuoi"

def so_pi = 3.1415926535

def sParameterLessClosure = "1 + 2 == ${-> 3}"
assert sParameterLessClosure == '1 + 2 == 3'

def sOneParameterClosure = "1 + 2 == ${w -> w << 3}"
assert sOneParameterClosure == '1 + 2 == 3'

Ví dụ liên quan đến eager GString và lazy GString

def number = 1 // Mục 1
def eagerGString = "value == ${number}"
def lazyGString = "value == ${-> number}"

assert eagerGString == "value == 1" // Mục 2
assert lazyGString == "value == 1" // Mục 3

number = 2  // Mục 4
assert eagerGString == "value == 1" // Mục 5
assert lazyGString == "value == 2" // Mục 6
  • Mục 1. Định ngĩa biến number chứa giá trị 1 ở đó chúng ta nội suy bên trong 2 GString, như là một biểu thức trong eagerGString và một closure trong lazyGString .
  • Mục 2. Chúng ta kỳ vọng rằng xâu kết quả chứa cùng giá trị là 1 cho eagerGString
  • Mục 3. Chúng ta kỳ vọng rằng xâu kết quả chứa cùng giá trị là 1 cho lazyGString
  • Mục 4. Sau đó, chúng ta thay đổi giá trị của biến thành một giá trị khác.
  • Mục 5. Với biểu thức nội suy thuần, giá trị thực sự ràng buộc vào thời điểm khởi tạo GString.
  • Mục 6. Nhưng với biểu thức closure, closure được gọi dựa trên mỗi lần ép giá trị của GSTring vào String, kết quả là một chuỗi được cập nhật chưuá giá trị số mới.

Một số dạng đặc biệt của string

def fooPattern = /.*chu_cuoi.*/
assert fooPattern == '.*chu_cuoi.*'

def escapeSlash = /The character \/ is a forward slash/
assert escapeSlash == 'The character / is a forward slash'

def nhieu_dong_chuoi_tron = /mot
hai
ba
bon/

assert nhieu_dong_chuoi_tron.contains('\n')

def mau_sac = 'xanh'
def noi_suy_chuoi_tron = /mot chiec xe mau ${mau_sac}/

assert noi_suy_chuoi_tron == 'mot chiec xe mau xanh'

All Rights Reserved