Showing posts with label Applicaion Build. JSP page. Show all posts
Showing posts with label Applicaion Build. JSP page. Show all posts

Tuesday, January 10, 2012

Deleting all JS comments from JSP page at build time: Part 2

Writing an ANT task is easy; all you need to extend is Task Class.  I have written one, it is simple java program.  This ant task is now being added in our application build tasks. This will help us in keeping the developers comments for readability and future reference, but cleaning all the JS comments from final build, hence mitigating the reputation risk. Here is the ant task declaration and configuration.

<taskdef name="jsscript" classname="com.aman.task.JSPDeleteScriptCommensRegExp"/>

<target name="allcomments">
<fileset id="jsp.fileset" dir="${src.dir}" includes="**/*.jsp"/>

<jsscript matchInline="(^|[^:\-'\&quot;])(//+.*(?!(\*/))$)" replaceInline="\1" matchMultiline="(^|[^/])(/\*+([\S\s](?!(/+\*+)))+?\*+/+)" replaceMultiline="\1">
            <fileset refid="jsp.fileset"/>
      </jsscript>
</target>

com.aman.task.JSPDeleteScriptCommensRegExp is the class that I have written to delete all the JS comment.

matchInlineis the property of the above class, this hold the inline comment pattern and this will be replaced with replaceInline  “\1”: this is the backreference of regular expression. We are replacing only the first group as second group is all comment.

matchMultilineis the again a similar property to delete multiline comments and this will be replaced by replaceMultiline – “\1”.

Matching and replacing the inline and multiline comments are handled differently.
Every line is searched for and replaced with pattern one at a time in inline matching, whereas in multiline all the code inside the script block is first buffred in and then matched and replaced.

I am not describing the regex used in detail, but if you have any concern please do contact me.

Here is the full code for reference

package com.aman.task;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.optional.ReplaceRegExp;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.RegularExpression;
import org.apache.tools.ant.types.Substitution;
import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.ant.util.regexp.Regexp;


public class JSPDeleteScriptCommensRegExp extends Task {

       private long totalline;
       private long comment;
       private long mcomment;
       private Vector filesets;
       private RegularExpression inlineRegex;
       private Substitution inlineSubs;
       private RegularExpression multilineRegex;
       private Substitution multiLineSubs;
       FileUtils fileUtils = FileUtils.getFileUtils();
     
       public JSPDeleteScriptCommensRegExp() {
                     super();
                     this.filesets = new Vector();
                    
                         this.inlineRegex = null;
                     this.inlineSubs = null;
       }
       
       public void setMatchInline(String match) {
                    if (inlineRegex != null) {
                         throw new BuildException("Only one inline regular expression is allowed");
                     }
                             inlineRegex = new RegularExpression();
                         inlineRegex.setPattern(match);
       }
       
       public void setReplaceInline(String replace) {
                if (inlineSubs != null) {
                         throw new BuildException("Only one inline substitution expression is "
                                                + "allowed");
                   }
     
              inlineSubs = new Substitution();
              inlineSubs.setExpression(replace);
      }
     
       public void setMatchMultiline(String match) {
              if (multilineRegex != null) {
                   throw new BuildException("Only one multiline regular expression is allowed");
               }
              multilineRegex = new RegularExpression();
              multilineRegex.setPattern(match);
      }
     
      public void setReplaceMultiline(String replace) {
             if (multiLineSubs != null) {
                        throw new BuildException("Only one multiline substitution expression is "
                                                + "allowed");
                   }
     
             multiLineSubs = new Substitution();
             multiLineSubs.setExpression(replace);
      }
       
       public void addFileset(FileSet set) {
             filesets.addElement(set);
     }
       
       protected String doReplace(RegularExpression r, Substitution s, String input, int options) {
                      String res = input;
                     Regexp regexp = r.getRegexp(getProject());
                 
                   if (regexp.matches(input, options)) {
                       log("Found match; substituting", Project.MSG_DEBUG);
                       res = regexp.substitute(input, s.getExpression(getProject()),
                                               options);
                   }
           
                    return res;
         }
       
