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}