Tutorial: Desenvolvimento de Software Baseado em Modelos na Prática com Epsilon, Parte 3

Do modelo para a execução.
Continuando a série de publicações deste tutorial, que ensina como desenvolver software utilizando a abordagem MDD, nesta terceira parte, iremos finalizar o desenvolvimento da ferramenta CASE, adicionando a funcionalidade de geração de código e como gerar a versão standalone do editor.

Sei que você deve estar pensando que é muita coisa, mas tá quase acabando. :)



O primeiro passo nesta parte é criar, no projeto "filesystem", o arquivo "filesystem.egl". Selecione "New > Epsilon > EGL Template". Este arquivo nada mais é do que um template, semelhante ao que criamos aqui, que será alimentado pelas informações dos nós e relacionamentos presentes no modelo criado na ferramenta que estamos desenvolvendo neste tutorial.

Todo template EGL (Epsilon Generation Language) compreende um número x de seções. Cada seção pode ser considerada estática ou dinâmica. Uma seção estática contém texto que irá aparecer sem nenhuma alteração, independente do modelo criado. Uma seção dinâmica, por outro lado, começa com a sequência "[%", e termina com a sequência "%]". As seções dinâmicas possuem ainda os objetos EOL (Epsilon Object Language) que serão executados pelo template.

Para os programadores PHP de plantão, isso é um pouco parecido com o Smarty ou qualquer outro framework PHP que possua templates.

Após termos criado o arquivo "filesystem.egl", iremos modificá-lo para realizar operações relativas aos nós e seus relacionamentos. O seguinte código deverá ser adicionado:

[%for (filesystem in Filesystem) {%]
   [%for (drive in filesystem.drives) {%]
      cd [%=drive.name%]:/
      [%for (content in drive.contents){%]
         [%if (not isEmpty(content)) {%]
     [%if (content.type.name=="Folder"){%]
        mkdir [%=content.name%]
        [%=verifySubFolders(content)%]
     [%} else {%]
        "echo.>"[%=content.name%]
     [%}%]
  [%}%]
      [%}%]
   [%}%]
[%}%]

[%
operation isEmpty (object) {
   if(object.size > 0){
      return false;
   }
   return true;
}
%]

[%
operation verifySubFolders (object) {
   if(not isEmpty(object)){
      %]cd [%=object.name%]
      [%
      for(folder in object.contents){
         if (folder.type.name=="Folder"){
            %]mkdir [%=folder.name%]
            [%verifySubFolders(folder);
         }
      %]cd..[%
      } 
   }
}
%]

O que este código faz é, basicamente, escrever uma sequência de comandos que serão utilizados no windows para executar o modelo em uma estrutura de arquivos e diretórios do Windows. Lembrando que é possível criar mais de um template que, neste caso do filesystem, permite que a ferramenta CASE atenda à diversos sistemas operacionais. Para escrever esta sequência de comandos, o arquivo .egl acessa nós do modelo criado, definindo essas características junto com os comandos do sistema operacional. A saída é um arquivo .bat contendo estas informações. No meu modelo, por exemplo, o código gerado foi o seguinte:
Código gerado.
Além disso, é preciso definir, na ferramenta, algum botão ou item de menu que realize essa transformação. A possibilidade de salvar o código gerado em algum formato também é interessante. Sendo assim, primeiro vamos definir o componente que irá ser responsável por realizar a transformação quando o usuário deseja. No seu projeto principal, "filesystem.diagram", crie um novo pacote chamado "filesystem.diagram.m2t" (m2t - model to text).

Tá Felipe, mas pra que isso?

Neste pacote serão criadas duas novas classes: a "M2TAction", responsável por carregar o arquivo .egl e realizar a transformação, e a "TransformationView", que se refere à visão do código no próprio editor, em outras palavras, é onde ficará a saída do texto.

A classe "M2TAction possuirá o seguinte formato:

package filesystem.diagram.m2t;


import java.net.URL;

