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.CPFFormatter; import br.com.caelum.stella.validation.error.CPFError; /** * Verifica se uma cadeia (String) é válida para o documento de CPF (Cadastro de * Pessoa Física). * * @author Leonardo Bessa */ public class CPFValidator implements Validator<String> { public static final Pattern FORMATED = Pattern.compile("(\\d{3})[.](\\d{3})[.](\\d{3})-(\\d{2})"); public static final Pattern UNFORMATED = Pattern.compile("(\\d{3})(\\d{3})(\\d{3})(\\d{2})"); private final boolean isFormatted; private final boolean isIgnoringRepeatedDigits; private final MessageProducer messageProducer; /** * Construtor padrão de validador de CPF. Este considera, por padrão, que as * cadeias não estão formatadas e utiliza um {@linkplain SimpleMessageProducer} * para geração de mensagens. */ public CPFValidator() { this(new SimpleMessageProducer(), false, false); } /** * Construtor de validador de CPF. O validador utiliza um * {@linkplain SimpleMessageProducer} para geração de mensagens. Leva em * conta se o valor está ou não formatado. * * @param isFormatted * considera cadeia no formato de CPF:"ddd.ddd.ddd-dd" onde "d" é * um dígito decimal. */ public CPFValidator(boolean isFormatted) { this(new SimpleMessageProducer(), isFormatted, false); } /** * Construtor de validador de CPF. O validador utiliza um * * @param isFormatted * indica se o CPF está formatado. * @param isIgnoringRepeatedDigits * condição para ignorar cadeias de CPF com todos os dígitos * repetidos. {@linkplain SimpleMessageProducer} para geração de * mensagens. */ public CPFValidator(boolean isFormatted, boolean isIgnoringRepeatedDigits) { this(new SimpleMessageProducer(), isFormatted, isIgnoringRepeatedDigits); } /** * <p> * Construtor do Validador de CPF. Leva em consideração se o valor está * formatado. * </p> * <p> * Por padrão o validador criado não aceita cadeias de CPF com todos os * dígitos repetidos, quando todas as outras condições de validação são * aceitas. Para considerar estes documentos válidos use o construtor * {@link #CPFValidator(MessageProducer, boolean, boolean)} com a váriavel * {@linkplain #isIgnoringRepeatedDigits} em <code>true</code>. * </p> * * @param messageProducer * produtor de mensagem de erro. * @param isFormatted * considera cadeia no formato de CPF: "ddd.ddd.ddd-dd" onde "d" * é um dígito decimal. */ public CPFValidator(MessageProducer messageProducer, boolean isFormatted) { this(messageProducer, isFormatted, false); } /** * @param messageProducer * produtor de mensagem de erro. * @param isFormatted * condição para considerar cadeia no formato de CPF: * "ddd.ddd.ddd-dd" onde "d" é um dígito decimal. * @param isIgnoringRepeatedDigits * condição para ignorar cadeias de CPF com todos os dígitos * repetidos. */ public CPFValidator(MessageProducer messageProducer, boolean isFormatted, boolean isIgnoringRepeatedDigits) { this.messageProducer = messageProducer; this.isFormatted = isFormatted; this.isIgnoringRepeatedDigits = isIgnoringRepeatedDigits; } /** * Valida se a cadeia está de acordo com as regras de um CPF. * * @see br.com.caelum.stella.validation.Validator#assertValid(java.lang.Object) * @return <code>true</code> se a cadeia é válida ou é nula; * <code>false</code> caso contrario. */ private List<ValidationMessage> getInvalidValues(String cpf) { List<ValidationMessage> errors = new ArrayList<ValidationMessage>(); if (cpf != null) { if (isFormatted != FORMATED.matcher(cpf).matches()) { errors.add(messageProducer.getMessage(CPFError.INVALID_FORMAT)); } String unformatedCPF = null; try { unformatedCPF = new CPFFormatter().unformat(cpf); } catch (IllegalArgumentException e) { errors.add(messageProducer.getMessage(CPFError.INVALID_DIGITS)); return errors; } if (unformatedCPF.length() != 11 || !unformatedCPF.matches("[0-9]*")) { errors.add(messageProducer.getMessage(CPFError.INVALID_DIGITS)); } if ((!isIgnoringRepeatedDigits) && hasAllRepeatedDigits(unformatedCPF)) { errors.add(messageProducer.getMessage(CPFError.REPEATED_DIGITS)); } String cpfSemDigito = unformatedCPF.substring(0, unformatedCPF.length() - 2); String digitos = unformatedCPF.substring(unformatedCPF.length() - 2); String digitosCalculados = calculaDigitos(cpfSemDigito); if (!digitos.equals(digitosCalculados)) { errors.add(messageProducer.getMessage(CPFError.INVALID_CHECK_DIGITS)); } } return errors; } /** * Faz o cálculo dos digitos usando a lógica de CPF * * @return String os dois dígitos calculados. */ private String calculaDigitos(String cpfSemDigito) { DigitoPara digitoPara = new DigitoPara(cpfSemDigito); digitoPara.comMultiplicadoresDeAte(2, 11).complementarAoModulo().trocandoPorSeEncontrar("0",10,11).mod(11); String digito1 = digitoPara.calcula(); digitoPara.addDigito(digito1); String digito2 = digitoPara.calcula(); return digito1 + digito2; } private boolean hasAllRepeatedDigits(String cpf) { for (int i = 1; i < cpf.length(); i++) { if (cpf.charAt(i) != cpf.charAt(0)) { return false; } } return true; } @Override public boolean isEligible(String value) { if (value == null) { return false; } boolean result; if (isFormatted) { result = FORMATED.matcher(value).matches(); } else { result = UNFORMATED.matcher(value).matches(); } return result; } @Override public void assertValid(String cpf) { List<ValidationMessage> errors = getInvalidValues(cpf); if (!errors.isEmpty()) { throw new InvalidStateException(errors); } } @Override public List<ValidationMessage> invalidMessagesFor(String cpf) { return getInvalidValues(cpf); } @Override public String generateRandomValid() { final String cpfSemDigitos = new DigitoGenerator().generate(9); final String cpfComDigitos = cpfSemDigitos + calculaDigitos(cpfSemDigitos); if (isFormatted) { return new CPFFormatter().format(cpfComDigitos); } return cpfComDigitos; } }