001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018//////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks; 021 022import java.io.File; 023import java.io.FileInputStream; 024import java.io.IOException; 025import java.util.Properties; 026import java.util.regex.Matcher; 027import java.util.regex.Pattern; 028 029import com.google.common.collect.HashMultiset; 030import com.google.common.collect.ImmutableMultiset; 031import com.google.common.collect.Multiset; 032import com.google.common.collect.Multiset.Entry; 033import com.google.common.io.Closeables; 034import com.puppycrawl.tools.checkstyle.StatelessCheck; 035import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck; 036import com.puppycrawl.tools.checkstyle.api.FileText; 037 038/** 039 * Checks the uniqueness of property keys (left from equal sign) in the 040 * properties file. 041 * 042 * @author Pavel Baranchikov 043 */ 044@StatelessCheck 045public class UniquePropertiesCheck extends AbstractFileSetCheck { 046 047 /** 048 * Localization key for check violation. 049 */ 050 public static final String MSG_KEY = "properties.duplicate.property"; 051 /** 052 * Localization key for IO exception occurred on file open. 053 */ 054 public static final String MSG_IO_EXCEPTION_KEY = "unable.open.cause"; 055 056 /** 057 * Pattern matching single space. 058 */ 059 private static final Pattern SPACE_PATTERN = Pattern.compile(" "); 060 061 /** 062 * Construct the check with default values. 063 */ 064 public UniquePropertiesCheck() { 065 setFileExtensions("properties"); 066 } 067 068 @Override 069 protected void processFiltered(File file, FileText fileText) { 070 final UniqueProperties properties = new UniqueProperties(); 071 FileInputStream fileInputStream = null; 072 try { 073 fileInputStream = new FileInputStream(file); 074 properties.load(fileInputStream); 075 } 076 catch (IOException ex) { 077 log(0, MSG_IO_EXCEPTION_KEY, file.getPath(), 078 ex.getLocalizedMessage()); 079 } 080 finally { 081 Closeables.closeQuietly(fileInputStream); 082 } 083 084 for (Entry<String> duplication : properties 085 .getDuplicatedKeys().entrySet()) { 086 final String keyName = duplication.getElement(); 087 final int lineNumber = getLineNumber(fileText, keyName); 088 // Number of occurrences is number of duplications + 1 089 log(lineNumber, MSG_KEY, keyName, duplication.getCount() + 1); 090 } 091 } 092 093 /** 094 * Method returns line number the key is detected in the checked properties 095 * files first. 096 * 097 * @param fileText 098 * {@link FileText} object contains the lines to process 099 * @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 145 private static final long serialVersionUID = 1L; 146 /** 147 * Multiset, holding duplicated keys. Keys are added here only if they 148 * already exist in Properties' inner map. 149 */ 150 private final Multiset<String> duplicatedKeys = HashMultiset 151 .create(); 152 153 /** 154 * Puts the value into properties by the key specified. 155 * @noinspection UseOfPropertiesAsHashtable 156 */ 157 @Override 158 public synchronized Object put(Object key, Object value) { 159 final Object oldValue = super.put(key, value); 160 if (oldValue != null && key instanceof String) { 161 final String keyString = (String) key; 162 duplicatedKeys.add(keyString); 163 } 164 return oldValue; 165 } 166 167 /** 168 * Retrieves a collections of duplicated properties keys. 169 * 170 * @return A collection of duplicated keys. 171 */ 172 public Multiset<String> getDuplicatedKeys() { 173 return ImmutableMultiset.copyOf(duplicatedKeys); 174 } 175 176 } 177 178}