Lớp SourceFormat.java cho phép ta có thể đọc những file.java và đưa chúng về một định dạng chuẩn.
Hàm khởi tạo public SourceFormat(String str): với tham số truyền vào là tên file .java muốn xử lý.
Hàm public void readFile(): để đọc nội dung các file được truyền vào và sinh ra ngoại lệ nếu như không tìm được file truyền vào.
comment trong file .java có định dạng là: /* <nội dung comment> */ hoặc comment cả dòng, // <dòng 1>.
Hàm public void makeSpaces(): Tạo ra các khoảng trắng tại 2 bên của các kí tự ( , ) , { , } , + , - , * , / , : , < , > và ,
Hàm public String solveString(String s): Xử lý từng dòng được đọc vào ở hàm
readFile().
Hàm public String returnString(): trả về nội dung code dưới dạng String trong file.java sau khi đã được chuẩn hóa.
4.4.2 Lớp RInterface.java
Lớp Rinterface.java lưu trữ cấu trúc dữ liệu của một relational interface. Bao gồm: tên của relational interface là String name, danh sách tên input dưới dạng
ArrayList<String> inputs, danh sách tên ouput dưới dạng ArrayList<String> outputs. Quan hệ String relation ở dạng chuẩn tắc tuyển của tập các biến, mỗi biến này đại diện cho một biểu thức (ràng buộc). Ta sử dụng Hashtable để lưu trữ từng cặp <biến, biểu thức> tương ứng.
Hàm khởi tạo RInterface(JavaMethod jm, int pos): Đây là hàm chính của lớp này. Với tham chiếu truyền vào là JavaMethod không phải là hàm main, hàm sẽ tạo ra một relational interface đại diện cho phương thức jm của lớp. Tham chiếu pos nói lên vị trí của phương thức jm trong lớp, điều này phục vụ cho việc đặt tên giá trị trả về.
Do công cụ mới chỉ giải quyết được trên các phương thức với nội dung đơn giản cho nên thuật toán của hàm này như sau:
Đầu tiên, hàm sẽ tiến hành việc kiểm tra xem JavaMethod jm có phải là hàm
main không bằng cách so sánh tên của jm. Nếu đúng thì return, còn nếu sai thì thực hiện tiếp chương trình.
Tên của relational interface tạo ra trùng với tên phương thức jm. Input của relational interface mới chính là các tham chiếu của jm.
Tên của output được đặt là y + <pos>. (ví dụ y1, y2, …).
Quan hệ chính là 1 biểu thức với vế trái là output, vế phải là biểu thức trả về, được ký hiệu là rx. Nếu như trong phương thức chứa câu lệnh điều khiển if…else hoặc if… thì mỗi một điều kiện của if hoặc else cũng được coi như là một quan hệ, được kí
hiệu là rif, relse. Lúc này quan hệ trả về sẽ là rif ∧ rx1 ∨ relse ∧ rx2 nếu như câu lệnh điều
khiển là if…else hoặc rif & rx1 nếu như câu lệnh điều khiển chỉ có if. Với rx1 và rx2 là quan hệ bên trong khối { } của if và else.
4.4.3 Lớp JavaFile.java
Lớp JavaFile.java cho phép ta đọc file .java và phân tích nội dung code của file này. Chuyển chúng về cấu trúc dữ liệu JavaClass.
Hàm khởi tạo public JavaFile(String sourceFiles): Tham số truyền vào là tên
của file. Sau đó chương trình sẽ tiến hành phân tích các từ tố (token), tách chúng thành từng lớp khác nhau và tiến hành xử lý trong các lớp. Các từ tố được lưu trữ dưới dạng một ArrayList<String>.
Hàm private void stringTokenizer(): Bằng việc sử dụng lớp StringTokenizer có trong Java, hàm nay sẽ tiến hành tách code của file java ( dưới dạng String ) thành một
ArrayList<String> tokens.
Hàm private ArrayList<JavaClass> parse(): Mục đích của hàm này là tìm các lớp có trong code của file .java. Từ mảng các từ tố, chương trình sẽ tìm những từ tố có giá trị là “Class” rồi từ đó phân tích tên, cũng như nội dung bên trong cặp { … } của lớp
private ArrayList<JavaClass> parse() {
ArrayList<JavaClass> _jcs = new ArrayList<JavaClass>();
while (pos < tokens.size()) { if (tokens.get(pos).equals(Keyword.CLASS)) { //< xử lý bên trong> } else { pos++; } } return _jcs; }
Hình 3.1: Hàm parse() trong lớp JavaFile.java
4.4.4 Lớp JavaClass.java
Lớp JavaClass mô tả cấu trúc của một class trong java. Bao gồm tên, danh sách các thuộc tính, danh sách các phương thức có trong lớp. Một số hàm chính trong lớp:
Hàm khởi tạo public JavaClass(): khởi tạo các giá trị thuộc tính.
Hàm public void parse(): Sử dụng kiến thức về cấu trúc ngữ pháp trong java,
hàm này có nhiệm vụ phân tích nội dung của class. Nội dung được phân tích là
ArrayList<String> contents. Để tách ra các thuộc tính hay các phương thức, ta đi từ những từ tố có giá trị là các mức độ truy cập như là: public, protected, private. Sau đó ta sẽ phân tích các từ tố tiếp theo để nhận biết ra đó là thuộc tính hay phương thức. Nếu một trong các từ tố tiếp theo có giá trị là “;” thì đó là thuộc tính, còn nếu có giá trị là “(” thì đó là phương thức.
Ví dụ: khai báo một thuộc tính là private static final int x = 2; hoặc private int x;
, còn khai báo một phương thức là: public int add (int a, int b) { <nội dung của phương thức nếu có> }
int pos = 0;
int size = contents.size(); while (pos < size) {
if (Keyword.isVisibilityKeyword(contents.get(pos))) { String visibility = contents.get(pos);
boolean isMethod = true;
for (int i = pos + 1; i < size; i++) { if (contents.get(i).equals(";")) { isMethod = false; break; } else if (contents.get(i).equals("(")) { break; } }
if (isMethod) { // qua trinh xu ly neu la phuong thuc
// …
} else { // qua trinh xu ly neu la thuoc tinh
// … } } pos++; } }
Hình 4.2: Hàm parse() trong lớp JavaClass.java
Tuy nhiên, hạn chế của cách làm này là dữ liệu đưa vào phải là được định dạng chuẩn, tức là phải có đầy đủ mức độ truy cập cho mỗi thuộc tính và phương thức.
Ngoài ra còn hàm public void display() được sử dụng để hiển thị thông tin của
mỗi class.
4.4.5 Lớp Tools.java
Đây là một trong những lớp quan trọng của công cụ, chứa những phương thức phục vụ cho việc giải quyết bài toán.
Hai biến toàn cục public static int numOfRelation và public static int numOfInput được dùng để lưu số lượng quan hệ và input được tạo ra. Chủ yếu phục
input giữa các interface sau khi chuyển đổi từ JavaClass sang Releational Interface, hàm này được tạo nên để đổi tên các tham chiếu của các phương thức.
Cách thức hoạt động của hàm này như sau: đầu tiên ứng với mỗi tham chiếu, ta
sẽ có một tên mới. Quy tắc đặt tên mới là: <tên mới> ≔ “x” + <numOfInput>. Sau
đó các cặp <tên mới> và <tên cũ> được lưu trữ trong Hashtable, với key là <tên cũ> và value là <tên mới>. Tiếp đó, ta tiến hành đổi tên trong nội dung của các phương thức dựa vào Hashtable đã tạo.
public static void rename(JavaMethod jm) { Hashtable name = new Hashtable();
// them ten, ten moi cua method vao hashtable ArrayList<JavaParameter> jp = jm.parameters; for (int j = 0; j < jp.size(); j++) {
numOfInput++;
String newName = "x" + String.valueOf(numOfInput); name.put(jp.get(j).name, newName);
jp.get(j).setName(newName); }
ArrayList<String> contents = jm.contents; for (int j = 0; j < contents.size(); j++) { String str = contents.get(j);
if (name.containsKey(str)) {
contents.set(j, (String) name.get(str)); }
}
name.clear(); }
Hình 4.3: Hàm Tools.rename(JavaMethod jm)
Hàm public static RInterface parse (ArrayList <String> arr, ArrayList <RInterface> ris): Hàm này được dùng để phân tích thứ tự kết hợp giữa các interface
với tham chiếu truyền vào là công thức kết hợp dưới dạng ArrayList<String>, và ArrayList<Rinterface>. Hàm sẽ trả về relational interface đã được kết hợp.
Ví dụ như truyền vào ArrayList<String> là “cong ( tru ( a , b ) , nhan ( a , b ) )” với ris bao gồm các Rinterface là { cong, tru, nhan chia }.
Thuật toán được sử dụng để phân tích chuỗi kết hợp này là như sau.
Bước 1: Ta sẽ đánh mức độ ưu tiên tính toán cho mỗi thành phần (thành phần là tên của relational interface hoặc tên của tham chiếu trong ArrayList<String> arr, bỏ qua: ‘(‘, ‘)’ và ‘,’).
Độ ưu tiên của mỗi thành phần ≔ <số lượng dấu ‘(‘ đứng trước nó> - <số lượng
dấu ‘)’ đứng trước nó>.
Đưa tất cả thành phần một danh sách, và đưa độ ưu tiên tương ứng của chúng vào một danh sách khác. Với ví dụ trên, ta sẽ có danh sách các thành phần arr1 = [cong, tru, a, b, nhan, a, b] , tương ứng mỗi thành phần ta có danh sách độ ưu tiên arr2 = [0, 1, 2, 2, 1, 2, 2]. Điều này có nghĩa là độ ưu tiên của cong là 0, độ ưu tiên của tru và nhan
là 1, độ ưu tiên của a và b trong cả 2 lần xuất hiện là 2. Việc xử lý bài toán sẽ đi từ độ ưu tiên cao nhất.
Gọi max là độ ưu tiên có giá trị lớn nhất. Như vậy, với danh sách trên thì ban đầu giá trị của max là 2.
Bước 2: Ta tiến hành duyệt danh sách từ độ ưu tiên từ đầu tới cuối. Giả sử tại vị trí i của danh sách, giá trị của độ ưu tiên này là max-1, tại những vị trí tiếp theo: i+1, …i + n, độ ưu tiên đều có giá trị là max ( độ ưu tiên tại vị trí i+n+1 có giá trị khác max). Ứng với mỗi vị trí từ i đến i+n trong danh sách thành phần, ta có danh sách các Rinterface là ri, ri+1, … ri+n. Chú ý rằng ri sẽ là null nếu như thành phần ở vị trí i trong danh sách các thành phần là tham chiếu (không phải là interface). Sau đó ta tiến hành kết hợp các interface này bằng việc sử dụng hàm Tools.compose (hàm compose sẽ được mô tả bên dưới) để lấy về interface đã được kết hợp r. Trong danh sách thành phần, đổi lại relational interface ở vị trí i thành r. Trong danh sách độ ưu tiên, xóa bỏ những độ ưu tiên có giá trị là max ra khỏi danh sách. Sau đó tiến hành bước 3.
Bước 3: max ≔ max – 1, với max ≥ 0 quay trở lại bước 2. Ngược lại chuyển
sang bước 4.
Bước 4: Trả về interface mới được tạo ra từ việc kết hợp trên.
Hàm public static RInterface compose (RInterface ri, ArrayList <RInterface> iarr): Tham chiếu của hàm ArrayList <RInterface> iarr cho ta biết, danh sách interface iarr là những interface song song, được kết nối vào interface ri. Vị trí kết nối của mỗi interface iarr[i] trong iarr với interface ri chính là thứ tự của i của
iarr. Điều này có nghĩa là interface iarr[i] được kết nối vào input thứ i của ri. Nếu iarr[i] là null thì không có kết nối nào ở cổng input tương ứng.
Đầu tiên, ta sẽ tiến hành kết nối song song tất cả các interface có trong iarr, tạo ra interface mới là para. Interface mới này có input là hợp của tất cả các input có trong
iarr, output là hợp của tất cả các output có trong iarr, quan hệ ξ là hợp của tất cả quan hệ có trong iarr.
Sau đó, hàm sẽ thực hiện việc kết hợp interface para với interface ri. Phép kết hợp được thực hiện ở đây là phép kết hợp bằng kết nối. Hàm này còn sử dụng 2 hàm là
getInputAssumptionFormula và getPHIformula để phục vụ cho việc tính toán. Cách thức kết hợp đã được nêu ra trong phần 4.3.3.
Hàm public static JavaMethod getMainMethod(JavaClass jc): nhiệm vụ của
hàm này là lấy ra được phương thức main trong JavaClass. Cách thức hoạt động của hàm này đơn giản chỉ là so sánh tên từng JavaMethod trong JavaClass với “main”, nếu có trường hợp thỏa mãn thì trả về JavaMethod đó, ngược lại trả về null.
Hàm public static RInterface getRInterfaceByName (String name, ArrayList
<RInterface> ris): với tham chiếu truyền vào là name và ris, hàm sẽ tiến hành tìm trong danh sách RInterface ris, nếu có hàm sẽ trả về RInterface có tên là name, nếu không hàm trả về null.
Hàm public static ArrayList<RInterface> getRInterfaceList(JavaClass jc):
Hàm này được dùng để lấy ra danh sách những Rinterface từ JavaClass. Nội dung hàm này đơn giản như sau:
public static ArrayList<RInterface> getRInterfaceList(JavaClass jc) {
numOfRelation = 0;
numOfInput = 0;
ArrayList<RInterface>ris = new ArrayList<RInterface>();
ArrayList<JavaMethod> jms = jc.methods;
for (int i = 0; i < jms.size(); i++) {
RInterface ri = new RInterface(jms.get(i), i);
ris.add(ri);
}
return ris;
}
Hình 4.4: Hàm Tools. getRInterfaceList(JavaClass jc)
Hàm public static String getInputAssumptionFormula(RInterface ri): Được
sử dụng để tính input assumption của quan hệ ξ có trong ri. Cách thức tính đã được nêu ra trong phần 4.3.2.
Hàm public static String getPHIformula(String relations, String in, Hashtable h): Được sử dụng để tính Φ của interface sau khi kết hợp. Cách thức tính
đã được nêu ra trong phần 4.3.
4.4.6 Lớp Expresstion.java
Lớp Expression.java dùng để mô tả một biểu thức trong java với cấu trúc <vế trái> <toán tử so sánh> <vế phải>. Trong đó <toán tử so sánh bao gồm> { =, !=, >, <,
≥, ≤ }. Trong đó, vế trái và vế phải là những ArrayList<String> lưu trữ toán tử và toán hạng. Các hàm chính:
Hàm public boolean hasOperand (String x): kiểm tra xem trong biểu thức có
chưa toán hạng x hay không, nếu có thì trả về là true, ngược lại thì trả về false.
Hàm public boolean hasOneOfOperands(ArrayList<String> arr): Với tham
chiếu truyền vào là danh sách tên các toán hạng. Hàm này sẽ kiểm tra xem biểu thức hiện thời có chứa một trong các toán hạng thuộc danh sách đó hay không. Nếu có thì trả về true, ngược lại trả về false.
Hàm public static boolean isBooleanOperator(String x): dùng để kiểm tra xem String x có phải là một toán tử so sánh hay không, nếu có thì trả về true, ngược lại sẽ
trả về false. Như đã nói ở trên, các toán tử so sánh bao gồm: { =, !=, >, <, ≥, ≤ }; Hàm public static Expression Combine(Expression e1, Expression e2, String
operand): Hàm này được có tác dụng rút gọn toán hạng operand từ 2 biểu thức e1 và
e2 và trả về biểu thức đã được rút gọn. Ví dụ với e1 ≔ a = b + 1, và e2 ≔ a = c,
operand ≔ a. Biểu thức trả về sẽ là c = b + 1;
Hàm public static ArrayList<Expression> Shorten(ArrayList<Expression> exps, Stack operands): Tương tự như hàm Combine, nhưng hàm này được sử dụng để
rút gọn một số toán hạng từ những biểu thức cho trước, và trả về danh sách các biểu thức mới được tạo ra.
Cách thức hoạt động của hàm này là lấy từng toán hạng trong stack ra, giả sử toán hạng op được lấy ra. Sau đó kiểm tra xem trong danh sách những biểu thức truyền vào, biểu thức nào có chứa toán hạng op thì sẽ đẩy những biểu thức này vào một
còn toán hạng op.
Ví dụ: Tập các biểu thức là: y1 = x1, y2 = x2 + 1, y1 = z1, y2 = z2, z1 = z2. Với tập toán hạng để rút gọn là { y1, y2, z1, z2 }. Giá trị trả về là x1 = x2 + 1.
Nếu như trong quá trình rút gọn mà có một lúc nào đó vế trái bằng về phải thì giá trị trả về sẽ là null.
Ví dụ: Tập các biểu thức là: y1 = x1, y2 = x1, y1 = z1, y2 = z2, z1 = z2. Với tập toán hạng để rút gọn là { y1, y2, z1, z2 }. Lúc này giá trị trả về là x1 = x1, tức là hệ các biểu thức luôn đúng. Không có ràng buộc nào được trả về, hàm return null;
while (!operands.empty()) {
ArrayList<Expression> tmp = new ArrayList<Expression>(); String opd = (String) operands.pop();
for (int i = exps.size() - 1; i >= 0; i--) {
//System.out.println("1." + exps.get(i).toString()); if(exps.get(i).hasOperand(opd)&& exps.get(i).booleanOperator.equals(Expression.EQUAL)) { tmp.add(exps.get(i)); exps.remove(i); } } if (tmp.size() > 1) {
for (int i = 1; i < tmp.size(); i++) {
Expression e1 = Expression.Combine(tmp.get(0), tmp.get(i), opd); if (e1.left.equals(e1.right)) { return null; } exps.add(e1); } } } return exps;
4.4.7 Lớp FOLOptimizer.java
Lớp này được tạo ra nhằm mục đích tối ưu biểu thức logic. Phục vụ cho việc tính toán Φ, input assumption in(ϕ) một cách dễ dàng hơn.
Hàm public static ArrayList<String> Optimize(String str): với dữ liệu đầu
vào là một biểu thức logic có kiểu là String, hàm này sẽ thực hiện việc chuyển biểu thức này thành một biểu thức tương đương ở dạng chuẩn tắc tuyển. Biểu thức mới tạo ra được tách thành các từ tố và là giá trị trả về của hàm.
Ví dụ với String str = “( a | b ) & ( c | d )”, với “&” là phép AND, và “|” là phép OR.
Hàm này sẽ chuyển str về dạng chuẩn tắc tuyển, String str1 mới ở dạng chuẩn tắc tuyển trong trường hợp này sẽ là: str1 = “a & c | a & d | b & c | b & d”
CHƯƠNG 5: CÀI ĐẶT VÀ THỬ NGHIỆM
Để minh họa cho công cụ chuyển đổi và kết hợp tự động các relational interface từ chương trước, chương này tôi sẽ mô tả chi tiết các bước cài đặt mà tôi đã thực hiện và kết quả thử nghiệm trên một số dữ liệu thực tế.