package br.com.caelum.stella.validation;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import br.com.caelum.stella.DigitoGenerator;
import br.com.caelum.stella.DigitoPara;
import br.com.caelum.stella.MessageProducer;
import br.com.caelum.stella.SimpleMessageProducer;
import br.com.caelum.stella.ValidationMessage;
import br.com.caelum.stella.format.CNPJFormatter;
import br.com.caelum.stella.format.RenavamFormatter;
import br.com.caelum.stella.validation.error.RenavamError;
/**
* <p>
* Verifica se uma cadeia (String) representa um Renavam válido.
* </p>
*
* <p>
* O Renavam, ou Registro nacional de veículos automotores, é o número único de
* cada veículo e é composto de 10 (dez) dígitos, mais um digito verificador.
* </p>
*
* Formato do Renavam: "dddd.dddddd-d" onde "d" é um digito decimal.
*
* @author Rafael Carvalho
*/
public class RenavamValidator implements Validator<String> {
public static final Pattern FORMATED = Pattern.compile("(\\d{4})[.](\\d{6})-(\\d{1})");
public static final Pattern UNFORMATED = Pattern.compile("(\\d{4})(\\d{6})(\\d{1})");
private boolean isFormatted = false;
private MessageProducer messageProducer;
/**
* Construtor padrão de validador do Renavam. Este considera, por padrão,
* que as cadeias estão formatadas e utiliza um
* {@linkplain SimpleMessageProducer} para geração de mensagens.
*/
public RenavamValidator() {
this.messageProducer = new SimpleMessageProducer();
}
/**
* Construtor de validador de Renavam. O validador utiliza um
* {@linkplain SimpleMessageProducer} para geração de mensagens.
*
* @param isFormatted
* Informa se a cadeia de caracteres a ser validada está ou não
* formatada
*/
public RenavamValidator(boolean isFormatted) {
this.isFormatted = isFormatted;
this.messageProducer = new SimpleMessageProducer();
}
/**
* <p>
* Construtor do validador de Renavam
* </p>
*
* @param messageProducer
* produtor de mensagem de erro.
* @param isFormatted
* Informa se a cadeia de caracteres a ser validada está ou não
* formatada
*/
public RenavamValidator(MessageProducer messageProducer, boolean isFormatted) {
this.isFormatted = isFormatted;
this.messageProducer = messageProducer;
}
public void assertValid(String renavam) {
List<ValidationMessage> errors = getInvalidValues(renavam);
if (!errors.isEmpty()) {
throw new InvalidStateException(errors);
}
}
public List<ValidationMessage> invalidMessagesFor(String renavam) {
return getInvalidValues(renavam);
}
/**
* Valida se a cadeia está de acordo com as regras de validação do Renavam.
*
* @see br.com.caelum.stella.validation.Validator#assertValid(java.lang.Object)
*
* @param renavam
* Cadeia de caracteres representando o Renavam a ser validado
* @return Uma lista de {@linkplain ValidationMessage} com os erros encontrados
* ou uma lista vazia, caso não haja nenhum erro.
*/
private List<ValidationMessage> getInvalidValues(String renavam) {
List<ValidationMessage> errors = new ArrayList<ValidationMessage>();
if (renavam != null) {
renavam = formataPadraoNovo(renavam);
if (isFormatted && !FORMATED.matcher(renavam).matches()) {
errors.add(messageProducer.getMessage(RenavamError.INVALID_FORMAT));
}
String unformatedRenavam = null;
try {
unformatedRenavam = new RenavamFormatter().unformat(renavam);
} catch (IllegalArgumentException e) {
errors.add(messageProducer.getMessage(RenavamError.INVALID_DIGITS));
return errors;
}
if (unformatedRenavam.length() != 11 || !unformatedRenavam.matches("[0-9]*")) {
errors.add(messageProducer.getMessage(RenavamError.INVALID_DIGITS));
}
String renavamSemDigito = unformatedRenavam.substring(0, unformatedRenavam.length() - 1);
String digito = unformatedRenavam.substring(unformatedRenavam.length() - 1);
String digitoCalculado = calculaDigito(renavamSemDigito);
if (!digito.equals(digitoCalculado)) {
errors.add(messageProducer.getMessage(RenavamError.INVALID_CHECK_DIGIT));
}
}
return errors;
}
private String formataPadraoNovo(String renavam) {
if ((isFormatted && renavam.length() == 11) || (!isFormatted && renavam.length() == 9)) {
return "00" + renavam;
}
return renavam;
}
private String calculaDigito(String renavamSemDigito) {
return new DigitoPara(renavamSemDigito).complementarAoModulo().trocandoPorSeEncontrar("0",10,11).mod(11).calcula();
}
public boolean isEligible(String renavam) {
boolean isEligible;
if (isFormatted) {
isEligible = RenavamFormatter.FORMATTED.matcher(renavam).matches();
} else {
isEligible = RenavamFormatter.UNFORMATTED.matcher(renavam).matches();
}
return isEligible;
}
@Override
public String generateRandomValid() {
final String renavamSemDigito = new DigitoGenerator().generate(10);
final String renavamComDigito = renavamSemDigito + calculaDigito(renavamSemDigito);
if (isFormatted) {
return new RenavamFormatter().format(renavamComDigito);
}
return renavamComDigito;
}
}