001: ////////////////////////////////////////////////////////////////////////////////
002: // checkstyle: Checks Java source code for adherence to a set of rules.
003: // Copyright (C) 2001-2007 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: ////////////////////////////////////////////////////////////////////////////////
019: package com.puppycrawl.tools.checkstyle.checks;
020:
021: import java.io.File;
022: import java.io.FileInputStream;
023: import java.io.FileNotFoundException;
024: import java.io.IOException;
025: import java.io.InputStream;
026: import java.util.Enumeration;
027: import java.util.HashMap;
028: import java.util.HashSet;
029: import java.util.Iterator;
030: import java.util.Map;
031: import java.util.Properties;
032: import java.util.Set;
033:
034: import com.puppycrawl.tools.checkstyle.Defn;
035: import com.puppycrawl.tools.checkstyle.api.AbstractFileSetCheck;
036: import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;
037: import com.puppycrawl.tools.checkstyle.api.MessageDispatcher;
038: import com.puppycrawl.tools.checkstyle.api.Utils;
039:
040: /**
041: * <p>
042: * The TranslationCheck class helps to ensure the correct translation of code by
043: * checking property files for consistency regarding their keys.
044: * Two property files describing one and the same context are consistent if they
045: * contain the same keys.
046: * </p>
047: * <p>
048: * An example of how to configure the check is:
049: * </p>
050: * <pre>
051: * <module name="Translation"/>
052: * </pre>
053: * @author Alexandra Bunge
054: * @author lkuehne
055: */
056: public class TranslationCheck extends AbstractFileSetCheck {
057: /**
058: * Creates a new <code>TranslationCheck</code> instance.
059: */
060: public TranslationCheck() {
061: setFileExtensions(new String[] { "properties" });
062: }
063:
064: /**
065: * Gets the basename (the unique prefix) of a property file. For example
066: * "xyz/messages" is the basename of "xyz/messages.properties",
067: * "xyz/messages_de_AT.properties", "xyz/messages_en.properties", etc.
068: *
069: * @param aFile the file
070: * @return the extracted basename
071: */
072: private static String extractPropertyIdentifier(final File aFile) {
073: final String filePath = aFile.getPath();
074: final int dirNameEnd = filePath.lastIndexOf(File.separatorChar);
075: final int baseNameStart = dirNameEnd + 1;
076: final int underscoreIdx = filePath.indexOf('_', baseNameStart);
077: final int dotIdx = filePath.indexOf('.', baseNameStart);
078: final int cutoffIdx = (underscoreIdx != -1) ? underscoreIdx
079: : dotIdx;
080: return filePath.substring(0, cutoffIdx);
081: }
082:
083: /**
084: * Arranges a set of property files by their prefix.
085: * The method returns a Map object. The filename prefixes
086: * work as keys each mapped to a set of files.
087: * @param aPropFiles the set of property files
088: * @return a Map object which holds the arranged property file sets
089: */
090: private static Map arrangePropertyFiles(File[] aPropFiles) {
091: final Map propFileMap = new HashMap();
092:
093: for (int i = 0; i < aPropFiles.length; i++) {
094: final File f = aPropFiles[i];
095: final String identifier = extractPropertyIdentifier(f);
096:
097: Set fileSet = (Set) propFileMap.get(identifier);
098: if (fileSet == null) {
099: fileSet = new HashSet();
100: propFileMap.put(identifier, fileSet);
101: }
102: fileSet.add(f);
103: }
104: return propFileMap;
105: }
106:
107: /**
108: * Loads the keys of the specified property file into a set.
109: * @param aFile the property file
110: * @return a Set object which holds the loaded keys
111: */
112: private Set loadKeys(File aFile) {
113: final Set keys = new HashSet();
114: InputStream inStream = null;
115:
116: try {
117: // Load file and properties.
118: inStream = new FileInputStream(aFile);
119: final Properties props = new Properties();
120: props.load(inStream);
121:
122: // Gather the keys and put them into a set
123: final Enumeration e = props.propertyNames();
124: while (e.hasMoreElements()) {
125: keys.add(e.nextElement());
126: }
127: } catch (final IOException e) {
128: logIOException(e, aFile);
129: } finally {
130: try {
131: if (inStream != null) {
132: inStream.close();
133: }
134: } catch (final IOException e) {
135: logIOException(e, aFile);
136: }
137: }
138: return keys;
139: }
140:
141: /**
142: * helper method to log an io exception.
143: * @param aEx the exception that occured
144: * @param aFile the file that could not be processed
145: */
146: private void logIOException(IOException aEx, File aFile) {
147: String[] args = null;
148: String key = "general.fileNotFound";
149: if (!(aEx instanceof FileNotFoundException)) {
150: args = new String[] { aEx.getMessage() };
151: key = "general.exception";
152: }
153: final LocalizedMessage message = new LocalizedMessage(0,
154: Defn.CHECKSTYLE_BUNDLE, key, args, getId(), this
155: .getClass());
156: final LocalizedMessage[] messages = new LocalizedMessage[] { message };
157: getMessageDispatcher().fireErrors(aFile.getPath(), messages);
158: Utils.getExceptionLogger().debug("IOException occured.", aEx);
159: }
160:
161: /**
162: * Compares the key sets of the given property files (arranged in a map)
163: * with the specified key set. All missing keys are reported.
164: * @param aKeys the set of keys to compare with
165: * @param aFileMap a Map from property files to their key sets
166: */
167: private void compareKeySets(Set aKeys, Map aFileMap) {
168: final Set fls = aFileMap.entrySet();
169:
170: for (final Iterator iter = fls.iterator(); iter.hasNext();) {
171: final Map.Entry entry = (Map.Entry) iter.next();
172: final File currentFile = (File) entry.getKey();
173: final MessageDispatcher dispatcher = getMessageDispatcher();
174: final String path = currentFile.getPath();
175: dispatcher.fireFileStarted(path);
176: final Set currentKeys = (Set) entry.getValue();
177:
178: // Clone the keys so that they are not lost
179: final Set keysClone = new HashSet(aKeys);
180: keysClone.removeAll(currentKeys);
181:
182: // Remaining elements in the key set are missing in the current file
183: if (!keysClone.isEmpty()) {
184: for (final Iterator it = keysClone.iterator(); it
185: .hasNext();) {
186: final Object key = it.next();
187: log(0, "translation.missingKey", key);
188: }
189: }
190: fireErrors(path);
191: dispatcher.fireFileFinished(path);
192: }
193: }
194:
195: /**
196: * Tests whether the given property files (arranged by their prefixes
197: * in a Map) contain the proper keys.
198: *
199: * Each group of files must have the same keys. If this is not the case
200: * an error message is posted giving information which key misses in
201: * which file.
202: *
203: * @param aPropFiles the property files organized as Map
204: */
205: private void checkPropertyFileSets(Map aPropFiles) {
206: final Set entrySet = aPropFiles.entrySet();
207:
208: for (final Iterator iterator = entrySet.iterator(); iterator
209: .hasNext();) {
210: final Map.Entry entry = (Map.Entry) iterator.next();
211: final Set files = (Set) entry.getValue();
212:
213: if (files.size() >= 2) {
214: // build a map from files to the keys they contain
215: final Set keys = new HashSet();
216: final Map fileMap = new HashMap();
217:
218: for (final Iterator iter = files.iterator(); iter
219: .hasNext();) {
220: final File file = (File) iter.next();
221: final Set fileKeys = loadKeys(file);
222: keys.addAll(fileKeys);
223: fileMap.put(file, fileKeys);
224: }
225:
226: // check the map for consistency
227: compareKeySets(keys, fileMap);
228: }
229: }
230: }
231:
232: /**
233: * This method searches for property files in the specified file array
234: * and checks whether the key usage is consistent.
235: *
236: * Two property files which have the same prefix should use the same
237: * keys. If this is not the case the missing keys are reported.
238: *
239: * @param aFiles {@inheritDoc}
240: * @see com.puppycrawl.tools.checkstyle.api.FileSetCheck
241: */
242: public void process(File[] aFiles) {
243: final File[] propertyFiles = filter(aFiles);
244: final Map propFilesMap = arrangePropertyFiles(propertyFiles);
245: checkPropertyFileSets(propFilesMap);
246: }
247:
248: }
|