четверг, 20 октября 2011 г.

Генератор кода


Работая java-программистом остро ощущаю нехватку средств метапрограммирования. Макросов не хватает катастрофически, а средства интроспекции не позволяют добиться той же гибкости и производительности. На днях, разрабатывая очередную формочку для android-приложения, окончательно ощутил себя обезьяной. Не выдержал и сделал маленький генератор кода для быстрого построения простых DSL-ов.
Пример приведу из Java/Swing. Чтобы реализовать MVC для нужно иметь три класса (и, соответственно, три файла для них); класс-контроллер должен их всех проинициализировать и подписать View на события от Model. В Model нужно держать набор данных и аксессоров к ним. В View, помимо самих компонентов, нужно иметь еще и метод, обновляющий их содержимое, когда изменяется модель. Вопрос: что из перечисленного действительно должен делать программист? Если формочка одна – то можно все и вручную сделать, но если формочек 10-20, то конкретно задалбывает писать все это вручную. Поэтому я сделал вот такой генератор кода.
Вот, что нам известно по ТЗ об очередной формочке, для которой делаем MVC. Форма состоит из двух полей (Text и Number), одноименные данные содержатся в модели. Их же из модели и нужно вытягивать, если она изменяется.
Генерим код следующим образом (файл scripts/mvc.clj):

(def *controller* "MyController")
(def *view* "MyView")
(def *model* "MyModel")
(def *view-components* [{:name "Text", :type "JTextField", :sync true}
{:name "Number", :type "JTextField", :sync true}])
(def *model-data* [{:name "Text", :type "String"}
{:name "Number", :type "int"}])

(defn process-view []
(eval-template "templates/View.soy"
{:vname *view*
:cname *controller*
:mname *model*
:components *view-components*}))

(defn process-controller []
(eval-template "templates/Controller.soy"
{:vname *view*
:cname *controller*
:mname *model*}))

(defn process-model []
(eval-template "templates/Model.soy"
{:name "MyModel"
:data *model-data*}))


(write-text (process-view) "MyView.java")
(write-text (process-controller) "MyController.java")
(write-text (process-model) "MyModel.java")

Шаблоны приводить не буду, они есть в проекте генератора. Сгенерированный файл MyView.clj:

package swingtest;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Observable;
import java.util.Observer;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextField;

public class MyView extends JFrame implements Observer{
private MyController _Controller;
private JTextField _Text;
private JTextField _Number;

MyView(MyController controller) {
_Controller = controller;
initLayout();
}

protected void initLayout() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setBounds(100, 100, 200, 100);
Box box = Box.createVerticalBox();

// Initialize your class here...
//
}

@Override
public void update(Observable o, Object arg) {
MyModel model = (MyModel)o;
_Text.setText(String.valueOf(model.getText()));
_Number.setText(String.valueOf(model.getNumber()));
}
}


Как видно из кода, сгенерен класс MyView, который содержит указанные поля и даже умеет автоматически обновлять их значения, когда меняется модель.
Класс модели:

import java.util.Observable;

public class MyModel extends Observable {
public void updateModel() {
setChanged();
notifyObservers();
clearChanged();
}

/* Accessors */

private String _Text;
private int _Number;

public String getText(){
return Text;
}

public void setText(String value){
_Text = value;
}
public int getNumber(){
return Number;
}

public void setNumber(int value){
_Number = value;
}
}

Модель не только содержит указанные данные, но также и готовые аксессоры к ним.

1 комментарий: