001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2014 Oliver Burn 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//////////////////////////////////////////////////////////////////////////////// 019package com.puppycrawl.tools.checkstyle.checks; 020 021import java.io.File; 022import java.io.FileInputStream; 023import java.io.IOException; 024import java.util.List; 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.Multiset; 031import com.google.common.collect.Multiset.Entry; 032import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck; 033 034/** 035 * Checks the uniqueness of property keys (left from equal sign) in the 036 * properties file. 037 * 038 * @author Pavel Baranchikov 039 */ 040public class UniquePropertiesCheck extends AbstractFileSetCheck 041{ 042 043 /** 044 * Localization key for check violation. 045 */ 046 public static final String MSG_KEY = "properties.duplicateproperty"; 047 /** 048 * Localization key for IO exception occurred on file open. 049 */ 050 public static final String IO_EXCEPTION_KEY = "unable.open.cause"; 051 052 /** 053 * Construct the check with default values. 054 */ 055 public UniquePropertiesCheck() 056 { 057 super.setFileExtensions(new String[] 058 {"properties"}); 059 } 060 061 @Override 062 protected void processFiltered(File file, List<String> lines) 063 { 064 final UniqueProperties properties = new UniqueProperties(); 065 066 try { 067 // As file is already read, there should not be any exceptions. 068 properties.load(new FileInputStream(file)); 069 } 070 catch (IOException e) { 071 log(0, IO_EXCEPTION_KEY, file.getPath(), 072 e.getLocalizedMessage()); 073 } 074 075 for (Entry<String> duplication : properties 076 .getDuplicatedStrings().entrySet()) 077 { 078 final String keyName = duplication.getElement(); 079 final int lineNumber = getLineNumber(lines, keyName); 080 // Number of occurrences is number of duplications + 1 081 log(lineNumber, MSG_KEY, keyName, duplication.getCount() + 1); 082 } 083 } 084 085 /** 086 * Method returns line number the key is detected in the checked properties 087 * files first. 088 * 089 * @param lines 090 * properties file lines list 091 * @param keyName 092 * key name to look for 093 * @return line number of first occurrence. If no key found in properties 094 * file, 0 is returned 095 */ 096 protected int getLineNumber(List<String> lines, String keyName) 097 { 098 final String keyPatternString = 099 "^" + keyName.replace(" ", "\\\\ ") + "[\\s:=].*$"; 100 final Pattern keyPattern = Pattern.compile(keyPatternString); 101 int lineNumber = 1; 102 final Matcher matcher = keyPattern.matcher(""); 103 for (String line : lines) { 104 matcher.reset(line); 105 if (matcher.matches()) { 106 break; 107 } 108 ++lineNumber; 109 } 110 if (lineNumber > lines.size()) { 111 lineNumber = 0; 112 } 113 return lineNumber; 114 } 115 116 /** 117 * Properties subclass to store duplicated property keys in a separate map. 118 * 119 * @author Pavel Baranchikov 120 */ 121 private static class UniqueProperties extends Properties 122 { 123 /** 124 * Default serial version id. 125 */ 126 private static final long serialVersionUID = 1L; 127 /** 128 * Multiset, holding duplicated keys. Keys are added here only if they 129 * already exist in Properties' inner map. 130 */ 131 private final Multiset<String> duplicatedStrings = HashMultiset 132 .create(); 133 134 @Override 135 public synchronized Object put(Object key, Object value) 136 { 137 final Object oldValue = super.put(key, value); 138 if (oldValue != null && key instanceof String) { 139 final String keyString = (String) key; 140 duplicatedStrings.add(keyString); 141 } 142 return oldValue; 143 } 144 145 public Multiset<String> getDuplicatedStrings() 146 { 147 return duplicatedStrings; 148 } 149 } 150}