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.List; 026import java.util.Properties; 027import java.util.regex.Matcher; 028import java.util.regex.Pattern; 029 030import com.google.common.collect.HashMultiset; 031import com.google.common.collect.ImmutableMultiset; 032import com.google.common.collect.Multiset; 033import com.google.common.collect.Multiset.Entry; 034import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck; 035 036/** 037 * Checks the uniqueness of property keys (left from equal sign) in the 038 * properties file. 039 * 040 * @author Pavel Baranchikov 041 */ 042public class UniquePropertiesCheck extends AbstractFileSetCheck { 043 044 /** 045 * Localization key for check violation. 046 */ 047 public static final String MSG_KEY = "properties.duplicate.property"; 048 /** 049 * Localization key for IO exception occurred on file open. 050 */ 051 public static final String MSG_IO_EXCEPTION_KEY = "unable.open.cause"; 052 053 /** 054 * Pattern matching single space. 055 */ 056 private static final Pattern SPACE_PATTERN = Pattern.compile(" "); 057 058 /** 059 * Construct the check with default values. 060 */ 061 public UniquePropertiesCheck() { 062 setFileExtensions("properties"); 063 } 064 065 @Override 066 protected void processFiltered(File file, List<String> lines) { 067 final UniqueProperties properties = new UniqueProperties(); 068 069 try { 070 final FileInputStream fileInputStream = new FileInputStream(file); 071 try { 072 // As file is already read, there should not be any exceptions. 073 properties.load(fileInputStream); 074 } 075 finally { 076 fileInputStream.close(); 077 } 078 } 079 catch (IOException ex) { 080 log(0, MSG_IO_EXCEPTION_KEY, file.getPath(), 081 ex.getLocalizedMessage()); 082 } 083 084 for (Entry<String> duplication : properties 085 .getDuplicatedKeys().entrySet()) { 086 final String keyName = duplication.getElement(); 087 final int lineNumber = getLineNumber(lines, 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 lines 098 * properties file lines list 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 protected static int getLineNumber(List<String> lines, String keyName) { 105 final String keyPatternString = "^" + SPACE_PATTERN.matcher(keyName) 106 .replaceAll(Matcher.quoteReplacement("\\\\ ")) + "[\\s:=].*$"; 107 final Pattern keyPattern = Pattern.compile(keyPatternString); 108 int lineNumber = 1; 109 final Matcher matcher = keyPattern.matcher(""); 110 for (String line : lines) { 111 matcher.reset(line); 112 if (matcher.matches()) { 113 break; 114 } 115 ++lineNumber; 116 } 117 if (lineNumber > lines.size()) { 118 lineNumber = 0; 119 } 120 return lineNumber; 121 } 122 123 /** 124 * Properties subclass to store duplicated property keys in a separate map. 125 * 126 * @author Pavel Baranchikov 127 */ 128 private static class UniqueProperties extends Properties { 129 private static final long serialVersionUID = 1L; 130 /** 131 * Multiset, holding duplicated keys. Keys are added here only if they 132 * already exist in Properties' inner map. 133 */ 134 private final Multiset<String> duplicatedKeys = HashMultiset 135 .create(); 136 137 @Override 138 public Object put(Object key, Object value) { 139 final Object oldValue = super.put(key, value); 140 if (oldValue != null && key instanceof String) { 141 final String keyString = (String) key; 142 duplicatedKeys.add(keyString); 143 } 144 return oldValue; 145 } 146 147 /** 148 * Retrieves a collections of duplicated properties keys. 149 * 150 * @return A collection of duplicated keys. 151 */ 152 public Multiset<String> getDuplicatedKeys() { 153 return ImmutableMultiset.copyOf(duplicatedKeys); 154 } 155 } 156}