[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots**\nIf applicable, add screenshots to help explain your problem.\n\n**Desktop (please complete the following information):**\n - OS: [e.g. iOS]\n - Browser [e.g. chrome, safari]\n - Version [e.g. 22]\n\n**Smartphone (please complete the following information):**\n - Device: [e.g. iPhone6]\n - OS: [e.g. iOS8.1]\n - Browser [e.g. stock browser, safari]\n - Version [e.g. 22]\n\n**Additional context**\nAdd any other context about the problem here.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest an idea for this project\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Describe alternatives you've considered**\nA clear and concise description of any alternative solutions or features you've considered.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n"
  },
  {
    "path": ".github/workflows/android_master.yml",
    "content": "name: Android Pull Request Master CI\n\non:\n  pull_request:\n    branches:\n      - 'master'\n\njobs:\n  Instrumented_Test:\n    runs-on: macOS-latest\n    strategy:\n      matrix:\n        api-level: [27, 29]\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v1\n        with:\n          fetch-depth: 1\n\n      - name: Instrumented Tests\n        uses: reactivecircus/android-emulator-runner@v1\n        with:\n          api-level: ${{ matrix.api-level }}\n          script: ./gradlew connectedCheck\n\n  unitTests:\n    name: Unit Tests\n    runs-on: ubuntu-18.04\n\n    steps:\n      - uses: actions/checkout@v1\n      - name: set up JDK 1.9\n        uses: actions/setup-java@v1\n        with:\n          java-version: 1.9\n      - name: Unit tests\n        run: bash ./gradlew test\n\n  linters:\n    name: Linters\n    runs-on: ubuntu-18.04\n\n    steps:\n      - uses: actions/checkout@v1\n      - name: set up JDK 1.9\n        uses: actions/setup-java@v1\n        with:\n          java-version: 1.9\n      - name: Checkstyle\n        run: bash ./gradlew checkstyle\n      - name: PMD\n        run: bash ./gradlew pmd"
  },
  {
    "path": ".gitignore",
    "content": ".gradle\n/local.properties\n/.idea/workspace.xml\n/.idea/libraries\n.DS_Store\n/build\n/captures\n.idea/\n*.iml\n\n# Built application files\n*.apk\n*.ap_\n\n# Files for the Dalvik VM\n*.dex\n\n# Java class files\n*.class\n\n# Generated files\nbin/\ngen/\n\n# Gradle files\n.gradle/\nbuild/\n\n# Local configuration file (sdk path, etc)\nlocal.properties\n\n# Proguard folder generated by Eclipse\nproguard/\n\n# Log Files\n*.log\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\n## 2.0.3\n    - Migração do bintray para Github Package Registry\n\n## 2.0.2\n    - Atualização androidx\n    - Migração CI para Github Actions\n    - Migração jitpack.io\n\n## 2.0.1\n    - Corrige bug no formatador da API 28\n    - Corrige bug do formatador numérico com símbolo\n\n## 2.0.0\n    - Alterado o pacote da bilbioteca. Junto a reformulação do nome da própria Concrete (remoção de Solutions)\n    - Corrige issue #19. Obrigado a @luisfernandezbr pelo fix\n    - Atualiza sistema de build\n\n### Quebras de API\n    - Corrige grafia do validador de telefone (TELFONE -> TELEFONE).\n\n## 1.2.0\n    - Adicionada configuração de `ValorMonetarioWatcher`\n        - É possível deixar o símbolo de Real\n        - É possível manter os zeros quando o campo é apagado em lote\n    - Adicionado Builder para criação de watchers de valor\n\n## 1.1.1:\n    - Corrigido bug de ArrayIndexOutOfBounds no `BoletoBancarioTextWatcher` após apagar em lote\n    - Adicionado teste de regressão para o caso acima na JVM que será executado no Travis\n\n## 1.1.0:\n    - Refatorados testes instrumentados\n    - Adicionado construtor para máscara genérica\n\n## 1.0.0:\n    - Removida necessidade de ter um validador/evento de validação na criação de Watchers\n\n## 0.1.0:\n    - Ajustes no código antes da versão 1.0\n\n## 0.0.9:\n    - Correção final para o formatador/validador de boleto. Tanto normal quanto tributo.\n## 0.0.8:\n    - Adição de formatdor/validador de CEP\n    - Correção de formatdor monetário ao rotacionar a tela\n## 0.0.7:\n    - Correção de validador de boleto quando setando o código de boleto inteiro (caso de uso: recebendo de um leitor de imagens)\n## 0.0.6:\n    - Watcher de CPF/CNPJ simultâneos\n## 0.0.5:\n    - Release no JCenter\n## 0.0.4:\n    - Watcher para valor monetário no padão Real (vírgula para casas de milhar e ponto para casas decimais)\n    - Re-estruturação do projeto para gerar os binários na pasta correta do Bintray\n## 0.0.3:\n    - Série de ajustes para Travis e JCenter (ainda não publicado)\n## 0.0.2:\n    - Formatadores de telefone\n## 0.0.1:\n    - Release inicial"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.\n\n## Our Standards\n\nExamples of behavior that contributes to creating a positive environment include:\n\n* Using welcoming and inclusive language\n* Being respectful of differing viewpoints and experiences\n* Gracefully accepting constructive criticism\n* Focusing on what is best for the community\n* Showing empathy towards other community members\n\nExamples of unacceptable behavior by participants include:\n\n* The use of sexualized language or imagery and unwelcome sexual attention or advances\n* Trolling, insulting/derogatory comments, and personal or political attacks\n* Public or private harassment\n* Publishing others' private information, such as a physical or electronic address, without explicit permission\n* Other conduct which could reasonably be considered inappropriate in a professional setting\n\n## Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.\n\n## Scope\n\nThis Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at bruno.silva@concrete.com.br. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.\n\nProject maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]\n\n[homepage]: http://contributor-covenant.org\n[version]: http://contributor-covenant.org/version/1/4/\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n"
  },
  {
    "path": "PULL_REQUEST_TEMPLATE.md",
    "content": "# Título para o PullRequest\nO que acha de deixar uma descrição simples sobre o seu PR?\n\n## Tipo de mudança\n- [ ] Bug fix \n- [ ] Nova Feature\n- [ ] Melhoria\n- [ ] Outra?\n\n## Está relacionada a uma issue?\nCole o link da issue aqui.\n\n## Quer adicionar outra informação?\nBasta escrever aqui.\n"
  },
  {
    "path": "README.md",
    "content": "# Android Canarinho\n\n![Build](https://github.com/concretesolutions/canarinho/actions/workflows/android_master.yml/badge.svg)\n\nEsta biblioteca é um conjunto de utilitários para trabalhar com padrões brasileiros no Android.\nInspirado em: https://github.com/caelum/caelum-stella.\n\nO foco aqui é o Android. Portanto, não é compatível com aplicações Java puras.\n\nEntre os padrões implementados temos:\n\n- Formatador e validador de CPF\n- Formatador e validador de CNPJ\n- Formatador e validador de boleto bancário (e linha digitável)\n- Formatador e validador de CEP\n- Formatador de telefone\n- [Formatador de valores financeiros](#formatador-de-valor-financeiro-no-padrão-real) (vírgula para milhares e ponto para decimais com duas casas)\n\nEstes são utilizados para implementar `TextWatcher`s que formatam e validam a digitação do usuário.\n\n## Exemplo de uso:\n\n### Validar um CPF\n\n```java\nif (Validador.CPF.ehValido(cpf))\n    Toast.makeText(context, \"Válido!\", Toast.LENGTH_SHORT).show();\nelse\n    Toast.makeText(context, \"Inválido!\", Toast.LENGTH_SHORT).show();\n```\n\n### Formatar um CPF\n\n```java\nString cpfFormatado = Formatador.CPF.formata(usuario.getCpf());\n```\n\n### Formatar um EditText para CPF sem validação\n\n```java\ncpfEditText.addTextChangedListener(new MascaraNumericaTextWatcher(\"###.###.###-##\"));\n```\n\n### Formatar um EditText para CPF com validação\n\n```java\ncpfEditText.addTextChangedListener(new MascaraNumericaTextWatcher.Builder()\n                                        .paraMascara(\"###.###.###-##\")\n                                        .comCallbackDeValidacao(new SampleEventoDeValidacao(context))\n                                        .comValidador(Validador.CPF)\n                                        .build());\n```\n\n## Formatador de valor financeiro no padrão Real\n\nPara deixar um usuário digitar valores monetários no padrão Real, basta adicionar um `ValorMonetarioWatcher` e alguns atributos ao `EditText`\n\n```java\n// Padrão sem símbolo de Real\neditText.addTextChangedListener(new ValorMonetarioWatcher());\neditText.append(\"1234567890\");\nassertThat(editText.getText().toString(), is(\"12.345.678,90\"));\n\n// Customizado com símbolo e mantendo zeros ao apagar em lote\neditText.addTextChangedListener(new ValorMonetarioWatcher.Builder()\n        .comSimboloReal()\n        .comMantemZerosAoLimpar()\n        .build());\neditText.append(\"1234567890\");\nassertThat(editText.getText().toString(), is(\"R$ 12.345.678,90\"));\n\neditText.getText().clear();\nassertThat(editText.getText().toString(), is(\"R$ 0,00\"));\n```\n\nExemplo de declaração no layout:\n\n```xml\n<!--\n     O inputType abre o teclado númerico e permite caracteres de \n    formatação. O text inicia com o valor zerado, porém não é \n    obrigatório.\n-->\n<android.support.design.widget.TextInputEditText\n    android:id=\"@+id/amount\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:hint=\"@string/hint_value\"\n    android:inputType=\"number\"\n    android:maxLength=\"13\"\n    android:text=\"0,00\" />\n```\n\n## Callback de validação\n\nOs `TextWatcher`s possuem a possibilidade de avisar o usuário conforme ele está digitando sobre algum erro de campo.\nPara isso, usamos um `EventoDeValidacao` que possui os seguintes callbacks:\n\n- `void invalido(String valorAtual, String mensagem)`: chamado quando o valor está inválido\n- `void parcialmenteValido(String valorAtual)`: chamado quando o valor ainda não está completo e também não está inválido\n- `void totalmenteValido(String valorAtual)`: chamado quando o valor está completo e válido\n\nUm exemplo de implementação:\n\n```java\npublic class SampleEventoDeValidacao implements EventoDeValidacao {\n\n    private final TextInputLayout textInputLayout;\n\n    public SampleEventoDeValidacao(TextInputLayout textInputLayout) {\n        this.textInputLayout = textInputLayout;\n    }\n\n    @Override\n    public void invalido(String valorAtual, String mensagem) {\n        textInputLayout.setError(mensagem);\n    }\n\n    @Override\n    public void parcialmenteValido(String valorAtual) {\n        textInputLayout.setErrorEnabled(false);\n        textInputLayout.setError(null);\n    }\n\n    @Override\n    public void totalmenteValido(String valorAtual) {\n        new AlertDialog.Builder(textInputLayout.getContext())\n                .setTitle(\"Campo válido!\")\n                .setMessage(valorAtual)\n                .show();\n    }\n}\n```\n\nVeja exemplos de implementação no sample.\n\n## Changelog\n\nVer [CHANGELOG.md](CHANGELOG.md)\n\n## Arquitetura\n\n- `Formatador`: Formata, desformata e verifica se um valor está formatado e se pode ser formatado. Opera com valores completos.\n- `Validador`: Valida de duas formas: absoluta (true ou false) e atualizando um objeto de validação (`ResultadoParcial`).\n- Watchers: implementações de `TextWatcher`s para formatação e validação contínua (conforme a digitação do usuário).\n\nPara exemplos, verifique os testes na pasta sample.\n\n## Gradle\n\n`allprojects {\n \t\trepositories {\n \t\t\t...\n \t\t\tmaven { url 'https://jitpack.io' }\n \t\t}\n \t}`\n\n`implementation 'br.com.concrete:canarinho:{latest version}'`\n\n## ATENÇÃO\n\nEste projeto é desenvolvido de boa vontade e com o intuito de ajudar. No entanto, todo o desenvolvimento é feito SEM GARANTIAS.\n\n## LICENÇA\n\nEste projeto é disponibilizado sob a licença Apache vesão 2.0. Ver declaração no arquivo LICENSE.txt\n"
  },
  {
    "path": "build.gradle",
    "content": "buildscript {\n    repositories {\n        google()\n        jcenter()\n    }\n\n    dependencies {\n        classpath 'com.android.tools.build:gradle:4.1.3'\n        classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4'\n        classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'\n    }\n}\n\nallprojects {\n    repositories {\n        google()\n        jcenter()\n    }\n}\n"
  },
  {
    "path": "canarinho/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "canarinho/build.gradle",
    "content": "apply plugin: 'com.android.library'\napply from: \"$rootDir/tools/linters/linters.gradle\"\napply from: \"$rootDir/tools/publish.gradle\"\n\n\next {\n    publishedGroupId = 'br.com.concrete'\n    libraryName = 'Android Canarinho'\n    artifact = 'canarinho'\n\n    libraryDescription = 'Utilitários Android para padrões Brasileiros'\n    libraryVersion = '2.0.3'\n\n    licenseName = 'The Apache Software License, Version 2.0'\n    licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'\n    allLicenses = [\"Apache-2.0\"]\n}\n\nandroid {\n    compileSdkVersion 31\n\n    defaultConfig {\n        minSdkVersion 21\n        targetSdkVersion 31\n    }\n}\n\n"
  },
  {
    "path": "canarinho/src/main/AndroidManifest.xml",
    "content": "<manifest package=\"br.com.concrete.canarinho\" />\n"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/DigitoPara.java",
    "content": "package br.com.concrete.canarinho;\n\nimport android.util.SparseArray;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.LinkedList;\nimport java.util.List;\n\n/**\n * Uma fluent interface para o cálculo de dígitos, que é usado em diversos boletos e\n * documentos.\n * <p>\n * Para exemplificar, o dígito do trecho 0000039104766 para os multiplicadores indo de\n * 2 a 7 e usando módulo 11 é a seguinte:\n * </p>\n * <pre>\n *  0  0  0  0  0  3  9  1  0  4  7  6  6 (trecho numérico)\n *  2  7  6  5  4  3  2  7  6  5  4  3  2 (multiplicadores, da direita para a esquerda e ciclando)\n *  ----------------------------------------- multiplicações algarismo a algarismo\n *   0  0  0  0  0  9 18  7  0 20 28 18 12 -- soma = 112\n * </pre>\n * <p>\n * Tira-se o módulo dessa soma e, então, calcula-se o complementar do módulo e, se o número\n * for 0, 10 ou 11, o dígito passa a ser 1.\n * </p>\n * <pre>\n *      soma = 112\n *      soma % 11 = 2\n *      11 - (soma % 11) = 9\n * </pre>\n * <p>\n * NOTE: Esta é uma versão otimizada para Android inspirada em\n * https://github.com/caelum/caelum-stella/blob/master/stella-core/src/main/java/br/com/caelum/stella/DigitoPara.java\n * </p>\n */\npublic final class DigitoPara {\n\n    private final List<Integer> numero = new LinkedList<>();\n    private final List<Integer> multiplicadores;\n    private final boolean complementar;\n    private final int modulo;\n    private final boolean somarIndividual;\n    private final SparseArray<String> substituicoes;\n\n    private DigitoPara(Builder builder) {\n\n        multiplicadores = builder.multiplicadores;\n        complementar = builder.complementar;\n        modulo = builder.modulo;\n        somarIndividual = builder.somarIndividual;\n        substituicoes = builder.substituicoes;\n    }\n\n    /**\n     * Faz a soma geral das multiplicações dos algarismos pelos multiplicadores, tira o\n     * módulo e devolve seu complementar.\n     *\n     * @param trecho Bloco para calcular o dígito\n     * @return String o dígito vindo do módulo com o número passado e configurações extra.\n     */\n    public final String calcula(String trecho) {\n\n        numero.clear();\n\n        final char[] digitos = trecho.toCharArray();\n\n        for (char digito : digitos) {\n            numero.add(Character.getNumericValue(digito));\n        }\n\n        Collections.reverse(numero);\n\n        int soma = 0;\n        int multiplicadorDaVez = 0;\n\n        for (int i = 0; i < numero.size(); i++) {\n            final int multiplicador = multiplicadores.get(multiplicadorDaVez);\n            final int total = numero.get(i) * multiplicador;\n            soma += somarIndividual ? somaDigitos(total) : total;\n            multiplicadorDaVez = proximoMultiplicador(multiplicadorDaVez);\n        }\n\n        int resultado = soma % modulo;\n\n        if (complementar) {\n            resultado = modulo - resultado;\n        }\n\n        if (substituicoes.get(resultado) != null) {\n            return substituicoes.get(resultado);\n        }\n\n        return String.valueOf(resultado);\n    }\n\n\n    /*\n     * soma os dígitos do número (até 2)\n     *\n     * Ex: 18 => 9 (1+8), 12 => 3 (1+2)\n     */\n    private int somaDigitos(int total) {\n        return (total / 10) + (total % 10);\n    }\n\n    /*\n     * Devolve o próximo multiplicador a ser usado, isto é, a próxima posição da lista de\n     * multiplicadores ou, se chegar ao fim da lista, a primeira posição, novamente.\n     */\n    private int proximoMultiplicador(int multiplicadorDaVez) {\n\n        int multiplicador = multiplicadorDaVez + 1;\n\n        if (multiplicador == multiplicadores.size()) {\n            multiplicador = 0;\n        }\n\n        return multiplicador;\n    }\n\n    /**\n     * Builder com interface fluente para criação de instâncias configuradas de\n     * {@link DigitoPara}.\n     */\n    public static final class Builder {\n\n        private List<Integer> multiplicadores = new ArrayList<>();\n        private boolean complementar;\n        private int modulo;\n        private boolean somarIndividual;\n        private final SparseArray<String> substituicoes = new SparseArray<>();\n\n        /**\n         * TODO Javadoc pendente.\n         *\n         * @param modulo Inteiro pelo qual o resto será tirado e também seu complementar.\n         *               O valor padrão é 11.\n         * @return this\n         */\n        public final Builder mod(int modulo) {\n            this.modulo = modulo;\n            return this;\n        }\n\n        /**\n         * Para multiplicadores (ou pesos) sequenciais e em ordem crescente, esse método permite\n         * criar a lista de multiplicadores que será usada ciclicamente, caso o número base seja\n         * maior do que a sequência de multiplicadores. Por padrão os multiplicadores são iniciados\n         * de 2 a 9. No momento em que você inserir outro valor este default será sobrescrito.\n         *\n         * @param inicio Primeiro número do intervalo sequencial de multiplicadores\n         * @param fim    Último número do intervalo sequencial de multiplicadores\n         * @return this\n         */\n        public final Builder comMultiplicadoresDeAte(int inicio, int fim) {\n\n            this.multiplicadores.clear();\n\n            for (int i = inicio; i <= fim; i++) {\n                multiplicadores.add(i);\n            }\n\n            return this;\n        }\n\n        /**\n         * <p>\n         * Indica se, ao calcular o módulo, a soma dos resultados da multiplicação deve ser\n         * considerado digito a dígito.\n         * </p>\n         * Ex: 2 X 9 = 18, irá somar 9 (1 + 8) invés de 18 ao total.\n         *\n         * @return this\n         */\n        public final Builder somandoIndividualmente() {\n            this.somarIndividual = true;\n            return this;\n        }\n\n        /**\n         * É comum que os geradores de dígito precisem do complementar do módulo em vez\n         * do módulo em sí. Então, a chamada desse método habilita a flag que é usada\n         * no método mod para decidir se o resultado devolvido é o módulo puro ou seu\n         * complementar.\n         *\n         * @return this\n         */\n        public final Builder complementarAoModulo() {\n            this.complementar = true;\n            return this;\n        }\n\n        /**\n         * Troca por uma String caso encontre qualquer dos inteiros passados como argumento.\n         *\n         * @param substituto String para substituir\n         * @param i          varargs de inteiros a serem substituídos\n         * @return this\n         */\n        public final Builder trocandoPorSeEncontrar(String substituto, Integer... i) {\n\n            substituicoes.clear();\n\n            for (Integer integer : i) {\n                substituicoes.put(integer, substituto);\n            }\n\n            return this;\n        }\n\n        /**\n         * Há documentos em que os multiplicadores não usam todos os números de um intervalo\n         * ou alteram sua ordem. Nesses casos, a lista de multiplicadores pode ser passada\n         * através de varargs.\n         *\n         * @param multiplicadoresEmOrdem Sequência de inteiros com os multiplicadores em ordem\n         * @return this\n         */\n        public final Builder comMultiplicadores(Integer... multiplicadoresEmOrdem) {\n            this.multiplicadores.clear();\n            this.multiplicadores.addAll(Arrays.asList(multiplicadoresEmOrdem));\n            return this;\n        }\n\n        /**\n         * Método responsável por criar o DigitoPara.\n         * Este método inicializará os seguintes valores padrões:\n         * <ul>\n         * <li>multiplicadores:  2 a 9</li>\n         * <li>módulo: 11</li>\n         * </ul>\n         *\n         * @return A instância imutável de DigitoPara\n         */\n        public final DigitoPara build() {\n\n            if (multiplicadores.size() == 0) {\n                comMultiplicadoresDeAte(2, 9);\n            }\n\n            if (modulo == 0) {\n                mod(11);\n            }\n\n            return new DigitoPara(this);\n        }\n    }\n}\n"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/formatador/Formatador.java",
    "content": "package br.com.concrete.canarinho.formatador;\n\nimport java.util.regex.Pattern;\n\n/**\n * Interface de formatação. Formata valores completos. Útil caso receba o valor\n * desformatado de uma API.\n */\npublic interface Formatador {\n\n    // Formatadores\n    /**\n     * Singleton de formatação de CEP.\n     */\n    Formatador CEP = new FormatadorBase(\n            Padroes.CEP_FORMATADO,\n            \"$1-$2\",\n            Padroes.CEP_DESFORMATADO,\n            \"$1$2\"\n    );\n\n    /**\n     * Singleton de formatação de CPF.\n     */\n    Formatador CPF = new FormatadorBase(\n            Padroes.CPF_FORMATADO,\n            \"$1.$2.$3-$4\",\n            Padroes.CPF_DESFORMATADO,\n            \"$1$2$3$4\"\n    );\n\n    /**\n     * Singleton de formatação de CNPJ.\n     */\n    Formatador CNPJ = new FormatadorBase(\n            Padroes.CNPJ_FORMATADO,\n            \"$1.$2.$3/$4-$5\",\n            Padroes.CNPJ_DESFORMATADO,\n            \"$1$2$3$4$5\"\n    );\n\n    /**\n     * Singleton de formatação de CPF e CNPJ.\n     */\n    Formatador CPF_CNPJ = FormatadorCPFCNPJ.getInstance();\n\n    /**\n     * Singleton de formatação de valores monetários.\n     */\n    FormatadorValor VALOR = FormatadorValor.getInstance(false);\n\n    /**\n     * Singleton de formatação de valores monetários com símbolo do Real.\n     */\n    FormatadorValor VALOR_COM_SIMBOLO = FormatadorValor.getInstance(true);\n\n    /**\n     * Singleton de formatação de boletos bancários.\n     */\n    Formatador BOLETO = FormatadorBoleto.getInstance();\n\n    /**\n     * Singleton de formatação de telefones (DDD) número.\n     */\n    Formatador TELEFONE = FormatadorTelefone.getInstance();\n\n    /**\n     * Singleton de formatação de linha digitável. Transforma números do código de barras em linha\n     * digitável.\n     */\n    Formatador LINHA_DIGITAVEL = FormatadorLinhaDigitavel.getInstance();\n\n    /**\n     * Formata um valor COMPLETO. Deve falhar caso o valor não esteja completo.\n     *\n     * @param value valor a formatar\n     * @return REsultado da formatação\n     */\n    String formata(String value);\n\n    /**\n     * Desformata um valor.\n     *\n     * @param value Valor a desformatar\n     * @return Resultado da desformatação\n     */\n    String desformata(String value);\n\n    /**\n     * Verifica se um parâmetro está formatado.\n     *\n     * @param value Valor para verificar\n     * @return True se estiver formatado, falso caso contrário.\n     */\n    boolean estaFormatado(String value);\n\n    /**\n     * Verifica se um parâmetro pode ser formatado.\n     *\n     * @param value Valor para verificar\n     * @return True se puder ser formatado, falso caso contrário.\n     */\n    boolean podeSerFormatado(String value);\n\n    /**\n     * Classe para guardar os padrões de experssões regulares usados no framework.\n     */\n    abstract class Padroes {\n\n        public static final Pattern PADRAO_SOMENTE_NUMEROS = Pattern.compile(\"[^0-9]\");\n        public static final Pattern CEP_FORMATADO = Pattern.compile(\"(\\\\d{5})-(\\\\d{3})\");\n        public static final Pattern CEP_DESFORMATADO = Pattern.compile(\"(\\\\d{5})(\\\\d{3})\");\n\n        public static final Pattern CNPJ_FORMATADO = Pattern.compile(\n                \"(\\\\d{2})[.](\\\\d{3})[.](\\\\d{3})/(\\\\d{4})-(\\\\d{2})\"\n        );\n\n        public static final Pattern CNPJ_DESFORMATADO = Pattern.compile(\n                \"(\\\\d{2})(\\\\d{3})(\\\\d{3})(\\\\d{4})(\\\\d{2})\"\n        );\n\n        public static final Pattern CPF_FORMATADO = Pattern.compile(\n                \"(\\\\d{3})[.](\\\\d{3})[.](\\\\d{3})-(\\\\d{2})\"\n        );\n\n        public static final Pattern CPF_DESFORMATADO = Pattern.compile(\n                \"(\\\\d{3})(\\\\d{3})(\\\\d{3})(\\\\d{2})\"\n        );\n\n        private Padroes() {\n        }\n    }\n\n}\n"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/formatador/FormatadorBase.java",
    "content": "package br.com.concrete.canarinho.formatador;\n\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\n/**\n * Classe utilitária para a implementação de todos os formatadores que precisam apenas da aplicação\n * de {@link Pattern}s.\n */\nfinal class FormatadorBase implements Formatador {\n\n    private final Pattern formatted;\n\n    private final String formattedReplacement;\n\n    private final Pattern unformatted;\n\n    private final String unformattedReplacement;\n\n    /**\n     * Constrói um formatador que recebe a configuração de formatação e substituição.\n     *\n     * @param formatted              Pattern de regex para formatar o conteúdo\n     * @param formattedReplacement   String com as posições de substituição dos grupos encontrados por regex\n     * @param unformatted            Pattern de regex para DESformatar o conteúdo\n     * @param unformattedReplacement String com as posições de substituição dos grupos encontrados por regex\n     */\n    FormatadorBase(\n            Pattern formatted,\n            String formattedReplacement,\n            Pattern unformatted,\n            String unformattedReplacement) {\n\n        this.formatted = formatted;\n        this.formattedReplacement = formattedReplacement;\n        this.unformatted = unformatted;\n        this.unformattedReplacement = unformattedReplacement;\n    }\n\n    @Override\n    public final String formata(String value) throws IllegalArgumentException {\n\n        if (value == null) {\n            throw new IllegalArgumentException(\"Value may not be null.\");\n        }\n\n        if (formatted.matcher(value).matches()) {\n            return value;\n        }\n\n        return matchAndReplace(unformatted.matcher(value), formattedReplacement);\n    }\n\n    @Override\n    public final String desformata(String value) throws IllegalArgumentException {\n\n        if (value == null) {\n            throw new IllegalArgumentException(\"Value may not be null.\");\n        }\n\n        if (unformatted.matcher(value).matches()) {\n            return value;\n        }\n\n        final Matcher matcher = formatted.matcher(value);\n        return matchAndReplace(matcher, unformattedReplacement);\n    }\n\n    @Override\n    public final boolean estaFormatado(String value) {\n\n        if (value == null) {\n            throw new IllegalArgumentException(\"value must not be null\");\n        }\n\n        return formatted.matcher(value).matches();\n    }\n\n    @Override\n    public final boolean podeSerFormatado(String value) {\n        return value != null && unformatted.matcher(value).matches();\n    }\n\n    private String matchAndReplace(Matcher matcher, String replacement) {\n\n        if (matcher.matches()) {\n            return matcher.replaceAll(replacement);\n        }\n\n        throw new IllegalArgumentException(\"Valor não está formatado propriamente.\");\n    }\n}\n"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/formatador/FormatadorBoleto.java",
    "content": "package br.com.concrete.canarinho.formatador;\n\nimport java.util.regex.Pattern;\n\n/**\n * Formatador especializado para linha digitável de boletos bancários. Ele detecta automaticamente\n * se o boleto é do tipo tributos ou de conveniados. Caso o boleto comece com o número '8', então\n * é aplicada a formatação: 888888888888 888888888888 888888888888 888888888888. Caso contrário,\n * usa-se o padrão 99999.99999 99999.999999 99999.999999 9 99999999999999.\n */\npublic final class FormatadorBoleto implements Formatador {\n\n    private static final Pattern TRIBUTO_FORMATADO =\n            Pattern.compile(\"(\\\\d{12})\\\\s(\\\\d{12})\\\\s(\\\\d{12})\\\\s(\\\\d{12})\");\n    private static final Pattern TRIBUTO_DESFORMATADO =\n            Pattern.compile(\"(\\\\d{12})(\\\\d{12})(\\\\d{12})(\\\\d{12})\");\n\n    private static final FormatadorBase FORMATADOR_TRIBUTOS = new FormatadorBase(\n            TRIBUTO_FORMATADO,\n            \"$1 $2 $3 $4\",\n            TRIBUTO_DESFORMATADO,\n            \"$1$2$3$4\"\n    );\n\n    private static final Pattern NORMAL_FORMATADO = Pattern.compile(\n            \"(\\\\d{5})[.](\\\\d{5})\\\\s(\\\\d{5})[.](\\\\d{6})\\\\s(\\\\d{5})[.](\\\\d{6})\\\\s(\\\\d)\\\\s(\\\\d{14})\"\n    );\n\n    private static final Pattern NORMAL_DESFORMATADO = Pattern.compile(\n            \"(\\\\d{5})(\\\\d{5})(\\\\d{5})(\\\\d{6})(\\\\d{5})(\\\\d{6})(\\\\d{1})(\\\\d{14})\"\n    );\n\n    private static final FormatadorBase FORMATADOR_NORMAL = new FormatadorBase(\n            NORMAL_FORMATADO,\n            \"$1.$2 $3.$4 $5.$6 $7 $8\",\n            NORMAL_DESFORMATADO,\n            \"$1$2$3$4$5$6$7$8\"\n    );\n\n    // No instance creation\n    private FormatadorBoleto() {\n    }\n\n    @Override\n    public String formata(String value) {\n\n        if (ehTributo(value)) {\n            return FORMATADOR_TRIBUTOS.formata(value);\n        }\n\n        return FORMATADOR_NORMAL.formata(value);\n    }\n\n    @Override\n    public String desformata(String value) {\n\n        if (ehTributo(value)) {\n            return FORMATADOR_TRIBUTOS.desformata(value);\n        }\n\n        return FORMATADOR_NORMAL.desformata(value);\n    }\n\n    @Override\n    public boolean estaFormatado(String value) {\n\n        if (ehTributo(value)) {\n            return FORMATADOR_TRIBUTOS.estaFormatado(value);\n        }\n\n        return FORMATADOR_NORMAL.estaFormatado(value);\n    }\n\n    @Override\n    public boolean podeSerFormatado(String value) {\n\n        if (ehTributo(value)) {\n            return FORMATADOR_TRIBUTOS.podeSerFormatado(value);\n        }\n\n        return FORMATADOR_NORMAL.podeSerFormatado(value);\n    }\n\n    private boolean ehTributo(String value) {\n\n        if (value == null) {\n            throw new IllegalArgumentException(\"Valor não pode ser nulo\");\n        }\n\n        return value.charAt(0) == '8';\n    }\n\n    static FormatadorBoleto getInstance() {\n        return SingletonHolder.INSTANCE;\n    }\n\n    private static class SingletonHolder {\n        private static final FormatadorBoleto INSTANCE = new FormatadorBoleto();\n    }\n}\n"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/formatador/FormatadorCEP.java",
    "content": "package br.com.concrete.canarinho.formatador;\n\n/**\n * Formatador para CEP. Segue o padrão 99999-999.\n */\npublic final class FormatadorCEP implements Formatador {\n\n    private FormatadorCEP() {\n    }\n\n    static FormatadorCEP getInstance() {\n        return SingletonHolder.INSTANCE;\n    }\n\n    @Override\n    public String formata(final String value) {\n        return Formatador.CEP.formata(value);\n    }\n\n    @Override\n    public String desformata(final String value) {\n        return Formatador.CEP.desformata(value);\n    }\n\n    @Override\n    public boolean estaFormatado(final String value) {\n        return Formatador.CEP.estaFormatado(value);\n    }\n\n    @Override\n    public boolean podeSerFormatado(final String value) {\n        if (value == null) {\n            return false;\n        }\n\n        return Formatador.CEP.podeSerFormatado(value);\n    }\n\n    private static class SingletonHolder {\n        private static final FormatadorCEP INSTANCE = new FormatadorCEP();\n    }\n}"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/formatador/FormatadorCPFCNPJ.java",
    "content": "package br.com.concrete.canarinho.formatador;\n\n/**\n * Formatador para CPF e CNPJ no mesmo campo. Formata como CPF até 11 dígitos numéricos. Depois\n * formata como CNPJ.\n */\npublic final class FormatadorCPFCNPJ implements Formatador {\n\n    private FormatadorCPFCNPJ() {\n    }\n\n    static FormatadorCPFCNPJ getInstance() {\n        return SingletonHolder.INSTANCE;\n    }\n\n    @Override\n    public String formata(final String value) {\n        if (ehCpf(value)) {\n            return Formatador.CPF.formata(value);\n        }\n\n        return Formatador.CNPJ.formata(value);\n    }\n\n    @Override\n    public String desformata(final String value) {\n        if (ehCpf(value)) {\n            return Formatador.CPF.desformata(value);\n        }\n\n        return Formatador.CNPJ.desformata(value);\n    }\n\n    @Override\n    public boolean estaFormatado(final String value) {\n        if (ehCpf(value)) {\n            return Formatador.CPF.estaFormatado(value);\n        }\n\n        return Formatador.CNPJ.estaFormatado(value);\n    }\n\n    @Override\n    public boolean podeSerFormatado(final String value) {\n        if (value == null) {\n            return false;\n        }\n\n        if (ehCpf(value)) {\n            return Formatador.CPF.podeSerFormatado(value);\n        }\n\n        return Formatador.CNPJ.podeSerFormatado(value);\n    }\n\n    private boolean ehCpf(String value) {\n        if (value == null) {\n            throw new IllegalArgumentException(\"Valor não pode ser nulo\");\n        }\n\n        final String desformatado = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(value)\n                .replaceAll(\"\");\n        return desformatado.length() < 12;\n    }\n\n    private static class SingletonHolder {\n        private static final FormatadorCPFCNPJ INSTANCE = new FormatadorCPFCNPJ();\n    }\n}"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/formatador/FormatadorLinhaDigitavel.java",
    "content": "package br.com.concrete.canarinho.formatador;\n\nimport br.com.concrete.canarinho.DigitoPara;\nimport br.com.concrete.canarinho.validator.ValidadorBoleto;\n\n/**\n * Transforma a linha digitável de um boleto em um código de boleto e vice-versa. Use o metodo\n * {@link #formata(String)} para transformar a linha digitavel em boleto e\n * {@link #desformata(String)}.\n * Para verificar se um valor esta em linha digitável ou em boleto, usar os métodos:\n * <ul>\n * <li>{@link #estaFormatado(String)}: indicará se está em formata de boleto</li>\n * <li>{@link #podeSerFormatado(String)}: indicará se é uma linha digitável</li>\n * </ul>\n */\npublic final class FormatadorLinhaDigitavel implements Formatador {\n\n    private FormatadorLinhaDigitavel() {\n    }\n\n    static FormatadorLinhaDigitavel getInstance() {\n        return SingletonHolder.INSTANCE;\n    }\n\n    @Override\n    public String formata(String value) {\n\n        if (value == null || value.length() != 44) {\n            throw new IllegalArgumentException(\"Linha digitável deve conter 44 caracteres. \"\n                    + \"Valor possui \" + value + \" caracteres\");\n        }\n\n        if (value.startsWith(\"8\")) {\n\n            final String primeiroBloco = value.substring(0, 11);\n            final String segundoBloco = value.substring(11, 22);\n            final String terceiroBloco = value.substring(22, 33);\n            final String quartoBloco = value.substring(33, 44);\n\n            // o terceiro dígito é o de valor real que define se será mod 10 ou mod 11\n            final boolean ehMod10 = value.charAt(2) == '6' || value.charAt(2) == '7';\n            final DigitoPara mod = ehMod10 ? ValidadorBoleto.MOD_10 : ValidadorBoleto.MOD_11;\n\n            final String primeiroDigito = mod.calcula(primeiroBloco);\n            final String segundoDigito = mod.calcula(segundoBloco);\n            final String terceiroDigito = mod.calcula(terceiroBloco);\n            final String quartoDigito = mod.calcula(quartoBloco);\n\n            return primeiroBloco + primeiroDigito + segundoBloco + segundoDigito\n                    + terceiroBloco + terceiroDigito + quartoBloco + quartoDigito;\n        }\n\n        String primeiroBloco = value.substring(0, 4); // 4\n        String segundoBloco = value.substring(4, 19); // 15\n        String terceiroBloco = value.substring(19, 24); // 5\n        String quartoBloco = value.substring(24, 34); // 10\n        String quintoBloco = value.substring(34, 44); // 10\n\n        // 1 - 3 - 4 - 5 - 2\n        final StringBuilder codigoOrdenado = new StringBuilder(primeiroBloco)\n                .append(terceiroBloco)\n                .append(quartoBloco)\n                .append(quintoBloco)\n                .append(segundoBloco);\n\n        primeiroBloco = codigoOrdenado.substring(0, 9);\n        segundoBloco = codigoOrdenado.substring(9, 19);\n        terceiroBloco = codigoOrdenado.substring(19, 29);\n        quartoBloco = codigoOrdenado.substring(29);\n\n        final String primeiroDigito = ValidadorBoleto.MOD_10.calcula(primeiroBloco);\n        final String segundoDigito = ValidadorBoleto.MOD_10.calcula(segundoBloco);\n        final String terceiroDigito = ValidadorBoleto.MOD_10.calcula(terceiroBloco);\n\n        return primeiroBloco + primeiroDigito + segundoBloco + segundoDigito\n                + terceiroBloco + terceiroDigito + quartoBloco;\n    }\n\n    @Override\n    public String desformata(String valor) {\n\n        if (valor == null || \"\".equals(valor)) {\n            throw new IllegalArgumentException(\"Valor não pode estar nulo.\");\n        }\n\n        String valorDesformatadao = Padroes.PADRAO_SOMENTE_NUMEROS.matcher(valor)\n                .replaceAll(\"\");\n\n        if (valorDesformatadao.charAt(0) == '8') {\n\n            if (valorDesformatadao.length() != 48) {\n                throw new IllegalArgumentException(\n                        \"Valor para boletos que iniciam com 8 deve conter 48 dígitos\"\n                );\n            }\n\n            final StringBuilder builder = new StringBuilder(valorDesformatadao);\n\n            final String primeiroBloco = builder.substring(0, 11);\n            final String segundoBloco = builder.substring(12, 23);\n            final String terceiroBloco = builder.substring(24, 35);\n            final String quartoBloco = builder.substring(36, 47);\n\n            return \"\" + primeiroBloco + segundoBloco + terceiroBloco + quartoBloco;\n        }\n\n        if (valorDesformatadao.length() != 47) {\n            throw new IllegalArgumentException(\"Valor para boletos deve conter 47 digitos\");\n        }\n\n        String primeiroBloco = valorDesformatadao.substring(0, 9);\n        String segundoBloco = valorDesformatadao.substring(10, 20);\n        String terceiroBloco = valorDesformatadao.substring(21, 31);\n        String quartoBloco = valorDesformatadao.substring(32, valorDesformatadao.length());\n\n        final StringBuilder boletoOrdenado = new StringBuilder(primeiroBloco)\n                .append(segundoBloco)\n                .append(terceiroBloco)\n                .append(quartoBloco);\n\n        // 1 - 3 - 4 - 5 - 2\n        primeiroBloco = boletoOrdenado.substring(0, 4); // 4\n        segundoBloco = boletoOrdenado.substring(29, 44); // 15\n        terceiroBloco = boletoOrdenado.substring(4, 9); // 5\n        quartoBloco = boletoOrdenado.substring(9, 19); // 10\n        final String quintoBloco = boletoOrdenado.substring(19, 29); // 10\n\n        return \"\" + primeiroBloco + segundoBloco + terceiroBloco + quartoBloco + quintoBloco;\n    }\n\n    @Override\n    public boolean estaFormatado(String value) {\n        return Formatador.BOLETO.estaFormatado(value);\n    }\n\n    @Override\n    public boolean podeSerFormatado(String value) {\n        return Padroes.PADRAO_SOMENTE_NUMEROS.matcher(value)\n                .replaceAll(\"\")\n                .length() == 44;\n    }\n\n    private static class SingletonHolder {\n        private static final FormatadorLinhaDigitavel INSTANCE = new FormatadorLinhaDigitavel();\n    }\n}\n"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/formatador/FormatadorTelefone.java",
    "content": "package br.com.concrete.canarinho.formatador;\n\nimport java.util.regex.Pattern;\n\n/**\n * Formata no padrão de telefone brasileiro: (99) 99999-9999 ou (99) 9999-9999.\n */\npublic final class FormatadorTelefone implements Formatador {\n\n    private static final Pattern NOVE_DIGITOS_FORMATADO = Pattern.compile(\n            \"\\\\((\\\\d{2})\\\\)\\\\s(\\\\d{5})-(\\\\d{4})\"\n    );\n\n    private static final Pattern NOVE_DIGITOS_DESFORMATADO = Pattern.compile(\n            \"(\\\\d{2})(\\\\d{5})(\\\\d{4})\"\n    );\n\n    private static final Pattern OITO_DIGITOS_FORMATADO = Pattern.compile(\n            \"\\\\((\\\\d{2})\\\\)\\\\s(\\\\d{4})-(\\\\d{4})\"\n    );\n\n    private static final Pattern OITO_DIGITOS_DESFORMATADO = Pattern.compile(\n            \"(\\\\d{2})(\\\\d{4})(\\\\d{4})\"\n    );\n\n    private static final FormatadorBase FORMATADOR_NOVE_DIGITOS = new FormatadorBase(\n            NOVE_DIGITOS_FORMATADO,\n            \"($1) $2-$3\",\n            NOVE_DIGITOS_DESFORMATADO,\n            \"$1$2$3\"\n    );\n\n    private static final FormatadorBase FORMATADOR_OITO_DIGITOS = new FormatadorBase(\n            OITO_DIGITOS_FORMATADO,\n            \"($1) $2-$3\",\n            OITO_DIGITOS_DESFORMATADO,\n            \"$1$2$3\"\n    );\n\n    private FormatadorTelefone() {\n    }\n\n    static FormatadorTelefone getInstance() {\n        return SingletonHolder.INSTANCE;\n    }\n\n    @Override\n    public String formata(String value) {\n        if (ehNoveDigitos(value)) {\n            return FORMATADOR_NOVE_DIGITOS.formata(value);\n        }\n\n        return FORMATADOR_OITO_DIGITOS.formata(value);\n    }\n\n    @Override\n    public String desformata(String value) {\n        if (ehNoveDigitos(value)) {\n            return FORMATADOR_NOVE_DIGITOS.desformata(value);\n        }\n\n        return FORMATADOR_OITO_DIGITOS.desformata(value);\n    }\n\n    @Override\n    public boolean estaFormatado(String value) {\n        if (ehNoveDigitos(value)) {\n            return FORMATADOR_NOVE_DIGITOS.estaFormatado(value);\n        }\n\n        return FORMATADOR_OITO_DIGITOS.estaFormatado(value);\n    }\n\n    @Override\n    public boolean podeSerFormatado(String value) {\n        if (ehNoveDigitos(value)) {\n            return FORMATADOR_NOVE_DIGITOS.podeSerFormatado(value);\n        }\n\n        return FORMATADOR_OITO_DIGITOS.podeSerFormatado(value);\n    }\n\n    private boolean ehNoveDigitos(String value) {\n        if (value == null) {\n            throw new IllegalArgumentException(\"Valor não pode ser nulo\");\n        }\n\n        return Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(value)\n                .replaceAll(\"\")\n                .length() > 10;\n    }\n\n    private static class SingletonHolder {\n        private static final FormatadorTelefone INSTANCE = new FormatadorTelefone();\n    }\n}\n"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/formatador/FormatadorValor.java",
    "content": "package br.com.concrete.canarinho.formatador;\n\nimport android.os.Build;\n\nimport java.math.BigDecimal;\nimport java.text.DecimalFormat;\nimport java.text.DecimalFormatSymbols;\nimport java.text.NumberFormat;\nimport java.text.ParsePosition;\nimport java.util.Locale;\nimport java.util.regex.Pattern;\n\n/**\n * Formatador de valores monetários. Possui duas versões:\n * <ul>\n * <li>Com símbolo do Real {@link Formatador#VALOR_COM_SIMBOLO}</li>\n * <li>Sem símbolo do Real {@link Formatador#VALOR}</li>\n * </ul>\n */\npublic final class FormatadorValor implements Formatador {\n\n    private static final DecimalFormat FORMATADOR_MOEDA = (DecimalFormat)\n            NumberFormat.getCurrencyInstance(new Locale(\"pt\", \"BR\"));\n    private static final Pattern PADRAO_DECIMAL = Pattern\n            .compile(\"^\\\\d+(\\\\.\\\\d{1,2})?$\");\n    private static final Pattern PADRAO_MOEDA = Pattern\n            .compile(\"\\\\d{1,3}(\\\\.\\\\d{3})*(,\\\\d{2})?\");\n\n    private static final String SIMBOLO_REAL = \"R$ \";\n\n    static {\n        final DecimalFormatSymbols decimalSymbols = FORMATADOR_MOEDA.getDecimalFormatSymbols();\n        decimalSymbols.setCurrencySymbol(\"\");\n        FORMATADOR_MOEDA.setMinimumFractionDigits(2);\n        FORMATADOR_MOEDA.setDecimalFormatSymbols(decimalSymbols);\n        FORMATADOR_MOEDA.setNegativePrefix(\"-\");\n        FORMATADOR_MOEDA.setNegativeSuffix(\"\");\n        FORMATADOR_MOEDA.setPositivePrefix(\"\");\n        FORMATADOR_MOEDA.setParseBigDecimal(true);\n    }\n\n    private final boolean adicionaSimboloReal;\n\n    // No instance creation\n    private FormatadorValor(boolean comSimboloReal) {\n        adicionaSimboloReal = comSimboloReal;\n    }\n\n    /**\n     * Busca uma instância do formatador com símbolo ou sem.\n     *\n     * @param comSimboloReal Flag para saber qual instância buscar.\n     * @return FormatadorValor de acordo com a flag\n     */\n    static FormatadorValor getInstance(boolean comSimboloReal) {\n        return comSimboloReal\n                ? SingletonHolder.INSTANCE_COM_SIMBOLO\n                : SingletonHolder.INSTANCE_SEM_SIMBOLO;\n    }\n\n    @Override\n    public String formata(String value) {\n        final String resultado = FORMATADOR_MOEDA.format(new BigDecimal(value));\n        return adicionaSimboloReal ? SIMBOLO_REAL + resultado : resultado;\n    }\n\n    @Override\n    public String desformata(String value) {\n\n        String realValue = value;\n        if (value.startsWith(SIMBOLO_REAL)) {\n            int offset = value.indexOf(SIMBOLO_REAL) + SIMBOLO_REAL.length();\n            realValue = value.substring(offset);\n        }\n\n        final BigDecimal valor;\n        if (Build.VERSION.SDK_INT < 28) {\n            valor = (BigDecimal) FORMATADOR_MOEDA.parse(realValue,\n                    new ParsePosition(0));\n        } else {\n            // Implementando o parse manual devido a um bug na API 28\n            valor = new BigDecimal(realValue.replaceAll(\"\\\\.\", \"\")\n                    .replace(\",\", \".\"));\n        }\n        return valor.toPlainString();\n    }\n\n    @Override\n    public boolean estaFormatado(String value) {\n\n        if (value == null) {\n            throw new IllegalArgumentException(\"Valor não pode ser nulo\");\n        }\n\n        return PADRAO_MOEDA.matcher(value).matches();\n    }\n\n    @Override\n    public boolean podeSerFormatado(String value) {\n\n        if (value == null) {\n            throw new IllegalArgumentException(\"Valor não pode ser nulo\");\n        }\n\n        return PADRAO_DECIMAL.matcher(value).matches();\n    }\n\n    private static class SingletonHolder {\n        private static final FormatadorValor INSTANCE_SEM_SIMBOLO = new FormatadorValor(false);\n        private static final FormatadorValor INSTANCE_COM_SIMBOLO = new FormatadorValor(true);\n    }\n}\n"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/validator/Validador.java",
    "content": "package br.com.concrete.canarinho.validator;\n\nimport android.text.Editable;\n\n/**\n * Interface de validação de campos. Há basicamente duas formas de validação:\n * <ul>\n * <li>Uma {@link String} completa</li>\n * <li>Um {@link Editable} e um {@link br.com.concrete.canarinho.validator.Validador.ResultadoParcial}</li>\n * </ul>\n * No primeiro caso o retorno será: true ou false. No segundo caso, o resultado será sempre\n * atualizado no objeto {@link br.com.concrete.canarinho.validator.Validador.ResultadoParcial} passado.\n */\npublic interface Validador {\n\n    /**\n     * Referência para o singleton de validação de CPF.\n     */\n    Validador CPF = ValidadorCPF.getInstance();\n\n    /**\n     * Referência para o singleton de validação de CNPJ.\n     */\n    Validador CNPJ = ValidadorCNPJ.getInstance();\n\n    /**\n     * Referência para o singleton de validação de boleto.\n     */\n    Validador BOLETO = ValidadorBoleto.getInstance();\n\n    /**\n     * Referência para o singleton de validação de telefone.\n     */\n    Validador TELEFONE = ValidadorTelefone.getInstance();\n\n    /**\n     * Referência para o singleton de validação de CEP.\n     */\n    Validador CEP = ValidadorCEP.getInstance();\n\n    /**\n     * Valida uma {@link String} completa.\n     *\n     * @param valor Valor a ser validado\n     * @return true se estiver válida e false caso contrário\n     */\n    boolean ehValido(String valor);\n\n    /**\n     * Valida um {@link Editable} retornando o\n     * {@link br.com.concrete.canarinho.validator.Validador.ResultadoParcial}.\n     *\n     * @param valor            Editable\n     * @param resultadoParcial Objeto com o estado da validação\n     * @return Objeto com o estado da validação atualizado\n     */\n    ResultadoParcial ehValido(Editable valor, ResultadoParcial resultadoParcial);\n\n    /**\n     * Value Object com o estado da validação.\n     */\n    class ResultadoParcial {\n\n        private boolean valido;\n        private boolean parcialmenteValido = true;\n        private String mensagem;\n\n        public boolean isValido() {\n            return valido;\n        }\n\n        public boolean isParcialmenteValido() {\n            return parcialmenteValido;\n        }\n\n        public String getMensagem() {\n            return mensagem;\n        }\n\n        /**\n         * Ajusta a validação com o valor de \"totalmente válido\".\n         *\n         * @param valido Flag totalmenteValido\n         * @return Fluent Interface \"this\"\n         */\n        public ResultadoParcial totalmenteValido(boolean valido) {\n            this.valido = valido;\n            return this;\n        }\n\n        /**\n         * Ajusta a validação com o valor de \"totalmente válido\".\n         *\n         * @param parcialmenteValido Flag parcialmenteValido\n         * @return Fluent Interface \"this\"\n         */\n        public ResultadoParcial parcialmenteValido(boolean parcialmenteValido) {\n            this.parcialmenteValido = parcialmenteValido;\n            return this;\n        }\n\n        /**\n         * Ajusta a mensagem de erro.\n         *\n         * @param mensagem Mensagem usada na apresentação do erro.\n         * @return Fluent Interface \"this\"\n         */\n        public ResultadoParcial mensagem(String mensagem) {\n            this.mensagem = mensagem;\n            return this;\n        }\n    }\n}\n"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/validator/ValidadorBoleto.java",
    "content": "package br.com.concrete.canarinho.validator;\n\nimport android.text.Editable;\nimport android.text.SpannableStringBuilder;\n\nimport br.com.concrete.canarinho.DigitoPara;\nimport br.com.concrete.canarinho.formatador.Formatador;\n\nimport java.util.regex.Pattern;\n\n/**\n * Implementação de @{link Validador} para boleto.\n *\n * @see Validador\n */\npublic final class ValidadorBoleto implements Validador {\n\n    /**\n     * Instância de módulo 10 para cálculo de digito verificador de boleto.\n     */\n    public static final DigitoPara MOD_10 = new DigitoPara.Builder()\n            .mod(10)\n            .comMultiplicadores(2, 1)\n            .somandoIndividualmente()\n            .trocandoPorSeEncontrar(\"0\", 10)\n            .complementarAoModulo()\n            .build();\n\n    /**\n     * Instância de módulo 11 para cálculo de digito verificador de boleto.\n     */\n    public static final DigitoPara MOD_11 = new DigitoPara.Builder()\n            .trocandoPorSeEncontrar(\"0\", 10, 11)\n            .complementarAoModulo()\n            .build();\n\n    private static final Pattern PADRAO_PARA_LIMPAR = Pattern.compile(\"[\\\\s.]\");\n    private static final Pattern PADRAO_APENAS_NUMEROS = Pattern.compile(\"[\\\\d]*\");\n\n    // No instance creation\n    private ValidadorBoleto() {\n    }\n\n    public static ValidadorBoleto getInstance() {\n        return SingletonHolder.INSTANCE;\n    }\n\n    @Override\n    public boolean ehValido(String valor) {\n\n        if (valor == null) {\n            throw new IllegalArgumentException(\"Campos não podem ser nulos\");\n        }\n\n        String valorSemFormatacao = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(valor).replaceAll(\"\");\n        return ehValido(new SpannableStringBuilder(valorSemFormatacao), new ResultadoParcial()).isValido();\n    }\n\n    @Override\n    public ResultadoParcial ehValido(Editable valor, ResultadoParcial resultadoParcial) {\n\n        if (resultadoParcial == null || valor == null) {\n            throw new IllegalArgumentException(\"Campos não podem ser nulos\");\n        }\n\n        final String valorDesformatado = PADRAO_PARA_LIMPAR.matcher(valor).replaceAll(\"\");\n\n        if (!PADRAO_APENAS_NUMEROS.matcher(valorDesformatado).matches()) {\n            throw new IllegalArgumentException(\"Apenas números, '.' e espaços são válidos\");\n        }\n\n        resultadoParcial.totalmenteValido(false);\n\n        if (valorDesformatado.length() == 0) {\n            return resultadoParcial\n                    .parcialmenteValido(true)\n                    .mensagem(\"\");\n        }\n\n        return ehTributo(valorDesformatado)\n                ? validaTributo(valorDesformatado, resultadoParcial)\n                : validaNormal(valorDesformatado, resultadoParcial);\n    }\n\n    private ResultadoParcial validaNormal(String valor, ResultadoParcial resultadoParcial) {\n\n        if (!validaBloco(valor, resultadoParcial, MOD_10, 10, 0, \"Primeiro\")) {\n            return resultadoParcial;\n        }\n\n        if (!validaBloco(valor, resultadoParcial, MOD_10, 21, 10, \"Segundo\")) {\n            return resultadoParcial;\n        }\n\n        if (!validaBloco(valor, resultadoParcial, MOD_10, 32, 21, \"Terceiro\")) {\n            return resultadoParcial;\n        }\n\n        if (valor.length() < 47) {\n            return resultadoParcial;\n        }\n\n        return resultadoParcial.parcialmenteValido(true).totalmenteValido(true);\n    }\n\n    private ResultadoParcial validaTributo(String valor, ResultadoParcial resultadoParcial) {\n\n        if (valor.length() < 3) {\n            return resultadoParcial.parcialmenteValido(true);\n        }\n\n        // A validação precisa levar em conta o terceiro dígito\n        final boolean ehMod10 = valor.charAt(2) == '6' || valor.charAt(2) == '7';\n        final DigitoPara digitoPara = ehMod10 ? MOD_10 : MOD_11;\n\n        if (!validaBloco(valor, resultadoParcial, digitoPara, 12, 0, \"Primeiro\")) {\n            return resultadoParcial;\n        }\n\n        if (!validaBloco(valor, resultadoParcial, digitoPara, 24, 12, \"Segundo\")) {\n            return resultadoParcial;\n        }\n\n        if (!validaBloco(valor, resultadoParcial, digitoPara, 36, 24, \"Terceiro\")) {\n            return resultadoParcial;\n        }\n\n        if (!validaBloco(valor, resultadoParcial, digitoPara, 48, 36, \"Quarto\")) {\n            return resultadoParcial;\n        }\n\n        // Retorna bloco válido\n        return resultadoParcial.parcialmenteValido(true).totalmenteValido(true);\n    }\n\n    private boolean ehTributo(CharSequence valor) {\n        return valor.charAt(0) == '8';\n    }\n\n    private boolean validaBloco(String valor, ResultadoParcial resultadoParcial, DigitoPara mod,\n                                int tamanhoMinimo, int st, String mensagem) {\n\n        if (valor.length() < tamanhoMinimo) {\n            resultadoParcial.parcialmenteValido(true);\n            return false;\n        }\n\n        final int end = tamanhoMinimo - 1;\n        // Valida primeiro bloco\n        final char digito = mod.calcula(valor.subSequence(st, end).toString()).charAt(0);\n\n        if (digito != valor.charAt(end)) {\n            return resultadoParcial\n                    .mensagem(mensagem + \" bloco inválido\")\n                    .parcialmenteValido(false)\n                    .isParcialmenteValido();\n        }\n\n        return true;\n    }\n\n    private static class SingletonHolder {\n        private static final ValidadorBoleto INSTANCE = new ValidadorBoleto();\n    }\n}\n"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/validator/ValidadorCEP.java",
    "content": "package br.com.concrete.canarinho.validator;\n\nimport android.text.Editable;\n\nimport br.com.concrete.canarinho.formatador.Formatador;\n\n/**\n * Implementação de @{link Validador} para CEP (Código de endereçamento Postal).\n *\n * @see Validador\n */\npublic final class ValidadorCEP implements Validador {\n\n    // No instance creation\n    private ValidadorCEP() {\n    }\n\n    public static ValidadorCEP getInstance() {\n        return SingletonHolder.INSTANCE;\n    }\n\n    @Override\n    public boolean ehValido(String valor) {\n\n        if (valor == null || valor.length() < 8) {\n            return false;\n        }\n\n        final String desformatado = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(valor).replaceAll(\"\");\n\n        return desformatado.length() == 8;\n    }\n\n    @Override\n    public ResultadoParcial ehValido(Editable valor, ResultadoParcial resultadoParcial) {\n\n        if (resultadoParcial == null || valor == null) {\n            throw new IllegalArgumentException(\"Valores não podem ser nulos\");\n        }\n\n        final String desformatado = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(valor).replaceAll(\"\");\n\n        if (!ehValido(desformatado)) {\n            return resultadoParcial\n                    .parcialmenteValido(desformatado.length() < 8)\n                    .mensagem(\"CEP inválido\")\n                    .totalmenteValido(false);\n        }\n\n        return resultadoParcial\n                .parcialmenteValido(true)\n                .totalmenteValido(true);\n    }\n\n    private static class SingletonHolder {\n        private static final ValidadorCEP INSTANCE = new ValidadorCEP();\n    }\n}\n"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/validator/ValidadorCNPJ.java",
    "content": "package br.com.concrete.canarinho.validator;\n\nimport android.text.Editable;\n\nimport br.com.concrete.canarinho.DigitoPara;\nimport br.com.concrete.canarinho.formatador.Formatador;\n\n/**\n * Implementação de @{link Validador} para CNPJ.\n *\n * @see Validador\n */\npublic final class ValidadorCNPJ implements Validador {\n\n    private static final DigitoPara DIGITO_PARA_CNPJ = new DigitoPara.Builder()\n            .complementarAoModulo()\n            .trocandoPorSeEncontrar(\"0\", 10, 11)\n            .build();\n\n    // No instance creation\n    private ValidadorCNPJ() {\n    }\n\n    public static ValidadorCNPJ getInstance() {\n        return SingletonHolder.INSTANCE;\n    }\n\n    @Override\n    public boolean ehValido(String value) {\n\n        if (value == null || value.length() < 14) {\n            return false;\n        }\n\n        final String desformatado = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(value).replaceAll(\"\");\n\n        if (desformatado.length() != 14) {\n            return false;\n        }\n\n        final String cnpjSemDigitos = desformatado.substring(0, desformatado.length() - 2);\n        final String digitos = desformatado.substring(desformatado.length() - 2);\n\n        final String dig1 = DIGITO_PARA_CNPJ.calcula(cnpjSemDigitos);\n        final String dig2 = DIGITO_PARA_CNPJ.calcula(cnpjSemDigitos + dig1);\n\n        return (dig1 + dig2).equals(digitos);\n    }\n\n    @Override\n    public ResultadoParcial ehValido(Editable valor, ResultadoParcial resultadoParcial) {\n\n        if (resultadoParcial == null || valor == null) {\n            throw new IllegalArgumentException(\"Valores não podem ser nulos\");\n        }\n\n        final String desformatado = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(valor).replaceAll(\"\");\n\n        if (!ehValido(desformatado)) {\n            return resultadoParcial\n                    .parcialmenteValido(desformatado.length() < 14)\n                    .mensagem(\"CNPJ inválido\")\n                    .totalmenteValido(false);\n        }\n\n        return resultadoParcial\n                .parcialmenteValido(true)\n                .totalmenteValido(true);\n    }\n\n    private static class SingletonHolder {\n        private static final ValidadorCNPJ INSTANCE = new ValidadorCNPJ();\n    }\n}"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/validator/ValidadorCPF.java",
    "content": "package br.com.concrete.canarinho.validator;\n\nimport android.text.Editable;\n\nimport br.com.concrete.canarinho.DigitoPara;\nimport br.com.concrete.canarinho.formatador.Formatador;\n\n/**\n * Implementação de @{link Validador} para CPF.\n *\n * @see Validador\n */\npublic final class ValidadorCPF implements Validador {\n\n    private static final DigitoPara DIGITO_PARA_CPF = new DigitoPara.Builder()\n            .comMultiplicadoresDeAte(2, 11)\n            .complementarAoModulo()\n            .trocandoPorSeEncontrar(\"0\", 10, 11)\n            .build();\n\n    // No instance creation\n    private ValidadorCPF() {\n    }\n\n    static ValidadorCPF getInstance() {\n        return SingletonHolder.INSTANCE;\n    }\n\n    @Override\n    public boolean ehValido(String value) {\n\n        if (value == null || value.length() < 11) {\n            return false;\n        }\n\n        final String desformatado = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(value).replaceAll(\"\");\n\n        if (desformatado.length() != 11) {\n            return false;\n        }\n\n        if (estaNaListaNegra(desformatado)) {\n            return false;\n        }\n\n        final String cpfSemDigito = desformatado.substring(0, desformatado.length() - 2);\n        final String digitos = desformatado.substring(desformatado.length() - 2);\n\n        final String dig1 = DIGITO_PARA_CPF.calcula(cpfSemDigito);\n        final String dig2 = DIGITO_PARA_CPF.calcula(cpfSemDigito + dig1);\n\n        return (dig1 + dig2).equals(digitos);\n    }\n\n    @Override\n    public ResultadoParcial ehValido(Editable valor, ResultadoParcial resultadoParcial) {\n\n        if (resultadoParcial == null || valor == null) {\n            throw new IllegalArgumentException(\"Valores não podem ser nulos\");\n        }\n\n        final String desformatado = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(valor).replaceAll(\"\");\n\n        if (!ehValido(desformatado)) {\n            return resultadoParcial\n                    .parcialmenteValido(desformatado.length() < 11)\n                    .mensagem(\"CPF inválido\")\n                    .totalmenteValido(false);\n        }\n\n        return resultadoParcial\n                .parcialmenteValido(true)\n                .totalmenteValido(true);\n    }\n\n    // De acordo ao cálculo dos digitos verificadores, os CPFs abaixo são válidos, entretanto os mesmo\n    // são considerados inválidos pela Receita Federal\n    // 00000000000, 11111111111, 22222222222, 33333333333, 44444444444, 55555555555,\n    // 66666666666, 77777777777, 88888888888, 99999999999, 12345678909\n    private boolean estaNaListaNegra(String valor) {\n\n        boolean igual = true;\n\n        for (int i = 1; i < 11 && igual; i++) {\n            if (valor.charAt(i) != valor.charAt(0)) {\n                igual = false;\n            }\n        }\n\n        return igual || valor.equals(\"12345678909\");\n    }\n\n    private static class SingletonHolder {\n        private static final ValidadorCPF INSTANCE = new ValidadorCPF();\n    }\n}"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/validator/ValidadorCPFCNPJ.java",
    "content": "package br.com.concrete.canarinho.validator;\n\nimport android.text.Editable;\n\nimport br.com.concrete.canarinho.formatador.Formatador;\n\npublic final class ValidadorCPFCNPJ implements Validador {\n\n    // No instance creation\n    private ValidadorCPFCNPJ() {\n    }\n\n    public static ValidadorCPFCNPJ getInstance() {\n        return SingletonHolder.INSTANCE;\n    }\n\n    @Override\n    public boolean ehValido(final String valor) {\n        if (valor == null || (valor.length() != 11 && valor.length() != 14)) {\n            return false;\n        }\n\n        if (ehCpf(valor)) {\n            return ValidadorCPF.getInstance().ehValido(valor);\n        }\n\n        return ValidadorCNPJ.getInstance().ehValido(valor);\n    }\n\n    @Override\n    public ResultadoParcial ehValido(final Editable valor, final ResultadoParcial resultadoParcial) {\n        if (resultadoParcial == null || valor == null) {\n            throw new IllegalArgumentException(\"Valores não podem ser nulos\");\n        }\n\n        if (ehCpf(valor.toString())) {\n            return ValidadorCPF.getInstance().ehValido(valor, resultadoParcial);\n        }\n\n        return ValidadorCNPJ.getInstance().ehValido(valor, resultadoParcial);\n    }\n\n    private boolean ehCpf(String valor) {\n        if (valor == null) {\n            throw new IllegalArgumentException(\"Valor não pode ser nulo\");\n        }\n\n        final String desformatado = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(valor).replaceAll(\"\");\n        return desformatado.length() < 12;\n    }\n\n    private static class SingletonHolder {\n        private static final ValidadorCPFCNPJ INSTANCE = new ValidadorCPFCNPJ();\n    }\n}"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/validator/ValidadorTelefone.java",
    "content": "package br.com.concrete.canarinho.validator;\n\nimport android.text.Editable;\n\nimport br.com.concrete.canarinho.formatador.Formatador;\n\npublic final class ValidadorTelefone implements Validador {\n\n    // No instance creation\n    private ValidadorTelefone() {\n    }\n\n    public static ValidadorTelefone getInstance() {\n        return SingletonHolder.INSTANCE;\n    }\n\n    @Override\n    public boolean ehValido(String valor) {\n        if (valor == null || valor.length() < 10) {\n            return false;\n        }\n\n        final String desformatado = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(valor).replaceAll(\"\");\n\n        return desformatado.length() == 10 || desformatado.length() == 11;\n    }\n\n    @Override\n    public ResultadoParcial ehValido(Editable valor, ResultadoParcial resultadoParcial) {\n        if (resultadoParcial == null || valor == null) {\n            throw new IllegalArgumentException(\"Valores não podem ser nulos\");\n        }\n\n        final String desformatado = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(valor).replaceAll(\"\");\n\n        if (!ehValido(desformatado)) {\n            return resultadoParcial\n                    .parcialmenteValido(desformatado.length() < 11)\n                    .mensagem(\"Telefone inválido\")\n                    .totalmenteValido(false);\n        }\n\n        return resultadoParcial\n                .parcialmenteValido(true)\n                .totalmenteValido(true);\n    }\n\n    private static class SingletonHolder {\n        private static final ValidadorTelefone INSTANCE = new ValidadorTelefone();\n    }\n}\n"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/watcher/BaseCanarinhoTextWatcher.java",
    "content": "package br.com.concrete.canarinho.watcher;\n\nimport android.text.Editable;\nimport android.text.Selection;\nimport android.text.TextWatcher;\n\nimport br.com.concrete.canarinho.formatador.Formatador;\nimport br.com.concrete.canarinho.validator.Validador;\nimport br.com.concrete.canarinho.watcher.evento.EventoDeValidacao;\n\n/**\n * Classe base para Watchers que possuem máscara e efetuam validação.\n *\n * @see Validador\n */\npublic abstract class BaseCanarinhoTextWatcher implements TextWatcher {\n\n    private boolean mudancaInterna = false;\n    private int tamanhoAnterior = 0;\n    private EventoDeValidacao eventoDeValidacao;\n\n    @Override\n    public void beforeTextChanged(CharSequence s, int start, int count, int after) {\n        // Não faz nada aqui\n    }\n\n    @Override\n    public void onTextChanged(CharSequence s, int start, int before, int count) {\n        // Não faz nada aqui\n    }\n\n    public boolean isMudancaInterna() {\n        return mudancaInterna;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public <T extends EventoDeValidacao> T getEventoDeValidacao() {\n        return (T) eventoDeValidacao;\n    }\n\n    public void setEventoDeValidacao(EventoDeValidacao eventoDeValidacao) {\n        this.eventoDeValidacao = eventoDeValidacao;\n    }\n\n    /**\n     * Utilitário para implementações de Watcher customizadas.\n     * Verifica se a ação foi de apagar um caracter\n     *\n     * @param s o Editable em uso\n     * @return True case a ação foi uma deleção e false caso contrário\n     */\n    protected boolean isApagouCaracter(Editable s) {\n        return tamanhoAnterior > s.length();\n    }\n\n    /**\n     * Utilitário para implementações de Watcher customizadas.\n     * Utilitário para atualizar o Editable com flags de atualização.\n     *\n     * @param validador        Validador utilizado para verificar o input\n     * @param resultadoParcial Objeto de validação\n     * @param s                Editable em uso\n     * @param builder          Valor atual da string\n     */\n    // Usa o Editable para atualizar o Editable\n    // O cursor SEMPRE sera posicionado no final do conteúdo\n    protected void atualizaTexto(Validador validador, Validador.ResultadoParcial resultadoParcial,\n                                 Editable s, StringBuilder builder) {\n\n        tamanhoAnterior = builder.length();\n        mudancaInterna = true;\n        s.replace(0, s.length(), builder, 0, builder.length());\n\n        if (builder.toString().equals(s.toString())) {\n            // TODO: estudar implantar a manutenção da posição do cursor\n            Selection.setSelection(s, builder.length());\n        }\n\n        efetuaValidacao(validador, resultadoParcial, s);\n        mudancaInterna = false;\n    }\n\n    /**\n     * Método que efetua a validação em si.\n     *\n     * @param validador        Validador utilizado para verificar o input\n     * @param resultadoParcial Objeto de validação\n     * @param s                Editable em uso\n     */\n    // CUIDADO AO ATUALIZAR O Editable AQUI!!!\n    protected void efetuaValidacao(Validador validador, Validador.ResultadoParcial resultadoParcial, Editable s) {\n\n        if (validador == null) {\n            return;\n        }\n\n        if (eventoDeValidacao == null) {\n            validador.ehValido(s.toString());\n            return;\n        }\n\n        validador.ehValido(s, resultadoParcial);\n\n        if (!resultadoParcial.isParcialmenteValido()) {\n            eventoDeValidacao.invalido(s.toString(), resultadoParcial.getMensagem());\n        } else if (!resultadoParcial.isValido()) {\n            eventoDeValidacao.parcialmenteValido(s.toString());\n        } else {\n            eventoDeValidacao.totalmenteValido(s.toString());\n        }\n    }\n\n    /**\n     * Implementação genérica para adição ou remoção de caracter.\n     *\n     * @param s       Editable em uso\n     * @param mascara máscara do Watcher\n     * @return Builder com o valor final\n     */\n    protected StringBuilder trataAdicaoRemocaoDeCaracter(Editable s, char[] mascara) {\n        return isApagouCaracter(s)\n                ? trataRemocaoDeCaracter(s, mascara)\n                : trataAdicaoDeCaracter(s, mascara);\n    }\n\n    private StringBuilder trataAdicaoDeCaracter(Editable s, char[] mascara) {\n        return carregarMascara(s.toString(), mascara);\n    }\n\n    // Só é chamado após uma deleção, portanto, é seguro chamar mascara[s.length()]\n    private StringBuilder trataRemocaoDeCaracter(Editable s, char[] mascara) {\n        final StringBuilder builder = new StringBuilder(s);\n\n        // Obtém a posição do último caracter excluído\n        final int posicaoUltimoCaracter = mascara.length > s.length() ? s.length() : mascara.length - 1;\n\n        // Verifica se o último caracter que foi excluído fazia parte da máscara\n        final boolean ultimoCaracterEraMascara = mascara[posicaoUltimoCaracter] != '#';\n\n        // Se o último caracter excluído fazia parte da máscara,\n        // deve excluir até o primeiro caracter que não faz parte da máscara\n        if (ultimoCaracterEraMascara) {\n            boolean encontrouCaracterValido = false;\n            while (builder.length() > 0 && !encontrouCaracterValido) {\n                encontrouCaracterValido = mascara[builder.length() - 1] == '#';\n                builder.deleteCharAt(builder.length() - 1);\n            }\n        }\n\n        // Caso haja mais de um caracter de formatação (da máscara) faz um loop\n        // até chegar em um caracter que não seja de formatação\n        while (builder.length() > 0 && mascara[builder.length() - 1] != '#') {\n            builder.deleteCharAt(builder.length() - 1);\n        }\n\n        return carregarMascara(builder.toString(), mascara);\n    }\n\n    private StringBuilder carregarMascara(String s, char[] mascara) {\n        final StringBuilder builder = new StringBuilder();\n        final String str = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(s).replaceAll(\"\");\n\n        // Só carregará a máscara se existir algum valor informado\n        if (str.length() > 0) {\n            int j = 0; // Acompanha a posição nos dígitos\n\n            // É recomendado não usar enhanced for em Android\n            for (int i = 0; i < mascara.length; i++) {\n\n                final char charMascara = mascara[i];\n\n                if (charMascara != '#') { // '#' -> caracter de formatação\n                    builder.append(charMascara);\n                    continue;\n                }\n\n                if (j >= str.length()) {\n                    break;\n                }\n\n                builder.append(str.charAt(j));\n                j++;\n            }\n        }\n\n        return builder;\n    }\n}\n"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/watcher/BoletoBancarioTextWatcher.java",
    "content": "package br.com.concrete.canarinho.watcher;\n\nimport android.text.Editable;\nimport android.text.InputFilter;\n\nimport br.com.concrete.canarinho.validator.Validador;\nimport br.com.concrete.canarinho.validator.ValidadorBoleto;\nimport br.com.concrete.canarinho.watcher.evento.EventoDeValidacao;\nimport br.com.concrete.canarinho.watcher.evento.EventoDeValidacaoDeBoleto;\n\nimport java.util.Arrays;\n\n/**\n * {@link android.text.TextWatcher} responsável por formatar e validar um {@link\n * android.widget.EditText} para boletos. Para usar este componente basta criar uma instância e\n * chamar {@link android.widget.EditText#addTextChangedListener(android.text.TextWatcher)}.\n */\npublic final class BoletoBancarioTextWatcher extends BaseCanarinhoTextWatcher {\n\n    private static final char[] BOLETO_NORMAL = \"#####.##### #####.###### #####.###### # ##############\".toCharArray();\n    private static final char[] BOLETO_TRIBUTO = \"############ ############ ############ ############\".toCharArray();\n    private static final InputFilter[] FILTRO_TRIBUTO = new InputFilter[]{\n        new InputFilter.LengthFilter(BOLETO_TRIBUTO.length)};\n    private static final InputFilter[] FILTRO_NORMAL = new InputFilter[]{\n        new InputFilter.LengthFilter(BOLETO_NORMAL.length)};\n\n    private final Validador validador = ValidadorBoleto.getInstance();\n    private final Validador.ResultadoParcial resultadoParcial = new Validador.ResultadoParcial();\n\n    /**\n     * TODO Javadoc pendente.\n     *\n     * @param callbackErros a descrever\n     */\n    public BoletoBancarioTextWatcher(EventoDeValidacao callbackErros) {\n        setEventoDeValidacao(callbackErros);\n    }\n\n    @Override\n    public final void afterTextChanged(Editable s) {\n\n        // retorna se a String é menor que o mínimo de caracteres\n        // para haver uma formatação ou se a mudança foi disparada\n        // pelo método atualizaTexto\n        if (isMudancaInterna()) {\n            return;\n        }\n\n        // Trata o caso em que tudo é apagado em lote\n        if (s.length() < 3) {\n            resultadoParcial.mensagem(null).parcialmenteValido(false).totalmenteValido(false);\n            if (getEventoDeValidacao() != null) {\n                getEventoDeValidacao().parcialmenteValido(\"\");\n            }\n        }\n\n        if (s.length() == 0) {\n            verificaFiltro(s, false);\n            return;\n        }\n\n        final boolean tributo = ehTributo(s);\n        final char[] mascara = tributo ? BOLETO_TRIBUTO : BOLETO_NORMAL;\n        verificaFiltro(s, tributo);\n\n        // Trata deleção e adição de forma diferente (só formata em adições)\n        final StringBuilder builder = trataAdicaoRemocaoDeCaracter(s, mascara);\n        atualizaTexto(validador, resultadoParcial, s, builder);\n    }\n\n    public Validador.ResultadoParcial getResultadoParcial() {\n        return resultadoParcial;\n    }\n\n    @Override\n    protected void efetuaValidacao(Validador validador, Validador.ResultadoParcial resultadoParcial, Editable s) {\n\n        validador.ehValido(s, resultadoParcial);\n\n        final EventoDeValidacao callbackErros = getEventoDeValidacao();\n\n        if (callbackErros == null) {\n            return;\n        }\n\n        final String valorAtual = s.toString();\n        if (!resultadoParcial.isParcialmenteValido()) {\n\n            final String mensagem = resultadoParcial.getMensagem();\n            callbackErros.invalido(valorAtual, mensagem);\n\n            if (callbackErros instanceof EventoDeValidacaoDeBoleto) {\n\n                final int bloco;\n\n                if (mensagem.startsWith(\"Primeiro\")) {\n                    bloco = 1;\n                } else if (mensagem.startsWith(\"Segundo\")) {\n                    bloco = 2;\n                } else if (mensagem.startsWith(\"Terceiro\")) {\n                    bloco = 3;\n                } else if (mensagem.startsWith(\"Quarto\")) {\n                    bloco = 4;\n                } else {\n                    throw new IllegalArgumentException(\"Valor não reconhecido para bloco\");\n                }\n\n                ((EventoDeValidacaoDeBoleto) callbackErros).invalido(valorAtual, bloco);\n            }\n\n        } else if (!resultadoParcial.isValido()) {\n            callbackErros.parcialmenteValido(valorAtual);\n        } else {\n            callbackErros.totalmenteValido(valorAtual);\n        }\n    }\n\n    private void verificaFiltro(final Editable s, final boolean tributo) {\n        // Filtro de tamanho\n        if (tributo && !Arrays.equals(s.getFilters(), FILTRO_TRIBUTO)) {\n            s.setFilters(FILTRO_TRIBUTO);\n        } else if (!tributo && !Arrays.equals(s.getFilters(), FILTRO_NORMAL)) {\n            s.setFilters(FILTRO_NORMAL);\n        }\n    }\n\n    // Boletos iniciados com 8 são tributos ou de concessionárias\n    private boolean ehTributo(Editable e) {\n        return e.charAt(0) == '8';\n    }\n}\n"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/watcher/CEPTextWatcher.java",
    "content": "package br.com.concrete.canarinho.watcher;\n\nimport android.text.Editable;\nimport android.text.InputFilter;\n\nimport br.com.concrete.canarinho.validator.Validador;\nimport br.com.concrete.canarinho.validator.ValidadorCEP;\nimport br.com.concrete.canarinho.watcher.evento.EventoDeValidacao;\n\n/**\n * {@link android.text.TextWatcher} responsável por formatar e validar um {@link android.widget.EditText} para CEPs.\n * Para usar este componente basta criar uma instância e chamar\n * {@link android.widget.EditText#addTextChangedListener(android.text.TextWatcher)}.\n */\npublic final class CEPTextWatcher extends BaseCanarinhoTextWatcher {\n\n    private static final char[] CEP_DIGITOS = \"#####-###\".toCharArray();\n\n    private static final InputFilter[] FILTRO_OITO_DIGITOS = new InputFilter[]{\n        new InputFilter.LengthFilter(CEP_DIGITOS.length)};\n\n    private final Validador validador = ValidadorCEP.getInstance();\n    private final Validador.ResultadoParcial resultadoParcial = new Validador.ResultadoParcial();\n\n    /**\n     * Inicializa o validador com um callback de erros.\n     *\n     * @param callbackErros Instância que será chamada quando houverem erros.\n     */\n    public CEPTextWatcher(EventoDeValidacao callbackErros) {\n        setEventoDeValidacao(callbackErros);\n    }\n\n    @Override\n    public void afterTextChanged(Editable s) {\n\n        if (isMudancaInterna()) {\n            return;\n        }\n\n        s.setFilters(FILTRO_OITO_DIGITOS);\n\n        final StringBuilder builder = trataAdicaoRemocaoDeCaracter(s, CEP_DIGITOS);\n\n        atualizaTexto(validador, resultadoParcial, s, builder);\n    }\n}\n"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/watcher/CPFCNPJTextWatcher.java",
    "content": "package br.com.concrete.canarinho.watcher;\n\nimport android.text.Editable;\nimport android.text.InputFilter;\n\nimport br.com.concrete.canarinho.formatador.Formatador;\nimport br.com.concrete.canarinho.validator.Validador;\nimport br.com.concrete.canarinho.validator.ValidadorCPFCNPJ;\nimport br.com.concrete.canarinho.watcher.evento.EventoDeValidacao;\n\n/**\n * {@link android.text.TextWatcher} responsável por formatar e validar um\n * {@link android.widget.EditText} para CPF / CNPJ.\n * Para usar este componente basta criar uma instância e chamar\n * {@link android.widget.EditText#addTextChangedListener(android.text.TextWatcher)}.\n */\npublic class CPFCNPJTextWatcher extends BaseCanarinhoTextWatcher {\n\n    private static final char[] CPF = \"###.###.###-##\".toCharArray();\n    private static final char[] CNPJ = \"##.###.###/####-##\".toCharArray();\n    private static final InputFilter[] FILTRO_CPF_CNPJ = new InputFilter[]{new InputFilter.LengthFilter(CNPJ.length)};\n\n    private final Validador validador = ValidadorCPFCNPJ.getInstance();\n    private final Validador.ResultadoParcial resultadoParcial = new Validador.ResultadoParcial();\n\n    /**\n     * TODO Javadoc pendente.\n     */\n    public CPFCNPJTextWatcher() {\n    }\n\n    /**\n     * TODO Javadoc pendente.\n     *\n     * @param callbackErros a descrever\n     */\n    public CPFCNPJTextWatcher(EventoDeValidacao callbackErros) {\n        setEventoDeValidacao(callbackErros);\n    }\n\n    @Override\n    public void afterTextChanged(final Editable s) {\n\n        if (isMudancaInterna()) {\n            return;\n        }\n\n        s.setFilters(FILTRO_CPF_CNPJ);\n\n        final char[] mascara = ehCpf(s) ? CPF : CNPJ;\n        final StringBuilder builder = trataAdicaoRemocaoDeCaracter(s, mascara);\n        atualizaTexto(validador, resultadoParcial, s, builder);\n    }\n\n    // Verifica se o valor informado é cpf\n    private boolean ehCpf(Editable e) {\n        return Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(e).replaceAll(\"\").length() < 12;\n    }\n}"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/watcher/MascaraNumericaTextWatcher.java",
    "content": "package br.com.concrete.canarinho.watcher;\n\nimport android.text.Editable;\nimport android.text.InputFilter;\n\nimport br.com.concrete.canarinho.validator.Validador;\nimport br.com.concrete.canarinho.watcher.evento.EventoDeValidacao;\n\nimport java.util.Arrays;\n\n/**\n * Máscara c/ validação genérica para campos numéricos.\n *\n * @see br.com.concrete.canarinho.watcher.MascaraNumericaTextWatcher.Builder\n */\npublic final class MascaraNumericaTextWatcher extends BaseCanarinhoTextWatcher {\n\n    private final Validador.ResultadoParcial resultadoParcial = new Validador.ResultadoParcial();\n    private final Validador validador;\n    private final char[] mascara;\n    private final InputFilter[] filtroNumerico;\n\n    /**\n     * Construtor para adicionar uma máscara sem validação.\n     *\n     * @param mascara Máscara para efetuar a formatação\n     */\n    public MascaraNumericaTextWatcher(String mascara) {\n        this(new Builder().paraMascara(mascara));\n    }\n\n    private MascaraNumericaTextWatcher(Builder builder) {\n        this.mascara = builder.mascara.toCharArray();\n        this.validador = builder.validador;\n\n        final int length = mascara.length;\n        this.filtroNumerico = new InputFilter[]{new InputFilter.LengthFilter(length)};\n\n        setEventoDeValidacao(builder.eventoDeValidacao);\n    }\n\n    @Override\n    public void afterTextChanged(Editable s) {\n\n        // retorna se a mudança foi disparada pelo método atualizaTexto\n        if (isMudancaInterna()) {\n            return;\n        }\n\n        // Filtro de tamanho\n        if (!Arrays.equals(s.getFilters(), filtroNumerico)) {\n            s.setFilters(filtroNumerico);\n        }\n\n        final StringBuilder builder = trataAdicaoRemocaoDeCaracter(s, mascara);\n\n        atualizaTexto(validador, resultadoParcial, s, builder);\n    }\n\n    /**\n     * Builder para construção de máscaras que validam.\n     */\n    public static final class Builder {\n\n        private Validador validador;\n        private EventoDeValidacao eventoDeValidacao;\n        private String mascara;\n\n        /**\n         * O validador que será usado. Será chamada a implementação de\n         * {@link Validador#ehValido(Editable, Validador.ResultadoParcial)}\n         *\n         * @param validador Implementação de {@link Validador}\n         * @return this para interface fluente\n         */\n        public Builder comValidador(Validador validador) {\n            this.validador = validador;\n            return this;\n        }\n\n        /**\n         * Para cada caracter digitado será validado de acordo com o Validador e o callback\n         * correspondente ao resultado da validação será chamado para que a interface possa ser atualizada.\n         *\n         * @param callbackErros {@link EventoDeValidacao} que será chamado durante a validação\n         * @return this para interface fluente\n         */\n        public Builder comCallbackDeValidacao(EventoDeValidacao callbackErros) {\n            this.eventoDeValidacao = callbackErros;\n            return this;\n        }\n\n        /**\n         * A máscara só pode conter os caracteres '#' no lugar dos números. Assim, a máscara\n         * '#####-##' irá aceitar apenas números no lugar de '#'. Ao digitar, o usuário irá ver:\n         * '12345-67'.\n         *\n         * @param mascara Máscara\n         * @return this para interface fluente\n         */\n        public Builder paraMascara(String mascara) {\n            this.mascara = mascara;\n            return this;\n        }\n\n        /**\n         * Constrói a máscara.\n         *\n         * @return A instância imutável da máscara.\n         */\n        public final MascaraNumericaTextWatcher build() {\n\n            if (mascara == null || mascara.isEmpty() || !mascara.contains(\"#\")) {\n                throw new IllegalArgumentException(\"Máscara precisa conter ao menos um caracter '#'\");\n            }\n\n            return new MascaraNumericaTextWatcher(this);\n        }\n    }\n}\n"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/watcher/TelefoneTextWatcher.java",
    "content": "package br.com.concrete.canarinho.watcher;\n\nimport android.text.Editable;\nimport android.text.InputFilter;\n\nimport br.com.concrete.canarinho.formatador.Formatador;\nimport br.com.concrete.canarinho.validator.Validador;\nimport br.com.concrete.canarinho.validator.ValidadorTelefone;\nimport br.com.concrete.canarinho.watcher.evento.EventoDeValidacao;\n\n/**\n * {@link android.text.TextWatcher} responsável por formatar e validar um\n * {@link android.widget.EditText} para telefones.\n * Para usar este componente basta criar uma instância e chamar\n * {@link android.widget.EditText#addTextChangedListener(android.text.TextWatcher)}.\n */\npublic final class TelefoneTextWatcher extends BaseCanarinhoTextWatcher {\n\n    private static final char[] TELEFONE_OITO_DIGITOS = \"(##) ####-####\".toCharArray();\n    private static final char[] TELEFONE_NOVE_DIGITOS = \"(##) #####-####\".toCharArray();\n    private static final InputFilter[] FILTRO_NOVE_DIGITOS = new InputFilter[]{\n        new InputFilter.LengthFilter(TELEFONE_NOVE_DIGITOS.length)};\n\n    private final Validador validador = ValidadorTelefone.getInstance();\n    private final Validador.ResultadoParcial resultadoParcial = new Validador.ResultadoParcial();\n\n    /**\n     * TODO Javadoc pendente.\n     *\n     * @param callbackErros a descrever\n     */\n    public TelefoneTextWatcher(EventoDeValidacao callbackErros) {\n        setEventoDeValidacao(callbackErros);\n    }\n\n    @Override\n    public void afterTextChanged(Editable s) {\n\n        if (isMudancaInterna()) {\n            return;\n        }\n\n        s.setFilters(FILTRO_NOVE_DIGITOS);\n\n        final char[] mascara = ehNoveDigitos(s) ? TELEFONE_NOVE_DIGITOS : TELEFONE_OITO_DIGITOS;\n        final StringBuilder builder = trataAdicaoRemocaoDeCaracter(s, mascara);\n\n        atualizaTexto(validador, resultadoParcial, s, builder);\n    }\n\n    // Verifica se o telefone possui 9 dígitos\n    private boolean ehNoveDigitos(Editable e) {\n        return Formatador.Padroes.PADRAO_SOMENTE_NUMEROS.matcher(e).replaceAll(\"\").length() > 10;\n    }\n}\n"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/watcher/ValorMonetarioWatcher.java",
    "content": "package br.com.concrete.canarinho.watcher;\n\nimport android.text.Editable;\nimport android.text.InputFilter;\nimport android.text.Selection;\nimport android.text.TextWatcher;\n\nimport br.com.concrete.canarinho.formatador.Formatador;\n\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\n\n/**\n * TextWatcher para valores monetários.\n */\npublic class ValorMonetarioWatcher implements TextWatcher {\n\n    private final boolean mantemZerosAoLimpar;\n    private final Formatador formatador;\n    private boolean mudancaInterna;\n\n    /**\n     * Constrói uma instância sem símbolo de Real (R$).\n     */\n    public ValorMonetarioWatcher() {\n        this(false, true);\n    }\n\n    /**\n     * Constrói uma instância com opção de símbolo de Real (R$).\n     *\n     * @param comSimboloReal      Flag para acrescentar ou não o símbolo\n     * @param mantemZerosAoLimpar Sempre que não houver números (apagar em lote) manter zeros\n     */\n    ValorMonetarioWatcher(boolean comSimboloReal, boolean mantemZerosAoLimpar) {\n        this.formatador = comSimboloReal\n                ? Formatador.VALOR_COM_SIMBOLO\n                : Formatador.VALOR;\n        this.mantemZerosAoLimpar = mantemZerosAoLimpar;\n    }\n\n    @Override\n    public void beforeTextChanged(CharSequence s, int start, int count, int after) {\n        // Não faz nada aqui\n    }\n\n    @Override\n    public void onTextChanged(CharSequence s, int start, int before, int count) {\n        // Não faz nada aqui\n    }\n\n    @Override\n    public void afterTextChanged(Editable s) {\n\n        if (mudancaInterna) {\n            return;\n        }\n\n        final String somenteNumeros = Formatador.Padroes.PADRAO_SOMENTE_NUMEROS\n                .matcher(s.toString())\n                .replaceAll(\"\");\n\n        // afterTextChanged é chamado ao rotacionar o dispositivo,\n        // essa condição evita que ao rotacionar a tela com o campo vazio ocorra NumberFormatException\n        if (somenteNumeros.length() == 0) {\n            if (mantemZerosAoLimpar) {\n                atualizaTexto(s, formatador.formata(\"000\"));\n            }\n            return;\n        }\n\n        final BigDecimal resultado = new BigDecimal(somenteNumeros)\n                .divide(new BigDecimal(100))\n                .setScale(2, RoundingMode.HALF_DOWN);\n\n        atualizaTexto(s, formatador.formata(resultado.toPlainString()));\n    }\n\n    private void atualizaTexto(Editable editable, String valor) {\n        mudancaInterna = true;\n\n        final InputFilter[] oldFilters = editable.getFilters();\n\n        editable.setFilters(new InputFilter[] {});\n        editable.replace(0, editable.length(), valor);\n\n        editable.setFilters(oldFilters);\n\n        if (valor.equals(editable.toString())) {\n            // TODO: estudar implantar a manutenção da posição do cursor\n            Selection.setSelection(editable, valor.length());\n        }\n\n        mudancaInterna = false;\n    }\n\n    /**\n     * Builder para facilitar a construção de instâncias de {@link ValorMonetarioWatcher}.\n     */\n    public static class Builder {\n\n        private boolean mantemZerosAoLimpar;\n        private boolean simboloReal;\n\n        /**\n         * Manterá os zeros ao limpar o campo.\n         *\n         * @return this Fluent interface\n         */\n        public Builder comMantemZerosAoLimpar() {\n            this.mantemZerosAoLimpar = true;\n            return this;\n        }\n\n        /**\n         * Inclui o símbolo de real na formatação.\n         *\n         * @return this Fluent interface\n         */\n        public Builder comSimboloReal() {\n            this.simboloReal = true;\n            return this;\n        }\n\n        /**\n         * Constrói a instância.\n         *\n         * @return Watcher para ser usado\n         */\n        public ValorMonetarioWatcher build() {\n            return new ValorMonetarioWatcher(simboloReal, mantemZerosAoLimpar);\n        }\n    }\n}\n"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/watcher/evento/EventoDeValidacao.java",
    "content": "package br.com.concrete.canarinho.watcher.evento;\n\n/**\n * Interface para quem estiver usando este TextWatcher poder ter uma ação quando um erro de validação acontecer.\n * Os métodos desta interface serão chamados quando for digitado um NOVO caracter e quando for APAGADO um caracter.\n */\npublic interface EventoDeValidacao {\n\n    /**\n     * Invocado quando os números digitados estão inválidos. Pode ser apenas um trecho ou o número completo.\n     *\n     * @param valorAtual O valor após a digitação.\n     * @param mensagem   A mensagem de erro da validação.\n     */\n    void invalido(String valorAtual, String mensagem);\n\n    /**\n     * Invocado quando os números digitados estão parcialmente válidos. Quando o número estiver completamente válido\n     * será chamado o callback {@link #totalmenteValido(String)}.\n     *\n     * @param valorAtual O valor após a digitação.\n     */\n    void parcialmenteValido(String valorAtual);\n\n    /**\n     * Invocado quando a máscara está completa e os números são válidos.\n     *\n     * @param valorAtual O valor após a digitação.\n     */\n    void totalmenteValido(String valorAtual);\n}\n"
  },
  {
    "path": "canarinho/src/main/java/br/com/concrete/canarinho/watcher/evento/EventoDeValidacaoDeBoleto.java",
    "content": "package br.com.concrete.canarinho.watcher.evento;\n\n/**\n * Evento de validação específico para boletos que permite saber qual o bloco que\n * contém caracteres inválidos.\n */\npublic interface EventoDeValidacaoDeBoleto extends EventoDeValidacao {\n\n    /**\n     * Invocado quando os números digitados estão inválidos. Pode ser apenas um trecho ou o número completo.\n     *\n     * @param valorAtual    O valor após a digitação.\n     * @param blocoInvalido O bloco com valor inválido\n     */\n    void invalido(String valorAtual, int blocoInvalido);\n\n}\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-7.0.2-bin.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8\norg.gradle.parallel=true\nandroid.useAndroidX=true\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched.\nif $cygwin ; then\n    [ -n \"$JAVA_HOME\" ] && JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\nfi\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >&-\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >&-\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windowz variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\ngoto execute\r\n\r\n:4NT_args\r\n@rem Get arguments from the 4NT Shell from JP Software\r\nset CMD_LINE_ARGS=%$\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "sample/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "sample/build.gradle",
    "content": "apply plugin: 'com.android.application'\napply plugin: 'jacoco'\n\nandroid {\n    compileSdkVersion 31\n\n    defaultConfig {\n        applicationId 'br.com.concrete.canarinho.sample'\n        minSdkVersion 21\n        targetSdkVersion 31\n        versionCode 1\n        versionName '1.0'\n\n        testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'\n    }\n\n    buildTypes {\n\n        debug {\n            testCoverageEnabled true\n        }\n\n        release {\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'\n        }\n    }\n\n    testOptions {\n        execution 'ANDROIDX_TEST_ORCHESTRATOR'\n        unitTests {\n            includeAndroidResources = true\n        }\n    }\n}\n\ndependencies {\n\n    implementation project(':canarinho')\n\n    implementation 'androidx.appcompat:appcompat:1.2.0'\n    implementation 'com.google.android.material:material:1.2.1'\n\n    androidTestUtil 'androidx.test:orchestrator:1.3.0'\n\n    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'\n    androidTestImplementation 'androidx.test:runner:1.3.0'\n    androidTestImplementation 'androidx.test:rules:1.3.0'\n    androidTestImplementation 'androidx.test:core:1.3.0'\n    androidTestImplementation 'androidx.test.ext:junit:1.1.2'\n\n    testImplementation 'junit:junit:4.13.2'\n    testImplementation \"org.robolectric:robolectric:4.7.3\"\n    testImplementation 'androidx.test:core:1.3.0'\n    testImplementation 'androidx.test.ext:junit:1.1.2'\n}\n\njacoco {\n    toolVersion = '0.8.3'\n}\n\ntask fullCoverageReport(type: JacocoReport) {\n    dependsOn 'createDebugCoverageReport'\n    dependsOn 'testDebugUnitTest'\n\n    reports {\n        xml.enabled true\n        html.enabled true\n    }\n\n    def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*',\n                      '**/*Test*.*', 'android/**/*.*']\n    def debugTree = fileTree(dir: \"${buildDir}/intermediates/classes/debug\", excludes: fileFilter)\n    def mainSrc = \"${project.projectDir}/src/main/java\"\n\n    sourceDirectories.from = files([mainSrc])\n    classDirectories.from = files([debugTree])\n    executionData.from = fileTree(dir: \"$buildDir\", includes: [\n            \"jacoco/testDebugUnitTest.exec\",\n            \"outputs/code_coverage/connected/*coverage.ec\"\n    ])\n}\n"
  },
  {
    "path": "sample/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# By default, the flags in this file are appended to flags specified\n# in /home/victornascimento/Android/Sdk/linters/proguard/proguard-android.txt\n# You can edit the include path and order by changing the proguardFiles\n# directive in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# Add any project specific keep options here:\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n"
  },
  {
    "path": "sample/src/androidTest/java/br/com/concrete/canarinho/sample/BugOnApi28Test.java",
    "content": "package br.com.concrete.canarinho.sample;\n\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport androidx.test.filters.SdkSuppress;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport br.com.concrete.canarinho.formatador.Formatador;\n\nimport static org.junit.Assert.assertEquals;\n\n@RunWith(AndroidJUnit4.class)\npublic class BugOnApi28Test {\n\n    @Test\n    @SdkSuppress(minSdkVersion = 28)\n    public void moneyFormatSuccessfulRunsOnApi28() {\n        final String value = \"1.000.000,00\";\n        assertEquals(\"1000000.00\", Formatador.VALOR.desformata(value));\n    }\n\n    @Test\n    @SdkSuppress(maxSdkVersion = 27)\n    public void moneyFormatSuccessfulRunsOnApi27() {\n        final String value = \"1.000.000,00\";\n        assertEquals(\"1000000.00\", Formatador.VALOR.desformata(value));\n    }\n}\n"
  },
  {
    "path": "sample/src/androidTest/java/br/com/concrete/canarinho/sample/DemoWatchersInstrumentationTest.java",
    "content": "package br.com.concrete.canarinho.sample;\n\nimport android.os.Build;\nimport android.view.View;\nimport android.widget.EditText;\nimport android.widget.SearchView;\n\nimport org.hamcrest.Matcher;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport androidx.test.espresso.UiController;\nimport androidx.test.espresso.ViewAction;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport androidx.test.rule.ActivityTestRule;\nimport br.com.concrete.canarinho.sample.ui.activity.MainActivity;\nimport br.com.concrete.canarinho.sample.ui.model.Watchers;\n\nimport static androidx.test.espresso.Espresso.onView;\nimport static androidx.test.espresso.action.ViewActions.clearText;\nimport static androidx.test.espresso.action.ViewActions.click;\nimport static androidx.test.espresso.action.ViewActions.pressBack;\nimport static androidx.test.espresso.action.ViewActions.scrollTo;\nimport static androidx.test.espresso.action.ViewActions.typeText;\nimport static androidx.test.espresso.assertion.ViewAssertions.matches;\nimport static androidx.test.espresso.matcher.ViewMatchers.isAssignableFrom;\nimport static androidx.test.espresso.matcher.ViewMatchers.isDescendantOfA;\nimport static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;\nimport static androidx.test.espresso.matcher.ViewMatchers.supportsInputMethods;\nimport static androidx.test.espresso.matcher.ViewMatchers.withId;\nimport static androidx.test.espresso.matcher.ViewMatchers.withText;\nimport static org.hamcrest.Matchers.allOf;\nimport static org.hamcrest.Matchers.anyOf;\n\n@RunWith(AndroidJUnit4.class)\npublic class DemoWatchersInstrumentationTest {\n\n    @Rule\n    public ActivityTestRule<MainActivity> rule = new ActivityTestRule<>(MainActivity.class);\n\n    @Test\n    public void consegueDigitarUmBoletoNormalValido() {\n\n        navigateToTab(Watchers.BOLETO_BANCARIO);\n\n        onView(allOf(withId(R.id.edit_text), isDisplayed()))\n                .perform(typeText(\"34199310130010011560900500990007800000000000000\"));\n\n        onView(withText(\"Campo válido!\")).check(matches(isDisplayed()));\n    }\n\n    @Test\n    public void consegueValidarUmBoletoSetandoOCodigoInteiro() {\n\n        navigateToTab(Watchers.BOLETO_BANCARIO);\n\n        onView(allOf(withId(R.id.edit_text), isDisplayed()))\n                .perform(paste(\"34199310130010011560900500990007800000000000000\"));\n\n        onView(withText(\"Campo válido!\")).check(matches(isDisplayed()));\n    }\n\n    @Test\n    public void consegueValidarUmBoletoTributoSetandoOCodigoInteiro() {\n\n        navigateToTab(Watchers.BOLETO_BANCARIO);\n\n        onView(allOf(withId(R.id.edit_text), isDisplayed()))\n                .perform(paste(\"812345678901812345678901812345678901812345678901\"));\n\n        onView(withText(\"Campo válido!\")).check(matches(isDisplayed()));\n    }\n\n    @Test\n    public void consegueDigitarUmBoletoNormalComBlocosInvalidos() {\n\n        navigateToTab(Watchers.BOLETO_BANCARIO);\n\n        // primeiro\n        onView(allOf(withId(R.id.edit_text), isDisplayed()))\n                .perform(typeText(\"23790125016\"));\n        onView(withText(\"Primeiro bloco inválido\")).check(matches(isDisplayed()));\n\n        // segundo\n        onView(allOf(withId(R.id.edit_text), isDisplayed()))\n                .perform(clearText(), typeText(\"2379012301600000030054\"));\n        onView(withText(\"Segundo bloco inválido\")).check(matches(isDisplayed()));\n\n        // terceiro\n        onView(allOf(withId(R.id.edit_text), isDisplayed()))\n                .perform(clearText(), typeText(\"23790123016000000005325000456708\"));\n        onView(withText(\"Terceiro bloco inválido\")).check(matches(isDisplayed()));\n    }\n\n    @Test\n    public void consegueDigitarUmBoletoTributoValido() {\n\n        navigateToTab(Watchers.BOLETO_BANCARIO);\n\n        // Boleto válido\n        onView(allOf(withId(R.id.edit_text), isDisplayed()))\n                .perform(typeText(\"848600000015523301622010506101307129620012111220\"));\n\n        onView(withText(\"Campo válido!\"))\n                .check(matches(isDisplayed()))\n                .perform(pressBack());\n\n        onView(allOf(withId(R.id.edit_text), isDisplayed()))\n                .perform(clearText(), typeText(\"836600000019078800481000998854924516001265611135\"));\n\n        onView(withText(\"Campo válido!\"))\n                .check(matches(isDisplayed()))\n                .perform(pressBack());\n    }\n\n    @Test\n    public void consegueDigitarUmBoletoNormalComBlocosInvalidosComMensagemCustomizada() {\n\n        navigateToTab(Watchers.BOLETO_BANCARIO_MSG_CUSTOM);\n\n        // primeiro\n        onView(allOf(withId(R.id.edit_text), isDisplayed()))\n                .perform(typeText(\"23790125016\"));\n        onView(withText(\"Primeira mensagem\")).check(matches(isDisplayed()));\n\n        // segundo\n        onView(allOf(withId(R.id.edit_text), isDisplayed()))\n                .perform(clearText(), typeText(\"2379012301600000030054\"));\n        onView(withText(\"Segunda mensagem\")).check(matches(isDisplayed()));\n\n        // terceiro\n        onView(allOf(withId(R.id.edit_text), isDisplayed()))\n                .perform(clearText(), typeText(\"23790123016000000005325000456708\"));\n        onView(withText(\"Terceira mensagem\")).check(matches(isDisplayed()));\n    }\n\n    @Test\n    public void consegueDigitarUmCPFValido() {\n\n        navigateToTab(Watchers.CPF);\n\n        onView(allOf(withId(R.id.edit_text), isDisplayed())).perform(typeText(\"46574356636\"));\n\n        onView(withText(\"Campo válido!\"))\n                .check(matches(isDisplayed()))\n                .perform(pressBack());\n    }\n\n    @Test\n    public void consegueDigitarUmCPFInvalido() {\n\n        navigateToTab(Watchers.CPF);\n\n        onView(allOf(withId(R.id.edit_text), isDisplayed())).perform(typeText(\"46574356637\"));\n        onView(withText(\"CPF inválido\")).check(matches(isDisplayed()));\n    }\n\n    @Test\n    public void consegueDigitarUmCNPJValido() {\n\n        navigateToTab(Watchers.CNPJ);\n\n        onView(allOf(withId(R.id.edit_text), isDisplayed())).perform(typeText(\"95621433000170\"));\n\n        onView(withText(\"Campo válido!\"))\n                .check(matches(isDisplayed()))\n                .perform(pressBack());\n    }\n\n    @Test\n    public void consegueDigitarUmCNPJInvalido() {\n\n        navigateToTab(Watchers.CNPJ);\n        onView(allOf(withId(R.id.edit_text), isDisplayed())).perform(typeText(\"95621433000180\"));\n        onView(withText(\"CNPJ inválido\")).check(matches(isDisplayed()));\n    }\n\n    @Test\n    public void consegueDigitarUmTelefoneValido() {\n\n        navigateToTab(Watchers.TELEFONE);\n\n        onView(allOf(withId(R.id.edit_text), isDisplayed())).perform(typeText(\"1112345678\"));\n\n        onView(withText(\"Campo válido!\"))\n                .check(matches(isDisplayed()))\n                .perform(pressBack());\n\n        onView(allOf(withId(R.id.edit_text), isDisplayed())).perform(typeText(\"11123456789\"));\n\n        onView(withText(\"Campo válido!\"))\n                .check(matches(isDisplayed()))\n                .perform(pressBack());\n    }\n\n    @Test\n    public void consegueDigitarUmValorMonetarioFormatado() throws InterruptedException {\n\n        navigateToTab(Watchers.VALOR_MONETARIO);\n\n        Thread.sleep(200L);\n\n        onView(allOf(withId(R.id.edit_text), isDisplayed()))\n                .perform(typeText(\"1\"))\n                .check(matches(withText(\"R$ 0,01\"))) // inicia com 0,0X\n                .perform(typeText(\"2\"))\n                .check(matches(withText(\"R$ 0,12\"))) // 0,XY\n                .perform(typeText(\"3\"))\n                .check(matches(withText(\"R$ 1,23\"))) // X,YZ\n                .perform(typeText(\"4\"))\n                .check(matches(withText(\"R$ 12,34\"))) // etc, etc, etc\n                .perform(typeText(\"5\"))\n                .check(matches(withText(\"R$ 123,45\")))\n                .perform(typeText(\"6\"))\n                .check(matches(withText(\"R$ 1.234,56\")))\n                .perform(typeText(\"7\"))\n                .check(matches(withText(\"R$ 12.345,67\")))\n                .perform(typeText(\"8\"))\n                .check(matches(withText(\"R$ 123.456,78\")))\n                .perform(typeText(\"9\"))\n                .check(matches(withText(\"R$ 1.234.567,89\")))\n                .perform(clearText())\n                .check(matches(withText(\"R$ 0,00\")));\n    }\n\n    @Test\n    public void consegueDigitarCPFCNPJValido() {\n\n        navigateToTab(Watchers.CPF_CNPJ);\n\n        onView(allOf(withId(R.id.edit_text), isDisplayed())).perform(typeText(\"46574356636\"));\n        onView(withText(\"Campo válido!\")).check(matches(isDisplayed())).perform(pressBack());\n        onView(allOf(withId(R.id.edit_text), isDisplayed())).perform(clearText(), typeText(\"95621433000170\"));\n        onView(withText(\"Campo válido!\")).check(matches(isDisplayed()));\n    }\n\n    @Test\n    public void consegueDigitarCPFCNPJInvalido() {\n\n        navigateToTab(Watchers.CPF_CNPJ);\n\n        onView(allOf(withId(R.id.edit_text), isDisplayed())).perform(typeText(\"46574356637\"));\n        onView(withText(\"CPF inválido\")).check(matches(isDisplayed()));\n\n        onView(allOf(withId(R.id.edit_text), isDisplayed())).perform(clearText(), typeText(\"15621433000180\"));\n        onView(withText(\"CNPJ inválido\")).check(matches(isDisplayed()));\n    }\n\n    @Test\n    public void consegueDigitarUmCEPValido() {\n\n        navigateToTab(Watchers.CEP);\n\n        onView(allOf(withId(R.id.edit_text), isDisplayed())).perform(typeText(\"49025090\"));\n\n        onView(withText(\"Campo válido!\"))\n                .check(matches(isDisplayed()));\n    }\n\n    @Test\n    public void consegueUtilizarUmaMascaraGenericaSemValidadorOuEvento() {\n\n        navigateToTab(Watchers.MASCARA_GENERICA);\n\n        onView(allOf(withId(R.id.edit_text), isDisplayed()))\n                .perform(typeText(\"12345\"))\n                .check(matches(withText(\"1-2-3-4-5\")));\n    }\n\n    private void navigateToTab(Watchers watcher) {\n        onView(\n                allOf(\n                        withText(watcher.getTitle()),\n                        isDescendantOfA(withId(R.id.tabs)))\n        )\n                .perform(scrollTo(), click());\n    }\n\n    private ViewAction paste(final String type) {\n        return new ViewAction() {\n            @Override\n            public Matcher<View> getConstraints() {\n                // noinspection unchecked\n                Matcher<View> matchers = allOf(isDisplayed());\n\n                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {\n                    return allOf(matchers, supportsInputMethods());\n                } else {\n                    // SearchView does not support input methods itself (rather it delegates to an internal text\n                    // view for input).\n                    return allOf(matchers, anyOf(supportsInputMethods(), isAssignableFrom(SearchView.class)));\n                }\n            }\n\n            @Override\n            public String getDescription() {\n                return \"Straight typing into view\";\n            }\n\n            @Override\n            public void perform(UiController uiController, View view) {\n                ((EditText) view).setText(type);\n            }\n        };\n    }\n}\n"
  },
  {
    "path": "sample/src/main/AndroidManifest.xml",
    "content": "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"br.com.concrete.canarinho.sample\">\n\n    <application\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"@string/app_name\"\n        android:theme=\"@style/AppTheme\">\n\n        <activity android:name=\"br.com.concrete.canarinho.sample.ui.activity.MainActivity\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n    </application>\n\n</manifest>\n"
  },
  {
    "path": "sample/src/main/java/br/com/concrete/canarinho/sample/ui/activity/MainActivity.java",
    "content": "package br.com.concrete.canarinho.sample.ui.activity;\n\nimport android.os.Bundle;\n\nimport com.google.android.material.tabs.TabLayout;\n\nimport androidx.appcompat.app.ActionBar;\nimport androidx.appcompat.app.AppCompatActivity;\nimport androidx.appcompat.widget.Toolbar;\nimport androidx.viewpager.widget.ViewPager;\nimport br.com.concrete.canarinho.sample.R;\nimport br.com.concrete.canarinho.sample.ui.adapter.WatchersPagerAdapter;\n\n/** */\npublic class MainActivity extends AppCompatActivity {\n\n    @Override\n    public void onCreate(Bundle savedInstanceState) {\n        super.onCreate(savedInstanceState);\n        setContentView(R.layout.main_activity);\n\n        final Toolbar toolbar = findViewById(R.id.toolbar);\n        setSupportActionBar(toolbar);\n\n        final ActionBar supportActionBar = getSupportActionBar();\n        if (supportActionBar != null) {\n            supportActionBar.setDisplayHomeAsUpEnabled(true);\n            supportActionBar.setDisplayShowTitleEnabled(false);\n        }\n\n        final ViewPager viewPager = findViewById(R.id.viewpager);\n        viewPager.setAdapter(new WatchersPagerAdapter(getSupportFragmentManager()));\n\n        final TabLayout tabs = findViewById(R.id.tabs);\n        tabs.setupWithViewPager(viewPager);\n    }\n}\n"
  },
  {
    "path": "sample/src/main/java/br/com/concrete/canarinho/sample/ui/adapter/WatchersPagerAdapter.java",
    "content": "package br.com.concrete.canarinho.sample.ui.adapter;\n\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentManager;\nimport androidx.fragment.app.FragmentPagerAdapter;\nimport br.com.concrete.canarinho.sample.ui.model.Watchers;\n\npublic class WatchersPagerAdapter extends FragmentPagerAdapter {\n\n    private Watchers[] models = Watchers.values();\n\n    public WatchersPagerAdapter(FragmentManager fragmentManager) {\n        super(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);\n    }\n\n    @Override\n    public Fragment getItem(int position) {\n        return models[position].buildFragment();\n    }\n\n    @Override\n    public int getCount() {\n        return models.length;\n    }\n\n    @Override\n    public CharSequence getPageTitle(int position) {\n        return models[position].getTitle();\n    }\n}\n"
  },
  {
    "path": "sample/src/main/java/br/com/concrete/canarinho/sample/ui/fragment/BaseWatcherFragment.java",
    "content": "package br.com.concrete.canarinho.sample.ui.fragment;\n\nimport androidx.fragment.app.Fragment;\nimport br.com.concrete.canarinho.sample.ui.model.Watchers;\n\npublic abstract class BaseWatcherFragment extends Fragment {\n\n    protected Watchers model;\n\n    public void setModel(Watchers model) {\n        this.model = model;\n    }\n}\n"
  },
  {
    "path": "sample/src/main/java/br/com/concrete/canarinho/sample/ui/fragment/CanarinhoValorMonetarioWatcherFragment.java",
    "content": "package br.com.concrete.canarinho.sample.ui.fragment;\n\nimport android.os.Bundle;\nimport android.text.TextWatcher;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.EditText;\nimport android.widget.TextView;\n\nimport com.google.android.material.textfield.TextInputLayout;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport br.com.concrete.canarinho.sample.R;\nimport br.com.concrete.canarinho.sample.ui.model.Watchers;\nimport br.com.concrete.canarinho.watcher.ValorMonetarioWatcher;\n\npublic class CanarinhoValorMonetarioWatcherFragment extends BaseWatcherFragment {\n\n    private EditText watcherEdit;\n    private TextView watcherTitle;\n    private TextInputLayout watcherInputLayout;\n    private TextWatcher currentWatcher;\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n\n        final View layout = inflater.inflate(R.layout.fragment_valor_monetario_watcher, null);\n        watcherTitle = layout.findViewById(R.id.watcher_title);\n        watcherInputLayout = layout.findViewById(R.id.edit_input_layout);\n        watcherEdit = layout.findViewById(R.id.edit_text);\n        setModel(Watchers.VALOR_MONETARIO);\n        bind(model);\n        return layout;\n    }\n\n    public CanarinhoValorMonetarioWatcherFragment bind(Watchers model) {\n\n        watcherTitle.setText(model.getTitle());\n        watcherEdit.setHint(model.getHint());\n\n        if (currentWatcher != null) {\n            watcherEdit.removeTextChangedListener(currentWatcher);\n        }\n\n        currentWatcher = new ValorMonetarioWatcher.Builder()\n                .comMantemZerosAoLimpar()\n                .comSimboloReal()\n                .build();\n        watcherEdit.addTextChangedListener(currentWatcher);\n\n        return this;\n    }\n}\n"
  },
  {
    "path": "sample/src/main/java/br/com/concrete/canarinho/sample/ui/fragment/WatcherFragment.java",
    "content": "package br.com.concrete.canarinho.sample.ui.fragment;\n\nimport android.os.Bundle;\nimport android.text.TextWatcher;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.EditText;\nimport android.widget.TextView;\n\nimport com.google.android.material.textfield.TextInputLayout;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\nimport br.com.concrete.canarinho.sample.R;\nimport br.com.concrete.canarinho.sample.ui.model.Watchers;\n\npublic class WatcherFragment extends BaseWatcherFragment {\n\n    private EditText watcherEdit;\n    private TextView watcherTitle;\n    private TextInputLayout watcherInputLayout;\n    private TextWatcher currentWatcher;\n\n    public static WatcherFragment newInstance(Watchers model) {\n        final WatcherFragment watcherFragment = new WatcherFragment();\n        watcherFragment.setArguments(new Bundle());\n        watcherFragment.setModel(model);\n        return watcherFragment;\n    }\n\n    @Nullable\n    @Override\n    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {\n\n        final View layout = inflater.inflate(R.layout.fragment_canarinho_watcher, null);\n        watcherTitle = layout.findViewById(R.id.watcher_title);\n        watcherInputLayout = layout.findViewById(R.id.edit_input_layout);\n        watcherEdit = layout.findViewById(R.id.edit_text);\n        bind(model);\n        return layout;\n    }\n\n    public WatcherFragment bind(Watchers model) {\n\n        if (currentWatcher != null) {\n            watcherEdit.removeTextChangedListener(currentWatcher);\n        }\n\n        currentWatcher = model.setupWatcher(watcherInputLayout);\n        watcherTitle.setText(model.getTitle());\n        watcherEdit.setHint(model.getHint());\n        watcherEdit.addTextChangedListener(currentWatcher);\n        return this;\n    }\n}\n"
  },
  {
    "path": "sample/src/main/java/br/com/concrete/canarinho/sample/ui/model/Watchers.java",
    "content": "package br.com.concrete.canarinho.sample.ui.model;\n\nimport android.text.TextWatcher;\n\nimport com.google.android.material.textfield.TextInputLayout;\n\nimport androidx.appcompat.app.AlertDialog;\nimport br.com.concrete.canarinho.sample.ui.fragment.BaseWatcherFragment;\nimport br.com.concrete.canarinho.sample.ui.fragment.CanarinhoValorMonetarioWatcherFragment;\nimport br.com.concrete.canarinho.sample.ui.fragment.WatcherFragment;\nimport br.com.concrete.canarinho.validator.Validador;\nimport br.com.concrete.canarinho.watcher.BoletoBancarioTextWatcher;\nimport br.com.concrete.canarinho.watcher.CEPTextWatcher;\nimport br.com.concrete.canarinho.watcher.CPFCNPJTextWatcher;\nimport br.com.concrete.canarinho.watcher.MascaraNumericaTextWatcher;\nimport br.com.concrete.canarinho.watcher.TelefoneTextWatcher;\nimport br.com.concrete.canarinho.watcher.evento.EventoDeValidacao;\nimport br.com.concrete.canarinho.watcher.evento.EventoDeValidacaoDeBoleto;\n\n/**\n */\npublic enum Watchers {\n\n    BOLETO_BANCARIO(\"Boleto Bancário\", \"Digite um boleto válido\") {\n        @Override\n        public WatcherFragment buildFragment() {\n            return WatcherFragment.newInstance(this);\n        }\n\n        @Override\n        public TextWatcher setupWatcher(TextInputLayout textInputLayout) {\n            return new BoletoBancarioTextWatcher(new SampleEventoDeValidacao(textInputLayout));\n        }\n    },\n    BOLETO_BANCARIO_MSG_CUSTOM(\"Boleto Bancário com mensagem\", \"Digite um boleto válido\") {\n        @Override\n        public WatcherFragment buildFragment() {\n            return WatcherFragment.newInstance(this);\n        }\n\n        @Override\n        public TextWatcher setupWatcher(TextInputLayout textInputLayout) {\n            return new BoletoBancarioTextWatcher(new EventoDeValidacaoBoleto(textInputLayout));\n        }\n    },\n\n    CPF(\"CPF\", \"Digite um CPF válido\") {\n        @Override\n        public WatcherFragment buildFragment() {\n            return WatcherFragment.newInstance(this);\n        }\n\n        @Override\n        public TextWatcher setupWatcher(TextInputLayout textInputLayout) {\n            return new MascaraNumericaTextWatcher.Builder()\n                    .paraMascara(\"###.###.###-##\")\n                    .comCallbackDeValidacao(new SampleEventoDeValidacao(textInputLayout))\n                    .comValidador(Validador.CPF)\n                    .build();\n        }\n    },\n\n    CNPJ(\"CNPJ\", \"Digite um CNPJ válido\") {\n        @Override\n        public WatcherFragment buildFragment() {\n            return WatcherFragment.newInstance(this);\n        }\n\n        @Override\n        public TextWatcher setupWatcher(TextInputLayout textInputLayout) {\n            return new MascaraNumericaTextWatcher.Builder()\n                    .paraMascara(\"##.###.###/####-##\")\n                    .comCallbackDeValidacao(new SampleEventoDeValidacao(textInputLayout))\n                    .comValidador(Validador.CNPJ)\n                    .build();\n        }\n    },\n\n    TELEFONE(\"Telefone\", \"Digite um telefone válido\") {\n        @Override\n        public WatcherFragment buildFragment() {\n            return WatcherFragment.newInstance(this);\n        }\n\n        @Override\n        public TextWatcher setupWatcher(TextInputLayout textInputLayout) {\n            return new TelefoneTextWatcher(new SampleEventoDeValidacao(textInputLayout));\n        }\n    },\n\n    CPF_CNPJ(\"CPF e CNPJ\", \"Digite um CPF ou CNPJ válido\") {\n        @Override\n        public WatcherFragment buildFragment() {\n            return WatcherFragment.newInstance(this);\n        }\n\n        @Override\n        public TextWatcher setupWatcher(TextInputLayout textInputLayout) {\n            return new CPFCNPJTextWatcher(new SampleEventoDeValidacao(textInputLayout));\n        }\n    },\n\n    CEP(\"CEP\", \"Digite um CEP válido\") {\n        @Override\n        public WatcherFragment buildFragment() {\n            return WatcherFragment.newInstance(this);\n        }\n\n        @Override\n        public TextWatcher setupWatcher(TextInputLayout textInputLayout) {\n            return new CEPTextWatcher(new SampleEventoDeValidacao(textInputLayout));\n        }\n    },\n\n    MASCARA_GENERICA(\"Máscara genérica\", \"5 números\") {\n        @Override\n        public WatcherFragment buildFragment() {\n            return WatcherFragment.newInstance(this);\n        }\n\n        @Override\n        public TextWatcher setupWatcher(TextInputLayout textInputLayout) {\n            return new MascaraNumericaTextWatcher(\"#-#-#-#-#\");\n        }\n    },\n\n    VALOR_MONETARIO(\"Valor monetário\", \"Digite um valor monetário\") {\n        @Override\n        public CanarinhoValorMonetarioWatcherFragment buildFragment() {\n            return new CanarinhoValorMonetarioWatcherFragment();\n        }\n\n        @Override\n        public TextWatcher setupWatcher(TextInputLayout textInputLayout) {\n            return null;\n        }\n    };\n\n    private final String title;\n    private final String hint;\n\n    Watchers(String title, String hint) {\n        this.hint = hint;\n        this.title = title;\n    }\n\n    public String getTitle() {\n        return title;\n    }\n\n    public String getHint() {\n        return hint;\n    }\n\n    public abstract BaseWatcherFragment buildFragment();\n\n    public abstract TextWatcher setupWatcher(TextInputLayout textInputLayout);\n\n    /**\n     * {@link EventoDeValidacao} que mostra uma mensagem de erro no {@link TextInputLayout} passado\n     * como argumento.\n     */\n    public static class SampleEventoDeValidacao implements EventoDeValidacao {\n\n        TextInputLayout textInputLayout;\n\n        public SampleEventoDeValidacao(TextInputLayout textInputLayout) {\n            this.textInputLayout = textInputLayout;\n        }\n\n        @Override\n        public void invalido(String valorAtual, String mensagem) {\n            textInputLayout.setError(mensagem);\n        }\n\n        @Override\n        public void parcialmenteValido(String valorAtual) {\n            textInputLayout.setErrorEnabled(false);\n            textInputLayout.setError(null);\n        }\n\n        @Override\n        public void totalmenteValido(String valorAtual) {\n            new AlertDialog.Builder(textInputLayout.getContext())\n                    .setTitle(\"Campo válido!\")\n                    .setMessage(valorAtual)\n                    .show();\n        }\n    }\n\n    /**\n     * {@link EventoDeValidacao} que valida blocos de boleto.\n     *\n     * @see SampleEventoDeValidacao\n     */\n    public static class EventoDeValidacaoBoleto\n            extends SampleEventoDeValidacao\n            implements EventoDeValidacaoDeBoleto {\n\n        EventoDeValidacaoBoleto(TextInputLayout textInputLayout) {\n            super(textInputLayout);\n        }\n\n        @Override\n        public void invalido(String valorAtual, int blocoInvalido) {\n\n            if (blocoInvalido == 1)\n                textInputLayout.setError(\"Primeira mensagem\");\n\n            else if (blocoInvalido == 2)\n                textInputLayout.setError(\"Segunda mensagem\");\n\n            else if (blocoInvalido == 3)\n                textInputLayout.setError(\"Terceira mensagem\");\n\n            else if (blocoInvalido == 4)\n                textInputLayout.setError(\"Quarta mensagem\");\n\n        }\n    }\n\n}\n"
  },
  {
    "path": "sample/src/main/res/layout/fragment_canarinho_watcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <androidx.appcompat.widget.LinearLayoutCompat\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:padding=\"6dp\">\n\n        <TextView\n            android:id=\"@+id/watcher_title\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:textAppearance=\"@style/TextAppearance.AppCompat.Title\" />\n\n        <com.google.android.material.textfield.TextInputLayout\n            android:id=\"@+id/edit_input_layout\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:errorEnabled=\"true\">\n\n            <com.google.android.material.textfield.TextInputEditText\n                android:id=\"@+id/edit_text\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_margin=\"4dp\"\n                android:inputType=\"number\" />\n\n        </com.google.android.material.textfield.TextInputLayout>\n\n    </androidx.appcompat.widget.LinearLayoutCompat>\n</com.google.android.material.card.MaterialCardView>"
  },
  {
    "path": "sample/src/main/res/layout/fragment_valor_monetario_watcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.card.MaterialCardView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <androidx.appcompat.widget.LinearLayoutCompat\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:padding=\"6dp\">\n\n        <TextView\n            android:id=\"@+id/watcher_title\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:textAppearance=\"@style/TextAppearance.AppCompat.Title\" />\n\n        <com.google.android.material.textfield.TextInputLayout\n            android:id=\"@+id/edit_input_layout\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:errorEnabled=\"true\">\n\n            <com.google.android.material.textfield.TextInputEditText\n                android:id=\"@+id/edit_text\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_margin=\"4dp\"\n                android:inputType=\"number\" />\n\n        </com.google.android.material.textfield.TextInputLayout>\n\n    </androidx.appcompat.widget.LinearLayoutCompat>\n</com.google.android.material.card.MaterialCardView>"
  },
  {
    "path": "sample/src/main/res/layout/main_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/rootLayout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <com.google.android.material.appbar.AppBarLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <androidx.appcompat.widget.Toolbar\n            android:id=\"@+id/toolbar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"?attr/actionBarSize\"\n            android:background=\"#008800\"\n            android:theme=\"@style/ThemeOverlay.AppCompat.Dark.ActionBar\"\n            app:layout_scrollFlags=\"scroll|enterAlwaysCollapsed\"\n            app:popupTheme=\"@style/ThemeOverlay.AppCompat.Light\">\n\n            <ImageView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginEnd=\"12dp\"\n                android:layout_marginRight=\"12dp\"\n                android:contentDescription=\"@string/app_name\"\n                android:src=\"@mipmap/ic_launcher\" />\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/app_name\"\n                android:textAppearance=\"@style/TextAppearance.AppCompat.Widget.ActionBar.Title\" />\n\n        </androidx.appcompat.widget.Toolbar>\n\n        <com.google.android.material.tabs.TabLayout\n            android:id=\"@+id/tabs\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:tabGravity=\"fill\"\n            app:tabMode=\"scrollable\" />\n\n    </com.google.android.material.appbar.AppBarLayout>\n\n    <androidx.viewpager.widget.ViewPager\n        android:id=\"@+id/viewpager\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        app:layout_behavior=\"@string/appbar_scrolling_view_behavior\" />\n\n</androidx.coordinatorlayout.widget.CoordinatorLayout>"
  },
  {
    "path": "sample/src/main/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">Android Canarinho</string>\n</resources>\n"
  },
  {
    "path": "sample/src/main/res/values/styles.xml",
    "content": "<resources>\n    <!-- Base application theme. -->\n    <style name=\"AppTheme\" parent=\"Theme.MaterialComponents.Light.NoActionBar\">\n        <item name=\"windowActionBar\">false</item>\n        <item name=\"windowNoTitle\">true</item>\n        <item name=\"android:windowBackground\">@android:color/white</item>\n    </style>\n</resources>\n"
  },
  {
    "path": "sample/src/test/java/br/com/concrete/canarinho/test/TesteFormatadorBOLETO.java",
    "content": "package br.com.concrete.canarinho.test;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\n\nimport br.com.concrete.canarinho.formatador.Formatador;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\n\n@RunWith(AndroidJUnit4.class)\npublic class TesteFormatadorBOLETO {\n\n    @Test\n    public void consegueFormatar() {\n        assertThat(Formatador.BOLETO.formata(\"12345123451234512345612345123456712345678901234\"),\n                is(\"12345.12345 12345.123456 12345.123456 7 12345678901234\"));\n\n        assertThat(Formatador.BOLETO.formata(\"23790123016000000005325000456704364670000013580\"),\n                is(\"23790.12301 60000.000053 25000.456704 3 64670000013580\"));\n\n        assertThat(Formatador.BOLETO.formata(\"812345678901812345678901812345678901812345678901\"),\n                is(\"812345678901 812345678901 812345678901 812345678901\"));\n\n        assertThat(Formatador.BOLETO.formata(\"23790.12301 60000.000053 25000.456704 3 64670000013580\"),\n                is(\"23790.12301 60000.000053 25000.456704 3 64670000013580\"));\n\n        assertThat(Formatador.BOLETO.formata(\"812345678901 812345678901 812345678901 812345678901\"),\n                is(\"812345678901 812345678901 812345678901 812345678901\"));\n    }\n\n    @Test\n    public void consegueDesformatar() {\n\n        assertThat(Formatador.BOLETO.desformata(\"12345.12345 12345.123456 12345.123456 7 12345678901234\"),\n                is(\"12345123451234512345612345123456712345678901234\"));\n\n        assertThat(Formatador.BOLETO.desformata(\"23790.12301 60000.000053 25000.456704 3 64670000013580\"),\n                is(\"23790123016000000005325000456704364670000013580\"));\n\n        assertThat(Formatador.BOLETO.desformata(\"812345678901 812345678901 812345678901 812345678901\"),\n                is(\"812345678901812345678901812345678901812345678901\"));\n\n        assertThat(Formatador.BOLETO.desformata(\"23790123016000000005325000456704364670000013580\"),\n                is(\"23790123016000000005325000456704364670000013580\"));\n\n        assertThat(Formatador.BOLETO.desformata(\"812345678901812345678901812345678901812345678901\"),\n                is(\"812345678901812345678901812345678901812345678901\"));\n    }\n\n    @Test\n    public void consegueDizerSeEstaFormatado() {\n\n        assertThat(Formatador.BOLETO.estaFormatado(\"12345.12345 12345.123456 12345.123456 7 12345678901234\"), is(true));\n        assertThat(Formatador.BOLETO.estaFormatado(\"23790.12301 60000.000053 25000.456704 3 64670000013580\"), is(true));\n\n        assertThat(Formatador.BOLETO.estaFormatado(\"812345678901812345678901812345678901812345678901\"), is(false));\n        assertThat(Formatador.BOLETO.estaFormatado(\"23790123016000000005325000456704364670000013580\"), is(false));\n        assertThat(Formatador.BOLETO.estaFormatado(\"812345678901812345678901812345678901812345678901\"), is(false));\n    }\n\n    @Test\n    public void consegueDizerSePodeFormatar() {\n        // TODO\n    }\n}\n"
  },
  {
    "path": "sample/src/test/java/br/com/concrete/canarinho/test/TesteFormatadorCEP.java",
    "content": "package br.com.concrete.canarinho.test;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\n\nimport br.com.concrete.canarinho.formatador.Formatador;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.junit.Assert.fail;\n\n@RunWith(AndroidJUnit4.class)\npublic class TesteFormatadorCEP {\n\n    @Test\n    public void consegueFormatar() {\n\n        // Gerados automaticamente para testes\n        assertThat(Formatador.CEP.formata(\"00000-000\"), is(\"00000-000\"));\n        assertThat(Formatador.CEP.formata(\"00000000\"), is(\"00000-000\"));\n\n        assertThat(Formatador.CEP.formata(\"12345-123\"), is(\"12345-123\"));\n        assertThat(Formatador.CEP.formata(\"12345678\"), is(\"12345-678\"));\n\n        assertThrowsFormat(\"\");\n        assertThrowsFormat(\"123123\");\n    }\n\n    @Test\n    public void consegueDesformatar() {\n\n        // Gerados automaticamente para testes\n        assertThat(Formatador.CEP.desformata(\"00000-000\"), is(\"00000000\"));\n        assertThat(Formatador.CEP.desformata(\"00000000\"), is(\"00000000\"));\n\n        assertThat(Formatador.CEP.desformata(\"12345-123\"), is(\"12345123\"));\n        assertThat(Formatador.CEP.desformata(\"12345678\"), is(\"12345678\"));\n\n        assertThrowsDesformat(\"\");\n        assertThrowsDesformat(\"123123\");\n    }\n\n    @Test\n    public void consegueDizerSeEstaFormatado() {\n\n        // Gerados automaticamente para testes\n        assertThat(Formatador.CEP.estaFormatado(\"12345-678\"), is(true));\n        assertThat(Formatador.CEP.estaFormatado(\"12345678\"), is(false));\n        assertThat(Formatador.CEP.estaFormatado(\"00000-000\"), is(true));\n        assertThat(Formatador.CEP.estaFormatado(\"12345-67\"), is(false));\n\n        assertThat(Formatador.CEP.estaFormatado(\"047486\"), is(false));\n        assertThat(Formatador.CEP.estaFormatado(\"\"), is(false));\n        try {\n            Formatador.CEP.estaFormatado(null);\n            fail(\"Should have thrown!!!\");\n        } catch (IllegalArgumentException e) {\n        }\n    }\n\n    @Test\n    public void consegueDizerSePodeFormatar() {\n\n        // Gerados automaticamente para testes\n        assertThat(Formatador.CEP.podeSerFormatado(\"12345-678\"), is(false));\n        assertThat(Formatador.CEP.podeSerFormatado(\"12345678\"), is(true));\n        assertThat(Formatador.CEP.podeSerFormatado(\"020\"), is(false));\n        assertThat(Formatador.CEP.podeSerFormatado(\"\"), is(false));\n        assertThat(Formatador.CEP.podeSerFormatado(null), is(false));\n    }\n\n    private void assertThrowsFormat(String valor) {\n        try {\n            Formatador.CEP.formata(valor);\n            fail(\"Deveria ter jogado exceção!!!\");\n        } catch (IllegalArgumentException e) {\n        }\n    }\n\n    private void assertThrowsDesformat(String valor) {\n        try {\n            Formatador.CEP.desformata(valor);\n            fail(\"Deveria ter jogado exceção!!!\");\n        } catch (IllegalArgumentException e) {\n        }\n    }\n}\n"
  },
  {
    "path": "sample/src/test/java/br/com/concrete/canarinho/test/TesteFormatadorCNPJ.java",
    "content": "package br.com.concrete.canarinho.test;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\n\nimport br.com.concrete.canarinho.formatador.Formatador;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.junit.Assert.fail;\n\n@RunWith(AndroidJUnit4.class)\npublic class TesteFormatadorCNPJ {\n\n    @Test\n    public void consegueFormatar() {\n\n        // Gerados automaticamente para testes\n        assertThat(Formatador.CNPJ.formata(\"50.713.534/0001-33\"), is(\"50.713.534/0001-33\"));\n        assertThat(Formatador.CNPJ.formata(\"50713534000133\"), is(\"50.713.534/0001-33\"));\n\n        assertThat(Formatador.CNPJ.formata(\"72.606.598/0001-78\"), is(\"72.606.598/0001-78\"));\n        assertThat(Formatador.CNPJ.formata(\"72606598000178\"), is(\"72.606.598/0001-78\"));\n\n        assertThat(Formatador.CNPJ.formata(\"23.106.535/0001-47\"), is(\"23.106.535/0001-47\"));\n        assertThat(Formatador.CNPJ.formata(\"23106535000147\"), is(\"23.106.535/0001-47\"));\n\n        assertThrowsFormat(\"\");\n        assertThrowsFormat(\"123123\");\n        assertThrowsFormat(\"23.106.535/0001  -   47\");\n        assertThrowsFormat(\"       23.106.535/0001-47      \");\n    }\n\n    @Test\n    public void consegueDesformatar() {\n\n        // Gerados automaticamente para testes\n        assertThat(Formatador.CNPJ.desformata(\"50.713.534/0001-33\"), is(\"50713534000133\"));\n        assertThat(Formatador.CNPJ.desformata(\"50713534000133\"), is(\"50713534000133\"));\n\n        assertThat(Formatador.CNPJ.desformata(\"72.606.598/0001-78\"), is(\"72606598000178\"));\n        assertThat(Formatador.CNPJ.desformata(\"72606598000178\"), is(\"72606598000178\"));\n\n        assertThat(Formatador.CNPJ.desformata(\"23.106.535/0001-47\"), is(\"23106535000147\"));\n        assertThat(Formatador.CNPJ.desformata(\"23106535000147\"), is(\"23106535000147\"));\n\n        assertThrowsDesformat(\"\");\n        assertThrowsDesformat(\"123123\");\n        assertThrowsDesformat(\"123.123.123    -12\");\n        assertThrowsDesformat(\"       047.486.777-32      \");\n    }\n\n    @Test\n    public void consegueDizerSeEstaFormatado() {\n\n        // Gerados automaticamente para testes\n        assertThat(Formatador.CNPJ.estaFormatado(\"72.606.598/0001-78\"), is(true));\n        assertThat(Formatador.CNPJ.estaFormatado(\"72606598000178\"), is(false));\n        assertThat(Formatador.CNPJ.estaFormatado(\"23.106.535/0001-47\"), is(true));\n        assertThat(Formatador.CNPJ.estaFormatado(\"23106535000147\"), is(false));\n\n        assertThat(Formatador.CNPJ.estaFormatado(\"047486\"), is(false));\n        assertThat(Formatador.CNPJ.estaFormatado(\"\"), is(false));\n        try {\n            Formatador.CNPJ.estaFormatado(null);\n            fail(\"Should have thrown!!!\");\n        } catch (IllegalArgumentException e) {\n        }\n    }\n\n    @Test\n    public void consegueDizerSePodeFormatar() {\n\n        // Gerados automaticamente para testes\n        assertThat(Formatador.CNPJ.podeSerFormatado(\"23.106.535/0001-47\"), is(false));\n        assertThat(Formatador.CNPJ.podeSerFormatado(\"23106535000147\"), is(true));\n        assertThat(Formatador.CNPJ.podeSerFormatado(\"020\"), is(false));\n        assertThat(Formatador.CNPJ.podeSerFormatado(\"\"), is(false));\n        assertThat(Formatador.CNPJ.podeSerFormatado(null), is(false));\n    }\n\n    private void assertThrowsFormat(String valor) {\n        try {\n            Formatador.CNPJ.formata(valor);\n            fail(\"Deveria ter jogado exceção!!!\");\n        } catch (IllegalArgumentException e) {\n        }\n    }\n\n    private void assertThrowsDesformat(String valor) {\n        try {\n            Formatador.CNPJ.desformata(valor);\n            fail(\"Deveria ter jogado exceção!!!\");\n        } catch (IllegalArgumentException e) {\n        }\n    }\n}\n"
  },
  {
    "path": "sample/src/test/java/br/com/concrete/canarinho/test/TesteFormatadorCPF.java",
    "content": "package br.com.concrete.canarinho.test;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\n\nimport br.com.concrete.canarinho.formatador.Formatador;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.junit.Assert.fail;\n\n@RunWith(AndroidJUnit4.class)\npublic class TesteFormatadorCPF {\n\n    @Test\n    public void consegueFormatarCPF() {\n\n        // Gerados automaticamente para testes\n        assertThat(Formatador.CPF.formata(\"545.586.262-66\"), is(\"545.586.262-66\"));\n        assertThat(Formatador.CPF.formata(\"54558626266\"), is(\"545.586.262-66\"));\n\n        assertThat(Formatador.CPF.formata(\"020.724.833-87\"), is(\"020.724.833-87\"));\n        assertThat(Formatador.CPF.formata(\"02072483387\"), is(\"020.724.833-87\"));\n\n        assertThat(Formatador.CPF.formata(\"188.527.045-31\"), is(\"188.527.045-31\"));\n        assertThat(Formatador.CPF.formata(\"18852704531\"), is(\"188.527.045-31\"));\n\n        assertThat(Formatador.CPF.formata(\"047.486.777-32\"), is(\"047.486.777-32\"));\n        assertThat(Formatador.CPF.formata(\"04748677732\"), is(\"047.486.777-32\"));\n\n        assertThrowsFormat(\"\");\n        assertThrowsFormat(\"123123\");\n        assertThrowsFormat(\"123.123.123    -12\");\n        assertThrowsFormat(\"       047.486.777-32      \");\n    }\n\n    @Test\n    public void consegueDesformatarCPF() {\n\n        // Gerados automaticamente para testes\n        assertThat(Formatador.CPF.desformata(\"545.586.262-66\"), is(\"54558626266\"));\n        assertThat(Formatador.CPF.desformata(\"54558626266\"), is(\"54558626266\"));\n\n        assertThat(Formatador.CPF.desformata(\"020.724.833-87\"), is(\"02072483387\"));\n        assertThat(Formatador.CPF.desformata(\"02072483387\"), is(\"02072483387\"));\n\n        assertThat(Formatador.CPF.desformata(\"188.527.045-31\"), is(\"18852704531\"));\n        assertThat(Formatador.CPF.desformata(\"18852704531\"), is(\"18852704531\"));\n\n        assertThat(Formatador.CPF.desformata(\"047.486.777-32\"), is(\"04748677732\"));\n        assertThat(Formatador.CPF.desformata(\"04748677732\"), is(\"04748677732\"));\n\n        assertThrowsDesformat(\"\");\n        assertThrowsDesformat(\"123123\");\n        assertThrowsDesformat(\"123.123.123    -12\");\n        assertThrowsDesformat(\"       047.486.777-32      \");\n    }\n\n    @Test\n    public void consegueDizerSeEstaFormatado() {\n\n        // Gerados automaticamente para testes\n        assertThat(Formatador.CPF.estaFormatado(\"545.586.262-66\"), is(true));\n        assertThat(Formatador.CPF.estaFormatado(\"54558626266\"), is(false));\n        assertThat(Formatador.CPF.estaFormatado(\"020.724.833-87\"), is(true));\n        assertThat(Formatador.CPF.estaFormatado(\"02072483387\"), is(false));\n        assertThat(Formatador.CPF.estaFormatado(\"188.527.045-31\"), is(true));\n        assertThat(Formatador.CPF.estaFormatado(\"18852704531\"), is(false));\n        assertThat(Formatador.CPF.estaFormatado(\"047.486.777-32\"), is(true));\n        assertThat(Formatador.CPF.estaFormatado(\"04748677732\"), is(false));\n\n        assertThat(Formatador.CPF.estaFormatado(\"047486\"), is(false));\n        assertThat(Formatador.CPF.estaFormatado(\"\"), is(false));\n        try {\n            Formatador.CPF.estaFormatado(null);\n            fail(\"Should have thrown!!!\");\n        } catch (IllegalArgumentException e) {\n        }\n    }\n\n    @Test\n    public void consegueDizerSePodeFormatar() {\n\n        // Gerados automaticamente para testes\n        assertThat(Formatador.CPF.podeSerFormatado(\"545.586.262-66\"), is(false));\n        assertThat(Formatador.CPF.podeSerFormatado(\"54558626266\"), is(true));\n        assertThat(Formatador.CPF.podeSerFormatado(\"020\"), is(false));\n        assertThat(Formatador.CPF.podeSerFormatado(\"\"), is(false));\n        assertThat(Formatador.CPF.podeSerFormatado(null), is(false));\n    }\n\n    private void assertThrowsFormat(String valor) {\n        try {\n            Formatador.CPF.formata(valor);\n            fail(\"Should have thrown!!!\");\n        } catch (IllegalArgumentException e) {\n        }\n    }\n\n    private void assertThrowsDesformat(String valor) {\n        try {\n            Formatador.CPF.desformata(valor);\n            fail(\"Should have thrown!!!\");\n        } catch (IllegalArgumentException e) {\n        }\n    }\n}\n"
  },
  {
    "path": "sample/src/test/java/br/com/concrete/canarinho/test/TesteFormatadorCPFCNPJ.java",
    "content": "package br.com.concrete.canarinho.test;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\n\nimport br.com.concrete.canarinho.formatador.Formatador;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.junit.Assert.fail;\n\n@RunWith(AndroidJUnit4.class)\npublic class TesteFormatadorCPFCNPJ {\n\n    @Test\n    public void consegueFormatar() {\n\n        // Gerados automaticamente para testes\n        assertThat(Formatador.CPF_CNPJ.formata(\"50.713.534/0001-33\"), is(\"50.713.534/0001-33\"));\n        assertThat(Formatador.CPF_CNPJ.formata(\"50713534000133\"), is(\"50.713.534/0001-33\"));\n\n        assertThat(Formatador.CPF_CNPJ.formata(\"72.606.598/0001-78\"), is(\"72.606.598/0001-78\"));\n        assertThat(Formatador.CPF_CNPJ.formata(\"72606598000178\"), is(\"72.606.598/0001-78\"));\n\n        assertThat(Formatador.CPF_CNPJ.formata(\"23.106.535/0001-47\"), is(\"23.106.535/0001-47\"));\n        assertThat(Formatador.CPF_CNPJ.formata(\"23106535000147\"), is(\"23.106.535/0001-47\"));\n\n        assertThrowsFormat(\"\");\n        assertThrowsFormat(\"123123\");\n        assertThrowsFormat(\"23.106.535/0001  -   47\");\n        assertThrowsFormat(\"       23.106.535/0001-47      \");\n\n        // Gerados automaticamente para testes\n        assertThat(Formatador.CPF_CNPJ.formata(\"545.586.262-66\"), is(\"545.586.262-66\"));\n        assertThat(Formatador.CPF_CNPJ.formata(\"54558626266\"), is(\"545.586.262-66\"));\n\n        assertThat(Formatador.CPF_CNPJ.formata(\"020.724.833-87\"), is(\"020.724.833-87\"));\n        assertThat(Formatador.CPF_CNPJ.formata(\"02072483387\"), is(\"020.724.833-87\"));\n\n        assertThat(Formatador.CPF_CNPJ.formata(\"188.527.045-31\"), is(\"188.527.045-31\"));\n        assertThat(Formatador.CPF_CNPJ.formata(\"18852704531\"), is(\"188.527.045-31\"));\n\n        assertThat(Formatador.CPF_CNPJ.formata(\"047.486.777-32\"), is(\"047.486.777-32\"));\n        assertThat(Formatador.CPF_CNPJ.formata(\"04748677732\"), is(\"047.486.777-32\"));\n\n        assertThrowsFormat(\"\");\n        assertThrowsFormat(\"123123\");\n        assertThrowsFormat(\"123.123.123    -12\");\n        assertThrowsFormat(\"       047.486.777-32      \");\n    }\n\n    @Test\n    public void consegueDesformatar() {\n\n        // Gerados automaticamente para testes\n        assertThat(Formatador.CPF_CNPJ.desformata(\"50.713.534/0001-33\"), is(\"50713534000133\"));\n        assertThat(Formatador.CPF_CNPJ.desformata(\"50713534000133\"), is(\"50713534000133\"));\n\n        assertThat(Formatador.CPF_CNPJ.desformata(\"72.606.598/0001-78\"), is(\"72606598000178\"));\n        assertThat(Formatador.CPF_CNPJ.desformata(\"72606598000178\"), is(\"72606598000178\"));\n\n        assertThat(Formatador.CPF_CNPJ.desformata(\"23.106.535/0001-47\"), is(\"23106535000147\"));\n        assertThat(Formatador.CPF_CNPJ.desformata(\"23106535000147\"), is(\"23106535000147\"));\n\n        assertThrowsDesformat(\"\");\n        assertThrowsDesformat(\"123123\");\n        assertThrowsDesformat(\"123.123.123    -12\");\n        assertThrowsDesformat(\"       047.486.777-32      \");\n\n        // Gerados automaticamente para testes\n        assertThat(Formatador.CPF_CNPJ.desformata(\"545.586.262-66\"), is(\"54558626266\"));\n        assertThat(Formatador.CPF_CNPJ.desformata(\"54558626266\"), is(\"54558626266\"));\n\n        assertThat(Formatador.CPF_CNPJ.desformata(\"020.724.833-87\"), is(\"02072483387\"));\n        assertThat(Formatador.CPF_CNPJ.desformata(\"02072483387\"), is(\"02072483387\"));\n\n        assertThat(Formatador.CPF_CNPJ.desformata(\"188.527.045-31\"), is(\"18852704531\"));\n        assertThat(Formatador.CPF_CNPJ.desformata(\"18852704531\"), is(\"18852704531\"));\n\n        assertThat(Formatador.CPF_CNPJ.desformata(\"047.486.777-32\"), is(\"04748677732\"));\n        assertThat(Formatador.CPF_CNPJ.desformata(\"04748677732\"), is(\"04748677732\"));\n\n        assertThrowsDesformat(\"\");\n        assertThrowsDesformat(\"123123\");\n        assertThrowsDesformat(\"123.123.123    -12\");\n        assertThrowsDesformat(\"       047.486.777-32      \");\n    }\n\n    @Test\n    public void consegueDizerSeEstaFormatado() {\n\n        // Gerados automaticamente para testes\n        assertThat(Formatador.CPF_CNPJ.estaFormatado(\"72.606.598/0001-78\"), is(true));\n        assertThat(Formatador.CPF_CNPJ.estaFormatado(\"72606598000178\"), is(false));\n        assertThat(Formatador.CPF_CNPJ.estaFormatado(\"23.106.535/0001-47\"), is(true));\n        assertThat(Formatador.CPF_CNPJ.estaFormatado(\"23106535000147\"), is(false));\n\n        assertThat(Formatador.CPF_CNPJ.estaFormatado(\"047486\"), is(false));\n        assertThat(Formatador.CPF_CNPJ.estaFormatado(\"\"), is(false));\n        try {\n            Formatador.CPF_CNPJ.estaFormatado(null);\n            fail(\"Should have thrown!!!\");\n        } catch (IllegalArgumentException e) {\n        }\n\n        // Gerados automaticamente para testes\n        assertThat(Formatador.CPF_CNPJ.estaFormatado(\"545.586.262-66\"), is(true));\n        assertThat(Formatador.CPF_CNPJ.estaFormatado(\"54558626266\"), is(false));\n        assertThat(Formatador.CPF_CNPJ.estaFormatado(\"020.724.833-87\"), is(true));\n        assertThat(Formatador.CPF_CNPJ.estaFormatado(\"02072483387\"), is(false));\n        assertThat(Formatador.CPF_CNPJ.estaFormatado(\"188.527.045-31\"), is(true));\n        assertThat(Formatador.CPF_CNPJ.estaFormatado(\"18852704531\"), is(false));\n        assertThat(Formatador.CPF_CNPJ.estaFormatado(\"047.486.777-32\"), is(true));\n        assertThat(Formatador.CPF_CNPJ.estaFormatado(\"04748677732\"), is(false));\n\n        assertThat(Formatador.CPF_CNPJ.estaFormatado(\"047486\"), is(false));\n        assertThat(Formatador.CPF_CNPJ.estaFormatado(\"\"), is(false));\n        try {\n            Formatador.CPF_CNPJ.estaFormatado(null);\n            fail(\"Should have thrown!!!\");\n        } catch (IllegalArgumentException e) {\n        }\n    }\n\n    @Test\n    public void consegueDizerSePodeFormatar() {\n\n        // Gerados automaticamente para testes\n        assertThat(Formatador.CPF_CNPJ.podeSerFormatado(\"23.106.535/0001-47\"), is(false));\n        assertThat(Formatador.CPF_CNPJ.podeSerFormatado(\"23106535000147\"), is(true));\n        assertThat(Formatador.CPF_CNPJ.podeSerFormatado(\"020\"), is(false));\n        assertThat(Formatador.CPF_CNPJ.podeSerFormatado(\"\"), is(false));\n        assertThat(Formatador.CPF_CNPJ.podeSerFormatado(null), is(false));\n\n        // Gerados automaticamente para testes\n        assertThat(Formatador.CPF_CNPJ.podeSerFormatado(\"545.586.262-66\"), is(false));\n        assertThat(Formatador.CPF_CNPJ.podeSerFormatado(\"54558626266\"), is(true));\n        assertThat(Formatador.CPF_CNPJ.podeSerFormatado(\"020\"), is(false));\n        assertThat(Formatador.CPF_CNPJ.podeSerFormatado(\"\"), is(false));\n        assertThat(Formatador.CPF_CNPJ.podeSerFormatado(null), is(false));\n    }\n\n    private void assertThrowsFormat(String valor) {\n        try {\n            Formatador.CPF_CNPJ.formata(valor);\n            fail(\"Deveria ter jogado exceção!!!\");\n        } catch (IllegalArgumentException e) {\n        }\n    }\n\n    private void assertThrowsDesformat(String valor) {\n        try {\n            Formatador.CPF_CNPJ.desformata(valor);\n            fail(\"Deveria ter jogado exceção!!!\");\n        } catch (IllegalArgumentException e) {\n        }\n    }\n}\n"
  },
  {
    "path": "sample/src/test/java/br/com/concrete/canarinho/test/TesteFormatadorLinhaDigitavel.java",
    "content": "package br.com.concrete.canarinho.test;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\n\nimport br.com.concrete.canarinho.formatador.Formatador;\n\nimport static org.hamcrest.CoreMatchers.equalTo;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.CoreMatchers.is;\n\n@RunWith(AndroidJUnit4.class)\npublic class TesteFormatadorLinhaDigitavel {\n\n    @Test\n    public void consegueFormatarEDesformatar() {\n\n        assertThat(Formatador.LINHA_DIGITAVEL.desformata(\"812345678901812345678901812345678901812345678901\"),\n                is(\"81234567890812345678908123456789081234567890\"));\n\n        final String original = \"23790123016000000005325000456704964680000013580\";\n        final String boleto = Formatador.LINHA_DIGITAVEL.desformata(original);\n        final String linhaDigitavel = Formatador.LINHA_DIGITAVEL.formata(boleto);\n\n        assertThat(linhaDigitavel, equalTo(original));\n    }\n\n    @Test\n    public void consegueDizerSeEstaFormatado() {\n        assertThat(Formatador.LINHA_DIGITAVEL.estaFormatado(\"81234567890812345678908123456789081234567890\"), is(false));\n        assertThat(Formatador.LINHA_DIGITAVEL.estaFormatado(\"812345678901 812345678901 812345678901 812345678901\"), is(true));\n        assertThat(Formatador.LINHA_DIGITAVEL.estaFormatado(\"23790.12301 60000.000053 25000.456704 9 64680000013580\"), is(true));\n    }\n\n    @Test\n    public void consegueDizerSePodeFormatar() {\n        assertThat(Formatador.LINHA_DIGITAVEL.podeSerFormatado(\"8123456789081234567890812345678908123456\"), is(false));\n        assertThat(Formatador.LINHA_DIGITAVEL.podeSerFormatado(\"23790.1230 60000.00005 25000.45670 9 64680000013580\"), is(true));\n    }\n}\n"
  },
  {
    "path": "sample/src/test/java/br/com/concrete/canarinho/test/TesteFormatadorTelefone.java",
    "content": "package br.com.concrete.canarinho.test;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\n\nimport br.com.concrete.canarinho.formatador.Formatador;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.Assert.fail;\n\n@RunWith(AndroidJUnit4.class)\npublic class TesteFormatadorTelefone {\n\n    @Test\n    public void consegueFormatar() {\n        assertThat(Formatador.TELEFONE.formata(\"1112345678\"),\n                is(\"(11) 1234-5678\"));\n\n        assertThat(Formatador.TELEFONE.formata(\"11123456789\"),\n                is(\"(11) 12345-6789\"));\n\n        try {\n            Formatador.TELEFONE.formata(null);\n            fail(\"Deveria ter jogado exceção!!!\");\n        } catch (IllegalArgumentException e) {\n        }\n    }\n\n    @Test\n    public void consegueDesformatar() {\n        assertThat(Formatador.TELEFONE.desformata(\"(11) 1234-5678\"),\n                is(\"1112345678\"));\n\n        assertThat(Formatador.TELEFONE.desformata(\"(11) 12345-6789\"),\n                is(\"11123456789\"));\n\n        try {\n            Formatador.TELEFONE.desformata(null);\n            fail(\"Deveria ter jogado exceção!!!\");\n        } catch (IllegalArgumentException e) {\n        }\n    }\n\n    @Test\n    public void consegueDizerSeEstaFormatado() {\n        assertThat(Formatador.TELEFONE.estaFormatado(\"(11) 1234-5678\"), is(true));\n        assertThat(Formatador.TELEFONE.estaFormatado(\"(11) 12345-6789\"), is(true));\n\n        assertThat(Formatador.TELEFONE.estaFormatado(\"(11) 1234-56789\"), is(false));\n        assertThat(Formatador.TELEFONE.estaFormatado(\"1112345678\"), is(false));\n        assertThat(Formatador.TELEFONE.estaFormatado(\"11123456789\"), is(false));\n\n        try {\n            Formatador.TELEFONE.estaFormatado(null);\n            fail(\"Deveria ter jogado exceção!!!\");\n        } catch (IllegalArgumentException e) {\n        }\n    }\n\n    @Test\n    public void consegueDizerSePodeFormatar() {\n        assertThat(Formatador.TELEFONE.podeSerFormatado(\"11\"), is(false));\n        assertThat(Formatador.TELEFONE.podeSerFormatado(\"111234567890\"), is(false));\n\n        assertThat(Formatador.TELEFONE.podeSerFormatado(\"1112345678\"), is(true));\n        assertThat(Formatador.TELEFONE.podeSerFormatado(\"11123456789\"), is(true));\n\n        try {\n            Formatador.TELEFONE.podeSerFormatado(null);\n            fail(\"Deveria ter jogado exceção!!!\");\n        } catch (IllegalArgumentException e) {\n        }\n    }\n\n}\n"
  },
  {
    "path": "sample/src/test/java/br/com/concrete/canarinho/test/TesteFormatadorValor.java",
    "content": "package br.com.concrete.canarinho.test;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\n\nimport br.com.concrete.canarinho.formatador.Formatador;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.junit.Assert.fail;\n\n@RunWith(AndroidJUnit4.class)\npublic class TesteFormatadorValor {\n\n    @Test\n    public void consegueFormatar() {\n\n        assertThat(Formatador.VALOR.formata(\"1.00\"), is(\"1,00\"));\n        assertThat(Formatador.VALOR.formata(\"1.0\"), is(\"1,00\"));\n        assertThat(Formatador.VALOR.formata(\"1\"), is(\"1,00\"));\n        assertThat(Formatador.VALOR.formata(\"1000\"), is(\"1.000,00\"));\n        assertThat(Formatador.VALOR.formata(\"1.23\"), is(\"1,23\"));\n        assertThat(Formatador.VALOR.formata(\"1.233\"), is(\"1,23\"));\n        assertThat(Formatador.VALOR.formata(\"1.01\"), is(\"1,01\"));\n        assertThat(Formatador.VALOR.formata(\"1.1\"), is(\"1,10\"));\n        assertThat(Formatador.VALOR.formata(\"1234.56\"), is(\"1.234,56\"));\n        assertThat(Formatador.VALOR.formata(\"1234.5\"), is(\"1.234,50\"));\n    }\n\n    @Test\n    public void consegueFormatarComSimbolo() {\n\n        assertThat(Formatador.VALOR_COM_SIMBOLO.formata(\"1.00\"), is(\"R$ 1,00\"));\n        assertThat(Formatador.VALOR_COM_SIMBOLO.formata(\"1.0\"), is(\"R$ 1,00\"));\n        assertThat(Formatador.VALOR_COM_SIMBOLO.formata(\"1\"), is(\"R$ 1,00\"));\n        assertThat(Formatador.VALOR_COM_SIMBOLO.formata(\"1000\"), is(\"R$ 1.000,00\"));\n        assertThat(Formatador.VALOR_COM_SIMBOLO.formata(\"1.23\"), is(\"R$ 1,23\"));\n        assertThat(Formatador.VALOR_COM_SIMBOLO.formata(\"1.233\"), is(\"R$ 1,23\"));\n        assertThat(Formatador.VALOR_COM_SIMBOLO.formata(\"1.01\"), is(\"R$ 1,01\"));\n        assertThat(Formatador.VALOR_COM_SIMBOLO.formata(\"1.1\"), is(\"R$ 1,10\"));\n        assertThat(Formatador.VALOR_COM_SIMBOLO.formata(\"1234.56\"), is(\"R$ 1.234,56\"));\n        assertThat(Formatador.VALOR_COM_SIMBOLO.formata(\"1234.5\"), is(\"R$ 1.234,50\"));\n    }\n\n    @Test\n    public void consegueDesformatar() {\n        assertThat(Formatador.VALOR.desformata(\"1,00\"), is(\"1.00\"));\n        assertThat(Formatador.VALOR.desformata(\"1.234,10\"), is(\"1234.10\"));\n        assertThat(Formatador.VALOR.desformata(\"0,10\"), is(\"0.10\"));\n    }\n\n    @Test\n    public void consegueDesformatarComSimbolo() {\n        assertThat(Formatador.VALOR_COM_SIMBOLO.desformata(\"R$ 1,00\"), is(\"1.00\"));\n        assertThat(Formatador.VALOR_COM_SIMBOLO.desformata(\"R$ 1.234,10\"), is(\"1234.10\"));\n        assertThat(Formatador.VALOR_COM_SIMBOLO.desformata(\"R$ 0,10\"), is(\"0.10\"));\n    }\n\n    @Test\n    public void consegueDizerSeEstaFormatado() {\n\n        assertThat(Formatador.VALOR.estaFormatado(\"1,00\"), is(true));\n        assertThat(Formatador.VALOR.estaFormatado(\"1.234,10\"), is(true));\n        assertThat(Formatador.VALOR.estaFormatado(\"1.34,10\"), is(false));\n\n        try {\n            Formatador.VALOR.estaFormatado(null);\n            fail(\"Should have thrown!!!\");\n        } catch (IllegalArgumentException e) {\n        }\n    }\n\n    @Test\n    public void consegueDizerSePodeFormatar() {\n\n        assertThat(Formatador.VALOR.podeSerFormatado(\"1.00\"), is(true));\n        assertThat(Formatador.VALOR.podeSerFormatado(\"10\"), is(true));\n        assertThat(Formatador.VALOR.podeSerFormatado(\"10.1\"), is(true));\n        assertThat(Formatador.VALOR.podeSerFormatado(\"10,10\"), is(false));\n\n        try {\n            Formatador.VALOR.podeSerFormatado(null);\n            fail(\"Should have thrown!!!\");\n        } catch (IllegalArgumentException e) {\n        }\n    }\n}\n"
  },
  {
    "path": "sample/src/test/java/br/com/concrete/canarinho/test/TesteValidadores.java",
    "content": "package br.com.concrete.canarinho.test;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\n\nimport br.com.concrete.canarinho.validator.Validador;\n\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.hamcrest.CoreMatchers.is;\n\n@RunWith(AndroidJUnit4.class)\npublic class TesteValidadores {\n\n    @Test\n    public void consegueValidarCPF() {\n\n        // Gerados automaticamente para testes\n        assertThat(Validador.CPF.ehValido(\"545.586.262-66\"), is(true));\n        assertThat(Validador.CPF.ehValido(\"655.274.921-02\"), is(true));\n        assertThat(Validador.CPF.ehValido(\"020.724.833-87\"), is(true));\n        assertThat(Validador.CPF.ehValido(\"188.527.045-31\"), is(true));\n        assertThat(Validador.CPF.ehValido(\"047.486.777-32\"), is(true));\n\n        assertThat(Validador.CPF.ehValido(\"54558626266\"), is(true));\n        assertThat(Validador.CPF.ehValido(\"65527492102\"), is(true));\n        assertThat(Validador.CPF.ehValido(\"02072483387\"), is(true));\n        assertThat(Validador.CPF.ehValido(\"18852704531\"), is(true));\n        assertThat(Validador.CPF.ehValido(\"04748677732\"), is(true));\n\n        assertThat(Validador.CPF.ehValido(\"545.111.262-66\"), is(false));\n        assertThat(Validador.CPF.ehValido(\"655.111.921-02\"), is(false));\n        assertThat(Validador.CPF.ehValido(\"020.111.833-87\"), is(false));\n        assertThat(Validador.CPF.ehValido(\"188.111.045-31\"), is(false));\n        assertThat(Validador.CPF.ehValido(\"047.111.777-32\"), is(false));\n\n        assertThat(Validador.CPF.ehValido(\"54111626266\"), is(false));\n        assertThat(Validador.CPF.ehValido(\"65111492102\"), is(false));\n        assertThat(Validador.CPF.ehValido(\"02111483387\"), is(false));\n        assertThat(Validador.CPF.ehValido(\"18111704531\"), is(false));\n        assertThat(Validador.CPF.ehValido(\"04111677732\"), is(false));\n    }\n\n    @Test\n    public void consegueValidarCNPJ() {\n\n        // Gerados automaticamente para testes\n        assertThat(Validador.CNPJ.ehValido(\"50.713.534/0001-33\"), is(true));\n        assertThat(Validador.CNPJ.ehValido(\"77.135.038/0001-04\"), is(true));\n        assertThat(Validador.CNPJ.ehValido(\"58.613.246/0001-19\"), is(true));\n        assertThat(Validador.CNPJ.ehValido(\"50713534000133\"), is(true));\n        assertThat(Validador.CNPJ.ehValido(\"77135038000104\"), is(true));\n\n        // Gerados automaticamente para testes\n        assertThat(Validador.CNPJ.ehValido(\"50.713.111/0001-33\"), is(false));\n        assertThat(Validador.CNPJ.ehValido(\"77.135.111/0001-04\"), is(false));\n        assertThat(Validador.CNPJ.ehValido(\"50713531110133\"), is(false));\n        assertThat(Validador.CNPJ.ehValido(\"77135031110104\"), is(false));\n    }\n\n    @Test\n    public void consegueValidarBoletoNormal() {\n\n        // boleto bradesco\n        assertThat(Validador.BOLETO\n                .ehValido(\"23790.12301 60000.000053 25000.456704 9 64680000013580\"), is(true));\n\n        // boleto bradesco\n        assertThat(Validador.BOLETO\n                .ehValido(\"23790123016000000005325000456704964680000013580\"), is(true));\n\n        // boleto banco do brasil\n        assertThat(Validador.BOLETO\n                .ehValido(\"00199.38414 90480.002550 84666.970219 4 64290000007726\"), is(true));\n\n\n        // Boleto itau desformatado\n        assertThat(Validador.BOLETO\n                .ehValido(\"34191750090000159091820521070001664890002370000\"), is(true));\n\n        // Boleto itau\n        assertThat(Validador.BOLETO\n                .ehValido(\"34191.75009 00001.590918 20521.070001 6 64890002370000\"), is(true));\n\n        // boleto Banestes\n        assertThat(Validador.BOLETO\n                .ehValido(\"02190.00015 05000.000017 23452.021696 2 25230000034000\"), is(true));\n\n        // boleto Caixa\n        assertThat(Validador.BOLETO\n                .ehValido(\"10491.44338 55119.000002 00000.000141 3 25230000093423\"), is(true));\n\n        // boleto Sudameris\n        assertThat(Validador.BOLETO\n                .ehValido(\"34790.12001 12345.695006 10502.000002 5 25230000034000\"), is(true));\n    }\n\n    @Test\n    public void consegueValidarBoletoTributoSemSerTaxa() {\n\n        assertThat(Validador.BOLETO\n                .ehValido(\"848600000015 523301622010 506101307129 620012111220\"), is(true));\n    }\n\n    @Test\n    public void consegueValidarBoletoTributoDeTaxa() {\n\n        assertThat(Validador.BOLETO\n                .ehValido(\"836600000019 078800481000 998854924516 001265611135\"), is(true));\n\n        assertThat(Validador.BOLETO\n                .ehValido(\"826100000007 265400971429 620232390612 725103150621\"), is(true));\n    }\n\n    @Test\n    public void consegueValidarTelefone() {\n        assertThat(Validador.TELEFONE.ehValido(\"1112345678\"), is(true));\n        assertThat(Validador.TELEFONE.ehValido(\"11123456789\"), is(true));\n        assertThat(Validador.TELEFONE.ehValido(\"(11) 12345-6789\"), is(true));\n        assertThat(Validador.TELEFONE.ehValido(\"111234567890\"), is(false));\n        assertThat(Validador.TELEFONE.ehValido(\"1112345\"), is(false));\n    }\n\n    @Test\n    public void consegueValidarCEP() {\n        assertThat(Validador.CEP.ehValido(\"12345678\"), is(true));\n        assertThat(Validador.CEP.ehValido(\"12345-678\"), is(true));\n        assertThat(Validador.CEP.ehValido(\"12345-67\"), is(false));\n        assertThat(Validador.CEP.ehValido(\"1234-678\"), is(false));\n        assertThat(Validador.CEP.ehValido(\"\"), is(false));\n    }\n}\n"
  },
  {
    "path": "sample/src/test/java/br/com/concrete/canarinho/test/watcher/BoletoTextWatcherTest.java",
    "content": "package br.com.concrete.canarinho.test.watcher;\n\nimport android.widget.EditText;\n\nimport com.google.android.material.textfield.TextInputLayout;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\n\nimport androidx.test.core.app.ActivityScenario;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport br.com.concrete.canarinho.sample.ui.activity.MainActivity;\nimport br.com.concrete.canarinho.sample.ui.model.Watchers;\nimport br.com.concrete.canarinho.watcher.BoletoBancarioTextWatcher;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.junit.Assert.assertThat;\n\n@RunWith(AndroidJUnit4.class)\npublic class BoletoTextWatcherTest {\n\n    private BoletoBancarioTextWatcher watcher;\n    private EditText editText;\n\n    @Before\n    public void setUp() {\n        final ActivityScenario<MainActivity> scenario = ActivityScenario.launch(MainActivity.class);\n        scenario.onActivity(new ActivityScenario.ActivityAction<MainActivity>() {\n            @Override\n            public void perform(MainActivity activity) {\n                final TextInputLayout textInputLayout = new TextInputLayout(activity);\n                textInputLayout.addView(editText = new EditText(activity));\n\n                final Watchers.SampleEventoDeValidacao sampleEventoDeValidacao =\n                        new Watchers.SampleEventoDeValidacao(textInputLayout);\n\n                editText.addTextChangedListener(watcher = new BoletoBancarioTextWatcher(sampleEventoDeValidacao));\n\n                activity.setContentView(textInputLayout);\n            }\n        });\n    }\n\n    @Test\n    public void typing_canValidateEmptyState() {\n        editText.append(\"\");\n        assertThat(editText.getText().toString(), is(\"\"));\n        assertThat(watcher.getResultadoParcial().isParcialmenteValido(), is(true));\n    }\n\n    @Test\n    public void typing_canValidateProperCharacters() {\n        editText.append(\"1bas2nas3lamsd4\");\n        assertThat(editText.getText().toString(), is(\"1234\"));\n        assertThat(watcher.getResultadoParcial().isParcialmenteValido(), is(true));\n    }\n\n    @Test\n    public void deleting_canEmptyEditText() {\n        editText.append(\"1234\");\n        assertThat(editText.getText().toString(), is(\"1234\"));\n        assertThat(watcher.getResultadoParcial().isParcialmenteValido(), is(true));\n\n        editText.getEditableText().clear();\n        assertThat(editText.getText().toString(), is(\"\"));\n    }\n\n    // Teste de regressão\n    @Test\n    public void deleting_afterEmptyingEditTextItKeepsValidatingInput() {\n        editText.append(\"1234\");\n        assertThat(editText.getText().toString(), is(\"1234\"));\n        assertThat(watcher.getResultadoParcial().isParcialmenteValido(), is(true));\n\n        editText.getEditableText().clear();\n        assertThat(editText.getText().toString(), is(\"\"));\n\n        // menos caracteres que o tamanho inicial para saber qual máscara aplicar\n        editText.append(\"$$\");\n        assertThat(editText.getText().toString(), is(\"\"));\n    }\n}\n"
  },
  {
    "path": "sample/src/test/java/br/com/concrete/canarinho/test/watcher/ValorMonetarioWatcherTest.java",
    "content": "package br.com.concrete.canarinho.test.watcher;\n\nimport android.app.Activity;\nimport android.widget.EditText;\n\nimport com.google.android.material.textfield.TextInputLayout;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport androidx.test.ext.junit.runners.AndroidJUnit4;\nimport org.robolectric.android.controller.ActivityController;\n\nimport br.com.concrete.canarinho.watcher.ValorMonetarioWatcher;\n\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.junit.Assert.assertThat;\nimport static org.robolectric.Robolectric.buildActivity;\n\n@RunWith(AndroidJUnit4.class)\npublic class ValorMonetarioWatcherTest {\n\n    private EditText editText;\n\n    @Before\n    public void setUp() {\n        final ActivityController<Activity> activityController = buildActivity(Activity.class);\n        final Activity activity = activityController.create().get();\n        activityController.start().resume().visible();\n\n        TextInputLayout textInputLayout;\n        activity.setContentView(textInputLayout = new TextInputLayout(activity));\n        textInputLayout.addView(editText = new EditText(activity));\n        editText.setText(\"0,00\");\n    }\n\n    @Test\n    public void watcher_formataOk() {\n        editText.addTextChangedListener(new ValorMonetarioWatcher());\n        editText.append(\"1234567890\");\n        assertThat(editText.getText().toString(), is(\"12.345.678,90\"));\n    }\n\n    @Test\n    public void watcher_formataOkComSimbolo() {\n        editText.addTextChangedListener(new ValorMonetarioWatcher.Builder()\n                .comSimboloReal()\n                .comMantemZerosAoLimpar()\n                .build());\n        editText.append(\"1234567890\");\n        assertThat(editText.getText().toString(), is(\"R$ 12.345.678,90\"));\n    }\n\n    @Test\n    public void watcher_canEmptyTextAndKeepZeroes() {\n        editText.addTextChangedListener(new ValorMonetarioWatcher.Builder()\n                .comSimboloReal()\n                .comMantemZerosAoLimpar()\n                .build());\n        editText.append(\"1234567890\");\n        assertThat(editText.getText().toString(), is(\"R$ 12.345.678,90\"));\n\n        editText.getText().clear();\n        assertThat(editText.getText().toString(), is(\"R$ 0,00\"));\n\n        editText.getText().append('1');\n        assertThat(editText.getText().toString(), is(\"R$ 0,01\"));\n    }\n\n    @Test\n    public void watcher_canEmptyTextWithoutZeroes() {\n        editText.addTextChangedListener(new ValorMonetarioWatcher.Builder()\n                .comSimboloReal()\n                .build());\n        editText.append(\"1234567890\");\n        assertThat(editText.getText().toString(), is(\"R$ 12.345.678,90\"));\n\n        editText.getText().clear();\n        assertThat(editText.getText().toString(), is(\"\"));\n\n        editText.getText().append('1');\n        assertThat(editText.getText().toString(), is(\"R$ 0,01\"));\n    }\n}\n"
  },
  {
    "path": "settings.gradle",
    "content": "include ':sample', ':canarinho'\n"
  },
  {
    "path": "tools/linters/checkstyle/checkstyle.xml",
    "content": "<?xml version=\"1.0\"?><!DOCTYPE module PUBLIC\n    \"-//Puppy Crawl//DTD Check Configuration 1.3//EN\"\n    \"http://checkstyle.sourceforge.net/dtds/configuration_1_3.dtd\">\n\n<!--\n    Checkstyle configuration that checks the Google coding conventions from Google Java Style\n    that can be found at https://google.github.io/styleguide/javaguide.html.\n\n    Checkstyle is very configurable. Be sure to read the documentation at\n    http://checkstyle.sf.net (or in your downloaded distribution).\n\n    To completely disable a check, just comment it out or delete it from the file.\n\n    Authors: Max Vetrenko, Ruslan Diachenko, Roman Ivanov.\n -->\n\n<module name=\"Checker\">\n    <property name=\"charset\" value=\"UTF-8\" />\n\n    <property name=\"severity\" value=\"warning\" />\n\n    <property name=\"fileExtensions\" value=\"java, properties, xml\" />\n    <!-- Checks for whitespace                               -->\n    <!-- See http://checkstyle.sf.net/config_whitespace.html -->\n    <module name=\"FileTabCharacter\">\n        <property name=\"eachLine\" value=\"true\" />\n    </module>\n\n    <module name=\"TreeWalker\">\n        <module name=\"OuterTypeFilename\" />\n        <module name=\"IllegalTokenText\">\n            <property name=\"tokens\" value=\"STRING_LITERAL, CHAR_LITERAL\" />\n            <property name=\"format\"\n                value=\"\\\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\\\(0(10|11|12|14|15|42|47)|134)\" />\n            <property name=\"message\"\n                value=\"Consider using special escape sequence instead of octal value or Unicode escaped value.\" />\n        </module>\n        <module name=\"AvoidEscapedUnicodeCharacters\">\n            <property name=\"allowEscapesForControlCharacters\" value=\"true\" />\n            <property name=\"allowByTailComment\" value=\"true\" />\n            <property name=\"allowNonPrintableEscapes\" value=\"true\" />\n        </module>\n        <module name=\"LineLength\">\n            <property name=\"max\" value=\"120\" />\n            <property name=\"ignorePattern\"\n                value=\"^package.*|^import.*|a href|href|http://|https://|ftp://\" />\n        </module>\n        <module name=\"AvoidStarImport\" />\n        <module name=\"OneTopLevelClass\" />\n        <module name=\"NoLineWrap\" />\n        <module name=\"EmptyBlock\">\n            <property name=\"option\" value=\"TEXT\" />\n            <property name=\"tokens\"\n                value=\"LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH\" />\n        </module>\n        <module name=\"NeedBraces\" />\n        <module name=\"LeftCurly\" />\n        <module name=\"RightCurly\">\n            <property name=\"id\" value=\"RightCurlySame\" />\n            <property name=\"tokens\"\n                value=\"LITERAL_TRY, LITERAL_CATCH, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_DO\" />\n        </module>\n        <module name=\"RightCurly\">\n            <property name=\"id\" value=\"RightCurlyAlone\" />\n            <property name=\"option\" value=\"alone\" />\n            <property name=\"tokens\"\n                value=\"CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, STATIC_INIT, INSTANCE_INIT\" />\n        </module>\n        <module name=\"WhitespaceAround\">\n            <property name=\"allowEmptyConstructors\" value=\"true\" />\n            <property name=\"allowEmptyMethods\" value=\"true\" />\n            <property name=\"allowEmptyTypes\" value=\"true\" />\n            <property name=\"allowEmptyLoops\" value=\"true\" />\n            <message key=\"ws.notFollowed\"\n                value=\"WhitespaceAround: ''{0}'' is not followed by whitespace. Empty blocks may only be represented as '{}' when not part of a multi-block statement (4.1.3)\" />\n            <message key=\"ws.notPreceded\"\n                value=\"WhitespaceAround: ''{0}'' is not preceded with whitespace.\" />\n        </module>\n        <module name=\"OneStatementPerLine\" />\n        <module name=\"MultipleVariableDeclarations\" />\n        <module name=\"ArrayTypeStyle\" />\n        <module name=\"MissingSwitchDefault\" />\n        <module name=\"FallThrough\" />\n        <module name=\"UpperEll\" />\n        <module name=\"ModifierOrder\" />\n        <module name=\"EmptyLineSeparator\">\n            <property name=\"allowNoEmptyLineBetweenFields\" value=\"true\" />\n        </module>\n        <module name=\"SeparatorWrap\">\n            <property name=\"id\" value=\"SeparatorWrapDot\" />\n            <property name=\"tokens\" value=\"DOT\" />\n            <property name=\"option\" value=\"nl\" />\n        </module>\n        <module name=\"SeparatorWrap\">\n            <property name=\"id\" value=\"SeparatorWrapComma\" />\n            <property name=\"tokens\" value=\"COMMA\" />\n            <property name=\"option\" value=\"EOL\" />\n        </module>\n        <module name=\"SeparatorWrap\">\n            <!-- ELLIPSIS is EOL until https://github.com/google/styleguide/issues/258 -->\n            <property name=\"id\" value=\"SeparatorWrapEllipsis\" />\n            <property name=\"tokens\" value=\"ELLIPSIS\" />\n            <property name=\"option\" value=\"EOL\" />\n        </module>\n        <module name=\"SeparatorWrap\">\n            <!-- ARRAY_DECLARATOR is EOL until https://github.com/google/styleguide/issues/259 -->\n            <property name=\"id\" value=\"SeparatorWrapArrayDeclarator\" />\n            <property name=\"tokens\" value=\"ARRAY_DECLARATOR\" />\n            <property name=\"option\" value=\"EOL\" />\n        </module>\n        <module name=\"SeparatorWrap\">\n            <property name=\"id\" value=\"SeparatorWrapMethodRef\" />\n            <property name=\"tokens\" value=\"METHOD_REF\" />\n            <property name=\"option\" value=\"nl\" />\n        </module>\n        <module name=\"PackageName\">\n            <property name=\"format\" value=\"^[a-z]+(\\.[a-z][a-z0-9]*)*$\" />\n            <message key=\"name.invalidPattern\"\n                value=\"Package name ''{0}'' must match pattern ''{1}''.\" />\n        </module>\n        <module name=\"TypeName\">\n            <message key=\"name.invalidPattern\"\n                value=\"Type name ''{0}'' must match pattern ''{1}''.\" />\n        </module>\n        <module name=\"MemberName\">\n            <property name=\"format\" value=\"^[a-z][a-z0-9][a-zA-Z0-9]*$\" />\n            <message key=\"name.invalidPattern\"\n                value=\"Member name ''{0}'' must match pattern ''{1}''.\" />\n        </module>\n        <module name=\"ParameterName\">\n            <property name=\"format\" value=\"^[a-z]([a-z0-9][a-zA-Z0-9]*)?$\" />\n            <message key=\"name.invalidPattern\"\n                value=\"Parameter name ''{0}'' must match pattern ''{1}''.\" />\n        </module>\n        <module name=\"CatchParameterName\">\n            <property name=\"format\" value=\"^[a-z]([a-z0-9][a-zA-Z0-9]*)?$\" />\n            <message key=\"name.invalidPattern\"\n                value=\"Catch parameter name ''{0}'' must match pattern ''{1}''.\" />\n        </module>\n        <module name=\"LocalVariableName\">\n            <property name=\"tokens\" value=\"VARIABLE_DEF\" />\n            <property name=\"format\" value=\"^[a-z]([a-z0-9][a-zA-Z0-9]*)?$\" />\n            <message key=\"name.invalidPattern\"\n                value=\"Local variable name ''{0}'' must match pattern ''{1}''.\" />\n        </module>\n        <module name=\"ClassTypeParameterName\">\n            <property name=\"format\" value=\"(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)\" />\n            <message key=\"name.invalidPattern\"\n                value=\"Class type name ''{0}'' must match pattern ''{1}''.\" />\n        </module>\n        <module name=\"MethodTypeParameterName\">\n            <property name=\"format\" value=\"(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)\" />\n            <message key=\"name.invalidPattern\"\n                value=\"Method type name ''{0}'' must match pattern ''{1}''.\" />\n        </module>\n        <module name=\"InterfaceTypeParameterName\">\n            <property name=\"format\" value=\"(^[A-Z][0-9]?)$|([A-Z][a-zA-Z0-9]*[T]$)\" />\n            <message key=\"name.invalidPattern\"\n                value=\"Interface type name ''{0}'' must match pattern ''{1}''.\" />\n        </module>\n        <module name=\"NoFinalizer\" />\n        <module name=\"GenericWhitespace\">\n            <message key=\"ws.followed\"\n                value=\"GenericWhitespace ''{0}'' is followed by whitespace.\" />\n            <message key=\"ws.preceded\"\n                value=\"GenericWhitespace ''{0}'' is preceded with whitespace.\" />\n            <message key=\"ws.illegalFollow\"\n                value=\"GenericWhitespace ''{0}'' should followed by whitespace.\" />\n            <message key=\"ws.notPreceded\"\n                value=\"GenericWhitespace ''{0}'' is not preceded with whitespace.\" />\n        </module>\n        <module name=\"Indentation\">\n            <property name=\"basicOffset\" value=\"4\" />\n            <property name=\"braceAdjustment\" value=\"0\" />\n            <property name=\"caseIndent\" value=\"4\" />\n            <property name=\"throwsIndent\" value=\"4\" />\n            <property name=\"lineWrappingIndentation\" value=\"4\" />\n            <property name=\"arrayInitIndent\" value=\"4\" />\n        </module>\n        <module name=\"AbbreviationAsWordInName\">\n            <property name=\"ignoreFinal\" value=\"false\" />\n            <property name=\"allowedAbbreviationLength\" value=\"1\" />\n        </module>\n        <module name=\"OverloadMethodsDeclarationOrder\" />\n        <module name=\"VariableDeclarationUsageDistance\" />\n        <module name=\"CustomImportOrder\">\n            <property name=\"sortImportsInGroupAlphabetically\" value=\"true\" />\n            <property name=\"separateLineBetweenGroups\" value=\"true\" />\n            <property name=\"customImportOrderRules\" value=\"STATIC###THIRD_PARTY_PACKAGE\" />\n        </module>\n        <module name=\"MethodParamPad\" />\n        <module name=\"NoWhitespaceBefore\">\n            <property name=\"tokens\"\n                value=\"COMMA, SEMI, POST_INC, POST_DEC, DOT, ELLIPSIS, METHOD_REF\" />\n            <property name=\"allowLineBreaks\" value=\"true\" />\n        </module>\n        <module name=\"ParenPad\" />\n        <module name=\"OperatorWrap\">\n            <property name=\"option\" value=\"NL\" />\n            <property name=\"tokens\"\n                value=\"BAND, BOR, BSR, BXOR, DIV, EQUAL, GE, GT, LAND, LE, LITERAL_INSTANCEOF, LOR, LT, MINUS, MOD, NOT_EQUAL, PLUS, QUESTION, SL, SR, STAR, METHOD_REF \" />\n        </module>\n        <module name=\"AnnotationLocation\">\n            <property name=\"id\" value=\"AnnotationLocationMostCases\" />\n            <property name=\"tokens\"\n                value=\"CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF\" />\n        </module>\n        <module name=\"AnnotationLocation\">\n            <property name=\"id\" value=\"AnnotationLocationVariables\" />\n            <property name=\"tokens\" value=\"VARIABLE_DEF\" />\n            <property name=\"allowSamelineMultipleAnnotations\" value=\"true\" />\n        </module>\n        <module name=\"NonEmptyAtclauseDescription\" />\n        <module name=\"JavadocTagContinuationIndentation\" />\n        <module name=\"SummaryJavadoc\">\n            <property name=\"forbiddenSummaryFragments\"\n                value=\"^@return the *|^This method returns |^A [{]@code [a-zA-Z0-9]+[}]( is a )\" />\n        </module>\n        <module name=\"JavadocParagraph\" />\n        <module name=\"AtclauseOrder\">\n            <property name=\"tagOrder\" value=\"@param, @return, @throws, @deprecated\" />\n            <property name=\"target\"\n                value=\"CLASS_DEF, INTERFACE_DEF, ENUM_DEF, METHOD_DEF, CTOR_DEF, VARIABLE_DEF\" />\n        </module>\n        <module name=\"JavadocMethod\">\n            <property name=\"scope\" value=\"public\" />\n            <property name=\"allowMissingParamTags\" value=\"true\" />\n            <property name=\"allowMissingThrowsTags\" value=\"true\" />\n            <property name=\"allowMissingReturnTag\" value=\"true\" />\n            <property name=\"minLineCount\" value=\"2\" />\n            <property name=\"allowedAnnotations\" value=\"Override, TestSolution\" />\n            <property name=\"allowThrowsTagsForSubclasses\" value=\"true\" />\n        </module>\n        <module name=\"MethodName\">\n            <property name=\"format\" value=\"^[a-z][a-z0-9][a-zA-Z0-9_]*$\" />\n            <message key=\"name.invalidPattern\"\n                value=\"Method name ''{0}'' must match pattern ''{1}''.\" />\n        </module>\n        <module name=\"SingleLineJavadoc\">\n            <property name=\"ignoreInlineTags\" value=\"false\" />\n        </module>\n        <module name=\"EmptyCatchBlock\">\n            <property name=\"exceptionVariableName\" value=\"expected\" />\n        </module>\n        <module name=\"CommentsIndentation\" />\n    </module>\n</module>\n"
  },
  {
    "path": "tools/linters/checkstyle/suppressions.xml",
    "content": "<?xml version=\"1.0\"?>\n<!DOCTYPE suppressions PUBLIC\n    \"-//Puppy Crawl//DTD Suppressions 1.1//EN\"\n    \"http://www.puppycrawl.com/dtds/suppressions_1_1.dtd\">\n\n<suppressions>\n\n    <suppress checks=\"[a-zA-Z0-9]*\" files=\"R.java\" />\n    <suppress checks=\"[a-zA-Z0-9]*\" files=\"BuildConfig.java\" />\n    <suppress checks=\"[a-zA-Z0-9]*\" files=\"TestSolution\" />\n\n</suppressions>"
  },
  {
    "path": "tools/linters/linters.gradle",
    "content": "apply plugin: 'checkstyle'\napply plugin: 'pmd'\n\ncheck.dependsOn 'checkstyle', 'pmd'\n\ncheckstyle {\n    toolVersion '8.17'\n    configFile file(\"$rootDir/tools/linters/checkstyle/checkstyle.xml\")\n    configProperties.checkstyleSuppressionsPath = file(\n            \"$rootDir/tools/linters/checkstyle/suppressions.xml\"\n    ).absolutePath\n}\n\npmd {\n    toolVersion = '6.11.0'\n    ignoreFailures = false\n    ruleSetFiles = files(\"$rootDir/tools/linters/pmd/pmd-ruleset.xml\")\n    ruleSets = []\n}\n\ntask checkstyle(type: Checkstyle, group: 'verification') {\n    source 'src'\n    include '**/*.java'\n    exclude '**/gen/**'\n    exclude '**/test/**'\n    exclude '**/androidTest/**'\n    exclude '**/R.java'\n    exclude '**/BuildConfig.java'\n    classpath = files()\n}\n\ntask pmd(type: Pmd, group: 'verification') {\n    source 'src'\n    include '**/*.java'\n    exclude('**/gen/**', '**/debug/**')\n\n    reports {\n        xml.enabled = true\n        html.enabled = true\n        xml.destination file(\"$projectDir/build/reports/pmd/pmd.xml\")\n        html.destination file(\"$projectDir/build/reports/pmd/pmd.html\")\n    }\n}"
  },
  {
    "path": "tools/linters/pmd/pmd-ruleset.xml",
    "content": "<?xml version=\"1.0\"?>\n<ruleset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" name=\"Android Application Rules\"\n    xmlns=\"http://pmd.sf.net/ruleset/1.0.0\"\n    xsi:noNamespaceSchemaLocation=\"http://pmd.sf.net/ruleset_xml_schema.xsd\"\n    xsi:schemaLocation=\"http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd\">\n\n    <description>Custom ruleset for Android application</description>\n\n    <exclude-pattern>.*/R.java</exclude-pattern>\n    <exclude-pattern>.*/gen/.*</exclude-pattern>\n\n    <rule ref=\"rulesets/java/android.xml\" />\n    <rule ref=\"rulesets/java/clone.xml\" />\n    <rule ref=\"rulesets/java/finalizers.xml\" />\n    <rule ref=\"rulesets/java/imports.xml\">\n        <!-- Espresso is designed this way !-->\n        <exclude name=\"TooManyStaticImports\" />\n    </rule>\n    <rule ref=\"rulesets/java/logging-java.xml\" />\n    <rule ref=\"rulesets/java/braces.xml\" />\n    <rule ref=\"rulesets/java/strings.xml\" />\n    <rule ref=\"rulesets/java/basic.xml\" />\n    <rule ref=\"rulesets/java/naming.xml\">\n        <exclude name=\"AbstractNaming\" />\n        <exclude name=\"LongVariable\" />\n        <exclude name=\"ShortMethodName\" />\n        <exclude name=\"ShortVariable\" />\n        <exclude name=\"VariableNamingConventions\" />\n    </rule>\n</ruleset>"
  },
  {
    "path": "tools/publish.gradle",
    "content": "apply plugin: 'maven-publish'\n\nafterEvaluate {\n    publishing {\n        publications {\n            stable(MavenPublication) {\n                groupId 'br.com.concrete'\n                artifactId 'canarinho'\n                version '2.0.3'\n                from components.release\n            }\n        }\n        repositories {\n            maven {\n                url = String.valueOf(System.getenv(\"GPR_URL\"))\n                credentials {\n                    username = String.valueOf(System.getenv(\"GPR_USER\"))\n                    password = String.valueOf(System.getenv(\"GPR_PASSWORD\"))\n                }\n            }\n        }\n    }\n}"
  }
]