PMDLauncher.java :  » IDE » tIDE » tide » exttools » PMD » Java Open Source

Java Open Source » IDE » tIDE 
tIDE » tide » exttools » PMD » PMDLauncher.java
package tide.exttools.PMD;

import tide.sources.SourceFile;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
import org.xml.sax.SAXException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import tide.editor.linemessages.*;
import tide.editor.*;
import tide.project.*;
import tide.utils.*;
import snow.utils.gui.*;
import snow.utils.storage.*;
import snow.utils.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.*;
import java.awt.event.*;
import java.io.*;
import java.util.*;
import java.util.regex.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import javax.xml.transform.stream.StreamSource;

/** PMD is nice and quick.
*/
public final class PMDLauncher// implements ChangedFilesSaveListener
{
  private static PMDLauncher instance = null;

  private PMDLauncher()
  {
  }

  public static PMDLauncher getInstance()
  {
     if(instance==null) instance = new PMDLauncher();
     return instance;
  }

  /** Should be called at startup and each project settings changes and load ops
  *
  public void configureAutoScan()
  {
     ProjectSettings settings = MainEditorFrame.instance.getActualProject();
     boolean isAuto = settings.getBooleanProperty("pmd_autoApplyOnChanges",false) && PMDSettingsDialog.isConfigured();


     if(isAuto)
     {
        // added only once !
        MainEditorFrame.instance.editorPanel.addChangedFilesSaveListener(this);
     }
     else
     {
        MainEditorFrame.instance.editorPanel.removeChangedFilesSaveListener(this);
     }
  }*/

  /** ChangedFilesSaveListener interface.
  */
  public void filesSaved(final Set<SourceFile> sources)
  {
     if(!PMDSettingsDialog.isConfigured()) return;


     // Enforce EDT, because we are not necessary beeing called from edt here
     final ProgressModalDialog[] pmd = new ProgressModalDialog[1];
     SwingSafeRunnable.invokeAndWait(new Runnable() { public void run() {   // MUST WAIT HERE!
       pmd[0] = new ProgressModalDialog(MainEditorFrame.instance,
              "PMD autoanalysis ("+sources.size()+" file"+(sources.size()==1?"":"s")+")", false);
       pmd[0].startDelayed(30000L); // big delay, normally never visible...
     }});

     Thread t = new Thread()
     {
        public void run()
        {
           try
           {
              _analyse(true, pmd[0], sources.toArray(new SourceFile[sources.size()]));  // Silent
              /*for(SourceFile sf : sources)
              {
                 if(pmd.getWasCancelled()) break;
                _analyse(sf, true, pmd);  // Silent
              }*/
           } catch(Exception ex)
           {
              ex.printStackTrace();
           }
           finally
           {
              pmd[0].closeDialog();
           }
        }
     };
     t.start();
  }

  public static void _analyse(boolean silentMode, ProgressModalDialog pmd, SourceFile... sfs) throws Exception
  {

     if(sfs.length==1)
     {
        SourceFile sf = sfs[0];
        if(sf.isSourceFile())
        {
          LineMessagesManager.getInstance().removeMessagesFor(sf.getJavaName(), PMDMessage.class );
          analyse(silentMode, pmd, Arrays.asList(sf.javaFile));
        }
        else if(sf.isDirectory())
        {
          String pn = sf.getPackageName();
          if(pn.length()>0) pn+=".";
          LineMessagesManager.getInstance().removeMessagesForJavaNameStartingWith(pn, PMDMessage.class);
          File d = new File(MainEditorFrame.instance.getActualProject().getSources_Home(),
             sf.getJavaName().replace(".", "/"));
          analyse( silentMode, pmd, Arrays.asList(d) );
        }
     }
     else
     {
          List<File> lf = new ArrayList<File>();
          for(SourceFile sfi : sfs)
          {
            LineMessagesManager.getInstance().removeMessagesFor(sfi.getJavaName(), PMDMessage.class );
            lf.add(sfi.javaFile);
          }
          analyse(silentMode, pmd, lf);
     }
  }

  /** As argument: either a source or a directory.
  *  must be called in a separate thread
  */
  private static void analyse(boolean silentMode, ProgressModalDialog pmd, List<File> sourcesToAnalyse) throws Exception
  {
    ProjectSettings actualProject = MainEditorFrame.instance.getActualProject();

    boolean ignoreIrrelevant = actualProject.getBooleanProperty("pmd_ignoreIrrelevant", false);


    File pmdJar = new File(actualProject.getProperty("PMD_path", PMDSettingsDialog.defaultPMDLocation));
    if(!pmdJar.exists()) throw new Exception ("pmd jar not found at "+pmdJar.getAbsolutePath());

    File javaExePath = actualProject.getJava_TOOL();
    String options = actualProject.getProperty("PMD_Options", PMDSettingsDialog.defaultOptions);

    List<String> execCommand = new ArrayList<String>();
    execCommand.add( javaExePath.getAbsolutePath() );
    execCommand.add( "-jar" );
    execCommand.add( pmdJar.getAbsolutePath() );

    // 1: source (file or dir (recurse) )
    if(sourcesToAnalyse.size()==1)
    {
       execCommand.add( sourcesToAnalyse.get(0).getAbsolutePath() );
    }
    else
    {
       // comma "," separated (as saw in sourcecode of PMD)
       StringBuilder sb = new StringBuilder();
       for(int i=0; i<sourcesToAnalyse.size()-1; i++)
       {
          sb.append( sourcesToAnalyse.get(i).getAbsolutePath());
          sb.append( ",");
       }
       sb.append( sourcesToAnalyse.get(sourcesToAnalyse.size()-1).getAbsolutePath());
       execCommand.add( sb.toString() );
    }

    // 2: report format
    execCommand.add( "xml");  // html, xml

    // 3: rules list
    String rules = actualProject.getProperty("PMD_UsedRules", PMDSettingsDialog.recommandedPMDRules).trim();
    rules = rules.replace(" ",",");  // remove spaces
    rules = rules.replace("\n",",");  // and cr lf
    rules = rules.replace("\r","");  //
    rules = rules.replace(",,",",");  // remove double ,

    if(rules.toLowerCase().indexOf(".xml/")<0)
    {
       // OLD Style: nothing special specified...
       execCommand.add( rules );
    }
    else
    {
       // we have "subrules" => create
       File tf = new File(actualProject.getProjectSettingsFolder(), "pmdcustom.xml");
       tf.deleteOnExit();
       FileUtils.saveToFile( generateXMLCustomRuleSet(rules), tf);
       execCommand.add( tf.getAbsolutePath() );
    }

    // 4: opts
    if(options!=null && options.trim().length()>0)
    {
       execCommand.addAll( ProjectUtils.splitArgs(options,false));
    }

    if(!silentMode)
    {
       MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc.clearDocument();
       MainEditorFrame.instance.outputPanels.selectToolsTab(false);
       MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc.appendDatedLine("PMD analysis of "+sourcesToAnalyse+":\r\n");
       MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc.appendLine("Used rules: "+rules);

    }

    MainEditorFrame.debugOut("Running PMD: "+execCommand);
    Process proc = new ProcessBuilder(execCommand).start();

    pmd.setProcessToOptionalKill(proc);
    if(!silentMode) pmd.start();

    if(!silentMode)
    {
      MainEditorFrame.instance.outputPanels.processesManager.addProcess("PMD analysis of "+sourcesToAnalyse+"", proc, true);
    }

    // write the analysis result in a file
    File file = File.createTempFile("tIDE_pmd_analysis.tmp", ".xml");     //"temp/pmd_temp_res.xml");
    file.deleteOnExit();

    FileOutputStream fos = new FileOutputStream(file);
    StreamGobbler sg = new StreamGobbler(proc.getInputStream(), fos);
       //MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc.createWriterForThisDocument(false), "");
    sg.start();

    // errors
    StreamGobbler sge = new StreamGobbler(proc.getErrorStream(),
       MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc.createWriterForThisDocument(true), "PMD");
    sge.start();

    // wait until proc completed...
    proc.waitFor();

    fos.close();

    // XSLT
    String xsltRelPath = "tide/exttools/PMD/simple_pmd_report.xsl";
    InputStream is = PMDLauncher.class.getClassLoader().getResourceAsStream(xsltRelPath); // jar mode
    if(is==null)
    {
      // IDE mode (develop)
      //System.out.println("no PMD xsl found in jar");
      File fi = new File(xsltRelPath);
      if(fi.exists())
      {
        is = new FileInputStream( fi );
      }
      else
      {
        MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc.appendError("\nXSLT transform file not found in "+xsltRelPath);
        System.out.println("File not found: "+fi);
      }
    }


    if(is!=null)
    {
      try
      {
         String res = XMLUtils.transformXML( is, new StreamSource(file));

         Map[] stats = parseMessages(res, ignoreIrrelevant);

         if(!silentMode)
         {
            @SuppressWarnings("unchecked")
            Map<String, Integer>  catCounts   = (Map<String, Integer>) stats[0];
            @SuppressWarnings("unchecked")
            Map<Integer, Integer> priorCounts = (Map<Integer, Integer>)stats[1];
            int tot = 0;
            for(Integer ci : catCounts.values())
            {
               tot += ci;
            }
            MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc.appendLine("\n"+tot+" messages generated (see Messages tab)");

            // Stats print
            MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc.appendLine("\n"+priorCounts.size()+" priorit"+(priorCounts.size()==1?"y":"ies")+":");
            ArrayList<Integer> prior = new ArrayList<Integer>(priorCounts.keySet());
            Collections.sort(prior);

            for(Integer pi : prior)
            {
               int pic = priorCounts.get(pi);
               MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc.appendLine("\tpriority "+pi+":   \t"+pic+" message"+(pic==1?"":"s"));
            }

            MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc.appendLine("\n"+catCounts.size()+" categor"+(catCounts.size()==1?"y":"ies")+":");
            ArrayList<String> cats = new ArrayList<String>(catCounts.keySet());
            Collections.sort(cats);

            for(String cai : cats)
            {
               int ci = catCounts.get(cai);
               MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc.appendLine("\t"+ci+"\t   "+cai);
            }
         }
      }
      catch(Exception e)
      {
         // occurs often during auto analysis, when the file is not "compilable" and has been saved...
         // ex: javax.xml.transform.TransformerException: com.sun.org.apache.xml.internal.utils.WrappedRuntimeException: Premature end of file.
         if(!silentMode)
         {
           MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc.appendError("ERROR during PMD XSLT transform: "+e.getMessage()+"\n");
           MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc.appendLine(FileUtils.getFileStringContent(file));
         //e.printStackTrace();
         }
      }
      finally
      {
         is.close();
      }
    }
    else
    {
      MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc.appendLine(FileUtils.getFileStringContent(file));
    }

    if(!silentMode)
    {
      MainEditorFrame.instance.outputPanels.toolsOutputPanel.setCaret0();
    }
    LineMessagesManager.getInstance().refreshView();
  }

