001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2017 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.api.AbstractFileSetCheck; 035import com.puppycrawl.tools.checkstyle.api.FileText; 036 037/** 038 * Checks the uniqueness of property keys (left from equal sign) in the 039 * properties file. 040 * 041 * @author Pavel Baranchikov 042 */ 043public class UniquePropertiesCheck extends AbstractFileSetCheck { 044 045 /** 046 * Localization key for check violation. 047 */ 048 public static final String MSG_KEY = "properties.duplicate.property"; 049 /** 050 * Localization key for IO exception occurred on file open. 051 */ 052 public static final String MSG_IO_EXCEPTION_KEY = "unable.open.cause"; 053 054 /** 055 * Pattern matching single space. 056 */ 057 private static final Pattern SPACE_PATTERN = Pattern.compile(" "); 058 059 /** 060 * Construct the check with default values. 061 */ 062 public UniquePropertiesCheck() { 063 setFileExtensions("properties"); 064 } 065 066 @Override 067 protected void processFiltered(File file, FileText fileText) { 068 final UniqueProperties properties = new UniqueProperties(); 069 FileInputStream fileInputStream = null; 070 try { 071 fileInputStream = new FileInputStream(file); 072 properties.load(fileInputStream); 073 } 074 catch (IOException ex) { 075 log(0, MSG_IO_EXCEPTION_KEY, file.getPath(), 076 ex.getLocalizedMessage()); 077 } 078 finally { 079 Closeables.closeQuietly(fileInputStream); 080 } 081 082 for (Entry<String> duplication : properties 083 .getDuplicatedKeys().entrySet()) { 084 final String keyName = duplication.getElement(); 085 final int lineNumber = getLineNumber(fileText, keyName); 086 // Number of occurrences is number of duplications + 1 087 log(lineNumber, MSG_KEY, keyName, duplication.getCount() + 1); 088 } 089 } 090 091 /** 092 * Method returns line number the key is detected in the checked properties 093 * files first. 094 * 095 * @param fileText 096 * {@link FileText} object contains the lines to process 097 * @param keyName 098 * key name to look for 099 * @return line number of first occurrence. If no key found in properties 100 * file, 0 is returned 101 */ 102 private static int getLineNumber(FileText fileText, String keyName) { 103 final Pattern keyPattern = getKeyPattern(keyName); 104 int lineNumber = 1; 105 final Matcher matcher = keyPattern.matcher(""); 106 for (int index = 0; index < fileText.size(); index++) { 107 final String line = fileText.get(index); 108 matcher.reset(line); 109 if (matcher.matches()) { 110 break; 111 } 112 ++lineNumber; 113 } 114 // -1 as check seeks for the first duplicate occurance in file, 115 // so it cannot be the last line. 116 if (lineNumber > fileText.size() - 1) { 117 lineNumber = 0; 118 } 119 return lineNumber; 120 } 121 122 /** 123 * Method returns regular expression pattern given key name. 124 * 125 * @param keyName 126 * key name to look for 127 * @return regular expression pattern given key name 128 */ 129 private static Pattern getKeyPattern(String keyName) { 130 final String keyPatternString = "^" + SPACE_PATTERN.matcher(keyName) 131 .replaceAll(Matcher.quoteReplacement("\\\\ ")) + "[\\s:=].*$"; 132 return Pattern.compile(keyPatternString); 133 } 134 135 /** 136 * Properties subclass to store duplicated property keys in a separate map. 137 * 138 * @author Pavel Baranchikov 139 * @noinspection ClassExtendsConcreteCollection, SerializableHasSerializationMethods 140 */ 141 private static class UniqueProperties extends Properties { 142 private static final long serialVersionUID = 1L; 143 /** 144 * Multiset, holding duplicated keys. Keys are added here only if they 145 * already exist in Properties' inner map. 146 */ 147 private final Multiset<String> duplicatedKeys = HashMultiset 148 .create(); 149 150 /** 151 * Puts the value into properties by the key specified. 152 * @noinspection UseOfPropertiesAsHashtable 153 */ 154 @Override 155 public synchronized Object put(Object key, Object value) { 156 final Object oldValue = super.put(key, value); 157 if (oldValue != null && key instanceof String) { 158 final String keyString = (String) key; 159 duplicatedKeys.add(keyString); 160 } 161 return oldValue; 162 } 163 164 /** 165 * Retrieves a collections of duplicated properties keys. 166 * 167 * @return A collection of duplicated keys. 168 */ 169 public Multiset<String> getDuplicatedKeys() { 170 return ImmutableMultiset.copyOf(duplicatedKeys); 171 } 172 } 173}