       protected void doReplaceInline(File f, int options) throws IOException {
           
            File temp = fileUtils.createTempFile("replace", ".txt", null);
        temp.deleteOnExit();

        Reader r = null;
        Writer w = null;

        try {
          
            r = new FileReader(f);
            w = new FileWriter(temp);
          
            BufferedReader br = new BufferedReader(r);
            BufferedWriter bw = new BufferedWriter(w);
            PrintWriter pw = new PrintWriter(bw);

            boolean changes = false;

           
            StringBuffer linebuf = new StringBuffer();
            String line = null;
            String res = null;
            int c;
            boolean scriptFound = false;
            Pattern scriptStart = Pattern.compile("<script[\\S\\s]*?>");
            Pattern inlineScript = Pattern.compile("<script[\\S\\s]*?((/>)|(</script>))");
            Pattern scriptend = Pattern.compile("</script");
            while((line = br.readLine()) != null)
            {
                  totalline++;
                  Matcher scriptMacther = scriptStart.matcher(line);
                  Matcher inlineMatcher = inlineScript.matcher(line);
                  Matcher sciptendMatcher = scriptend.matcher(line);
                  if(inlineMatcher.find())
                  {
                        // dont do anything just print it in final file and escape
                  }
                  else if(scriptMacther.find())
                  {
                        scriptFound = true;
                       
                  }
                  else if(sciptendMatcher.find())
                  {
                        scriptFound = false;
                       
                  }
                 
                  if(scriptFound)
                  {
                        res  = doReplace(inlineRegex, inlineSubs, line, options);
                        if (!res.equals(line)) {
                        changes = true;
                        comment++;
                    }

                    pw.println(res);
                  }
                  else
                  {
                        pw.println(line);
                  }
            }
            pw.flush();
            r.close();
            r = null;
            w.close();
            w = null;
            if (changes) {
                log("File has changed; saving the updated file", Project.MSG_VERBOSE);
                try {
                    fileUtils.rename(temp, f);
                    temp = null;
                } catch (IOException e) {
                    throw new BuildException("Couldn't rename temporary file "
                                             + temp, getLocation());
                }
            } else {
                log("No change made", Project.MSG_DEBUG);
            }
        } finally {
            try {
                if (r != null) {
                    r.close();
                }
            } catch (Exception e) {
                // ignore any secondary exceptions
            }

            try {
                if (w != null) {
                    w.close();
                }
            } catch (Exception e) {
                // ignore any secondary exceptions
            }
            if (temp != null) {
                temp.delete();
            }
        }  
      }
       
       protected void doReplaceMuliline(File f, int options) throws IOException {
                 
               File temp = fileUtils.createTempFile("replace", ".txt", null);
             temp.deleteOnExit();

             Reader r = null;
             Writer w = null;

             try {
               
                 r = new FileReader(f);
                 w = new FileWriter(temp);
               
                 BufferedReader br = new BufferedReader(r);
                 BufferedWriter bw = new BufferedWriter(w);
                 PrintWriter pw = new PrintWriter(bw);

                 boolean changes = false;

                 StringBuffer linebuf = new StringBuffer();
                 String line = null;
                 String res = null;
                 int c;
                 boolean scriptFound = false;
                 boolean startMatch = false;
                 Pattern scriptStart = Pattern.compile("<script[\\S\\s]*?>");
                 Pattern inlineScript = Pattern.compile("<script[\\S\\s]*?((/>)|(</script>))");
                 Pattern scriptend = Pattern.compile("</script");
                 while((line = br.readLine()) != null)
                 {
                 Matcher scriptMacther = scriptStart.matcher(line);
                 Matcher inlineMatcher = inlineScript.matcher(line);
                 Matcher sciptendMatcher = scriptend.matcher(line);
                 if(inlineMatcher.find())
                 {
                 }
                 else if(scriptMacther.find())
                 {
                        scriptFound = true;
                 }
                 else if(sciptendMatcher.find() && scriptFound)
                 {
                        startMatch = true;
                        scriptFound = false;
                 }
                
                 if(scriptFound || (!scriptFound && startMatch))
                 {
                        linebuf.append(line).append(System.getProperty("line.separator"));
                 }
                 else
                 {
                        pw.println(line);
                 }
                
                 if(startMatch)
                 {
                        line = linebuf.toString();
                        res  = doReplace(multilineRegex, multiLineSubs, line, options);
                        if (!res.equals(line)) {
                         changes = true;
                         mcomment++;
                        }

                     pw.print(res);
                     linebuf = new StringBuffer();
                     startMatch = false;
                 }
                 }
                 pw.flush();
                 r.close();
                 r = null;
                 w.close();
                 w = null;
                 if (changes) {
                     log("File has changed; saving the updated file", Project.MSG_VERBOSE);
                     try {
                         fileUtils.rename(temp, f);
                         temp = null;
                     } catch (IOException e) {
                         throw new BuildException("Couldn't rename temporary file "
                                                  + temp, getLocation());
                     }
                 } else {
                     log("No change made", Project.MSG_DEBUG);
                 }
             } finally {
                 try {
                     if (r != null) {
                         r.close();
                     }
                 } catch (Exception e) {
                     // ignore any secondary exceptions
                 }

                 try {
                     if (w != null) {
                         w.close();
                     }
                 } catch (Exception e) {
                     // ignore any secondary exceptions
                 }
                 if (temp != null) {
                     temp.delete();
                 }
             }  
            }


