package br.com.concretesolutions.canarinho; import android.util.SparseArray; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; /** * Uma fluent interface para o cálculo de dígitos, que é usado em diversos boletos e * documentos. * <p> * Para exemplificar, o dígito do trecho 0000039104766 para os multiplicadores indo de * 2 a 7 e usando módulo 11 é a seguinte: * </p> * <pre> * 0 0 0 0 0 3 9 1 0 4 7 6 6 (trecho numérico) * 2 7 6 5 4 3 2 7 6 5 4 3 2 (multiplicadores, da direita para a esquerda e ciclando) * ----------------------------------------- multiplicações algarismo a algarismo * 0 0 0 0 0 9 18 7 0 20 28 18 12 -- soma = 112 * </pre> * <p> * Tira-se o módulo dessa soma e, então, calcula-se o complementar do módulo e, se o número * for 0, 10 ou 11, o dígito passa a ser 1. * </p> * <pre> * soma = 112 * soma % 11 = 2 * 11 - (soma % 11) = 9 * </pre> * <p> * NOTE: Esta é uma versão otimizada para Android inspirada em * https://github.com/caelum/caelum-stella/blob/master/stella-core/src/main/java/br/com/caelum/stella/DigitoPara.java * </p> */ public final class DigitoPara { private final List<Integer> numero = new LinkedList<>(); private final List<Integer> multiplicadores; private final boolean complementar; private final int modulo; private final boolean somarIndividual; private final SparseArray<String> substituicoes; private DigitoPara(Builder builder) { multiplicadores = builder.multiplicadores; complementar = builder.complementar; modulo = builder.modulo; somarIndividual = builder.somarIndividual; substituicoes = builder.substituicoes; } /** * Faz a soma geral das multiplicações dos algarismos pelos multiplicadores, tira o * módulo e devolve seu complementar. * * @param trecho Bloco para calcular o dígito * @return String o dígito vindo do módulo com o número passado e configurações extra. */ public final String calcula(String trecho) { numero.clear(); final char[] digitos = trecho.toCharArray(); for (int i = 0; i < digitos.length; i++) { numero.add(Character.getNumericValue(digitos[i])); } Collections.reverse(numero); int soma = 0; int multiplicadorDaVez = 0; for (int i = 0; i < numero.size(); i++) { final int multiplicador = multiplicadores.get(multiplicadorDaVez); final int total = numero.get(i) * multiplicador; soma += somarIndividual ? somaDigitos(total) : total; multiplicadorDaVez = proximoMultiplicador(multiplicadorDaVez); } int resultado = soma % modulo; if (complementar) { resultado = modulo - resultado; } if (substituicoes.get(resultado) != null) { return substituicoes.get(resultado); } return String.valueOf(resultado); } /* * soma os dígitos do número (até 2) * * Ex: 18 => 9 (1+8), 12 => 3 (1+2) */ private int somaDigitos(int total) { return (total / 10) + (total % 10); } /* * Devolve o próximo multiplicador a ser usado, isto é, a próxima posição da lista de * multiplicadores ou, se chegar ao fim da lista, a primeira posição, novamente. */ private int proximoMultiplicador(int multiplicadorDaVez) { int multiplicador = multiplicadorDaVez + 1; if (multiplicador == multiplicadores.size()) { multiplicador = 0; } return multiplicador; } /** * Builder com interface fluente para criação de instâncias configuradas de * {@link DigitoPara} */ public static final class Builder { private List<Integer> multiplicadores = new ArrayList<>(); private boolean complementar; private int modulo; private boolean somarIndividual; private final SparseArray<String> substituicoes = new SparseArray<String>(); /** * @param modulo Inteiro pelo qual o resto será tirado e também seu complementar. * O valor padrão é 11. * @return this */ public final Builder mod(int modulo) { this.modulo = modulo; return this; } /** * Para multiplicadores (ou pesos) sequenciais e em ordem crescente, esse método permite * criar a lista de multiplicadores que será usada ciclicamente, caso o número base seja * maior do que a sequência de multiplicadores. Por padrão os multiplicadores são iniciados * de 2 a 9. No momento em que você inserir outro valor este default será sobrescrito. * * @param inicio Primeiro número do intervalo sequencial de multiplicadores * @param fim Último número do intervalo sequencial de multiplicadores * @return this */ public final Builder comMultiplicadoresDeAte(int inicio, int fim) { this.multiplicadores.clear(); for (int i = inicio; i <= fim; i++) { multiplicadores.add(i); } return this; } /** * <p> * Indica se, ao calcular o módulo, a soma dos resultados da multiplicação deve ser * considerado digito a dígito. * </p> * Ex: 2 X 9 = 18, irá somar 9 (1 + 8) invés de 18 ao total. * * @return this */ public final Builder somandoIndividualmente() { this.somarIndividual = true; return this; } /** * É comum que os geradores de dígito precisem do complementar do módulo em vez * do módulo em sí. Então, a chamada desse método habilita a flag que é usada * no método mod para decidir se o resultado devolvido é o módulo puro ou seu * complementar. * * @return this */ public final Builder complementarAoModulo() { this.complementar = true; return this; } /** * Troca por uma String caso encontre qualquer dos inteiros passados como argumento * * @param substituto String para substituir * @param i varargs de inteiros a serem substituídos * @return this */ public final Builder trocandoPorSeEncontrar(String substituto, Integer... i) { substituicoes.clear(); for (Integer integer : i) { substituicoes.put(integer, substituto); } return this; } /** * Há documentos em que os multiplicadores não usam todos os números de um intervalo * ou alteram sua ordem. Nesses casos, a lista de multiplicadores pode ser passada * através de varargs. * * @param multiplicadoresEmOrdem Sequência de inteiros com os multiplicadores em ordem * @return this */ public final Builder comMultiplicadores(Integer... multiplicadoresEmOrdem) { this.multiplicadores.clear(); this.multiplicadores.addAll(Arrays.asList(multiplicadoresEmOrdem)); return this; } /** * Método responsável por criar o DigitoPara. * Este método inicializará os seguintes valores padrões: * <ul> * <li>multiplicadores: 2 a 9</li> * <li>módulo: 11</li> * </ul> * * @return A instância imutável de DigitoPara */ public final DigitoPara build() { if (multiplicadores.size() == 0) { comMultiplicadoresDeAte(2, 9); } if (modulo == 0) { mod(11); } return new DigitoPara(this); } } }