package br.usp.ime.academicdevoir.infra; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.FileWriter; /** * Entidade responsável por corrigir uma questão de código Java. * * O código foi adaptado do projeto http://dl.dropbox.com/u/10977140/Aulas.rar */ public class TestadorDeCodigoJava { private String caminhoDoDiretorio; /* Endereço do caminhoDoDiretorio em que serao realizados os testes. */ public TestadorDeCodigoJava(String diretorio) { this.caminhoDoDiretorio = diretorio + "/"; } /** * Corrige questão de código java * @param codigo String com o código a ser testado * @param codigoDeTeste String com o código do teste a ser realizado * @return String com o resultado da correção automática * @throws Exception exceção lançado quando o direório para a realização dos * testes não é encintrado */ public synchronized String testaCodigoJava(String codigo, String codigoDeTeste) throws Exception { File diretorio; String nomeDaClasse; String resultadoDaCompilacao; // Verifica a existência do diretório em que os testes serao realizados if (!(diretorio = new File(caminhoDoDiretorio)).mkdirs()) { for (File arq : diretorio.listFiles()) { arq.delete(); } } nomeDaClasse = nomeDaPrimeiraClasseDefinidaNoCodigo(codigo); if (nomeDaClasse.equals("Nao ha classe definida.")) return "Nao ha classe definida."; // Compila código a ser testado. resultadoDaCompilacao = CompiladorJava.compila(caminhoDoDiretorio, geraArquivoComOCodigoJava(nomeDaClasse, codigo)); // Verifica se houve erro de compilação if (!resultadoDaCompilacao.equals("")) { return "Falha ao compilar o codigo.\n"+resultadoDaCompilacao; } // Gera o arquivo de teste //nomeDaClasse = "RealizaTesteNaClasseSubmetida"; codigoDeTeste = codigoDeTeste(codigoDeTeste); if (codigoDeTeste.startsWith("Problema no codigo de teste.")) return codigoDeTeste; geraArquivoComOCodigoJava(nomeDaClasse, codigo + "\n\n" +codigoDeTeste); resultadoDaCompilacao = CompiladorJava.compila(caminhoDoDiretorio, nomeDaClasse + ".java"); resultadoDaCompilacao = verificaAusenciaDeClasseOuMetodoASerTestado(resultadoDaCompilacao); if (!resultadoDaCompilacao.equals("")) return resultadoDaCompilacao; nomeDaClasse = "RealizaTesteNaClasseSubmetida"; // Executa programa return ExecutorDeProgramas.executaProgramaJava(caminhoDoDiretorio, nomeDaClasse); } /** * Corrige código de teste, no caso de haver chamadas a "assert" com menos * argumentos. * @param codigoDeTeste * @return String com o código de teste corrigido */ private String trataCodigoDeTeste(String codigoDeTeste) { codigoDeTeste = codigoDeTeste.replace("\r", ""); // Acrescenta parametro String no AssertEquals, caso nao tenha. int pos = codigoDeTeste.indexOf("assert"); while (pos != -1) { // Encontra o primeiro parametro while (codigoDeTeste.charAt(pos) != '(') pos++; pos++; int posFim = pos; int qtdeParentesesAberto = 0; while (qtdeParentesesAberto != 0 || codigoDeTeste.charAt(posFim) != ',') { if (codigoDeTeste.charAt(posFim) == '(') qtdeParentesesAberto++; if (codigoDeTeste.charAt(posFim) == ')') qtdeParentesesAberto--; if (qtdeParentesesAberto > 1000) return "Problema no codigo de teste." + " Notifique o professor." + " Nao foi possovel identificar o primeiro parametro." + " Excesso de paranteses abertos."; if (posFim == codigoDeTeste.length()-1) return "Problema no codigo de teste. " + "Notifique o professor. " + "Nao foi possivel identificar o primeiro parametro. " + "Fim do arquivo encontrado."; posFim++; } String parametro = codigoDeTeste.substring(pos, posFim); // Encontra o segundo parametro qtdeParentesesAberto = 0; posFim++; while (qtdeParentesesAberto != 0 || (codigoDeTeste.charAt(posFim) != ',' && codigoDeTeste.charAt(posFim) != ')')) { if (codigoDeTeste.charAt(posFim) == '(') qtdeParentesesAberto++; if (codigoDeTeste.charAt(posFim) == ')') qtdeParentesesAberto--; if (qtdeParentesesAberto > 1000) return "Problema no codigo de teste. " + "Notifique o professor. " + "Nao foi possivel identificar o ultimo parametro. " + "Excesso de parenteses abertos."; if (posFim == codigoDeTeste.length()-1) return "Problema no codigo de teste. " + "Notifique o professor. " + "Nao foi possivel identificar o ultimo parametro. " + "Fim do arquivo encontrado."; posFim++; } if (codigoDeTeste.charAt(posFim) == ')') { // Nao tem o ultimo parametro codigoDeTeste = codigoDeTeste.substring(0, posFim) + ",\"" + parametro.replace("\"", "\\\"") + "\"" + codigoDeTeste.substring(posFim); } pos = codigoDeTeste.indexOf("assert", pos); } return codigoDeTeste; } /** * Gera arquivo "nomeDaClasse.java" com o codigo fornecido. * @param nomeDaClasse nome da classe gerada pelo codigo recebido * @param codigo * @return String com o nome do arquivo gerado. * @throws Exception */ private synchronized String geraArquivoComOCodigoJava (String nomeDaClasse, String codigo) throws Exception { String nomeDoArquivo = nomeDaClasse + ".java"; File arquivo = new File(caminhoDoDiretorio + nomeDoArquivo); if (arquivo.exists()) arquivo.delete(); FileWriter arq = new FileWriter(arquivo); arq.write(codigo); // grava no arquivo o codigo arq.close(); return nomeDoArquivo; } /** * Devolve o nome da primeira classe no codigo fornecido. * @param codigo * @return nome da classe */ private String nomeDaPrimeiraClasseDefinidaNoCodigo(String codigo) { int pos = codigo.indexOf("class "); if (pos == -1) return "Nao ha classe definida."; pos = pos + "class ".length(); while (pos < codigo.length() && codigo.charAt(pos) == ' ') pos++; int posFim = pos; while (posFim < codigo.length() && (Character.isLetterOrDigit(codigo.charAt(posFim)) || codigo.charAt(posFim) == '_')) posFim++; if (posFim == pos) return "Nao ha classe definida."; // Nome da classe encontrada return codigo.substring(pos, posFim); } /** * Devolve o código de teste completo. Cria-se uma classe para os testes e * acrescentam-se os métodos "assert". * @param codigoDeTeste * @return codigo de teste completo * @throws Exception na abertura do arquivo com a definicao da classe e dos * métodos "assert" a serem acrescentado no código de teste. */ private String codigoDeTeste(String codigoDeTeste) throws Exception { String saida = ""; String linha; BufferedReader template; codigoDeTeste = trataCodigoDeTeste(codigoDeTeste); if (codigoDeTeste.startsWith("Problema no codigo de teste.")) return codigoDeTeste; try { template = new BufferedReader(new FileReader((new File(caminhoDoDiretorio)).getParentFile(). getParentFile().getParentFile(). getParent() + "/templates/TemplateDeTesteDeCodigoJava.java")); } catch (Exception ex) { throw new Exception ("Arquivo TemplateDeTesteDeCodigoJava.java nao encontrado"); } while ((linha = template.readLine()) != null) { if (linha.indexOf("/* Insert code here */") != -1) { saida = saida + codigoDeTeste + "\n"; } else { saida = saida + linha + "\n"; } } return saida; } /** * Verfica se está faltando algum método ou classe no código que está sendo * testado. * @param resultadoDaCompilacao * @return mensagem indicando a ausência de alguma classe ou método, ou "", * caso todos os métodos e classes a serem testados estejam no codigo sendo * testado. */ private String verificaAusenciaDeClasseOuMetodoASerTestado (String resultadoDaCompilacao) { if (!resultadoDaCompilacao.equals("")) { if (resultadoDaCompilacao.indexOf("cannot find symbol") != -1) { if (resultadoDaCompilacao.indexOf("symbol : class ") != -1) { String nome = extraiPalavraSeguinte(resultadoDaCompilacao, "symbol : class"); return "Nao foi encontrada a classe " + nome + "."; } else if (resultadoDaCompilacao.indexOf("symbol : method") != -1) { String nomeMetodo = extraiPalavraSeguinte( resultadoDaCompilacao, "symbol : method"); String nomeLocal = extraiPalavraSeguinte( resultadoDaCompilacao, "location: class "); return "Nao foi encontrado o metodo "+ nomeMetodo + " na classe " + nomeLocal + "."; } } else if (resultadoDaCompilacao.indexOf("cannot be applied to ") != -1) { String mensagem = extraiPalavraSeguinte(resultadoDaCompilacao, ": "); return "Problema com parametros: the method " + mensagem; } else if (resultadoDaCompilacao.indexOf("incompatible types") != -1) { String encontrado = extraiPalavraSeguinte(resultadoDaCompilacao, "found :"); String requerido = extraiPalavraSeguinte(resultadoDaCompilacao, "required: "); return "Tipos de dados incompativeis. Foi encontrado " + encontrado + " e era requerido " + requerido + "."; } return "Falha ao compilar o codigo de teste.\n"+resultadoDaCompilacao; } return ""; } /** * TODO Colocar esse mértodo em outro lugar. * Extrai a palavra seguinte à String dada por inicio no texto. * @param texto * @param inicio * @return palavra seguinte à inicio no texto */ private String extraiPalavraSeguinte(String texto, String inicio) { int tam = inicio.length(); if (texto.indexOf(inicio) == -1) return null; int posIni = texto.indexOf(inicio) + tam; int posFim = texto.indexOf('\n', posIni); return texto.substring(posIni, posFim); } }