  /** The formatted output is generated from the transformed pmd xml output.
  *   the format is some white line separated messages ending with the source position.
  */
  private static Map[] parseMessages(String formattedOutput, boolean ignoreIrrelevant) throws Exception
  {
     Map<String, Integer> catCountStat = new HashMap<String, Integer>();
     Map<Integer, Integer> priorityCount = new HashMap<Integer, Integer>();

     String pa = MainEditorFrame.instance.getActualProject().getSources_Home().getAbsolutePath();
     if(!pa.endsWith("\\")) pa+= "\\";

     BufferedReader sr = new BufferedReader( new StringReader(formattedOutput));
     StringBuilder actualMess = new StringBuilder();
     String line = null;
     int count = 0;
  wl:while((line=sr.readLine())!=null)
     {
        //System.out.println(""+line);
        if(line.startsWith("==="))  // separate sources
        {
           actualMess.setLength(0);
           continue wl;
        }

        actualMess.append("\n"+line);

        if(line.startsWith(" at "))
        {
           String javaName = line.substring(4+pa.length());
           int pos = javaName.indexOf(".java:");
           int lineNr = -1;
           int colNr = -1;
           if(pos>0)
           {
              String[] lns = javaName.substring(pos+6).split(":");
              javaName = javaName.substring(0,pos);
              if(lns!=null && lns.length>0 && lns[0].length()>0)
              {
                lineNr = Integer.parseInt(lns[0]);
                if(lns.length>1)
                {
                   colNr = Integer.parseInt(lns[1])-1;
                }
              }
           }
           javaName = javaName.replace("\\", ".");

           PMDMessage pmm = PMDMessage.createAndAdd(javaName, actualMess.toString().trim(), lineNr,colNr, ignoreIrrelevant);
           count++;
           actualMess.setLength(0);
           String cat = pmm.getCategory();

           if(LineMessagesManager.getInstance().getIrrelevantCategories().contains(cat))
           {
             // TODO: also include a stat of ignored...
             continue;
           }

           // stats.

           if(!catCountStat.containsKey(cat))  { catCountStat.put(cat,1);}
           else {catCountStat.put( cat, catCountStat.get(cat)+1);}

           int prior = pmm.getPriority();
           if(!priorityCount.containsKey(prior))  { priorityCount.put(prior, 1);}
           else {priorityCount.put( prior, priorityCount.get(prior)+1);}

        }
     }
     return new Map[]{catCountStat, priorityCount};
  }