import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.epsilon.egl.EglFileGeneratingTemplateFactory;
import org.eclipse.epsilon.egl.EglTemplateFactoryModuleAdapter;
import org.eclipse.epsilon.emc.emf.InMemoryEmfModel;
import org.eclipse.epsilon.eol.exceptions.EolRuntimeException;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.IWorkbenchWindowActionDelegate;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.osgi.framework.Bundle;

import filesystem.FilesystemPackage;
import filesystem.diagram.part.FilesystemDiagramEditor;
import filesystem.diagram.m2t.TransformationView;


public class M2TAction implements IWorkbenchWindowActionDelegate {

   public M2TAction() {
   }

   public void run(IAction action) {

      // Acessa o editor ativo
      IEditorPart editor = PlatformUI.getWorkbench().getActiveWorkbenchWindow(                                          ).getActivePage().getActiveEditor();

      // Caso seja um diagrama
      if (editor instanceof FilesystemDiagramEditor) {

         FilesystemDiagramEditor friendsDiagramEditor = (FilesystemDiagramEdit                                                         or) editor;

  // Obtém o modelo EMF do editor
  Resource resource = getFirstSemanticModelResource( friendsDiagramEdit                                      or.getEditingDomain().getResourceSet());

         if (resource == null) return;

     // Envolve o modelo EMF neste InMemoryEmfModel
     InMemoryEmfModel m = new InMemoryEmfModel("M", resource, Filesyste                                                     mPackage.eINSTANCE);

            // Constrói o módulo EGL
     EglTemplateFactoryModuleAdapter module = new EglTemplateFactoryMod                           uleAdapter(new EglFileGeneratingTemplateFactory());

     Bundle bundle = Platform.getBundle("filesystem");
     URL fileURL = bundle.getEntry("m2t/filesystem-win.egl");

     try {
        module.parse(FileLocator.resolve(fileURL).toURI());
     } catch (Exception e) {
        e.printStackTrace();
            }

     module.getContext().getModelRepository().addModel(m);

     TransformationView view = null;
     try {
        view = (TransformationView) PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().showView(TransformationView.ID);
     } catch (PartInitException e1) {
        e1.printStackTrace();
     }
   
     try {
        view.setInput((String) module.execute());
     } catch (EolRuntimeException e) {
        e.printStackTrace();
     }

 }

   }

   public Resource getFirstSemanticModelResource(ResourceSet resourceSet) {
      for (Resource resource : resourceSet.getResources()) {
         return resource;
      }
      return null;
   }

   public void selectionChanged(IAction action, ISelection selection) {}

   public void dispose() {}

   public void init(IWorkbenchWindow window) { }
}

No código acima perceba que adicionei um novo diretório ao projeto/pacote "filesystem", o diretório "m2t", só fiz isso para organizar melhor o projeto.

Como vimos, além da classe "M2TAction", é preciso criar uma visão para a geração de código. A implementação da classe "TransformationView" é a seguinte:

package filesystem.diagram.m2t;


import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.part.ViewPart;

public class TransformationView extends ViewPart {
 public static final String ID = "filesystem.diagram.m2t.TransformationView";

 private Text text;
 
 public TransformationView() {
  super();
 }

 public void setFocus() {
  text.setFocus();
 }

 public void createPartControl(Composite parent) {
  text = new Text(parent, SWT.BORDER | SWT.V_SCROLL);
  text.setText("código gerado aqui");
 }

 public void setInput(String parameter){
  text.setText(parameter);
 }
}

Mas Felipe, como os métodos dessas classes são chamados? Como diria um amigo meu, não é magia, é tecnologia.

Depois de criarmos estas classes, é necessário informar ao Epsilon que nossas novas implementações precisam fazer parte da build final da ferramenta que estamos desenvolvendo. Isso é feito editando o arquivo "plugin.xml" do projeto "filesystem.diagram". Abra o arquivo "plugin.xml", clique na aba de mesmo nome, "plugin.xml", e adicione os seguintes pontos de extensão:


