/* This file is part of PorExtenso. PorExtenso is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. PorExtenso is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with PorExtenso. If not, see <http://www.gnu.org/licenses/>. Copyright 2008, Marcelo Criscuolo. */ package br.com.jsti.porextenso; import java.math.BigDecimal; import java.math.BigInteger; import java.util.HashMap; import java.util.Map; import java.util.Stack; /** * @author Marcelo Criscuolo - criscuolo (dot) marcelo (at) gmail (dot) com * */ public class CurrencyWriter { private static final BigInteger THOUSAND = new BigInteger("1000"); private static final BigInteger HUNDRED = new BigInteger("100"); private static final String CENTO = "cento"; private static final String CEM = "cem"; /** * Nomes das grandezas numéricas no plural. O mapa a chave do mapa é o * expoente de dez do número e o valor é o seu nome no plural. Por exemplo: * para chave 3 (10^3) o valor é "mil", para a chave 6 (10^6) o valor é * "milhões", e assim por diante. Os nomes foram obtidos de um artigo * publicado na seção Dois Mais Dois na revista SuperInteressante nº. 15, de * dezembro de 1988 (Editora Abril, São Paulo/SP), disponível em * http://www.novomilenio.inf.br/idioma/19881200.htm. */ private final Map<Integer, String> grandezasPlural = new HashMap<Integer, String>(); private final Map<Integer, String> grandezasSingular = new HashMap<Integer, String>(); /** Nomes dos números. */ private final Map<Integer, String> nomes = new HashMap<Integer, String>(); private static final String MOEDA_SINGULAR = "real"; private static final String MOEDA_PLURAL = "reais"; private static final String FRACAO_SINGULAR = "centavo"; private static final String FRACAO_PLURAL = "centavos"; private static final String PARTICULA_ADITIVA = "e"; private static final String PARTICULA_DESCRITIVA = "de"; /** * O conversor reconhece números até a ordem dos setilhões, portanto, o * maior valor suportado atualmente é o representado abaixo. */ private static final BigDecimal MAX_SUPPORTED_VALUE = new BigDecimal("999999999999999999999999999.99"); private static CurrencyWriter instance = null; public static CurrencyWriter getInstance() { if (instance == null) { instance = new CurrencyWriter(); } return instance; } private CurrencyWriter() { preencherGrandezasPlural(); preencherGrandezasSingular(); preencherNomes(); } public String write(final BigDecimal amount) { if (null == amount) {throw new IllegalArgumentException();} /* * TODO substituir o método setScale, abaixo, pela versão cujo * parâmetro de arredondamento é um enum */ BigDecimal value = amount.setScale(2, BigDecimal.ROUND_HALF_EVEN); if (value.compareTo(BigDecimal.ZERO) <= 0) {return "";} if (MAX_SUPPORTED_VALUE.compareTo(value) < 0) { throw new IllegalArgumentException("Valor acima do limite suportado."); } Stack<Integer> decomposed = decompose(value); /* Se o número estiver, digamos, na casa dos milhões, a pilha * deverá conter 4 elementos sendo os dois últimos os das * centenas e dos centavos, respectivamente. Assim, o expoente de * dez que representa a grandeza no topo da pilha é o número de * (elementos - 2) * 3 */ int expoente = 3 * (decomposed.size() - 2); // TODO usar um índice de grupos em vez do expoente StringBuffer sb = new StringBuffer(); int lastNonZeroExponent = -1; while (!decomposed.empty()) { int valor = decomposed.pop(); if (valor > 0) { sb.append(" ").append(PARTICULA_ADITIVA).append(" "); sb.append(comporNomeGrupos(valor)); String nomeGrandeza = obterNomeGrandeza(expoente, valor); if (nomeGrandeza.length() > 0) { sb.append(" "); } sb.append(nomeGrandeza); lastNonZeroExponent = expoente; } switch (expoente) { // TODO ao invés desses switches e ifs, partir para a idéia das "Pendências"; talvez implementá-las com enum case 0: BigInteger parteInteira = value.toBigInteger(); if (BigInteger.ONE.equals(parteInteira)) { sb.append(" ").append(MOEDA_SINGULAR); } else if (parteInteira.compareTo(BigInteger.ZERO) > 0) { if (lastNonZeroExponent >= 6) { sb.append(" ").append(PARTICULA_DESCRITIVA); } sb.append(" ").append(MOEDA_PLURAL); } break; case -3: if (1 == valor) { sb.append(" ").append(FRACAO_SINGULAR); } else if (valor > 1) { sb.append(" ").append(FRACAO_PLURAL); } break; } expoente -= 3; } return sb.substring(3); } private StringBuffer comporNomeGrupos(int valor) { StringBuffer nome = new StringBuffer(); int centenas = valor - (valor % 100); int unidades = valor % 10; int dezenas = (valor - centenas) - unidades; int duasCasas = dezenas + unidades; if (centenas > 0) { nome.append(" ").append(PARTICULA_ADITIVA).append(" "); if (100 == centenas) { if (duasCasas > 0) { nome.append(CENTO); } else { nome.append(CEM); } } else { nome.append(nomes.get(centenas)); } } if (duasCasas > 0) { nome.append(" ").append(PARTICULA_ADITIVA).append(" "); if (duasCasas < 20) { nome.append(nomes.get(duasCasas)); } else { if (dezenas > 0) { nome.append(nomes.get(dezenas)); } if (unidades > 0) { nome.append(" ").append(PARTICULA_ADITIVA).append(" "); nome.append(nomes.get(unidades)); } } } return nome.delete(0, 3); } private String obterNomeGrandeza(int exponent, int value) { if (exponent < 3) {return "";} if (1 == value) { return grandezasSingular.get(exponent); } else { return grandezasPlural.get(exponent); } } private Stack<Integer> decompose(BigDecimal value) { BigInteger intermediate = value.multiply(new BigDecimal(100)).toBigInteger(); Stack<Integer> decomposed = new Stack<Integer>(); BigInteger[] result = intermediate.divideAndRemainder(HUNDRED); intermediate = result[0]; decomposed.add(result[1].intValue()); while (intermediate.compareTo(BigInteger.ZERO) > 0) { result = intermediate.divideAndRemainder(THOUSAND); intermediate = result[0]; decomposed.add(result[1].intValue()); } /* * Se o valor for apenas em centavos, adicionar zero para a casa dos * reais inteiros */ if (decomposed.size() == 1) { decomposed.add(0); } return decomposed; } private void preencherGrandezasPlural() { grandezasPlural.put(3, "mil"); grandezasPlural.put(6, "milhões"); grandezasPlural.put(9, "bilhões"); grandezasPlural.put(12, "trilhões"); grandezasPlural.put(15, "quatrilhões"); grandezasPlural.put(18, "quintilhões"); grandezasPlural.put(21, "sextilhões"); grandezasPlural.put(24, "setilhões"); } private void preencherGrandezasSingular() { grandezasSingular.put(3, "mil"); grandezasSingular.put(6, "milhão"); grandezasSingular.put(9, "bilhão"); grandezasSingular.put(12, "trilhão"); grandezasSingular.put(15, "quatrilhão"); grandezasSingular.put(18, "quintilhão"); grandezasSingular.put(21, "sextilhão"); grandezasSingular.put(24, "setilhão"); } private void preencherNomes() { nomes.put(1, "um"); nomes.put(2, "dois"); nomes.put(3, "três"); nomes.put(4, "quatro"); nomes.put(5, "cinco"); nomes.put(6, "seis"); nomes.put(7, "sete"); nomes.put(8, "oito"); nomes.put(9, "nove"); nomes.put(10, "dez"); nomes.put(11, "onze"); nomes.put(12, "doze"); nomes.put(13, "treze"); nomes.put(14, "quatorze"); nomes.put(15, "quinze"); nomes.put(16, "dezesseis"); nomes.put(17, "dezessete"); nomes.put(18, "dezoito"); nomes.put(19, "dezenove"); nomes.put(20, "vinte"); nomes.put(30, "trinta"); nomes.put(40, "quarenta"); nomes.put(50, "cinquenta"); nomes.put(60, "sessenta"); nomes.put(70, "setenta"); nomes.put(80, "oitenta"); nomes.put(90, "noventa"); nomes.put(200, "duzentos"); nomes.put(300, "trezentos"); nomes.put(400, "quatrocentos"); nomes.put(500, "quinhentos"); nomes.put(600, "seiscentos"); nomes.put(700, "setecentos"); nomes.put(800, "oitocentos"); nomes.put(900, "novecentos"); } }