      public void execute() throws BuildException {
            if (inlineRegex == null) {
                  throw new BuildException("No inline expression to match.");
            }
            if (inlineSubs == null) {
                  throw new BuildException("Nothing to replace inline expression with.");
            }
           
            if (multilineRegex == null) {
                  throw new BuildException("No multiline expression to match.");
            }
            if (multiLineSubs == null) {
                  throw new BuildException("Nothing to replace multiline expression with.");
            }

           
            int options = 0;

           
            options |= Regexp.REPLACE_ALL;
        options |= Regexp.MATCH_CASE_INSENSITIVE;
   
        int sz = filesets.size();

       for (int i = 0; i < sz; i++) {
        FileSet fs = (FileSet) (filesets.elementAt(i));
        DirectoryScanner ds = fs.getDirectoryScanner(getProject());

        String[] files = ds.getIncludedFiles();

        for (int j = 0; j < files.length; j++) {
            File f = new File(fs.getDir(getProject()), files[j]);

            if (f.exists()) {
                try {
                  long tempTotalLine =totalline;
                  long tempComment = comment;
                  long tempMComment = mcomment;
                  String fileNameWithPath = f.getAbsolutePath();
                    doReplaceInline(f, options);
                    doReplaceMuliline(f, options);
                    log("File - "+fileNameWithPath +" Line - "+(totalline-tempTotalLine)+"            inline comments - "+(comment-tempComment)+"           multiline comments - "+(mcomment-tempMComment), Project.MSG_INFO);
                } catch (Exception e) {
                    log("An error occurred processing file: '"
                        + f.getAbsolutePath() + "': " + e.toString(),
                        Project.MSG_ERR);
                }
            } else {
                log("The following file is missing: '"
                    + f.getAbsolutePath() + "'", Project.MSG_ERR);
                  }
            }
      }
       log("Total Line - "+totalline+"          total inline comments - "+comment+"       total multiline comments - "+mcomment);
      }
     
     
}


Sunday, January 8, 2012

Deleting all JS comments from JSP page at build time: Part 1


Deleting JS comments from JSP pages

I have been asked to delete all the JS comments from JSP page. The task is tedious and daunting. Deleting even a single unexpected character can make the application unstable. If I make a 2-3 year old stable system, unstable, I am fired. And If I have to sit and delete all the comments manually, I will set myself on fire. Seriously..

So this is the time to do some brainstorming to avoid above two situations. I started with regex to find the script block in JSP. After 1-2 hours of struggle, I have found out regex

<script[\\S\\s]*?>    - This regex will find all the opening script tag.
<script[\\S\\s]*?((/>)|(</script>))   - This will find all the script block which include external js file.
</script  - This regex will find all the end script tag. (Attention: no closing angle bracket at the end. This is to avoid </script --%>. Yes, some developer can comment the entire script block with jsp comments.)

No I have 3 tools to strip off all the script blocks from JSP. I am doing this to make sure that I am deleting only JS comments and nothing else.

Now second step is to delete all multi-line comments first. Why multi-line comments first, why not inline comments…
After 2 hours of testing multi-line first or inline first, I have the answer, what would happen if I am deleting inline comments first and I hit something like this


<script type="text/javascript">
/*
            document.write("<h1>This is a heading</h1>");
            document.write("<p>This is a paragraph.</p>");
            document.write("<p>This is another paragraph.</p>");
//*********************
//*/
</script>

Second and third comments will get deleted and next when I will be deleting multi-line, entire multi-line comment will not get detected. And I am screwed.

So, I decided to delete the multi-line comments first. What I am trying to do is search a pattern inside a matched pattern. If you know how to do this, please let me know..

I have tried some regex, here are my findings

This regex
(<script([\S\s]*?)>)(([\S\s]*?)([^:'\-"]//.*[^'"]$)([\S\s]*?))(</script>)

is keeping only first comments in backreference, hence only first comment in script block can be deleted.
And this regex

(<script([\S\s]*?)>)(([\S\s]*?)([^:'\-"]//.*[^'"]$)([\S\s]*?))*(</script>)

only keeps the last comments in backreference, hence this will not work too.

I have to delete all the comments in script block. I decided to write a program to strip off all the script blocks from the JSP and then delete all the multi-line comments first and then all inline comments.

Writing a java program from scratch and establishing all the infra needs like logging, file and directory handling is wasting of time, focus on main task. Yes I will write the task only: ANT Task.
All plan set, started to write a ANT task to delete all the JS comments from JSP files.

I will discuss the ANT task in my next post.