<extension point="org.eclipse.ui.views">
        <view id="filesystem.diagram.m2t.TransformationView"
             name="Exportar para script"
          class="filesystem.diagram.m2t.TransformationView"
          icon="icons/obj16/transformation.png"/>
</extension>

<extension point="org.eclipse.ui.actionSets">
   <actionSet
            description="Exportar para script  "
            id="filesystem.diagram.m2t.M2TAction.actionSet"
            label="Exportar para script  "
            visible="true">
      <action
               class="filesystem.diagram.m2t.M2TAction"
               icon="icons/obj16/transformation.png"
               id="filesystem.diagram.m2t.M2TAction"
               label="Exportar para script"
               mode="FORCE_TEXT"
               style="push"
               toolbarPath="transformationGroup"
               tooltip="Exportar para script  ">
      </action>
   </actionSet>
</extension>

Observe que no primeiro "<extension>" eu adicionei o atributo "icon". Caso você faça checkout deste projeto no repositório, a imagem definida já estará presente. Para aqueles que estão seguindo o tutorial sem fazer checkout, basta adicionar uma imagem de preferência, alterando ainda o nome do arquivo, ou salvar a "transformation.png" para o projeto.
transformation.png
.O segredo da transformação acontece no segundo "<extension>", onde adicionamos a ação a ser chamada quando clicarmos no botão "Exportar para script".

Para finalizar, a última etapa é criar a versão standalone da nossa ferramenta CASE. Permitindo salvar este código gerado a partir do modelo, ambos criados na ferramenta que desenvolvemos.
Você vai querer distribuir sua ferramenta CASE, e não acho que uma versão em forma de plugin no Eclipse vá ser muito interessante.
A versão standalone contém apenas as funcionalidades básicas que definimos, ao contrário do runtime que utilizamos anteriormente, onde se executava uma perspectiva da nossa ferramenta dentro de uma instância do Eclipse.

Como desejamos gerar uma build básica do nosso editor, devemos fazer as seguintes modificações e implementações no projeto "filesystem". No diretório "model", altere o arquivo "filesystem.emf", aquele que descreve os nossos modelos. A anotação "@gmf.diagram" deverá ser modificada para a seguinte forma:

@gmf.diagram(foo="bar", onefile="true", rcp="true", diagram.extension="bat")
class Filesystem { val Drive[*] drives; val Sync[*] syncs; }

Após isso, gere novamente seu editor através do arquivo "filsesytem.emf" (clique com o botão direito no arquivo, "Eugenia > Generate GMF Editor"). Finalizada a geração, você deve criar o chamado product, responsável pelo empacotamento do que for necessário para criação da versão standalone.

Ainda no projeto "filesystem", crie o arquivo "product.product" ("New > Other > Plug-in Development > Product Configuration", ou, simplesmente, digite product configuration no campo de filtro).
Arquivo "Product Configuration".
Abra o arquivo "product.product" e defina as informações da seguinte forma:
Configurações do arquivo "product.product".
Neste ponto já é possível executar a versão standalone do projeto. Mas como a intenção é poder distribuí-la e gerar uma versão independente das outras funcionalidades de Epsilon/Eclipse, nesta mesma área de configurações, é possível utilizar o "Eclipse Product Export Wizard", basta clicar no link e a seguinte janela será exibida:
Eclipse Product Export Wizard
E assim finalizamos nosso tutorial de desenvolvimento baseado em modelos com o Epsilon. Espero que tenha sido possível demonstrar as inúmeras possibilidades que existem nesta metodologia de desenvolvimento, e que isso possa ser útil para algum de vocês. Até a próxima!

Felipe Alencar

Felipe Alencar é doutorando em Ciência da Computação na UFPE, professor, desenvolvedor e acredita que só não virou jogador de futebol, surfista ou músico profissional por falta de tempo e talento.

Nenhum comentário:

Postar um comentário