Coverage Report - com.puppycrawl.tools.checkstyle.checks.UniquePropertiesCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
UniquePropertiesCheck
100%
35/35
100%
8/8
2.167
UniquePropertiesCheck$1
N/A
N/A
2.167
UniquePropertiesCheck$UniqueProperties
100%
9/9
100%
4/4
2.167
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2017 the original author or authors.
 4  
 //
 5  
 // This library is free software; you can redistribute it and/or
 6  
 // modify it under the terms of the GNU Lesser General Public
 7  
 // License as published by the Free Software Foundation; either
 8  
 // version 2.1 of the License, or (at your option) any later version.
 9  
 //
 10  
 // This library is distributed in the hope that it will be useful,
 11  
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 // Lesser General Public License for more details.
 14  
 //
 15  
 // You should have received a copy of the GNU Lesser General Public
 16  
 // License along with this library; if not, write to the Free Software
 17  
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 18  
 ////////////////////////////////////////////////////////////////////////////////
 19  
 
 20  
 package com.puppycrawl.tools.checkstyle.checks;
 21  
 
 22  
 import java.io.File;
 23  
 import java.io.FileInputStream;
 24  
 import java.io.IOException;
 25  
 import java.util.Properties;
 26  
 import java.util.regex.Matcher;
 27  
 import java.util.regex.Pattern;
 28  
 
 29  
 import com.google.common.collect.HashMultiset;
 30  
 import com.google.common.collect.ImmutableMultiset;
 31  
 import com.google.common.collect.Multiset;
 32  
 import com.google.common.collect.Multiset.Entry;
 33  
 import com.google.common.io.Closeables;
 34  
 import com.puppycrawl.tools.checkstyle.StatelessCheck;
 35  
 import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
 36  
 import com.puppycrawl.tools.checkstyle.api.FileText;
 37  
 
 38  
 /**
 39  
  * Checks the uniqueness of property keys (left from equal sign) in the
 40  
  * properties file.
 41  
  *
 42  
  * @author Pavel Baranchikov
 43  
  */
 44  
 @StatelessCheck
 45  
 public class UniquePropertiesCheck extends AbstractFileSetCheck {
 46  
 
 47  
     /**
 48  
      * Localization key for check violation.
 49  
      */
 50  
     public static final String MSG_KEY = "properties.duplicate.property";
 51  
     /**
 52  
      * Localization key for IO exception occurred on file open.
 53  
      */
 54  
     public static final String MSG_IO_EXCEPTION_KEY = "unable.open.cause";
 55  
 
 56  
     /**
 57  
      * Pattern matching single space.
 58  
      */
 59  2
     private static final Pattern SPACE_PATTERN = Pattern.compile(" ");
 60  
 
 61  
     /**
 62  
      * Construct the check with default values.
 63  
      */
 64  8
     public UniquePropertiesCheck() {
 65  8
         setFileExtensions("properties");
 66  8
     }
 67  
 
 68  
     @Override
 69  
     protected void processFiltered(File file, FileText fileText) {
 70  4
         final UniqueProperties properties = new UniqueProperties();
 71  4
         FileInputStream fileInputStream = null;
 72  
         try {
 73  4
             fileInputStream = new FileInputStream(file);
 74  3
             properties.load(fileInputStream);
 75  
         }
 76  1
         catch (IOException ex) {
 77  2
             log(0, MSG_IO_EXCEPTION_KEY, file.getPath(),
 78  1
                     ex.getLocalizedMessage());
 79  
         }
 80  
         finally {
 81  4
             Closeables.closeQuietly(fileInputStream);
 82  4
         }
 83  
 
 84  8
         for (Entry<String> duplication : properties
 85  4
                 .getDuplicatedKeys().entrySet()) {
 86  7
             final String keyName = duplication.getElement();
 87  7
             final int lineNumber = getLineNumber(fileText, keyName);
 88  
             // Number of occurrences is number of duplications + 1
 89  7
             log(lineNumber, MSG_KEY, keyName, duplication.getCount() + 1);
 90  7
         }
 91  4
     }
 92  
 
 93  
     /**
 94  
      * Method returns line number the key is detected in the checked properties
 95  
      * files first.
 96  
      *
 97  
      * @param fileText
 98  
      *            {@link FileText} object contains the lines to process
 99  
      * @param keyName
 100  
      *            key name to look for
 101  
      * @return line number of first occurrence. If no key found in properties
 102  
      *         file, 0 is returned
 103  
      */
 104  
     private static int getLineNumber(FileText fileText, String keyName) {
 105  8
         final Pattern keyPattern = getKeyPattern(keyName);
 106  8
         int lineNumber = 1;
 107  8
         final Matcher matcher = keyPattern.matcher("");
 108  109
         for (int index = 0; index < fileText.size(); index++) {
 109  108
             final String line = fileText.get(index);
 110  108
             matcher.reset(line);
 111  108
             if (matcher.matches()) {
 112  7
                 break;
 113  
             }
 114  101
             ++lineNumber;
 115  
         }
 116  
         // -1 as check seeks for the first duplicate occurrence in file,
 117  
         // so it cannot be the last line.
 118  8
         if (lineNumber > fileText.size() - 1) {
 119  1
             lineNumber = 0;
 120  
         }
 121  8
         return lineNumber;
 122  
     }
 123  
 
 124  
     /**
 125  
      * Method returns regular expression pattern given key name.
 126  
      *
 127  
      * @param keyName
 128  
      *            key name to look for
 129  
      * @return regular expression pattern given key name
 130  
      */
 131  
     private static Pattern getKeyPattern(String keyName) {
 132  8
         final String keyPatternString = "^" + SPACE_PATTERN.matcher(keyName)
 133  8
                 .replaceAll(Matcher.quoteReplacement("\\\\ ")) + "[\\s:=].*$";
 134  8
         return Pattern.compile(keyPatternString);
 135  
     }
 136  
 
 137  
     /**
 138  
      * Properties subclass to store duplicated property keys in a separate map.
 139  
      *
 140  
      * @author Pavel Baranchikov
 141  
      * @noinspection ClassExtendsConcreteCollection, SerializableHasSerializationMethods
 142  
      */
 143  9
     private static class UniqueProperties extends Properties {
 144  
         private static final long serialVersionUID = 1L;
 145  
         /**
 146  
          * Multiset, holding duplicated keys. Keys are added here only if they
 147  
          * already exist in Properties' inner map.
 148  
          */
 149  10
         private final Multiset<String> duplicatedKeys = HashMultiset
 150  5
                 .create();
 151  
 
 152  
         /**
 153  
          * Puts the value into properties by the key specified.
 154  
          * @noinspection UseOfPropertiesAsHashtable
 155  
          */
 156  
         @Override
 157  
         public synchronized Object put(Object key, Object value) {
 158  44
             final Object oldValue = super.put(key, value);
 159  44
             if (oldValue != null && key instanceof String) {
 160  9
                 final String keyString = (String) key;
 161  9
                 duplicatedKeys.add(keyString);
 162  
             }
 163  44
             return oldValue;
 164  
         }
 165  
 
 166  
         /**
 167  
          * Retrieves a collections of duplicated properties keys.
 168  
          *
 169  
          * @return A collection of duplicated keys.
 170  
          */
 171  
         public Multiset<String> getDuplicatedKeys() {
 172  4
             return ImmutableMultiset.copyOf(duplicatedKeys);
 173  
         }
 174  
     }
 175  
 }