  public static String generateXMLCustomRuleSet(String rulesList)
  {
     StringBuilder sb = new StringBuilder();
     sb.append("<?xml version=\"1.0\"?>");
     sb.append("\n<ruleset name=\"tIDE custom PMD ruleset\"");
     sb.append("\nxmlns=\"http://pmd.sf.net/ruleset/1.0.0\"");
     sb.append("\nxmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"");
     sb.append("\nxsi:schemaLocation=\"http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd\"");
     sb.append("\nxsi:noNamespaceSchemaLocation=\"http://pmd.sf.net/ruleset_xml_schema.xsd\">");
     sb.append("\n\n<description>");
     sb.append("\nThis ruleset contains the PMD rules used by tIDE");
     sb.append("\n</description>\n");

     for(String ri : rulesList.split(","))
     {
        sb.append("\n<rule ref=\""+ri.trim()+"\"/>");
     }

     sb.append("\n</ruleset>");
     return sb.toString();
  }


  /** read all rules from jar.
  *   in the rulesets/ directory
  */
  public static RuleSet readRulesetsNames(File pmdJarFile, boolean withrules) throws Exception
  {
    ArrayList<String> rs = new ArrayList<String>();
    JarFile jf = null;
    RuleSet root = new RuleSet("","","");
    try
    {
      jf = new JarFile(pmdJarFile);
      Enumeration<JarEntry> entries = jf.entries();
      while(entries.hasMoreElements())
      {
        JarEntry je = entries.nextElement();
        if(je.getName().startsWith("rulesets/"))
        {
           String en = je.getName();
           if(en.toLowerCase().endsWith(".xml"))
           {
             rs.add(en);

             if(withrules)
             {
               String cont = FileUtils.getStringContent( jf.getInputStream(je));
               RuleSet ruse = parseRules(en, cont);
               root.add(ruse);
             }
           }

        }
      }
    }
    catch(Exception e)
    {
      throw e;
    }
    finally
    {
       jf.close();
    }
    return root;
  }

  static int nn=0;


  // minor bug: 38 != rulesets/releases/39.xml is named 38
  private static RuleSet parseRules(String fullName, String cont)
  {
     // Use the default (non-validating) parser
     SAXParserFactory factory = SAXParserFactory.newInstance();

     // Parse the file
     try
     {
        SAXParser saxParser = factory.newSAXParser();

        InputSource is = new InputSource(new StringReader(cont));
        RulesetParser rp = new RulesetParser();
        saxParser.parse( is, rp);
        RuleSet rs = new RuleSet(fullName, rp.rulesetName, rp.rulesetDescription);

        // verification:
        /*
        int pos = fullName.lastIndexOf('/');
        String name = fullName.substring(pos+1, fullName.length()-4);
        if(!name.equals(rp.rulesetName))
        {
           //System.out.println("Ruleset name mismatch xml file name: "+rp.rulesetName+" != "+fullName);
        }*/

        for(Rule r : rp.rules)
        {
           rs.add(r);
        }

        return rs;
     }
     catch(Exception ex)
     {
        ex.printStackTrace();
        System.out.println("Cannot parse <"+cont+">");
        throw new RuntimeException("cnp");
     }
  }

 /* public static void main(String[] aa)
  {
     PMDSettingsDialog.main(aa);
  }*/

  static class RulesetParser extends DefaultHandler
  {

    List<Rule> rules = new ArrayList<Rule>();
    String rulesetName;
    String rulesetDescription;

    boolean isInRule = false;
    StringBuilder lastChars = new StringBuilder();
    Rule actualRule = null;

    @Override
    public void startDocument()
    {
    }

    @Override
    public void endDocument()
    {
    }

    void pa(Attributes at)
    {
       for(int i=0; i<at.getLength(); i++)
       {
          System.out.print(""+at.getLocalName(i)+": "+at.getValue(i)+", ");
       }
       System.out.println("");
    }

    @Override
    public void startElement(String namespaceURI, String sName, String qName, Attributes attributes)
        throws SAXException
    {
       // reset
       lastChars.setLength(0);

       //sn is empty.
       //pa(attributes);
       if(qName.equalsIgnoreCase("ruleset"))
       {
          rulesetName = attributes.getValue("name");
       }
       else if(qName.equalsIgnoreCase("rule"))
       {
          isInRule = true;
          actualRule = new Rule();
          actualRule.setName( attributes.getValue("name") );
          actualRule.setMessage( attributes.getValue("message") );
          actualRule.setReference( attributes.getValue("ref") );

          rules.add(actualRule);
       }
    }

    @Override
    public void endElement(String namespaceURI,
                           String sName, // simple name
                           String qName  // qualified name
                          )   throws SAXException
    {
       if(!isInRule)
       {
          if(qName.equalsIgnoreCase("description"))
          {
             rulesetDescription = lastChars.toString().trim();
             lastChars.setLength(0);
          }
/*          else
          {
             //System.out.println("end1? "+qName);
          }*/
       }
       else
       {
          if(qName.equalsIgnoreCase("description"))
          {
             actualRule.setDescription(lastChars.toString().trim());
             lastChars.setLength(0);
          }
          else if(qName.equalsIgnoreCase("priority"))
          {
             actualRule.setPriority(lastChars.toString().trim());
             lastChars.setLength(0);
          }
         /* else
          {
             //System.out.println("end2? "+qName);
          }*/
       }
    }

    @Override
    public void characters(char[] buf, int offset, int len)  throws SAXException
    {
       if(lastChars.length()>0) lastChars.append(" ");
       lastChars.append( new String(buf,offset,len).trim() );
    }
  }
}

/*
Mandatory arguments:
1) A java source code filename or directory
2) A report format
3) A ruleset filename or a comma-delimited string of ruleset filenames

For example:
c:\> java -jar pmd-4.0.jar c:\my\source\code html unusedcode

Optional arguments that may be put before or after the mandatory arguments:
-debug: prints debugging information
-targetjdk: specifies a language version to target - 1.3, 1.4, 1.5 or 1.6; default is 1.5
-cpus: specifies the number of threads to create
-encoding: specifies the character set encoding of the source code files PMD is reading (i.e., UTF-8)
-excludemarker: specifies the String that marks the a line which PMD should ignore; default is NOPMD
-shortnames: prints shortened filenames in the report
-linkprefix: path to HTML source, for summary html renderer only
-lineprefix: custom anchor to affected line in the source file, for summary html renderer only
-minimumpriority: rule priority threshold; rules with lower priority than they will not be used
-nojava: do not check Java files; default to check Java files
-jsp: check JSP/JSF files; default to do not check JSP/JSF files
-reportfile: send report output to a file; default to System.out
-benchmark: output a benchmark report upon completion; default to System.err

For example:
c:\> java -jar pmd-4.0.jar c:\my\source\code text unusedcode,imports -targetjdk 1.5 -debug
c:\> java -jar pmd-4.0.jar c:\my\source\code xml basic,design -encoding UTF-8
*/
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.