Universidade Federal de Pernambuco – UFPE
Centro de Informática - CIn
Tópicos Avançados em
Engenharia de Software
Utilizando técnicas de transformação
de programas e geração de código
Dupla:
Caio César Sabino Silva (ccss2@cin.ufpe.br)
Lais Sousa de Andrade (lsa@cin.ufpe.br)
Professor: Paulo Borba (phmb@cin.ufpe.br)
Sumário
1. Introdução
2. Resolvendo problemas encontrados no projeto
2.1. Problema 1: Baixo grau de parametrização das propriedades do banco de dados
2.1.1. Situação anterior
2.1.1.1. hibernate.cfg.xml
2.1.2. Valores observados
2.1.3. Princípios utilizados
2.1.4. Refatorando o código
2.1.4.1. hibernate.cfg.vm
2.1.4.2. velocity.xml
2.1.4.3. RGMSGenerator.java
2.1.5. Situação posterior
2.2. Problema 2: Código exaustivo no ManagerImpl
2.2.1. Situação anterior
2.2.1.1. ManagerImpl.java
2.2.2. Valores observados
2.2.3. Princípios utilizados
2.2.4. Resolvendo o problema
2.2.5. Situação posterior
2.3. Problema 3: Código exaustivo em Servlets de remoção
2.3.1. Situação anterior
2.3.2. Valores observados
2.3.3. Princípios utilizados
2.3.4. Resolvendo o problema
2.3.4.1. Source file de Membro.java
2.3.4.2. Source file de Publicacao.java
2.3.4.3. Template LHS da transformação
2.3.4.4. Template RHS da transformação
2.3.4.5. transform.jtp
2.3.4.6. Code Template para o Eclipse
2.3.5. Situação posterior
2.4. Problema 4: Código repetitivo em função dos tipos de Notícia
2.4.1. Situação anterior
2.4.1.1. AdicionarNoticiaServlet
2.4.1.2. Noticia
2.4.2. Valores observados
2.4.3. Princípios utilizados
2.4.4. Refatorando o código
2.4.4.1. Source file de Noticia.java
2.4.4.2. Template LHS da transformação
2.4.4.3. Template RHS da transformação
2.4.4.4. transform.jtp
2.4.5. Situação posterior
1. Introdução
Nesta atividade, o código-fonte do sistema será refatorado utilizando técnicas de
transformação de programas e geração de código de modo a aumentar a produtividade de
desenvolvimento e permitir reuso de conceitos.
Para cada refatoramento feito, será descrito a situação anterior, o motivo do
refatoramento, o passo-a-passo tomado no refatoramento, descrevendo as técnicas utilizadas e
por fim mostrando e comparando a situação final com a anterior.
2. Resolvendo problemas encontrados no projeto
Alguns problemas de modularidade e reuso de código foram encontrados e
solucionados na atividade anterior. Algumas soluções ainda podem ser melhoradas e alguns
problemas menores ainda restam no sistema.
Em cada subseção, um problema será estudado e analisado de modo a construir uma
solução satisfatória para o mesmo.
2.1. Problema 1: Baixo grau de parametrização das propriedades do
banco de dados
O arquivo de definição de propriedades do Hibernate (hibernate.cfg.xml) possui alguns
dados relativos ao mapeamento objeto-relacional e outros referentes à conexão com o banco
de dados. Entretanto a parte referente ao banco de dados possui dados fixos inicializados
específicos a um banco de dados.
A resolução desse problema implicará na implementação da funcionalidade de
funcionar para diversos tipos de banco de dados, de forma a gerar uma versão específica
para cada configuração desejada.
2.1.1. Situação anterior
O arquivo hibernate.cfg.xml é assim:
2.1.1.1. hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">org.postgresql.Driver</property> <property name="hibernate.dialect">org.hibernate.dialect.PostgreSQLDialect</property>
<property name="hibernate.hbm2ddl.auto">update</property> <property name="hibernate.connection.url">
jdbc:postgresql://localhost/postgres/rgms
</property>
<property name="hibernate.connection.username">postgres</property> <property name="hibernate.connection.password">password</property>
<mapping class="br.ufpe.cin.rgms.membro.modelo.Estudante"/> <mapping class="br.ufpe.cin.rgms.linhasdepesquisa.modelo.LinhaPesquisa"/> <mapping class="br.ufpe.cin.rgms.publicacao.modelo.Publicacao"/> <mapping class="br.ufpe.cin.rgms.publicacao.modelo.PropriedadePublicacao"/> <mapping class="br.ufpe.cin.rgms.noticia.modelo.Noticia"/> <mapping class="br.ufpe.cin.rgms.noticia.modelo.NoticiaCadastrada"/> <mapping class="br.ufpe.cin.rgms.noticia.modelo.NoticiaTwitter"/> </session-factory> </hibernate-configuration>
2.1.2. Valores observados
Percebe-se que o arquivo de propriedades do hibernate pode se tornar mais flexível: os
parâmetros marcados de vermelho variam de SGBD para SGBD. Os valores de azul são os
utilizados durante o desenvolvimento. Caso o sistema ofereça a possibilidade de gerar uma
versão para cada banco de dados, tais valores não podem ser fixos. É importante que haja uma
parametrização de modo que cada versão tenha seus dados de comunicação com o banco de
dados definidos antes da geração do sistema final.
2.1.3. Princípios utilizados
Em geral, cada versão do sistema terá uma configuração desse arquivo de propriedades
fixa. Embora haja também a possibilidade de alterar em tempo de execução tais parâmetros,
nesse refatoramento não estamos preocupados com isso: estamos apenas preocupado em ter
a capacidade de gerar versões do sistema com configurações diferentes desse arquivo nas
propriedades variáveis.
Para tal, é necessário que esse arquivo seja aberto para redefinição nos pontos
marcados de azul. Isto é, precisamos que esse módulo, digamos, seja aberto para extensão de
certa forma.
2.1.4. Refatorando o código
Uma técnica que foi vista anteriormente para tornar o arquivo aberto a redefinição é
utilizando parametrização, mas devemos perceber que apenas queremos parametrizar para
poder gerar um arquivo fixo em tempo de compilação. Isso pode ser atingido com técnica de
pré-compilação (com geração de código), de forma que os parâmetros variáveis do banco de
dados sejam substituídos por valores e tal arquivo seja pré-processado substituindo cada
parâmetro por seu valor, gerando em tempo de compilação um arquivo fixo para cada versão.
Com a técnica de geração de código, vamos utilizar o Velocity, com o plugin
VeloEclipse, para fazer o pré-processamento:
<hibernate-configuration> <session-factory>
<property name="hibernate.connection.driver_class">$driver</property> <property name="hibernate.dialect">$dialect</property>
<property name="hibernate.hbm2ddl.auto">update</property> <property name="hibernate.connection.url">$url</property>
<property name="hibernate.connection.username">$user</property> <property name="hibernate.connection.password">$password</property> <mapping class="br.ufpe.cin.rgms.membro.modelo.Membro"/> <mapping class="br.ufpe.cin.rgms.membro.modelo.Estudante"/> <mapping class="br.ufpe.cin.rgms.linhasdepesquisa.modelo.LinhaPesquisa"/> <mapping class="br.ufpe.cin.rgms.publicacao.modelo.Publicacao"/> <mapping class="br.ufpe.cin.rgms.publicacao.modelo.PropriedadePublicacao"/> <mapping class="br.ufpe.cin.rgms.noticia.modelo.Noticia"/> <mapping class="br.ufpe.cin.rgms.noticia.modelo.NoticiaCadastrada"/> <mapping class="br.ufpe.cin.rgms.noticia.modelo.NoticiaTwitter"/> </session-factory> </hibernate-configuration>
Editor do VeloEclipse com autocomplete e syntax highlighting
2.1.4.2. velocity.xml
A princípio, vamos gerar dinamicamente com o Velocity usando o RGMSGenerator, mas
não usaremos mais o antigo arquivo de propriedades:
publicacao_from_bibtex=true driver=org.postgresql.Driver dialect=org.hibernate.dialect.PostgreSQLDialect url=jdbc:postgresql://localhost/postgres/rgms user=postgres password=password
Apenas alteraremos para que o RGMSGenerator gere os arquivos do Velocity no próprio
projeto, para facilitar seu uso durante os testes, através de um xml que define os arquivos a
serem pré-processados no projeto:
Para isso, definimos um xml que indica quais arquivos serão pré-processados e
indicando o nome do arquivo de saída
[azul]
e o ambiente no qual tal pré-processamento
ocorrerá
[vermelho]
:
<?xml version="1.0" encoding="UTF-8"?> <velocity>
<files>
<preprocessing input=”src/hibernate.cfg.vm” output=”src/hibernate.cfg.xml”/>
<preprocessing input=”WebContent/WEB-INF/web.vm” output=”WebContent/WEB-INF/web.xml”/> <preprocessing input=”WebContent/adicionarpublicacao.vm”
output=”WebContent/adicionarpublicacao.jsp”/>
</files> <context>
<variable name=”publicacao_from_bibtex”>true</variable> <variable name=”driver”>org.postgresql.Driver</variable>
<variable name=”dialect”>org.hibernate.dialect.PostgreSQLDialect</variable> <variable name=”url”>jdbc:postgresql://localhost/postgres/rgms</variable> <variable name=”user”>postgres</variable>
<variable name=”password”>password</variable>
</context> </velocity>
2.1.4.3. RGMSGenerator.java
Após isso, o código do RGMSGenerator foi alterado para poder pré-processar em
função da lista de arquivos e o contexto do xml:
public class RGMSGenerator {
private VelocityEngine engine; private VelocityContext context; private XMLHelper helper; private Document xml;
public RGMSGenerator() throws Exception { this.initVelocity();
this.setUpContext(); }
private void setUpContext() throws SAXException, IOException, ParserConfigurationException {
this.xml = this.helper.getXML(new File("velocity.xml")); for(Element variable :
this.helper.getElements(this.xml.getDocumentElement(), "variable")){ context.put(variable.getAttribute("name"), helper.getContent(variable)); }
}
private void initVelocity() throws Exception { this.engine = new VelocityEngine(); Properties config = new Properties(); this.engine.init(config);
this.context = new VelocityContext(); this.helper = XMLHelper.getInstance(); }
public void process() throws ResourceNotFoundException, ParseErrorException, Exception{ for(Element processing :
this.helper.getElements(this.xml.getDocumentElement(), "preprocessing")){ Template t = engine.getTemplate( processing.getAttribute("input") ); PrintWriter writer = new PrintWriter(processing.getAttribute("output")); t.merge( context, writer );
writer.close(); }
}
public static void main( String args[] ) throws Exception{ new RGMSGenerator().process();
} }
2.1.5. Situação posterior
Os impactos deste refatoramento foram:
●
implementação, de certa forma, de uma funcionalidade: possibilidade de gerar versões
diferentes do sistema para bancos de dados distintos.
●
otimização da utilização do velocity com o uso de dois arquivos num mesmo projeto, o
template do velocity (arquivo com extensão .vm) e o arquivo final.
2.2. Problema 2: Código exaustivo no ManagerImpl
O código da classe ManagerImpl é bastante repetitivo em função das funcionalidades do
DAO.
2.2.1. Situação anterior
Na classe ManagerImpl, destacamos trechos interessantes:
2.2.1.1. ManagerImpl.java
public class ManagerImpl<Tipo extends AbstractBusinessEntity> implements IManager<Tipo> { private static ApplicationContext context;
protected Dao<Tipo> dao; static {
ManagerImpl.context =
new ClassPathXmlApplicationContext("classpath:applicationContext.xml"); }
protected void validar(Tipo tipo) throws RGMSException{
IValidationUtil validation = (IValidationUtil) context.getBean("XMLValidationUtil"); if(!validation.validate(tipo)){
throw new RGMSException("Dados inválidos na inserção"); }
}
public Dao<Tipo> getDao() { return dao;
}
public void setDao(Dao<Tipo> dao) { this.dao = dao;
}
public void inserir(Tipo tipo) throws RGMSException { validar(tipo);
Persistence.getInstance().beginTransaction(); this.dao.adicionar(tipo);
Persistence.getInstance().commit(); }
public void alterar(Tipo tipo) throws RGMSException{ this.validar(tipo);
Persistence.getInstance().beginTransaction(); this.dao.atualizar(tipo);
Persistence.getInstance().commit(); }
public List<Tipo> listar() {
Persistence.getInstance().beginTransaction();
List<Tipo> retorno = this.dao.listarTudo();
Persistence.getInstance().commit();
return retorno;
}
public Tipo consultarUnicoResultado(long id) { Persistence.getInstance().beginTransaction();
Tipo retorno = this.dao.procurar(id);
Persistence.getInstance().commit();
return retorno;
}
public void remover(Tipo tipo){
Persistence.getInstance().beginTransaction();
this.dao.remover(tipo);
Persistence.getInstance().commit(); }
public void remover(long id){
Persistence.getInstance().beginTransaction();
this.dao.remover(id);
Persistence.getInstance().commit(); }
public void rebind(Tipo tipo){
Persistence.getInstance().beginTransaction();
this.dao.rebind(tipo);
Persistence.getInstance().commit(); }
public Tipo searchOnUniqueField(String field, Object value) { Persistence.getInstance().beginTransaction();
Tipo retorno = this.dao.queryOnUniqueField(field, value);
Persistence.getInstance().commit();
}
public List<Tipo> list(HashMap<String, Object> restrictions) { Persistence.getInstance().beginTransaction();
List<Tipo> retorno = this.dao.queryOnRestrictionMap(restrictions);
Persistence.getInstance().commit();
return retorno;
}
public boolean exists(Tipo membro, String[] fields) { Persistence.getInstance().beginTransaction();
boolean retorno = this.dao.exists(membro, fields);
Persistence.getInstance().commit();
return retorno;
}
public List<Tipo> consulta(String atributo, String query){ Persistence.getInstance().beginTransaction();
List<Tipo> retorno = this.dao.consulta(atributo,query);
Persistence.getInstance().commit();
return retorno;
}
}
A parte de
azul
é comum aos métodos e consiste em basicamente um método que abre
e fecha a transação com o banco de dados. A parte variável de
vermelho
corresponde aos
parâmetros e o retorno que um método pode ter e que coincide com os mesmos do DAO. A
parte de
verde
é opcional: aparece em uns ou não dependendo se o método retorna algo ou
não.
2.2.2. Valores observados
O código destacado no ManagerImpl:
●
é repetitivo: percebe-se que os diversos métodos de azul fazem uma mesma coisa:
abrir a transação, delegar uma função ao DAO e fechar a transação. Pela falta dessa
abstração, cada operação é implementada manualmente, e o programador tem que,
para cada funcionalidade do DAO, criar um novo método no Manager que faz as
mesmas coisas dos outros, só mudando a função delegada.
●
diminui a produtividade de desenvolvimento: Caso a forma como a função seja
delegada ao DAO mude, o código de todos os métodos do Manager precisam ser
alterados manualmente. Igualmente acontece quando adicionamos funcionalidade ao
DAO: precisamos acrescentar no Manager também. Isso tudo gera uma produtividade
menor.
2.2.3. Princípios utilizados
O princípio utilizado nesse refatoramento é basicamente o de minimizar repetições. Há
uma observação, entretanto, de que será minimizada a repetição de código implementado pelo
programador, e não o código final da aplicação. Isso será explicado melhor na próxima seção.
2.2.4. Resolvendo o problema
Para resolver o problema de produtividade, pensamos inicialmente em usar a
ferramenta JaTS, mas ela não suporta tipos parametrizados, ocorrendo erro de Parsing na
definiçao dos parâmetros de uma classe parametrizada. Dessa forma, vamos definir um Code
Template no Eclipse:
public
${r}
${name}
(
${paramList}
){
Persistence.getInstance().beginTransaction();
${r}
retorno = this.dao.
${name}
(
${argList}
);
Persistence.getInstance().commit();
return retorno;
}
Agora ao digitar “delegate_dao_method”, pode-se usar o auto-complete e preencher
apenas as variações destacadas:
public void
${name}
(
${paramList}
){
Persistence.getInstance().beginTransaction();
this.dao.
${name}
(
${argList}
);
Persistence.getInstance().commit();
}
2.2.5. Situação posterior
Perceba que as repetições no código no final não foram resolvidas, uma vez que o
código gerado continua o mesmo, entretanto boa parte do código é gerada pelo Eclipse,
apenas as variações são fornecidas pelo programador. Então podemos dizer que essa
repetição de código não afeta a produtividade mais, uma vez que é gerada automaticamente
pela ferramenta.
2.3. Problema 3: Código exaustivo em Servlets de remoção
Alguns Servlets pequenos ainda possuem código bastante semelhante entre si.
Novamente, vamos apenas remover a repetição de código digitada pelo programador e não no
código final.
2.3.1. Situação anterior
Vejamos dois exemplos de servlets do tipo Remover:
Código muito semelhante dos Servlets
2.3.2. Valores observados
O código destacado no servlets:
●
é repetitivo;
●
diminui a produtividade de desenvolvimento: Caso a forma como a função é
realizada seja alterada, todo o código precisa ser alterado manualmente em todos os
locais.
2.3.3. Princípios utilizados
O princípio utilizado nesse refatoramento é basicamente o de minimizar repetições
implementadas pelo programador.
2.3.4. Resolvendo o problema
Para resolver o problema de produtividade, pensamos inicialmente em usar a
ferramenta JaTS para realizar uma transformação de programa que irá gerar o Servlet de
remoção em função de uma entidade.
Basicamente termos entidades como Membro e Publicacao e um template de saída
que gerará um Servlet de remoção para cada uma delas. Há um inconveniente em que o
JaTS não suporta tipos genéricos, logo o código das classes básicas foi comentado nas
partes em que utiliza Generics ou Annotations para podermos realizar a transformação com
a ferramenta. Outro problema são as variações entre entidades que deviam ser parâmetros a
serem configurados para cada entidade. Isso foi contornado definindo constantes na entidade e
guardando em variáveis no LHS.
Vamos mostrar o exemplo aplicado a Membro.java e Publicacao.java:
2.3.4.1. Source file de Membro.java
Apenas a parte que interessa ao template foi extraída:
public class Membro extends AbstractBusinessEntity {public final static String ENTITY_PARAMETER = "membro"; public final static String STATUS_FIELD = "membrostatus";
public final static String OPERATION_MESSAGE = "Membro removido com sucesso."; public final static String DESTINATION_PAGE = "membrostatus.jsp";
}
2.3.4.2. Source file de Publicacao.java
public class Publicacao extends AbstractBusinessEntity{
public final static String STATUS_FIELD = "publicacaostatus";
public final static String OPERATION_MESSAGE = "Publicacao removida com sucesso."; public final static String DESTINATION_PAGE = "publicacaostatus.jsp";
}
2.3.4.3. Template LHS da transformação
ClassPattern #EntityClass {
package br.ufpe.cin.rgms.#PackageName.modelo; ImportDeclarationSet:#C_IDS;
ModifierList:#M class #Entity extends AbstractBusinessEntity {
public final static String ENTITY_PARAMETER = #EntityParameter; public final static String STATUS_FIELD = #StatusField;
public final static String OPERATION_MESSAGE = #OperationMessage; public final static String DESTINATION_PAGE = #DestinationPage; } }
2.3.4.4. Template RHS da transformação
ClassPattern #ServletClass { package br.ufpe.cin.rgms.#PackageName.controle; import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import br.ufpe.cin.rgms.Facade; import br.ufpe.cin.rgms.#PackageName.modelo.#Entity;public class #<(#Entity.addPrefix("Remover")).addSuffix("Servlet")># extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
#Entity entity = Facade.getInstance().
#<#Entity.addPrefix("get")># ((String) request.getParameter( #EntityParameter ));
request.setAttribute( #StatusField, #OperationMessage );
RequestDispatcher view = request.getRequestDispatcher(#DestinationPage); view.forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { }
} }
2.3.4.5. transform.jtp
As transformações precisaram ser feitas em arquivos diferentes. Se colocassemos os
dois source files para os templates de sourcePattern, ele dava erro por encontrar dois matches.
●
Publicacao.java -> RemoverPublicacaoServlet_out.java
#Arquivo de descricao de transformacao
filesDirectory=D:/Downloads/Project/Graduation/TAES/RGMS_Work/jats/servlet-remover wizardJarName=null source.0=Publicacao.java sourceJarName=null applyOutputDir=D:/Downloads/Project/Graduation/TAES/RGMS_Work/jats/servlet-remover/Saida/ templateLeft.0=sourcePattern.jats templateRight.0=targetPattern.jats
Publicacao.java -> RemoverPublicacaoServlet_out.java package br.ufpe.cin.rgms.publicacao.controle; import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import br.ufpe.cin.rgms.Facade; import br.ufpe.cin.rgms.publicacao.modelo.Publicacao; public class RemoverPublicacaoServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
Publicacao entity =
Facade.getInstance().getPublicacao((String)request.getParameter("publicacao")); Facade.getInstance().removerPublicacao(entity);
request.setAttribute("publicacaostatus", "Publicacao removida com sucesso."); RequestDispatcher view = request.getRequestDispatcher("publicacaostatus.jsp"); view.forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
} }
●
Membro.java -> RemoverMembroServlet_out.java
#Arquivo de descricao de transformacao
wizardJarName=null source.0=Membro.java sourceJarName=null applyOutputDir=D:/Downloads/Project/Graduation/TAES/RGMS_Work/jats/servlet-remover/Saida/ templateLeft.0=sourcePattern.jats templateRight.0=targetPattern.jats Membro.java -> RemoverMembroServlet_out.java package br.ufpe.cin.rgms.membro.controle; import java.io.IOException; import javax.servlet.RequestDispatcher; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import br.ufpe.cin.rgms.Facade; import br.ufpe.cin.rgms.membro.modelo.Membro;
public class RemoverMembroServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
Membro entity = Facade.getInstance().getMembro((String)request.getParameter("membro")); Facade.getInstance().removerMembro(entity);
request.setAttribute("membrostatus", "Membro removido com sucesso."); RequestDispatcher view = request.getRequestDispatcher("membrostatus.jsp"); view.forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
} }
2.3.4.6. Code Template para o Eclipse
Alternativamente poderiamos gerar um template para o Eclipse:
public class Remover${entity}Servlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
${entity} entity =
Facade.getInstance().get${entity}((String)request.getParameter(${parameter})); Facade.getInstance().remover${entity}(entity);
request.setAttribute(${messageAttribute}, ${message});
RequestDispatcher view = request.getRequestDispatcher(${page}); view.forward(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{}
}
Como o template do Eclipse é mais prático de usar, optamos por usar essa solução.
2.3.5. Situação posterior
Após a criação dos templates do JaTS ou do Eclipse, temos agora um mecanismo de
geração de Servlets de remoção em função da definição de uma entidade básica com alguns
parâmetros extras (as constantes que foram inseridas nessas classes). Caso a lógica do Servlet
seja alterada, basta alterarmos os RHS dos templates e regerar tais classes, ao invés de fazer
manualmente as alterações. Dessa forma, tratamos o problema da produtividade em função de
mudanças que possam ocorrer nessas classes.
2.4. Problema 4: Código repetitivo em função dos tipos de Notícia
No sistema, há dois tipos de Notícias: notícia cadastrada e notícia twitter. As notícias
foram modeladas com mecanismo de herança, pois cada subtipo possui atributos específicos.
Mas cada notícia tem um atributo tipo que indica a propriedade do arquivo de idiomas
relacionada a um tipo de notícia. Eventualmente para cada subtipo de Noticia haverá somente
um tipo, então no código dos servlets, é verificado o tipo da notícia e o subtipo correto é
instanciado e manipulado. Essa verificação entretanto é variável em função da quantidade de
tipos de notícia.
2.4.1. Situação anterior
Vejamos a classe AdicionarNoticiaServlet:
2.4.1.1. AdicionarNoticiaServlet
public class AdicionarNoticiaServlet extends NoticiaModelMapperServlet { @Override
protected Noticia getObject() { Noticia ret = null;
String tipo = (String) this.getFormfields().get("tipo"); if (tipo.equals(Noticia.CADASTRADA)) {
ret = new NoticiaCadastrada(); }
if (tipo.equals(Noticia.TWITTER)) { ret = new NoticiaTwitter(); }
ret.setTipo(tipo); return ret; }
@Override
protected void handleObject(Noticia object) throws RGMSException { Facade.getInstance().inserirNoticia(object);
}
@Override
protected void handleSuccess(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException { request.setAttribute("noticiastatus","Noticia cadastrada com sucesso."); }
}
2.4.1.2. Noticia
@Entity
public abstract class Noticia extends AbstractBusinessEntity implements Comparable<Noticia>{ public final static String CADASTRADA = "noticia_cadastrada";
public final static String TWITTER = "noticia_twitter"; private Membro autor;
private String tipo;
public Noticia(){
// TODO Auto-generated constructor stub }
public Noticia(Membro autor, String tipo){ this.autor = autor;
this.tipo = tipo; }
@OneToOne()
public Membro getAutor() { return autor; }
public void setAutor(Membro autor) { this.autor = autor;
} @Basic
public String getTipo() { return tipo; }
public void setTipo(String tipo) { this.tipo = tipo;
}
public abstract int compareTo(Noticia noticia); }
2.4.2. Valores observados
Percebe-se que o código desse Servlet de Noticias:
●
é repetitivo: um número de verificações proporcional à quantidade de tipos de notícia
deve ser feito.
2.4.3. Princípios utilizados
Vamos usar o princípio de minimização de repetições do código que é implementado
pelo programador.
2.4.4. Refatorando o código
Uma abordagem que contemplaria os dois valores do problema seria utilizando AOM,
permitindo alteração dos tipos de Notícia em tempo de execução. Entretanto o custo de
alteração dessa técnica seria muito maior que a abordagem que mostraremos a seguir.
Vamos usar uma abordagem que gera o código que é repetitivo no Servlet. O código
final continuará contendo a repetição de código, entretanto, como tal código é gerado, não há
qualquer prejuízo sobre a produtividade do desenvolvimento. Além do mais, em caso de
mudanças nos tipos de notícia, regerando o código automaticamente, o sistema estará correto
(embora não haja a possibilidade de alterar a definição em tempo de execução).
Usaremos o JaTS para isso, mas devido a suas restrições, alteraremos a classe de
fonte da seguinte forma:
2.4.4.1. Source file de Noticia.java
Com annotations e Generics removidos e alterando os nomes das constantes como os
sufixos dos nomes dos subtipos de Noticia correspondentes (por ausência de documentação de
métodos que manipulem String):
public abstract class Noticia extends AbstractBusinessEntity {
public final static String Cadastrada = "noticia_cadastrada"; public final static String Twitter = "noticia_twitter"; private Membro autor;
private String tipo;
public Noticia(){
// TODO Auto-generated constructor stub }
public Noticia(Membro autor, String tipo){ this.autor = autor;
this.tipo = tipo; }
public Membro getAutor() { return autor; }
public void setAutor(Membro autor) { this.autor = autor;
}
public String getTipo() { return tipo; }
public void setTipo(String tipo) { this.tipo = tipo;
}
public abstract int compareTo(Noticia noticia); }
2.4.4.2. Template LHS da transformação
ClassPattern #EntityClass {
package br.ufpe.cin.rgms.noticia.modelo; ImportDeclarationSet:#C_IDS;
ModifierList:#M class Noticia extends AbstractBusinessEntity {
FieldDeclarationSet:#FDS; InitializerSet:#BASIC_TYPE_INIT_SET; ConstructorDeclarationSet:#BASIC_TYPE_CDS; MethodDeclarationSet:#BASIC_TYPE_MDS; } }
2.4.4.3. Template RHS da transformação
ClassPattern #ServletClass { package br.ufpe.cin.rgms.noticia.controle; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import br.ufpe.cin.rgms.Facade; import br.ufpe.cin.rgms.base.RGMSException; import br.ufpe.cin.rgms.noticia.modelo.Noticia; import br.ufpe.cin.rgms.noticia.modelo.NoticiaCadastrada; import br.ufpe.cin.rgms.noticia.modelo.NoticiaTwitter;public class AdicionarNoticiaServlet extends NoticiaModelMapperServlet { protected Noticia getObject() {
Noticia ret = null;
String tipo = (String) this.getFormfields().get("tipo");
forall #A #in #FDS {
#if(#A.hasModifier("public") && #A.hasModifier("static") && #A.hasModifier("final")) {
forall #VD #in #< #A.getVariables() ># { if (tipo.equals(Noticia.#< #VD.getName() >#)) { ret = new #<#VD.getName().addPrefix("Noticia")>#(); } } } } ret.setTipo(tipo); return ret; }
protected void handleObject(Noticia object) throws RGMSException { Facade.getInstance().inserirNoticia(object);
}
protected void handleSuccess(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException { request.setAttribute("noticiastatus","Noticia cadastrada com sucesso."); }
} }
2.4.4.4. transform.jtp
#Arquivo de descricao de transformacao
filesDirectory=D:/Downloads/Project/Graduation/TAES/RGMS_Work/jats/add-noticia wizardJarName=null source.0=Noticia.java sourceJarName=null applyOutputDir=D:/Downloads/Project/Graduation/TAES/RGMS_Work/jats/add-noticia/Saida/ templateLeft.0=sourcePattern.jats templateRight.0=targetPattern.jats
O código gerado foi AdicionarNoticiaServlet_out.java:
package br.ufpe.cin.rgms.noticia.controle; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import br.ufpe.cin.rgms.Facade; import br.ufpe.cin.rgms.base.RGMSException; import br.ufpe.cin.rgms.noticia.modelo.Noticia; import br.ufpe.cin.rgms.noticia.modelo.NoticiaCadastrada; import br.ufpe.cin.rgms.noticia.modelo.NoticiaTwitter;public class AdicionarNoticiaServlet extends NoticiaModelMapperServlet {
protected Noticia getObject() { Noticia ret = null;
String tipo = (String)this.getFormfields().get("tipo");
if (tipo.equals(Noticia.Cadastrada)) { ret = new NoticiaCadastrada(); }
if (tipo.equals(Noticia.Twitter)) { ret = new NoticiaTwitter(); }
ret.setTipo(tipo); return ret; }
protected void handleObject(Noticia object) throws RGMSException{ Facade.getInstance().inserirNoticia(object);
}
protected void handleSuccess(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException{
} }