001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2020 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.IOException; 024import java.io.InputStream; 025import java.nio.file.Files; 026import java.util.ArrayList; 027import java.util.Collections; 028import java.util.Enumeration; 029import java.util.List; 030import java.util.Properties; 031import java.util.regex.Matcher; 032import java.util.regex.Pattern; 033 034import com.puppycrawl.tools.checkstyle.StatelessCheck; 035import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck; 036import com.puppycrawl.tools.checkstyle.api.FileText; 037 038/** 039 * <p>Detects if keys in properties files are in correct order.</p> 040 * <p> 041 * Rationale: Sorted properties make it easy for people to find required properties by name 042 * in file. It makes merges more easy. While there are no problems at runtime. 043 * This check is valuable only on files with string resources where order of lines 044 * does not matter at all, but this can be improved. 045 * E.g.: checkstyle/src/main/resources/com/puppycrawl/tools/checkstyle/messages.properties 046 * You may suppress warnings of this check for files that have an logical structure like 047 * build files or log4j configuration files. See SuppressionFilter. 048 * {@code 049 * <suppress checks="OrderedProperties" 050 * files="log4j.properties|ResourceBundle/Bug.*.properties|logging.properties"/> 051 * } 052 * </p> 053 * <p>Known limitation: The key should not contain a newline. 054 * The string compare will work, but not the line number reporting.</p> 055 * <ul> 056 * <li>Property {@code fileExtensions} - Specify file type extension of the files to check. 057 * Default value is {@code .properties}.</li> 058 * </ul> 059 * <p>To configure the check:</p> 060 * <pre><module name="OrderedProperties"/></pre> 061 * <p>Example properties file:</p> 062 * <pre> 063 * A =65 064 * a =97 065 * key =107 than nothing 066 * key.sub =k is 107 and dot is 46 067 * key.png =value - violation 068 * </pre> 069 * <p>We check order of key's only. Here we would like to use an Locale independent 070 * order mechanism, an binary order. The order is case insensitive and ascending.</p> 071 * <ul> 072 * <li>The capital A is on 65 and the lowercase a is on position 97 on the ascii table.</li> 073 * <li>Key and key.sub are in correct order here, because only keys are relevant. 074 * Therefore on line 5 you have only "key" an nothing behind. 075 * On line 6 you have "key." The dot is on position 46 which is higher than nothing. 076 * key.png will reported as violation because "png" comes before "sub".</li> 077 * </ul> 078 * 079 * @since 8.22 080 */ 081@StatelessCheck 082public class OrderedPropertiesCheck extends AbstractFileSetCheck { 083 084 /** 085 * Localization key for check violation. 086 */ 087 public static final String MSG_KEY = "properties.notSorted.property"; 088 /** 089 * Localization key for IO exception occurred on file open. 090 */ 091 public static final String MSG_IO_EXCEPTION_KEY = "unable.open.cause"; 092 /** 093 * Pattern matching single space. 094 */ 095 private static final Pattern SPACE_PATTERN = Pattern.compile(" "); 096 097 /** 098 * Construct the check with default values. 099 */ 100 public OrderedPropertiesCheck() { 101 setFileExtensions("properties"); 102 } 103 104 /** 105 * Processes the file and check order. 106 * 107 * @param file the file to be processed 108 * @param fileText the contents of the file. 109 * @noinspection EnumerationCanBeIteration 110 */ 111 @Override 112 protected void processFiltered(File file, FileText fileText) { 113 final SequencedProperties properties = new SequencedProperties(); 114 try (InputStream inputStream = Files.newInputStream(file.toPath())) { 115 properties.load(inputStream); 116 } 117 catch (IOException | IllegalArgumentException ex) { 118 log(1, MSG_IO_EXCEPTION_KEY, file.getPath(), ex.getLocalizedMessage()); 119 } 120 121 String previousProp = ""; 122 int startLineNo = 0; 123 124 final Enumeration<Object> keys = properties.keys(); 125 126 while (keys.hasMoreElements()) { 127 128 final String propKey = (String) keys.nextElement(); 129 130 if (String.CASE_INSENSITIVE_ORDER.compare(previousProp, propKey) > 0) { 131 132 final int lineNo = getLineNumber(startLineNo, fileText, previousProp, propKey); 133 log(lineNo + 1, MSG_KEY, propKey, previousProp); 134 // start searching at position of the last reported validation 135 startLineNo = lineNo; 136 } 137 138 previousProp = propKey; 139 } 140 } 141 142 /** 143 * Method returns the index number where the key is detected (starting at 0). 144 * To assure that we get the correct line it starts at the point 145 * of the last occurrence. 146 * Also the previousProp should be in file before propKey. 147 * 148 * @param startLineNo start searching at line 149 * @param fileText {@link FileText} object contains the lines to process 150 * @param previousProp key name found last iteration, works only if valid 151 * @param propKey key name to look for 152 * @return index number of first occurrence. If no key found in properties file, 0 is returned 153 */ 154 private static int getLineNumber(int startLineNo, FileText fileText, 155 String previousProp, String propKey) { 156 final int indexOfPreviousProp = getIndex(startLineNo, fileText, previousProp); 157 return getIndex(indexOfPreviousProp, fileText, propKey); 158 } 159 160 /** 161 * Inner method to get the index number of the position of keyName. 162 * 163 * @param startLineNo start searching at line 164 * @param fileText {@link FileText} object contains the lines to process 165 * @param keyName key name to look for 166 * @return index number of first occurrence. If no key found in properties file, 0 is returned 167 */ 168 private static int getIndex(int startLineNo, FileText fileText, String keyName) { 169 final Pattern keyPattern = getKeyPattern(keyName); 170 int indexNumber = 0; 171 final Matcher matcher = keyPattern.matcher(""); 172 for (int index = startLineNo; index < fileText.size(); index++) { 173 final String line = fileText.get(index); 174 matcher.reset(line); 175 if (matcher.matches()) { 176 indexNumber = index; 177 break; 178 } 179 } 180 return indexNumber; 181 } 182 183 /** 184 * Method returns regular expression pattern given key name. 185 * 186 * @param keyName 187 * key name to look for 188 * @return regular expression pattern given key name 189 */ 190 private static Pattern getKeyPattern(String keyName) { 191 final String keyPatternString = "^" + SPACE_PATTERN.matcher(keyName) 192 .replaceAll(Matcher.quoteReplacement("\\\\ ")) + "[\\s:=].*"; 193 return Pattern.compile(keyPatternString); 194 } 195 196 /** 197 * Private property implementation that keeps order of properties like in file. 198 * 199 * @noinspection ClassExtendsConcreteCollection, SerializableHasSerializationMethods 200 */ 201 private static class SequencedProperties extends Properties { 202 203 private static final long serialVersionUID = 1L; 204 205 /** 206 * Holding the keys in the same order than in the file. 207 */ 208 private final List<Object> keyList = new ArrayList<>(); 209 210 /** 211 * Returns a copy of the keys. 212 */ 213 @Override 214 public synchronized Enumeration<Object> keys() { 215 return Collections.enumeration(keyList); 216 } 217 218 /** 219 * Puts the value into list by its key. 220 * 221 * @noinspection UseOfPropertiesAsHashtable 222 * 223 * @param key the hashtable key 224 * @param value the value 225 * @return the previous value of the specified key in this hashtable, 226 * or null if it did not have one 227 * @throws NullPointerException - if the key or value is null 228 */ 229 @Override 230 public synchronized Object put(Object key, Object value) { 231 keyList.add(key); 232 233 return super.put(key, value); 234 } 235 } 236}