package net.sf.daotools.model;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import net.sf.daotools.exceptions.SQLSyntaxError;
import net.sf.daotools.util.Utils;
/**
* @author JOAO
*
*/
public class Converter {
private String sql, hql, hqlAppend, metodo;
/**
* Indica se as linhas que estão sendo processadas atualmente "fazem parte"
* do "from"
*/
private boolean linhasSaoFrom = false;
/**
* Number of line being processed.
*/
private int lnNumber = 0;
private Set<String> keywords;
private static Converter instance;
private StringBuffer hqlBuffer;
private Converter() {
this.initializeKeywords();
}
public static Converter getInstance() {
if (instance == null)
instance = new Converter();
return instance;
}
/**
* Initializes the keywords list
*/
private void initializeKeywords() {
this.keywords = new HashSet<String>(Arrays.asList(new String[] {
"select", "from", "where", "order", "by", "and", "or", "group",
"left", "on", "right", "in","outer" }));
}
/**
* Método que gera o HQL correspondente ao SQL passado como parâmetro. Não
* analisa (faz parsing) do código, apenas converte para camel case. <br>
* <br>
*
* No caso do "from" é utilizada uma lógica específica, pois o nome de cada
* POJO precisa começar com UpperCase. Espera-se que a palavra chave "from"
* esteja no início de uma linha, caso contrário o método não funciona.
*
* @param sql -a string que contém o código SQL.
* @return o hql gerado
*
* @throws SQLSyntaxError
*/
public String generateHQL(String sql) throws SQLSyntaxError {
this.sql = sql.toLowerCase();
this.hqlBuffer = new StringBuffer();
this.verificarSintaxeSql();
for (this.lnNumber = 0; this.lnNumber < this.obterLinhasSql().length; ++this.lnNumber) {
String linha = this.obterLinhaSql(this.lnNumber);
if (linha.startsWith("from ")) {
// APENAS VAI GERAR O CÒDIGO CORRETO SE O "from" ESTIVER NO
// INÍCIO DE UMA LINHA
this.processarLinhaInicioFrom(linha);
} else if (this.linhasSaoFrom) {
this.processarFrom(linha);
} else {
// para todos os outros casos basta chamar o método
// "converterParaCamelCase"
linha = this.converterParaCamelCase(linha, false);
hqlBuffer.append(linha + "\n");
}
}
this.hql = hqlBuffer.toString();
return this.hql;
}
/**
* Verifica se a sintaxe do sql digitado está correta. Se não estiver
* dispara uma {@link IllegalArgumentException}
*
* @throws SQLSyntaxError
*/
private void verificarSintaxeSql() throws SQLSyntaxError {
for (this.lnNumber = 0; this.lnNumber < this.obterLinhasSql().length; ++this.lnNumber) {
final String linhaAtual = obterLinhaSql(this.lnNumber);
final String proximaLinha = obterLinhaSql(this.lnNumber + 1);
// por enquando apenas faz a verificação de virgulas.
// qdo for adicionada mais verificação, as linhas abaixo devem virar
// um método
final boolean atualNaoTerminaComVirgula = !linhaAtual.trim()
.endsWith(",");
final boolean proxNaoComecaComKeyword = Utils
.isStringNotVoid(proximaLinha)
&& !this.linhaComecaComKeyword(proximaLinha);
if (atualNaoTerminaComVirgula && proxNaoComecaComKeyword) {
throw new SQLSyntaxError(this.lnNumber, "");
}
if (!atualNaoTerminaComVirgula && !proxNaoComecaComKeyword) {
throw new SQLSyntaxError(this.lnNumber, "");
}
}
}
/**
* verifica se uma linha começa com uma keyword
*
* @param linha
* @return
*/
private boolean linhaComecaComKeyword(String linha) {
if (Utils.isStringVoid(linha)) {
// linha vazia não começa com a keyword...
return false;
}
return this.keywords.contains(linha.trim().split(" ")[0]);
}
/**
* Retorna uma linha específica do sql
*
* @param i
* @return
*/
private String obterLinhaSql(int i) {
String[] linhas = this.obterLinhasSql();
if (i >= linhas.length) {
return null;
}
return linhas[i];
}
/**
* Gera o hql para a linha que começa com a palavra "from"
*
* @param linha
*/
private void processarLinhaInicioFrom(String linha) {
this.hqlBuffer.append("from ");
this.processarFrom(this.linhaSemKeyword(linha, "from"));
}
/**
* Gera o hql para linhas relativas ao "from"
*
* @param linha
*/
private void processarFrom(String linha) {
String[] tabelas = this.obterTabelasDoFrom(linha);
for (int i = 0; i < tabelas.length; ++i) {
final String nomeTabela = tabelas[i].trim();
if (Utils.isStringNotVoid(nomeTabela)) {
this.hqlBuffer.append(this.converterParaCamelCase(nomeTabela,
true));
this.verificarSeProximaLinhaEhFrom(linha);
final boolean haMaisTabelasNessaLinha = i < (tabelas.length - 1);
if (haMaisTabelasNessaLinha || this.linhasSaoFrom) {
this.hqlBuffer.append(this.linhasSaoFrom ? "," : ", ");
}
}
}
this.hqlBuffer.append("\n");
}
/**
* Verifica se a próxima linha ainda é relativa ao from
*
* @param linhaAtual
*/
private void verificarSeProximaLinhaEhFrom(String linhaAtual) {
if (linhaAtual.trim().endsWith(",")) {
this.linhasSaoFrom = true;
} else {
this.linhasSaoFrom = false;
}
}
/**
* Retorna uma array contendo o nome das tabelas que estão no "from"
*
* @param linha
* @return
*/
private String[] obterTabelasDoFrom(String linha) {
return linha.split(",");
}
/**
* Elimina o "from" do início de uma linha
*
* @param linha
* @return
*/
private String linhaSemKeyword(String linha, String keyword) {
return linha.substring((keyword.length() + 1));
}
/**
* Retorna um array de String contendo as linhas de um sql.
*
* @return
*/
private String[] obterLinhasSql() {
return this.sql.split("\n");
}
/**
* Converte uma string para camel case. O boolean informa se a primeira
* letra deve ser maiúscula (nome de classe) ou não (nome de variável).
*
* @param string
* @param primeiraMaiuscula
* @return
*/
private String converterParaCamelCase(String string,
boolean primeiraMaiuscula) {
// se a string for vazia, vai retornar uma string vazia
if (Utils.isStringVoid(string)) {
return "";
}
StringBuffer sb = new StringBuffer();
String[] partes = string.split("_");
if (primeiraMaiuscula) {
sb.append(partes[0].substring(0, 1).toUpperCase()
+ partes[0].substring(1));
} else {
sb.append(partes[0]);
}
for (int i = 1; i < partes.length; ++i) {
sb.append(partes[i].substring(0, 1).toUpperCase()
+ partes[i].substring(1));
}
return sb.toString();
}
/**
* Gera o código HQL já dentro de um <code>sql.append(" codigo ")</code>;<br>
* <br>
* Utiliza o método <code>criarHQL(String sql)</code> para gerar o HQL.
*
* @param sql -
* a string que contém o código SQL.
* @return
* @throws SQLSyntaxError
*/
public String criarHQLAppend(String sql) throws SQLSyntaxError {
this.generateHQL(sql);
StringBuffer sb = new StringBuffer();
String[] linhas = this.hql.split("\n");
for (String linha : linhas) {
sb.append("sql.append(\" " + linha + " \");\n");
}
this.hqlAppend = sb.toString();
return this.hqlAppend;
}
public String criarMetodo(String sql) {
// ANTIGO "gerarHQL" - copiado aqui por conter c�digo que pode ser util
// neste m�todo
// this.sql = sql.toLowerCase();
// StringBuffer sbHql = new StringBuffer();
// String[] linhas = this.sql.split("\n");
// for (String linha : linhas) {
// if (linha.startsWith("select ")) {
// // adiciona a keyword "select" no HQL
// sbHql.append("select ");
// // elemina "select " da string que ser� processada
// linha = linha.substring(7);
// String[] atrSelect = linha.split(",");
// for (int i = 0; i < atrSelect.length; ++i) {
// String atr = atrSelect[i].trim();
// String[] atrArray = atr.split("[.]");
// sbHql.append(atrArray[0] + "." +
// this.converterParaCamelCase(atrArray[1], false));
// if (i < (atrSelect.length - 1 )) {
// sbHql.append(", ");
// }
// }
// sbHql.append("\n");
// }
// else if (linha.startsWith("from ")) {
// // adiciona a keyword "from" no HQL
// sbHql.append("from ");
// // elemina "from " da string que ser� processada
// linha = linha.substring(5);
// String[] tabelasFrom = linha.split(",");
// for (int i = 0; i < tabelasFrom.length; ++i) {
// String atr = tabelasFrom[i].trim();
// String[] atrArray = atr.split(" ");
// sbHql.append(this.converterParaCamelCase(atrArray[0], true) + " " +
// atrArray[1]);
// if (i < (tabelasFrom.length - 1 )) {
// sbHql.append(", ");
// }
// }
// sbHql.append("\n");
// }
// else if (linha.startsWith("where ")) {
// sbHql.append("where ");
// linha = linha.substring(6);
// String[] condWhere = linha.split("[=]");
// for (int i = 0; i < condWhere.length; ++i) {
// String atr = condWhere[i].trim();
// if (atr.contains(".")) {
// String[] atrArray = atr.split("[.]");
// sbHql.append(atrArray[0] + "." +
// this.converterParaCamelCase(atrArray[1], false));
// }
// else {
// sbHql.append(atr);
// }
// if (i < (condWhere.length - 1 )) {
// sbHql.append(" = ");
// }
// }
// sbHql.append("\n");
// }
// else if (linha.startsWith("and ")) {
// sbHql.append("and ");
// linha = linha.substring(4);
// String[] condWhere = linha.split("[=]");
// for (int i = 0; i < condWhere.length; ++i) {
// String atr = condWhere[i].trim();
// if (atr.contains(".")) {
// String[] atrArray = atr.split("[.]");
// sbHql.append(atrArray[0] + "." +
// this.converterParaCamelCase(atrArray[1], false));
// }
// else {
// sbHql.append(atr);
// }
// if (i < (condWhere.length - 1 )) {
// sbHql.append(" = ");
// }
// }
// sbHql.append("\n");
// }
// else {
// // converte a linha para camelcase, e mostra msg de erro dizendo que
// pode haver erros
// sbHql.append(converterParaCamelCase(linha, false)+ "\n");
// sbHql.append("\nERRO: Nesta vers�o do programa uma linha pode come�ar
// apenas com ");
// sbHql.append("\"select\", \"from\", \"where\" ou \"and\".\n");
// sbHql.append("ERRO: a linha \"" + linha + "\" foi convertida para
// camel case, ");
// sbHql.append("mas pode haver erros nela.\n");
// }
// }
// this.hql = sbHql.toString();
return this.metodo;
}
}