001    package hirondelle.web4jtools.metrics.tabs;
002    
003    import static hirondelle.web4j.util.Consts.EMPTY_STRING;
004    import static hirondelle.web4j.util.Consts.NEW_LINE;
005    import static hirondelle.web4j.util.Consts.SPACE;
006    import hirondelle.web4j.action.ResponsePage;
007    import hirondelle.web4j.request.RequestParser;
008    import hirondelle.web4j.util.Util;
009    import hirondelle.web4jtools.metrics.base.FileInfo;
010    import hirondelle.web4jtools.metrics.base.MetricsAction;
011    import hirondelle.web4jtools.util.Ensure;
012    
013    import java.io.BufferedWriter;
014    import java.io.File;
015    import java.io.FileNotFoundException;
016    import java.io.FileOutputStream;
017    import java.io.IOException;
018    import java.io.OutputStreamWriter;
019    import java.io.Writer;
020    import java.util.Iterator;
021    import java.util.Scanner;
022    import java.util.logging.Logger;
023    
024    import javax.servlet.ServletConfig;
025    
026    /**
027    * Replace all tab characters in source code with spaces.
028    * The number of spaces used is controlled by the 
029    * <tt>NumSpacesForTabs</tt> setting in <tt>web.xml</tt>.
030    * 
031    * <P>Since this action can potentially make a large number of <em>irreversible</em>
032    * edits to your source code, it is prudent to make a back up copy of your source before 
033    * executing. 
034    */
035    public class TabsRemoveAction extends MetricsAction {
036    
037      /** Read in config from web.xml.  Called only upon startup. */
038      public static void readConfig(ServletConfig aConfig){
039        fNUM_SPACES_FOR_TABS = aConfig.getInitParameter(NUM_SPACES_FOR_TABS);
040        Ensure.isPresentInWebXml(NUM_SPACES_FOR_TABS, fNUM_SPACES_FOR_TABS);
041        if( Integer.valueOf(fNUM_SPACES_FOR_TABS) < 1 ) {
042          throw new IllegalArgumentException(NUM_SPACES_FOR_TABS + " setting in web.xml must be 1 or more. Value : " + Util.quote(fNUM_SPACES_FOR_TABS));
043        }
044        fREPLACEMENT_FOR_TAB = getReplacementForTabs();
045        fENCODING = aConfig.getInitParameter(ENCODING);
046        Ensure.isPresentInWebXml(ENCODING, fENCODING);
047      }
048      
049      /** Full constructor.  */
050      public TabsRemoveAction(RequestParser aRequestParser) {
051        super(FORWARD, aRequestParser);
052      }
053    
054      /** 
055      * For all source code files, replace tab characters with spaces. 
056      */
057      @Override protected void calculateMetric() {
058        int tabsCount = 0;
059        Iterator<FileInfo> iter = fFileList.iterator();
060        while ( iter.hasNext() ){
061          FileInfo file = iter.next();
062          if ( file.getNumTabs() > 0 ) {
063            fLogger.fine("Replacing tabs in " + file.getName().getRawString());
064            tabsCount = tabsCount + replaceTabsAndUpdateFileInfo(file);
065          }
066        }
067        fLogger.fine("Total number of tab characters replaced in source files: " + tabsCount);
068        addMessage("Please re-scan your source code (Summary page) to verify no TAB chars remain.");
069        setResponsePage(REDIRECT_TO_LISTING);
070      }
071      
072      // PRIVATE  //
073      private static final String NUM_SPACES_FOR_TABS = "NumSpacesForTabs";
074      private static String fNUM_SPACES_FOR_TABS;
075      private static String fREPLACEMENT_FOR_TAB;
076      private static final String ENCODING = "CharacterSetForGeneratedFiles";
077      private static String fENCODING;
078      
079      private static final ResponsePage FORWARD = new ResponsePage("Files With TAB Chars", "view.jsp", TabsRemoveAction.class);
080      private static final ResponsePage REDIRECT_TO_LISTING = new ResponsePage("TabsListAction.do");
081      
082      private static final Logger fLogger = Util.getLogger(TabsRemoveAction.class);
083      
084      private int replaceTabsAndUpdateFileInfo(FileInfo aFile) {
085        assert(aFile.getNumTabs() > 0);
086        int result = aFile.getNumTabs();
087        File file = new File(aFile.getName().getRawString());
088        String fileContent = getContentOf(file);
089        String updatedContent = replaceTabs(fileContent);
090        save(updatedContent, file);
091        return result;
092      }
093      
094      private String getContentOf(File aFile){
095        StringBuilder result = new StringBuilder();
096        try { 
097          Scanner scanner = new Scanner(aFile);
098          while ( scanner.hasNextLine() ) {
099            result.append(scanner.nextLine() + NEW_LINE);
100          }
101          scanner.close();
102        }
103        catch(FileNotFoundException ex){
104          fLogger.severe("Cannot open file named " + aFile.getName());
105        }
106        return result.toString();
107      }
108      
109      private static String getReplacementForTabs(){
110        String result = EMPTY_STRING;
111        Integer numSpaces = Integer.valueOf(fNUM_SPACES_FOR_TABS);
112        for (int idx=1; idx <= numSpaces; ++idx){
113          result = result + SPACE;
114        }
115        fLogger.fine("Replacement for tabs : " + Util.quote(result));
116        return result;
117      }
118      
119      private String replaceTabs(String aFileContent){
120        return aFileContent.replace("\t", fREPLACEMENT_FOR_TAB);
121      }
122      
123      /** Write the new contents to the file, using encoding specified in web.xml.  */
124      private void  save(String aUpdatedContent, File aFile){
125        fLogger.fine("Updating contents of " + aFile.getAbsolutePath());
126        Writer output = null;
127        try {
128          FileOutputStream fos = new FileOutputStream(aFile.getAbsolutePath());
129          OutputStreamWriter out = new OutputStreamWriter(fos, fENCODING); 
130          output = new BufferedWriter(out);
131          output.write( aUpdatedContent );
132        }
133        catch (IOException ex){
134          fLogger.severe("Replacing tags, but cannot write to file named : " + Util.quote(aFile.getAbsolutePath()));
135        }
136        finally {
137          try {
138            if (output != null) output.close();
139          }
140          catch (IOException ex) {
141            fLogger.severe("Replacing tags, but cannot close file named : " + Util.quote(aFile.getAbsolutePath()));
142          }
143        }    
144      }
145    }