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;
020:
021: import java.io.FileInputStream;
022: import java.io.FileNotFoundException;
023: import java.io.FileOutputStream;
024: import java.io.IOException;
025: import java.io.ByteArrayOutputStream;
026: import java.io.ObjectOutputStream;
027: import java.io.Serializable;
028: import java.util.Properties;
029: import java.security.MessageDigest;
030:
031: import com.puppycrawl.tools.checkstyle.api.Configuration;
032: import com.puppycrawl.tools.checkstyle.api.Utils;
033:
034: /**
035: * This class maintains a persistent store of the files that have
036: * checked ok and their associated timestamp. It uses a property file
037: * for storage. A hashcode of the Configuration is stored in the
038: * cache file to ensure the cache is invalidated when the
039: * configuration has changed.
040: *
041: * @author Oliver Burn
042: */
043: final class PropertyCacheFile {
044: /**
045: * The property key to use for storing the hashcode of the
046: * configuration. To avoid nameclashes with the files that are
047: * checked the key is chosen in such a way that it cannot be a
048: * valid file name.
049: */
050: private static final String CONFIG_HASH_KEY = "configuration*?";
051:
052: /** name of file to store details **/
053: private final String mDetailsFile;
054: /** the details on files **/
055: private final Properties mDetails = new Properties();
056:
057: /**
058: * Creates a new <code>PropertyCacheFile</code> instance.
059: *
060: * @param aCurrentConfig the current configuration, not null
061: * @param aFileName the cache file
062: */
063: PropertyCacheFile(Configuration aCurrentConfig, String aFileName) {
064: boolean setInActive = true;
065: if (aFileName != null) {
066: FileInputStream inStream = null;
067: // get the current config so if the file isn't found
068: // the first time the hash will be added to output file
069: final String currentConfigHash = getConfigHashCode(aCurrentConfig);
070: try {
071: inStream = new FileInputStream(aFileName);
072: mDetails.load(inStream);
073: final String cachedConfigHash = mDetails
074: .getProperty(CONFIG_HASH_KEY);
075: setInActive = false;
076: if ((cachedConfigHash == null)
077: || !cachedConfigHash.equals(currentConfigHash)) {
078: // Detected configuration change - clear cache
079: mDetails.clear();
080: mDetails.put(CONFIG_HASH_KEY, currentConfigHash);
081: }
082: } catch (final FileNotFoundException e) {
083: // Ignore, the cache does not exist
084: setInActive = false;
085: // put the hash in the file if the file is going to be created
086: mDetails.put(CONFIG_HASH_KEY, currentConfigHash);
087: } catch (final IOException e) {
088: Utils.getExceptionLogger().debug(
089: "Unable to open cache file, ignoring.", e);
090: } finally {
091: if (inStream != null) {
092: try {
093: inStream.close();
094: } catch (final IOException ex) {
095: Utils.getExceptionLogger().debug(
096: "Unable to close cache file.", ex);
097: }
098: }
099: }
100: }
101: mDetailsFile = (setInActive) ? null : aFileName;
102: }
103:
104: /** Cleans up the object and updates the cache file. **/
105: void destroy() {
106: if (mDetailsFile != null) {
107: FileOutputStream out = null;
108: try {
109: out = new FileOutputStream(mDetailsFile);
110: mDetails.store(out, null);
111: } catch (final IOException e) {
112: Utils.getExceptionLogger().debug(
113: "Unable to save cache file.", e);
114: } finally {
115: if (out != null) {
116: try {
117: out.flush();
118: out.close();
119: } catch (final IOException ex) {
120: Utils.getExceptionLogger().debug(
121: "Unable to close cache file.", ex);
122: }
123: }
124: }
125: }
126: }
127:
128: /**
129: * @return whether the specified file has already been checked ok
130: * @param aFileName the file to check
131: * @param aTimestamp the timestamp of the file to check
132: */
133: boolean alreadyChecked(String aFileName, long aTimestamp) {
134: final String lastChecked = mDetails.getProperty(aFileName);
135: return (lastChecked != null)
136: && (lastChecked.equals(Long.toString(aTimestamp)));
137: }
138:
139: /**
140: * Records that a file checked ok.
141: * @param aFileName name of the file that checked ok
142: * @param aTimestamp the timestamp of the file
143: */
144: void checkedOk(String aFileName, long aTimestamp) {
145: mDetails.put(aFileName, Long.toString(aTimestamp));
146: }
147:
148: /**
149: * Calculates the hashcode for a GlobalProperties.
150: *
151: * @param aConfiguration the GlobalProperties
152: * @return the hashcode for <code>aConfiguration</code>
153: */
154: private String getConfigHashCode(Serializable aConfiguration) {
155: try {
156: // im-memory serialization of Configuration
157:
158: final ByteArrayOutputStream baos = new ByteArrayOutputStream();
159: final ObjectOutputStream oos = new ObjectOutputStream(baos);
160: oos.writeObject(aConfiguration);
161: oos.flush();
162: oos.close();
163:
164: // Instead of hexEncoding baos.toByteArray() directly we
165: // use a message digest here to keep the length of the
166: // hashcode reasonable
167:
168: final MessageDigest md = MessageDigest.getInstance("SHA");
169: md.update(baos.toByteArray());
170:
171: return hexEncode(md.digest());
172: } catch (final Exception ex) { // IO, NoSuchAlgorithm
173: Utils.getExceptionLogger().debug(
174: "Unable to calculate hashcode.", ex);
175: return "ALWAYS FRESH: " + System.currentTimeMillis();
176: }
177: }
178:
179: /** hex digits */
180: private static final char[] HEX_CHARS = { '0', '1', '2', '3', '4',
181: '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', };
182:
183: /** mask for last byte */
184: private static final int MASK_0X0F = 0x0F;
185:
186: /** bit shift */
187: private static final int SHIFT_4 = 4;
188:
189: /**
190: * Hex-encodes a byte array.
191: * @param aByteArray the byte array
192: * @return hex encoding of <code>aByteArray</code>
193: */
194: private static String hexEncode(byte[] aByteArray) {
195: final StringBuffer buf = new StringBuffer(2 * aByteArray.length);
196: for (int i = 0; i < aByteArray.length; i++) {
197: final int b = aByteArray[i];
198: final int low = b & MASK_0X0F;
199: final int high = (b >> SHIFT_4) & MASK_0X0F;
200: buf.append(HEX_CHARS[high]);
201: buf.append(HEX_CHARS[low]);
202: }
203: return buf.toString();
204: }
205: }
|