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}