Coverage Report - com.puppycrawl.tools.checkstyle.PropertyCacheFile
 
Classes in this File Line Coverage Branch Coverage Complexity
PropertyCacheFile
20%
13/62
22%
4/18
3
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2014  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;
 20  
 
 21  
 import java.io.FileInputStream;
 22  
 import java.io.FileNotFoundException;
 23  
 import java.io.FileOutputStream;
 24  
 import java.io.IOException;
 25  
 import java.io.ByteArrayOutputStream;
 26  
 import java.io.ObjectOutputStream;
 27  
 import java.io.OutputStream;
 28  
 import java.io.Serializable;
 29  
 import java.util.Properties;
 30  
 import java.security.MessageDigest;
 31  
 
 32  
 
 33  
 import com.puppycrawl.tools.checkstyle.api.Configuration;
 34  
 import com.puppycrawl.tools.checkstyle.api.Utils;
 35  
 
 36  
 /**
 37  
  * This class maintains a persistent store of the files that have
 38  
  * checked ok and their associated timestamp. It uses a property file
 39  
  * for storage.  A hashcode of the Configuration is stored in the
 40  
  * cache file to ensure the cache is invalidated when the
 41  
  * configuration has changed.
 42  
  *
 43  
  * @author Oliver Burn
 44  
  */
 45  
 final class PropertyCacheFile
 46  
 {
 47  
     /**
 48  
      * The property key to use for storing the hashcode of the
 49  
      * configuration. To avoid nameclashes with the files that are
 50  
      * checked the key is chosen in such a way that it cannot be a
 51  
      * valid file name.
 52  
      */
 53  
     private static final String CONFIG_HASH_KEY = "configuration*?";
 54  
 
 55  
     /** name of file to store details **/
 56  
     private final String mDetailsFile;
 57  
     /** the details on files **/
 58  546
     private final Properties mDetails = new Properties();
 59  
 
 60  
     /**
 61  
      * Creates a new <code>PropertyCacheFile</code> instance.
 62  
      *
 63  
      * @param aCurrentConfig the current configuration, not null
 64  
      * @param aFileName the cache file
 65  
      */
 66  
     PropertyCacheFile(Configuration aCurrentConfig, String aFileName)
 67  546
     {
 68  546
         boolean setInActive = true;
 69  546
         if (aFileName != null) {
 70  0
             FileInputStream inStream = null;
 71  
             // get the current config so if the file isn't found
 72  
             // the first time the hash will be added to output file
 73  0
             final String currentConfigHash = getConfigHashCode(aCurrentConfig);
 74  
             try {
 75  0
                 inStream = new FileInputStream(aFileName);
 76  0
                 mDetails.load(inStream);
 77  0
                 final String cachedConfigHash =
 78  
                     mDetails.getProperty(CONFIG_HASH_KEY);
 79  0
                 setInActive = false;
 80  0
                 if ((cachedConfigHash == null)
 81  
                     || !cachedConfigHash.equals(currentConfigHash))
 82  
                 {
 83  
                     // Detected configuration change - clear cache
 84  0
                     mDetails.clear();
 85  0
                     mDetails.put(CONFIG_HASH_KEY, currentConfigHash);
 86  
                 }
 87  
             }
 88  0
             catch (final FileNotFoundException e) {
 89  
                 // Ignore, the cache does not exist
 90  0
                 setInActive = false;
 91  
                 // put the hash in the file if the file is going to be created
 92  0
                 mDetails.put(CONFIG_HASH_KEY, currentConfigHash);
 93  
             }
 94  0
             catch (final IOException e) {
 95  0
                 Utils.getExceptionLogger()
 96  
                     .debug("Unable to open cache file, ignoring.", e);
 97  
             }
 98  
             finally {
 99  0
                 Utils.closeQuietly(inStream);
 100  0
             }
 101  
         }
 102  546
         mDetailsFile = (setInActive) ? null : aFileName;
 103  546
     }
 104  
 
 105  
     /** Cleans up the object and updates the cache file. **/
 106  
     void destroy()
 107  
     {
 108  543
         if (mDetailsFile != null) {
 109  0
             FileOutputStream out = null;
 110  
             try {
 111  0
                 out = new FileOutputStream(mDetailsFile);
 112  0
                 mDetails.store(out, null);
 113  
             }
 114  0
             catch (final IOException e) {
 115  0
                 Utils.getExceptionLogger()
 116  
                     .debug("Unable to save cache file.", e);
 117  
             }
 118  
             finally {
 119  0
                 this.flushAndCloseOutStream(out);
 120  0
             }
 121  
         }
 122  543
     }
 123  
 
 124  
     /**
 125  
      * Flushes and closes output stream.
 126  
      * @param aStream the output stream
 127  
      */
 128  
     private void flushAndCloseOutStream(OutputStream aStream)
 129  
     {
 130  0
         if (aStream != null) {
 131  
             try {
 132  0
                 aStream.flush();
 133  
             }
 134  0
             catch (final IOException ex) {
 135  0
                 Utils.getExceptionLogger()
 136  
                     .debug("Unable to flush output stream.", ex);
 137  
             }
 138  
             finally {
 139  0
                 Utils.closeQuietly(aStream);
 140  0
             }
 141  
         }
 142  0
     }
 143  
 
 144  
     /**
 145  
      * @return whether the specified file has already been checked ok
 146  
      * @param aFileName the file to check
 147  
      * @param aTimestamp the timestamp of the file to check
 148  
      */
 149  
     boolean alreadyChecked(String aFileName, long aTimestamp)
 150  
     {
 151  543
         final String lastChecked = mDetails.getProperty(aFileName);
 152  543
         return (lastChecked != null)
 153  
             && (lastChecked.equals(Long.toString(aTimestamp)));
 154  
     }
 155  
 
 156  
     /**
 157  
      * Records that a file checked ok.
 158  
      * @param aFileName name of the file that checked ok
 159  
      * @param aTimestamp the timestamp of the file
 160  
      */
 161  
     void checkedOk(String aFileName, long aTimestamp)
 162  
     {
 163  120
         mDetails.put(aFileName, Long.toString(aTimestamp));
 164  120
     }
 165  
 
 166  
     /**
 167  
      * Calculates the hashcode for a GlobalProperties.
 168  
      *
 169  
      * @param aConfiguration the GlobalProperties
 170  
      * @return the hashcode for <code>aConfiguration</code>
 171  
      */
 172  
     private String getConfigHashCode(Serializable aConfiguration)
 173  
     {
 174  
         try {
 175  
             // im-memory serialization of Configuration
 176  
 
 177  0
             final ByteArrayOutputStream baos = new ByteArrayOutputStream();
 178  0
             ObjectOutputStream oos = null;
 179  
             try {
 180  0
                 oos = new ObjectOutputStream(baos);
 181  0
                 oos.writeObject(aConfiguration);
 182  
             }
 183  
             finally {
 184  0
                 this.flushAndCloseOutStream(oos);
 185  0
             }
 186  
 
 187  
             // Instead of hexEncoding baos.toByteArray() directly we
 188  
             // use a message digest here to keep the length of the
 189  
             // hashcode reasonable
 190  
 
 191  0
             final MessageDigest md = MessageDigest.getInstance("SHA");
 192  0
             md.update(baos.toByteArray());
 193  
 
 194  0
             return hexEncode(md.digest());
 195  
         }
 196  0
         catch (final Exception ex) { // IO, NoSuchAlgorithm
 197  0
             Utils.getExceptionLogger()
 198  
                 .debug("Unable to calculate hashcode.", ex);
 199  0
             return "ALWAYS FRESH: " + System.currentTimeMillis();
 200  
         }
 201  
     }
 202  
 
 203  
     /** hex digits */
 204  1
     private static final char[] HEX_CHARS = {
 205  
         '0', '1', '2', '3', '4', '5', '6', '7',
 206  
         '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
 207  
     };
 208  
 
 209  
     /** mask for last byte */
 210  
     private static final int MASK_0X0F = 0x0F;
 211  
 
 212  
     /** bit shift */
 213  
     private static final int SHIFT_4 = 4;
 214  
 
 215  
     /**
 216  
      * Hex-encodes a byte array.
 217  
      * @param aByteArray the byte array
 218  
      * @return hex encoding of <code>aByteArray</code>
 219  
      */
 220  
     private static String hexEncode(byte[] aByteArray)
 221  
     {
 222  0
         final StringBuffer buf = new StringBuffer(2 * aByteArray.length);
 223  0
         for (final byte b : aByteArray) {
 224  0
             final int low = b & MASK_0X0F;
 225  0
             final int high = (b >> SHIFT_4) & MASK_0X0F;
 226  0
             buf.append(HEX_CHARS[high]);
 227  0
             buf.append(HEX_CHARS[low]);
 228  
         }
 229  0
         return buf.toString();
 230  
     }
 231  
 }