View Javadoc
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      private static final Pattern SPACE_PATTERN = Pattern.compile(" ");
60  
61      /**
62       * Construct the check with default values.
63       */
64      public UniquePropertiesCheck() {
65          setFileExtensions("properties");
66      }
67  
68      @Override
69      protected void processFiltered(File file, FileText fileText) {
70          final UniqueProperties properties = new UniqueProperties();
71          FileInputStream fileInputStream = null;
72          try {
73              fileInputStream = new FileInputStream(file);
74              properties.load(fileInputStream);
75          }
76          catch (IOException ex) {
77              log(0, MSG_IO_EXCEPTION_KEY, file.getPath(),
78                      ex.getLocalizedMessage());
79          }
80          finally {
81              Closeables.closeQuietly(fileInputStream);
82          }
83  
84          for (Entry<String> duplication : properties
85                  .getDuplicatedKeys().entrySet()) {
86              final String keyName = duplication.getElement();
87              final int lineNumber = getLineNumber(fileText, keyName);
88              // Number of occurrences is number of duplications + 1
89              log(lineNumber, MSG_KEY, keyName, duplication.getCount() + 1);
90          }
91      }
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         final Pattern keyPattern = getKeyPattern(keyName);
106         int lineNumber = 1;
107         final Matcher matcher = keyPattern.matcher("");
108         for (int index = 0; index < fileText.size(); index++) {
109             final String line = fileText.get(index);
110             matcher.reset(line);
111             if (matcher.matches()) {
112                 break;
113             }
114             ++lineNumber;
115         }
116         // -1 as check seeks for the first duplicate occurrence in file,
117         // so it cannot be the last line.
118         if (lineNumber > fileText.size() - 1) {
119             lineNumber = 0;
120         }
121         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         final String keyPatternString = "^" + SPACE_PATTERN.matcher(keyName)
133                 .replaceAll(Matcher.quoteReplacement("\\\\ ")) + "[\\s:=].*$";
134         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     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         private final Multiset<String> duplicatedKeys = HashMultiset
150                 .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             final Object oldValue = super.put(key, value);
159             if (oldValue != null && key instanceof String) {
160                 final String keyString = (String) key;
161                 duplicatedKeys.add(keyString);
162             }
163             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             return ImmutableMultiset.copyOf(duplicatedKeys);
173         }
174     }
175 }