Source code: com/puppycrawl/tools/checkstyle/checks/TranslationCheck.java
1 ////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code for adherence to a set of rules.
3 // Copyright (C) 2001-2003 Oliver Burn
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ////////////////////////////////////////////////////////////////////////////////
19 package com.puppycrawl.tools.checkstyle.checks;
20
21 import java.io.File;
22 import java.io.FileInputStream;
23 import java.io.FileNotFoundException;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.util.Enumeration;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.Iterator;
30 import java.util.Map;
31 import java.util.Properties;
32 import java.util.Set;
33
34 import com.puppycrawl.tools.checkstyle.Defn;
35 import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
36 import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
37 import com.puppycrawl.tools.checkstyle.api.MessageDispatcher;
38 import com.puppycrawl.tools.checkstyle.api.Utils;
39
40 /**
41 * <p>
42 * The TranslationCheck class helps to ensure the correct translation of code by
43 * checking property files for consistency regarding their keys.
44 * Two property files describing one and the same context are consistent if they
45 * contain the same keys.
46 * </p>
47 * <p>
48 * An example of how to configure the check is:
49 * </p>
50 * <pre>
51 * <module name="Translation"/>
52 * </pre>
53 * @author Alexandra Bunge
54 * @author lkuehne
55 */
56 public class TranslationCheck
57 extends AbstractFileSetCheck
58 {
59 /**
60 * Creates a new <code>TranslationCheck</code> instance.
61 */
62 public TranslationCheck()
63 {
64 setFileExtensions(new String[]{"properties"});
65 }
66
67 /**
68 * Gets the basename (the unique prefix) of a property file. For example
69 * "xyz/messages" is the basename of "xyz/messages.properties",
70 * "xyz/messages_de_AT.properties", "xyz/messages_en.properties", etc.
71 *
72 * @param aFile the file
73 * @return the extracted basename
74 */
75 private static String extractPropertyIdentifier(final File aFile)
76 {
77 final String filePath = aFile.getPath();
78 final int dirNameEnd = filePath.lastIndexOf(File.separatorChar);
79 final int baseNameStart = dirNameEnd + 1;
80 final int underscoreIdx = filePath.indexOf('_', baseNameStart);
81 final int dotIdx = filePath.indexOf('.', baseNameStart);
82 final int cutoffIdx = (underscoreIdx != -1) ? underscoreIdx : dotIdx;
83 return filePath.substring(0, cutoffIdx);
84 }
85
86 /**
87 * Arranges a set of property files by their prefix.
88 * The method returns a Map object. The filename prefixes
89 * work as keys each mapped to a set of files.
90 * @param aPropFiles the set of property files
91 * @return a Map object which holds the arranged property file sets
92 */
93 private static Map arrangePropertyFiles(File[] aPropFiles)
94 {
95 final Map propFileMap = new HashMap();
96
97 for (int i = 0; i < aPropFiles.length; i++) {
98 final File f = aPropFiles[i];
99 final String identifier = extractPropertyIdentifier(f);
100
101 Set fileSet = (Set) propFileMap.get(identifier);
102 if (fileSet == null) {
103 fileSet = new HashSet();
104 propFileMap.put(identifier, fileSet);
105 }
106 fileSet.add(f);
107 }
108 return propFileMap;
109 }
110
111
112 /**
113 * Loads the keys of the specified property file into a set.
114 * @param aFile the property file
115 * @return a Set object which holds the loaded keys
116 */
117 private Set loadKeys(File aFile)
118 {
119 final Set keys = new HashSet();
120 InputStream inStream = null;
121
122 try {
123 // Load file and properties.
124 inStream = new FileInputStream(aFile);
125 Properties props = new Properties();
126 props.load(inStream);
127
128 // Gather the keys and put them into a set
129 final Enumeration e = props.propertyNames();
130 while (e.hasMoreElements()) {
131 keys.add(e.nextElement());
132 }
133 }
134 catch (IOException e) {
135 logIOException(e, aFile);
136 }
137 finally {
138 try {
139 inStream.close();
140 }
141 catch (IOException e) {
142 logIOException(e, aFile);
143 }
144 }
145 return keys;
146 }
147
148 /**
149 * helper method to log an io exception.
150 * @param aEx the exception that occured
151 * @param aFile the file that could not be processed
152 */
153 private void logIOException(IOException aEx, File aFile)
154 {
155 String[] args = null;
156 String key = "general.fileNotFound";
157 if (!(aEx instanceof FileNotFoundException)) {
158 args = new String[] {aEx.getMessage()};
159 key = "general.exception";
160 }
161 final LocalizedMessage message =
162 new LocalizedMessage(
163 0,
164 Defn.CHECKSTYLE_BUNDLE,
165 key,
166 args,
167 this.getClass());
168 final LocalizedMessage[] messages = new LocalizedMessage[] {message};
169 getMessageDispatcher().fireErrors(aFile.getPath(), messages);
170 Utils.getExceptionLogger().debug("IOException occured.", aEx);
171 }
172
173
174 /**
175 * Compares the key sets of the given property files (arranged in a map)
176 * with the specified key set. All missing keys are reported.
177 * @param aKeys the set of keys to compare with
178 * @param aFileMap a Map from property files to their key sets
179 */
180 private void compareKeySets(Set aKeys, Map aFileMap)
181 {
182 final Set fls = aFileMap.keySet();
183
184 for (Iterator iter = fls.iterator(); iter.hasNext();) {
185 final File currentFile = (File) iter.next();
186 final MessageDispatcher dispatcher = getMessageDispatcher();
187 final String path = currentFile.getPath();
188 dispatcher.fireFileStarted(path);
189 final Set currentKeys = (Set) aFileMap.get(currentFile);
190
191 // Clone the keys so that they are not lost
192 final Set keysClone = new HashSet(aKeys);
193 keysClone.removeAll(currentKeys);
194
195 // Remaining elements in the key set are missing in the current file
196 if (!keysClone.isEmpty()) {
197 for (Iterator it = keysClone.iterator(); it.hasNext();) {
198 Object key = it.next();
199 log(0, "translation.missingKey", key);
200 }
201 }
202 fireErrors(path);
203 dispatcher.fireFileFinished(path);
204 }
205 }
206
207
208 /**
209 * Tests whether the given property files (arranged by their prefixes
210 * in a Map) contain the proper keys.
211 *
212 * Each group of files must have the same keys. If this is not the case
213 * an error message is posted giving information which key misses in
214 * which file.
215 *
216 * @param aPropFiles the property files organized as Map
217 */
218 private void checkPropertyFileSets(Map aPropFiles)
219 {
220 final Set keySet = aPropFiles.keySet();
221
222 for (Iterator iterator = keySet.iterator(); iterator.hasNext();) {
223 final String baseName = (String) iterator.next();
224 final Set files = (Set) aPropFiles.get(baseName);
225
226 if (files.size() >= 2) {
227 // build a map from files to the keys they contain
228 final Set keys = new HashSet();
229 final Map fileMap = new HashMap();
230
231 for (Iterator iter = files.iterator(); iter.hasNext();) {
232 final File file = (File) iter.next();
233 final Set fileKeys = loadKeys(file);
234 keys.addAll(fileKeys);
235 fileMap.put(file, fileKeys);
236 }
237
238 // check the map for consistency
239 compareKeySets(keys, fileMap);
240 }
241 }
242 }
243
244
245 /**
246 * This method searches for property files in the specified file array
247 * and checks whether the key usage is consistent.
248 *
249 * Two property files which have the same prefix should use the same
250 * keys. If this is not the case the missing keys are reported.
251 *
252 * @see com.puppycrawl.tools.checkstyle.api.FileSetCheck
253 */
254 public void process(File[] aFiles)
255 {
256 final File[] propertyFiles = filter(aFiles);
257 final Map propFilesMap = arrangePropertyFiles(propertyFiles);
258 checkPropertyFileSets(propFilesMap);
259 }
260
261
262
263 }