This is an automated email from the ASF dual-hosted git repository.

tmysik pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans.git


The following commit(s) were added to refs/heads/master by this push:
     new f0f6f89  [NETBEANS-4339] PHP Mess Detector - added option to use 
custom rule set file
     new 31908a7  Merge pull request #2160 from 
KacerCZ/netbeans-4339-phpmd-rule-file
f0f6f89 is described below

commit f0f6f899e10adef7d475928da519621261bd18c7
Author: Tomas Prochazka <ka...@razdva.cz>
AuthorDate: Fri May 29 23:09:31 2020 +0200

    [NETBEANS-4339] PHP Mess Detector - added option to use custom rule set file
---
 .../php/analysis/MessDetectorAnalyzerImpl.java     |  54 ++++++++---
 .../modules/php/analysis/MessDetectorParams.java   |  48 +++++++++
 .../php/analysis/commands/MessDetector.java        |  37 +++++--
 .../php/analysis/options/AnalysisOptions.java      |  10 ++
 .../analysis/options/AnalysisOptionsValidator.java |  28 ++++--
 .../options/ValidatorMessDetectorParameter.java    |  70 +++++++++++++
 .../ui/MessDetectorRuleSetsListCellRenderer.java   |  45 +++++++++
 .../php/analysis/ui/analyzer/Bundle.properties     |   2 +
 .../ui/analyzer/MessDetectorCustomizerPanel.form   |  49 ++++++++--
 .../ui/analyzer/MessDetectorCustomizerPanel.java   |  98 ++++++++++++++++---
 .../php/analysis/ui/options/Bundle.properties      |   3 +
 .../ui/options/MessDetectorOptionsPanel.form       |  62 +++++++++---
 .../ui/options/MessDetectorOptionsPanel.java       | 108 ++++++++++++++++-----
 13 files changed, 530 insertions(+), 84 deletions(-)

diff --git 
a/php/php.code.analysis/src/org/netbeans/modules/php/analysis/MessDetectorAnalyzerImpl.java
 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/MessDetectorAnalyzerImpl.java
index e5631a0..204272b 100644
--- 
a/php/php.code.analysis/src/org/netbeans/modules/php/analysis/MessDetectorAnalyzerImpl.java
+++ 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/MessDetectorAnalyzerImpl.java
@@ -18,6 +18,7 @@
  */
 package org.netbeans.modules.php.analysis;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -37,11 +38,13 @@ import 
org.netbeans.modules.php.analysis.ui.analyzer.MessDetectorCustomizerPanel
 import org.netbeans.modules.php.analysis.util.AnalysisUtils;
 import org.netbeans.modules.php.analysis.util.Mappers;
 import org.netbeans.modules.php.api.executable.InvalidPhpExecutableException;
+import org.netbeans.modules.php.api.util.StringUtils;
 import org.netbeans.modules.php.api.validation.ValidationResult;
 import org.netbeans.modules.refactoring.api.Scope;
 import org.netbeans.spi.editor.hints.ErrorDescription;
 import org.netbeans.spi.editor.hints.HintsController;
 import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
 import org.openide.util.NbBundle;
 import org.openide.util.lookup.ServiceProvider;
 
@@ -76,8 +79,8 @@ public class MessDetectorAnalyzerImpl implements Analyzer {
             return Collections.emptyList();
         }
 
-        List<String> messDetectorRuleSets = getValidMessDetectorRuleSets();
-        if (messDetectorRuleSets == null) {
+        MessDetectorParams messDetectorParams = getValidMessDetectorParams();
+        if (messDetectorParams == null) {
             
context.reportAnalysisProblem(Bundle.MessDetectorAnalyzerImpl_messDetector_ruleSets_error(),
 Bundle.MessDetectorAnalyzerImpl_messDetector_ruleSets_error_description());
             return Collections.emptyList();
         }
@@ -92,7 +95,7 @@ public class MessDetectorAnalyzerImpl implements Analyzer {
 
         context.start(totalCount);
         try {
-            return doAnalyze(scope, messDetector, messDetectorRuleSets, 
fileCount);
+            return doAnalyze(scope, messDetector, messDetectorParams, 
fileCount);
         } finally {
             context.finish();
         }
@@ -109,7 +112,8 @@ public class MessDetectorAnalyzerImpl implements Analyzer {
         "MessDetectorAnalyzerImpl.analyze.error=Mess detector analysis error",
         "MessDetectorAnalyzerImpl.analyze.error.description=Error occurred 
during mess detector analysis, review Output window for more information.",
     })
-    private Iterable<? extends ErrorDescription> doAnalyze(Scope scope, 
MessDetector messDetector, List<String> messDetectorRuleSets, Map<FileObject, 
Integer> fileCount) {
+    private Iterable<? extends ErrorDescription> doAnalyze(Scope scope, 
MessDetector messDetector,
+            MessDetectorParams params, Map<FileObject, Integer> fileCount) {
         List<ErrorDescription> errors = new ArrayList<>();
         int progress = 0;
         messDetector.startAnalyzeGroup();
@@ -117,7 +121,7 @@ public class MessDetectorAnalyzerImpl implements Analyzer {
             if (cancelled.get()) {
                 return Collections.emptyList();
             }
-            List<org.netbeans.modules.php.analysis.results.Result> results = 
messDetector.analyze(messDetectorRuleSets, root);
+            List<org.netbeans.modules.php.analysis.results.Result> results = 
messDetector.analyze(params, root);
             if (results == null) {
                 
context.reportAnalysisProblem(Bundle.MessDetectorAnalyzerImpl_analyze_error(), 
Bundle.MessDetectorAnalyzerImpl_analyze_error_description());
                 return Collections.emptyList();
@@ -131,7 +135,7 @@ public class MessDetectorAnalyzerImpl implements Analyzer {
             if (cancelled.get()) {
                 return Collections.emptyList();
             }
-            List<org.netbeans.modules.php.analysis.results.Result> results = 
messDetector.analyze(messDetectorRuleSets, file);
+            List<org.netbeans.modules.php.analysis.results.Result> results = 
messDetector.analyze(params, file);
             if (results == null) {
                 
context.reportAnalysisProblem(Bundle.MessDetectorAnalyzerImpl_analyze_error(), 
Bundle.MessDetectorAnalyzerImpl_analyze_error_description());
                 return Collections.emptyList();
@@ -155,7 +159,7 @@ public class MessDetectorAnalyzerImpl implements Analyzer {
             if (dataChildren.isEmpty()) {
                 continue;
             }
-            List<org.netbeans.modules.php.analysis.results.Result> results = 
messDetector.analyze(messDetectorRuleSets, dataChildren);
+            List<org.netbeans.modules.php.analysis.results.Result> results = 
messDetector.analyze(params, dataChildren);
             if (results == null) {
                 
context.reportAnalysisProblem(Bundle.MessDetectorAnalyzerImpl_analyze_error(), 
Bundle.MessDetectorAnalyzerImpl_analyze_error_description());
                 return Collections.emptyList();
@@ -177,22 +181,48 @@ public class MessDetectorAnalyzerImpl implements Analyzer 
{
         return null;
     }
 
+    @CheckForNull
+    private MessDetectorParams getValidMessDetectorParams() {
+        MessDetectorParams messDetectorParams = new MessDetectorParams()
+                .setRuleSets(getValidMessDetectorRuleSets())
+                .setRuleSetFile(getValidRuleSetFile());
+        ValidationResult result = new AnalysisOptionsValidator()
+                .validateMessDetector(messDetectorParams)
+                .getResult();
+        if (result.hasErrors() || result.hasWarnings()) {
+            return null;
+        }
+        return messDetectorParams;
+    }
+
     private List<String> getValidMessDetectorRuleSets() {
         List<String> messDetectorRuleSets = 
MessDetectorCustomizerPanel.getRuleSets(context.getSettings());
         if (messDetectorRuleSets == null) {
             messDetectorRuleSets = 
AnalysisOptions.getInstance().getMessDetectorRuleSets();
         }
         assert messDetectorRuleSets != null;
-        ValidationResult result = new AnalysisOptionsValidator()
-                .validateMessDetectorRuleSets(messDetectorRuleSets)
-                .getResult();
-        if (result.hasErrors()
-                || result.hasWarnings()) {
+        if (messDetectorRuleSets.isEmpty()) {
             return null;
         }
         return messDetectorRuleSets;
     }
 
+    @CheckForNull
+    private FileObject getValidRuleSetFile() {
+        String ruleSetFile = null;
+        Preferences settings = context.getSettings();
+        if (settings != null) {
+            ruleSetFile = 
settings.get(MessDetectorCustomizerPanel.RULE_SET_FILE, null);
+        }
+        if (ruleSetFile == null) {
+            ruleSetFile = 
AnalysisOptions.getInstance().getMessDetectorRuleSetFilePath();
+        }
+        if (StringUtils.isEmpty(ruleSetFile)) {
+            return null;
+        }
+        return FileUtil.toFileObject(new File(ruleSetFile));
+    }
+
     //~ Inner classes
 
     @ServiceProvider(service=AnalyzerFactory.class)
diff --git 
a/php/php.code.analysis/src/org/netbeans/modules/php/analysis/MessDetectorParams.java
 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/MessDetectorParams.java
new file mode 100644
index 0000000..d4a39f6
--- /dev/null
+++ 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/MessDetectorParams.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.php.analysis;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.openide.filesystems.FileObject;
+
+public final class MessDetectorParams {
+
+    private List<String> ruleSets;
+    private FileObject ruleSetFile;
+
+    public List<String> getRuleSets() {
+        return Collections.unmodifiableList(ruleSets);
+    }
+
+    MessDetectorParams setRuleSets(List<String> ruleSets) {
+        this.ruleSets = new ArrayList<>(ruleSets);
+        return this;
+    }
+
+    public FileObject getRuleSetFile() {
+        return ruleSetFile;
+    }
+
+    MessDetectorParams setRuleSetFile(FileObject ruleSetFile) {
+        this.ruleSetFile = ruleSetFile;
+        return this;
+    }
+}
diff --git 
a/php/php.code.analysis/src/org/netbeans/modules/php/analysis/commands/MessDetector.java
 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/commands/MessDetector.java
index a0b2b3d..8419b0d 100644
--- 
a/php/php.code.analysis/src/org/netbeans/modules/php/analysis/commands/MessDetector.java
+++ 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/commands/MessDetector.java
@@ -31,6 +31,7 @@ import java.util.logging.Level;
 import java.util.logging.Logger;
 import org.netbeans.api.annotations.common.CheckForNull;
 import org.netbeans.api.extexecution.ExecutionDescriptor;
+import org.netbeans.modules.php.analysis.MessDetectorParams;
 import org.netbeans.modules.php.analysis.options.AnalysisOptions;
 import org.netbeans.modules.php.analysis.parsers.MessDetectorReportParser;
 import org.netbeans.modules.php.analysis.results.Result;
@@ -59,10 +60,12 @@ public final class MessDetector {
     private static final String REPORT_FORMAT_PARAM = "xml"; // NOI18N
     private static final String EXCLUDE_PARAM = "--exclude"; // NOI18N
     private static final String SUFFIXES_PARAM = "--suffixes"; // NOI18N
+    public static final String EMPTY_RULE_SET = "-"; // NOI18N
 
     // rule sets
     @org.netbeans.api.annotations.common.SuppressWarnings(value = 
"MS_MUTABLE_COLLECTION", justification = "It is immutable") // NOI18N
     public static final List<String> RULE_SETS = Arrays.asList(
+            EMPTY_RULE_SET,
             "codesize", // NOI18N
             "controversial", // NOI18N
             "design", // NOI18N
@@ -102,8 +105,8 @@ public final class MessDetector {
     }
 
     @CheckForNull
-    public List<Result> analyze(List<String> ruleSets, FileObject... files) {
-        return analyze(ruleSets, Arrays.asList(files));
+    public List<Result> analyze(MessDetectorParams params, FileObject... 
files) {
+        return analyze(params, Arrays.asList(files));
     }
 
     @NbBundle.Messages({
@@ -111,11 +114,11 @@ public final class MessDetector {
         "MessDetector.analyze=Mess Detector (analyze #{0})",
     })
     @CheckForNull
-    public List<Result> analyze(List<String> ruleSets, List<FileObject> files) 
{
+    public List<Result> analyze(MessDetectorParams params, List<FileObject> 
files) {
         assert assertValidFiles(files);
         try {
             Integer result = 
getExecutable(Bundle.MessDetector_analyze(analyzeGroupCounter++))
-                    .additionalParameters(getParameters(ruleSets, files))
+                    .additionalParameters(getParameters(params, files))
                     .runAndWait(getDescriptor(), "Running mess detector..."); 
// NOI18N
             if (result == null) {
                 return null;
@@ -155,14 +158,14 @@ public final class MessDetector {
                 });
     }
 
-    private List<String> getParameters(List<String> ruleSets, List<FileObject> 
files) {
+    private List<String> getParameters(MessDetectorParams parameters, 
List<FileObject> files) {
         List<String> params = new ArrayList<>();
         // paths
         params.add(joinFilePaths(files));
         // report format
         params.add(REPORT_FORMAT_PARAM);
         // rule sets
-        params.add(StringUtils.implode(ruleSets, ",")); // NOI18N
+        params.add(joinRuleSets(parameters.getRuleSets(), 
parameters.getRuleSetFile()));
         // extensions
         params.add(SUFFIXES_PARAM);
         
params.add(StringUtils.implode(FileUtil.getMIMETypeExtensions(FileUtils.PHP_MIME_TYPE),
 ",")); // NOI18N
@@ -189,6 +192,28 @@ public final class MessDetector {
         return paths.toString();
     }
 
+    private String joinRuleSets(List<String> ruleSets, FileObject ruleSetFile) 
{
+        StringBuilder ruleSetsBuilder = new StringBuilder(200);
+        if (ruleSets != null) {
+            for (String ruleSet : ruleSets) {
+                if (ruleSet.equals(EMPTY_RULE_SET)) {
+                    continue;
+                }
+                if (ruleSetsBuilder.length() > 0) {
+                    ruleSetsBuilder.append(","); // NOI18N
+                }
+                ruleSetsBuilder.append(ruleSet);
+            }
+        }
+        if (ruleSetFile != null) {
+            if (ruleSetsBuilder.length() > 0) {
+                ruleSetsBuilder.append(","); // NOI18N
+            }
+            
ruleSetsBuilder.append(FileUtil.toFile(ruleSetFile).getAbsolutePath());
+        }
+        return ruleSetsBuilder.toString();
+    }
+
     private void addIgnoredFiles(List<String> params, List<FileObject> files) {
         Collection<String> ignoredFiles = new HashSet<>();
         for (FileObject file : files) {
diff --git 
a/php/php.code.analysis/src/org/netbeans/modules/php/analysis/options/AnalysisOptions.java
 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/options/AnalysisOptions.java
index 441f33a..de157a9 100644
--- 
a/php/php.code.analysis/src/org/netbeans/modules/php/analysis/options/AnalysisOptions.java
+++ 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/options/AnalysisOptions.java
@@ -43,6 +43,7 @@ public final class AnalysisOptions {
     // mess detector
     private static final String MESS_DETECTOR_PATH = "messDetector.path"; // 
NOI18N
     private static final String MESS_DETECTOR_RULE_SETS = 
"messDetector.ruleSets"; // NOI18N
+    private static final String MESS_DETECTOR_RULE_SET_FILE = 
"messDetector.ruleSetFile"; // NOI18N
     // coding standards fixer
     private static final String CODING_STANDARDS_FIXER_VERSION = 
"codingStandardsFixer.version"; // NOI18N
     private static final String CODING_STANDARDS_FIXER_PATH = 
"codingStandardsFixer.path"; // NOI18N
@@ -132,6 +133,15 @@ public final class AnalysisOptions {
         getPreferences().put(MESS_DETECTOR_RULE_SETS, 
AnalysisUtils.serialize(ruleSets));
     }
 
+    @CheckForNull
+    public String getMessDetectorRuleSetFilePath() {
+        return getPreferences().get(MESS_DETECTOR_RULE_SET_FILE, null);
+    }
+
+    public void setMessDetectorRuleSetFilePath(String ruleSetFilePath) {
+        getPreferences().put(MESS_DETECTOR_RULE_SET_FILE, ruleSetFilePath);
+    }
+
     // coding standards fixer
     @CheckForNull
     public String getCodingStandardsFixerVersion() {
diff --git 
a/php/php.code.analysis/src/org/netbeans/modules/php/analysis/options/AnalysisOptionsValidator.java
 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/options/AnalysisOptionsValidator.java
index c920289..1515806 100644
--- 
a/php/php.code.analysis/src/org/netbeans/modules/php/analysis/options/AnalysisOptionsValidator.java
+++ 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/options/AnalysisOptionsValidator.java
@@ -21,6 +21,7 @@ package org.netbeans.modules.php.analysis.options;
 import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import org.netbeans.modules.php.analysis.MessDetectorParams;
 import org.netbeans.modules.php.analysis.commands.CodeSniffer;
 import org.netbeans.modules.php.analysis.commands.CodingStandardsFixer;
 import org.netbeans.modules.php.analysis.commands.MessDetector;
@@ -28,6 +29,8 @@ import org.netbeans.modules.php.analysis.commands.PHPStan;
 import org.netbeans.modules.php.api.util.FileUtils;
 import org.netbeans.modules.php.api.util.StringUtils;
 import org.netbeans.modules.php.api.validation.ValidationResult;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
 import org.openide.util.NbBundle;
 
 public final class AnalysisOptionsValidator {
@@ -41,9 +44,16 @@ public final class AnalysisOptionsValidator {
         return this;
     }
 
-    public AnalysisOptionsValidator validateMessDetector(String 
messDetectorPath, List<String> messDetectorRuleSets) {
-        validateMessDetectorPath(messDetectorPath);
-        validateMessDetectorRuleSets(messDetectorRuleSets);
+    public AnalysisOptionsValidator 
validateMessDetector(ValidatorMessDetectorParameter param) {
+        validateMessDetectorPath(param.getMessDetectorPath());
+        validateMessDetectorRuleSets(param.getRuleSets(), 
param.getRuleSetFilePath());
+        return this;
+    }
+
+    public AnalysisOptionsValidator validateMessDetector(MessDetectorParams 
param) {
+        FileObject ruleSetFile = param.getRuleSetFile();
+        String ruleSetFilePath = ruleSetFile == null ? null : 
FileUtil.toFile(ruleSetFile).getAbsolutePath();
+        validateMessDetectorRuleSets(param.getRuleSets(), ruleSetFilePath);
         return this;
     }
 
@@ -80,16 +90,18 @@ public final class AnalysisOptionsValidator {
     }
 
     private AnalysisOptionsValidator validateMessDetectorPath(String 
messDetectorPath) {
-        String warning = MessDetector.validate(messDetectorPath);
-        if (warning != null) {
-            result.addWarning(new 
ValidationResult.Message("messDetector.path", warning)); // NOI18N
+        if (messDetectorPath != null) {
+            String warning = MessDetector.validate(messDetectorPath);
+            if (warning != null) {
+                result.addWarning(new 
ValidationResult.Message("messDetector.path", warning)); // NOI18N
+            }
         }
         return this;
     }
 
     
@NbBundle.Messages("AnalysisOptionsValidator.messDetector.ruleSets.empty=At 
least one rule set must be set.")
-    public AnalysisOptionsValidator validateMessDetectorRuleSets(List<String> 
messDetectorRuleSets) {
-        if (messDetectorRuleSets.isEmpty()) {
+    private AnalysisOptionsValidator validateMessDetectorRuleSets(List<String> 
messDetectorRuleSets, String ruleSetFile) {
+        if ((messDetectorRuleSets == null || messDetectorRuleSets.size() == 1 
&& messDetectorRuleSets.contains(MessDetector.EMPTY_RULE_SET)) && 
StringUtils.isEmpty(ruleSetFile)) {
             result.addWarning(new 
ValidationResult.Message("messDetector.ruleSets", 
Bundle.AnalysisOptionsValidator_messDetector_ruleSets_empty())); // NOI18N
         }
         return this;
diff --git 
a/php/php.code.analysis/src/org/netbeans/modules/php/analysis/options/ValidatorMessDetectorParameter.java
 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/options/ValidatorMessDetectorParameter.java
new file mode 100644
index 0000000..6510ca5
--- /dev/null
+++ 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/options/ValidatorMessDetectorParameter.java
@@ -0,0 +1,70 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.php.analysis.options;
+
+import java.util.Collections;
+import java.util.List;
+import 
org.netbeans.modules.php.analysis.ui.analyzer.MessDetectorCustomizerPanel;
+import org.netbeans.modules.php.analysis.ui.options.MessDetectorOptionsPanel;
+
+public final class ValidatorMessDetectorParameter {
+
+    private final String messDetectorPath;
+    private final List<String> ruleSets;
+    private final String ruleSetFilePath;
+
+    public static ValidatorMessDetectorParameter 
create(MessDetectorOptionsPanel panel) {
+        return new ValidatorMessDetectorParameter(panel);
+    }
+
+    public static ValidatorMessDetectorParameter 
create(MessDetectorCustomizerPanel panel) {
+        return new ValidatorMessDetectorParameter(panel);
+    }
+
+    private ValidatorMessDetectorParameter() {
+        messDetectorPath = null;
+        ruleSets = null;
+        ruleSetFilePath = null;
+    }
+
+    private ValidatorMessDetectorParameter(MessDetectorOptionsPanel panel) {
+        messDetectorPath = panel.getMessDetectorPath();
+        ruleSets = panel.getMessDetectorRuleSets();
+        ruleSetFilePath = panel.getMessDetectorRuleSetFilePath();
+    }
+
+    private ValidatorMessDetectorParameter(MessDetectorCustomizerPanel panel) {
+        messDetectorPath = null;
+        ruleSets = panel.getSelectedRuleSets();
+        ruleSetFilePath = panel.getRuleSetFile();
+    }
+
+    public String getMessDetectorPath() {
+        return messDetectorPath;
+    }
+
+    public List<String> getRuleSets() {
+        return Collections.unmodifiableList(ruleSets);
+    }
+
+    public String getRuleSetFilePath() {
+        return ruleSetFilePath;
+    }
+
+}
diff --git 
a/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/MessDetectorRuleSetsListCellRenderer.java
 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/MessDetectorRuleSetsListCellRenderer.java
new file mode 100644
index 0000000..7f3d648
--- /dev/null
+++ 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/MessDetectorRuleSetsListCellRenderer.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.php.analysis.ui;
+
+import java.awt.Component;
+import javax.swing.JList;
+import javax.swing.ListCellRenderer;
+import org.netbeans.modules.php.analysis.commands.MessDetector;
+import org.openide.util.NbBundle;
+
+public class MessDetectorRuleSetsListCellRenderer implements 
ListCellRenderer<String> {
+
+    private final ListCellRenderer<? super String> defaultRenderer;
+
+    public MessDetectorRuleSetsListCellRenderer(ListCellRenderer<? super 
String> defaultRenderer) {
+        this.defaultRenderer = defaultRenderer;
+    }
+
+    
@NbBundle.Messages("MessDetectorRuleSetsListCellRenderer.noneRuleSet.displayName=<none>")
+    @Override
+    public Component getListCellRendererComponent(JList<? extends String> 
list, String value, int index, boolean isSelected, boolean cellHasFocus) {
+        String ruleSet = value;
+        if (MessDetector.EMPTY_RULE_SET.equals(ruleSet)) {
+            ruleSet = 
Bundle.MessDetectorRuleSetsListCellRenderer_noneRuleSet_displayName();
+        }
+        return defaultRenderer.getListCellRendererComponent(list, ruleSet, 
index, isSelected, cellHasFocus);
+    }
+
+}
diff --git 
a/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/analyzer/Bundle.properties
 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/analyzer/Bundle.properties
index 52ab6ca..4106718 100644
--- 
a/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/analyzer/Bundle.properties
+++ 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/analyzer/Bundle.properties
@@ -20,6 +20,8 @@ CodeSnifferCustomizerPanel.standardLabel.text=S&tandard:
 
 # mess detector
 MessDetectorCustomizerPanel.ruleSetsLabel.text=Rule Sets:
+MessDetectorCustomizerPanel.ruleSetFileLabel.text=Rule Set &File:
+MessDetectorCustomizerPanel.ruleSetFileBrowseButton.text=&Browse...
 
 # coding standards fixer
 CodingStandardsFixerCustomizerPanel.configLabel.text=Confi&g:
diff --git 
a/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/analyzer/MessDetectorCustomizerPanel.form
 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/analyzer/MessDetectorCustomizerPanel.form
index 601927b..fa17e74 100644
--- 
a/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/analyzer/MessDetectorCustomizerPanel.form
+++ 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/analyzer/MessDetectorCustomizerPanel.form
@@ -38,18 +38,21 @@
     <DimensionLayout dim="0">
       <Group type="103" groupAlignment="0" attributes="0">
           <Group type="102" attributes="0">
+              <Component id="enabledCheckBox" min="-2" max="-2" 
attributes="0"/>
+              <EmptySpace min="0" pref="0" max="32767" attributes="0"/>
+          </Group>
+          <Group type="102" alignment="0" attributes="0">
+              <Group type="103" groupAlignment="0" attributes="0">
+                  <Component id="ruleSetsLabel" min="-2" max="-2" 
attributes="0"/>
+                  <Component id="ruleSetFileLabel" min="-2" max="-2" 
attributes="0"/>
+              </Group>
+              <EmptySpace max="-2" attributes="0"/>
               <Group type="103" groupAlignment="0" attributes="0">
-                  <Group type="102" attributes="0">
-                      <Component id="ruleSetsLabel" min="-2" max="-2" 
attributes="0"/>
-                      <EmptySpace min="-2" max="-2" attributes="0"/>
-                      <Component id="ruleSetsScrollPane" pref="166" 
max="32767" attributes="0"/>
-                  </Group>
-                  <Group type="102" attributes="0">
-                      <Component id="enabledCheckBox" min="-2" max="-2" 
attributes="0"/>
-                      <EmptySpace min="0" pref="0" max="32767" attributes="0"/>
-                  </Group>
+                  <Component id="ruleSetFileTextField" max="32767" 
attributes="0"/>
+                  <Component id="ruleSetsScrollPane" pref="0" max="32767" 
attributes="0"/>
               </Group>
               <EmptySpace max="-2" attributes="0"/>
+              <Component id="ruleSetFileBrowseButton" min="-2" max="-2" 
attributes="0"/>
           </Group>
       </Group>
     </DimensionLayout>
@@ -62,6 +65,12 @@
                   <Component id="ruleSetsLabel" min="-2" max="-2" 
attributes="0"/>
                   <Component id="ruleSetsScrollPane" min="-2" max="-2" 
attributes="0"/>
               </Group>
+              <EmptySpace max="-2" attributes="0"/>
+              <Group type="103" groupAlignment="3" attributes="0">
+                  <Component id="ruleSetFileTextField" alignment="3" min="-2" 
max="-2" attributes="0"/>
+                  <Component id="ruleSetFileLabel" alignment="3" min="-2" 
max="-2" attributes="0"/>
+                  <Component id="ruleSetFileBrowseButton" alignment="3" 
min="-2" max="-2" attributes="0"/>
+              </Group>
           </Group>
       </Group>
     </DimensionLayout>
@@ -98,5 +107,27 @@
         </Property>
       </Properties>
     </Component>
+    <Component class="javax.swing.JTextField" name="ruleSetFileTextField">
+    </Component>
+    <Component class="javax.swing.JLabel" name="ruleSetFileLabel">
+      <Properties>
+        <Property name="labelFor" type="java.awt.Component" 
editor="org.netbeans.modules.form.ComponentChooserEditor">
+          <ComponentRef name="ruleSetFileTextField"/>
+        </Property>
+        <Property name="text" type="java.lang.String" 
editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString 
bundle="org/netbeans/modules/php/analysis/ui/analyzer/Bundle.properties" 
key="MessDetectorCustomizerPanel.ruleSetFileLabel.text" 
replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, 
&quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+    </Component>
+    <Component class="javax.swing.JButton" name="ruleSetFileBrowseButton">
+      <Properties>
+        <Property name="text" type="java.lang.String" 
editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString 
bundle="org/netbeans/modules/php/analysis/ui/analyzer/Bundle.properties" 
key="MessDetectorCustomizerPanel.ruleSetFileBrowseButton.text" 
replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, 
&quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+      <Events>
+        <EventHandler event="actionPerformed" 
listener="java.awt.event.ActionListener" 
parameters="java.awt.event.ActionEvent" 
handler="ruleSetFileBrowseButtonActionPerformed"/>
+      </Events>
+    </Component>
   </SubComponents>
 </Form>
diff --git 
a/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/analyzer/MessDetectorCustomizerPanel.java
 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/analyzer/MessDetectorCustomizerPanel.java
index c361007..e6e31de 100644
--- 
a/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/analyzer/MessDetectorCustomizerPanel.java
+++ 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/analyzer/MessDetectorCustomizerPanel.java
@@ -18,25 +18,35 @@
  */
 package org.netbeans.modules.php.analysis.ui.analyzer;
 
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
 import java.util.List;
 import java.util.prefs.Preferences;
 import javax.swing.GroupLayout;
+import javax.swing.JButton;
 import javax.swing.JCheckBox;
 import javax.swing.JLabel;
 import javax.swing.JList;
 import javax.swing.JPanel;
 import javax.swing.JScrollPane;
+import javax.swing.JTextField;
 import javax.swing.LayoutStyle;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
 import javax.swing.event.ListSelectionEvent;
 import javax.swing.event.ListSelectionListener;
 import org.netbeans.api.annotations.common.CheckForNull;
 import org.netbeans.modules.analysis.spi.Analyzer;
 import org.netbeans.modules.php.analysis.options.AnalysisOptions;
 import org.netbeans.modules.php.analysis.options.AnalysisOptionsValidator;
+import 
org.netbeans.modules.php.analysis.options.ValidatorMessDetectorParameter;
+import 
org.netbeans.modules.php.analysis.ui.MessDetectorRuleSetsListCellRenderer;
 import org.netbeans.modules.php.analysis.ui.MessDetectorRuleSetsListModel;
 import org.netbeans.modules.php.analysis.util.AnalysisUtils;
 import org.netbeans.modules.php.api.validation.ValidationResult;
 import org.openide.awt.Mnemonics;
+import org.openide.filesystems.FileChooserBuilder;
 import org.openide.util.NbBundle;
 
 public class MessDetectorCustomizerPanel extends JPanel {
@@ -45,6 +55,8 @@ public class MessDetectorCustomizerPanel extends JPanel {
 
     public static final String ENABLED = "messDetector.enabled"; // NOI18N
     public static final String RULE_SETS = "messDetector.ruleSets"; // NOI18N
+    public static final String RULE_SET_FILE = "messDetector.ruleSetFile"; // 
NOI18N
+    private static final String RULE_SET_FILE_LAST_FOLDER_SUFFIX = 
".messDetector.ruleSetFile"; // NOI18N
 
     private final MessDetectorRuleSetsListModel ruleSetsListModel = new 
MessDetectorRuleSetsListModel();
     final Analyzer.CustomizerContext<Void, MessDetectorCustomizerPanel> 
context;
@@ -83,6 +95,7 @@ public class MessDetectorCustomizerPanel extends JPanel {
         setRuleSetsComponentsEnabled(isEnabled);
 
         ruleSetsList.setModel(ruleSetsListModel);
+        ruleSetsList.setCellRenderer(new 
MessDetectorRuleSetsListCellRenderer(ruleSetsList.getCellRenderer()));
 
         // rule sets
         List<String> ruleSets = getRuleSets(settings);
@@ -91,6 +104,9 @@ public class MessDetectorCustomizerPanel extends JPanel {
         }
         selectRuleSets(ruleSets);
 
+        String ruleSetFile = settings.get(RULE_SET_FILE, 
AnalysisOptions.getInstance().getMessDetectorRuleSetFilePath());
+        ruleSetFileTextField.setText(ruleSetFile);
+
         // listeners
         ruleSetsList.getSelectionModel().addListSelectionListener(new 
ListSelectionListener() {
             @Override
@@ -101,12 +117,33 @@ public class MessDetectorCustomizerPanel extends JPanel {
                 validateAndSetData();
             }
         });
+
+        ruleSetFileTextField.getDocument().addDocumentListener(new 
DocumentListener() {
+            @Override
+            public void insertUpdate(DocumentEvent e) {
+                validateAndSetData();
+            }
+
+            @Override
+            public void removeUpdate(DocumentEvent e) {
+                validateAndSetData();
+            }
+
+            @Override
+            public void changedUpdate(DocumentEvent e) {
+                validateAndSetData();
+            }
+        });
     }
 
-    List<String> getSelectedRuleSets() {
+    public List<String> getSelectedRuleSets() {
         return ruleSetsList.getSelectedValuesList();
     }
 
+    public String getRuleSetFile() {
+        return ruleSetFileTextField.getText().trim();
+    }
+
     void selectRuleSets(List<String> ruleSets) {
         ruleSetsList.clearSelection();
         for (String ruleSet : ruleSets) {
@@ -124,7 +161,7 @@ public class MessDetectorCustomizerPanel extends JPanel {
 
     private boolean validateData() {
         ValidationResult result = new AnalysisOptionsValidator()
-                .validateMessDetectorRuleSets(getSelectedRuleSets())
+                
.validateMessDetector(ValidatorMessDetectorParameter.create(this))
                 .getResult();
         if (result.hasErrors()) {
             context.setError(result.getErrors().get(0).getMessage());
@@ -140,6 +177,7 @@ public class MessDetectorCustomizerPanel extends JPanel {
 
     private void setData() {
         settings.put(RULE_SETS, 
AnalysisUtils.serialize(getSelectedRuleSets()));
+        settings.put(RULE_SET_FILE, getRuleSetFile());
     }
 
     private void setMessDetectorEnabled() {
@@ -150,6 +188,9 @@ public class MessDetectorCustomizerPanel extends JPanel {
         ruleSetsLabel.setEnabled(isEnabled);
         ruleSetsList.setEnabled(isEnabled);
         ruleSetsScrollPane.setEnabled(isEnabled);
+        ruleSetFileLabel.setEnabled(isEnabled);
+        ruleSetFileTextField.setEnabled(isEnabled);
+        ruleSetFileBrowseButton.setEnabled(isEnabled);
     }
 
     /**
@@ -164,6 +205,9 @@ public class MessDetectorCustomizerPanel extends JPanel {
         ruleSetsScrollPane = new JScrollPane();
         ruleSetsList = new JList<>();
         enabledCheckBox = new JCheckBox();
+        ruleSetFileTextField = new JTextField();
+        ruleSetFileLabel = new JLabel();
+        ruleSetFileBrowseButton = new JButton();
 
         ruleSetsLabel.setLabelFor(ruleSetsList);
         Mnemonics.setLocalizedText(ruleSetsLabel, 
NbBundle.getMessage(MessDetectorCustomizerPanel.class, 
"MessDetectorCustomizerPanel.ruleSetsLabel.text")); // NOI18N
@@ -172,19 +216,32 @@ public class MessDetectorCustomizerPanel extends JPanel {
 
         Mnemonics.setLocalizedText(enabledCheckBox, 
NbBundle.getMessage(MessDetectorCustomizerPanel.class, 
"MessDetectorCustomizerPanel.enabledCheckBox.text")); // NOI18N
 
+        ruleSetFileLabel.setLabelFor(ruleSetFileTextField);
+        Mnemonics.setLocalizedText(ruleSetFileLabel, 
NbBundle.getMessage(MessDetectorCustomizerPanel.class, 
"MessDetectorCustomizerPanel.ruleSetFileLabel.text")); // NOI18N
+
+        Mnemonics.setLocalizedText(ruleSetFileBrowseButton, 
NbBundle.getMessage(MessDetectorCustomizerPanel.class, 
"MessDetectorCustomizerPanel.ruleSetFileBrowseButton.text")); // NOI18N
+        ruleSetFileBrowseButton.addActionListener(new ActionListener() {
+            public void actionPerformed(ActionEvent evt) {
+                ruleSetFileBrowseButtonActionPerformed(evt);
+            }
+        });
+
         GroupLayout layout = new GroupLayout(this);
         this.setLayout(layout);
         
layout.setHorizontalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
             .addGroup(layout.createSequentialGroup()
+                .addComponent(enabledCheckBox)
+                .addGap(0, 0, Short.MAX_VALUE))
+            .addGroup(layout.createSequentialGroup()
                 
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
-                    .addGroup(layout.createSequentialGroup()
-                        .addComponent(ruleSetsLabel)
-                        
.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
-                        .addComponent(ruleSetsScrollPane, 
GroupLayout.DEFAULT_SIZE, 166, Short.MAX_VALUE))
-                    .addGroup(layout.createSequentialGroup()
-                        .addComponent(enabledCheckBox)
-                        .addGap(0, 0, Short.MAX_VALUE)))
-                .addContainerGap())
+                    .addComponent(ruleSetsLabel)
+                    .addComponent(ruleSetFileLabel))
+                .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
+                
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
+                    .addComponent(ruleSetFileTextField)
+                    .addComponent(ruleSetsScrollPane, 
GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE))
+                .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
+                .addComponent(ruleSetFileBrowseButton))
         );
         
layout.setVerticalGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
             .addGroup(GroupLayout.Alignment.TRAILING, 
layout.createSequentialGroup()
@@ -192,12 +249,31 @@ public class MessDetectorCustomizerPanel extends JPanel {
                 .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
                 
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
                     .addComponent(ruleSetsLabel)
-                    .addComponent(ruleSetsScrollPane, 
GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, 
GroupLayout.PREFERRED_SIZE)))
+                    .addComponent(ruleSetsScrollPane, 
GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, 
GroupLayout.PREFERRED_SIZE))
+                .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED)
+                
.addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
+                    .addComponent(ruleSetFileTextField, 
GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, 
GroupLayout.PREFERRED_SIZE)
+                    .addComponent(ruleSetFileLabel)
+                    .addComponent(ruleSetFileBrowseButton)))
         );
     }// </editor-fold>//GEN-END:initComponents
 
+    
@NbBundle.Messages("MessDetectorCustomizerPanel.ruleSetFile.browse.title=Select 
Mess Detector Rule Set File")
+    private void ruleSetFileBrowseButtonActionPerformed(ActionEvent evt) 
{//GEN-FIRST:event_ruleSetFileBrowseButtonActionPerformed
+         File file = new 
FileChooserBuilder(MessDetectorCustomizerPanel.class.getName() + 
RULE_SET_FILE_LAST_FOLDER_SUFFIX)
+                .setFilesOnly(true)
+                
.setTitle(Bundle.MessDetectorCustomizerPanel_ruleSetFile_browse_title())
+                .showOpenDialog();
+        if (file != null) {
+            ruleSetFileTextField.setText(file.getAbsolutePath());
+        }
+    }//GEN-LAST:event_ruleSetFileBrowseButtonActionPerformed
+
     // Variables declaration - do not modify//GEN-BEGIN:variables
     private JCheckBox enabledCheckBox;
+    private JButton ruleSetFileBrowseButton;
+    private JLabel ruleSetFileLabel;
+    private JTextField ruleSetFileTextField;
     private JLabel ruleSetsLabel;
     private JList<String> ruleSetsList;
     private JScrollPane ruleSetsScrollPane;
diff --git 
a/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/options/Bundle.properties
 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/options/Bundle.properties
index ce0babe..c58565a 100644
--- 
a/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/options/Bundle.properties
+++ 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/options/Bundle.properties
@@ -26,6 +26,9 @@ 
MessDetectorOptionsPanel.messDetectorRuleSetsLabel.text=&Default Rule Sets:
 MessDetectorOptionsPanel.messDetectorLabel.text=&Mess Detector:
 MessDetectorOptionsPanel.noteLabel.text=<html><i>Note:</i></html>
 MessDetectorOptionsPanel.minVersionInfoLabel.text=Mess Detector 1.4 or newer 
is supported.
+MessDetectorOptionsPanel.messDetectorRuleSetFileLabel.text=Rule Set &File:
+MessDetectorOptionsPanel.messDetectorRuleSetFileBrowseButton.text=B&rowse...
+MessDetectorOptionsPanel.messDetectorRuleSetFileLabel.AccessibleContext.accessibleName=Rule
 Set &File:
 
 # code sniffer
 CodeSnifferOptionsPanel.codeSnifferSearchButton.text=&Search...
diff --git 
a/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/options/MessDetectorOptionsPanel.form
 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/options/MessDetectorOptionsPanel.form
index 1f4be97..d4daf9c 100644
--- 
a/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/options/MessDetectorOptionsPanel.form
+++ 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/options/MessDetectorOptionsPanel.form
@@ -37,20 +37,17 @@
   <Layout>
     <DimensionLayout dim="0">
       <Group type="103" groupAlignment="0" attributes="0">
-          <Group type="102" attributes="0">
-              <Component id="noteLabel" min="-2" max="-2" attributes="0"/>
-              <EmptySpace min="0" pref="496" max="32767" attributes="0"/>
-          </Group>
           <Group type="102" alignment="0" attributes="0">
               <Group type="103" groupAlignment="0" attributes="0">
                   <Component id="messDetectorLabel" min="-2" max="-2" 
attributes="0"/>
                   <Component id="messDetectorRuleSetsLabel" min="-2" max="-2" 
attributes="0"/>
+                  <Component id="messDetectorRuleSetFileLabel" min="-2" 
max="-2" attributes="0"/>
               </Group>
-              <EmptySpace min="-2" pref="13" max="-2" attributes="0"/>
+              <EmptySpace max="-2" attributes="0"/>
               <Group type="103" groupAlignment="0" attributes="0">
                   <Group type="102" alignment="1" attributes="0">
                       <Group type="103" groupAlignment="1" attributes="0">
-                          <Component id="messDetectorRuleSetsScrollPane" 
alignment="0" pref="0" max="32767" attributes="0"/>
+                          <Component id="messDetectorRuleSetsScrollPane" 
alignment="0" max="32767" attributes="0"/>
                           <Component id="messDetectorTextField" max="32767" 
attributes="0"/>
                       </Group>
                       <EmptySpace max="-2" attributes="0"/>
@@ -59,8 +56,14 @@
                       <Component id="messDetectorSearchButton" linkSize="1" 
min="-2" max="-2" attributes="0"/>
                   </Group>
                   <Group type="102" attributes="0">
+                      <Component id="messDetectorRuleSetFileTextField" 
max="32767" attributes="0"/>
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Component id="messDetectorRuleSetFileBrowseButton" 
min="-2" max="-2" attributes="0"/>
+                      <EmptySpace min="-2" pref="83" max="-2" attributes="0"/>
+                  </Group>
+                  <Group type="102" attributes="0">
                       <Component id="messDetectorHintLabel" min="-2" max="-2" 
attributes="0"/>
-                      <EmptySpace min="0" pref="0" max="32767" attributes="0"/>
+                      <EmptySpace max="-2" attributes="0"/>
                   </Group>
               </Group>
           </Group>
@@ -72,12 +75,15 @@
               </Group>
               <EmptySpace max="32767" attributes="0"/>
           </Group>
+          <Group type="102" alignment="0" attributes="0">
+              <Component id="noteLabel" min="-2" max="-2" attributes="0"/>
+              <EmptySpace min="0" pref="0" max="32767" attributes="0"/>
+          </Group>
       </Group>
     </DimensionLayout>
     <DimensionLayout dim="1">
       <Group type="103" groupAlignment="0" attributes="0">
           <Group type="102" alignment="0" attributes="0">
-              <EmptySpace min="-2" pref="0" max="-2" attributes="0"/>
               <Group type="103" groupAlignment="3" attributes="0">
                   <Component id="messDetectorTextField" alignment="3" min="-2" 
max="-2" attributes="0"/>
                   <Component id="messDetectorSearchButton" alignment="3" 
min="-2" max="-2" attributes="0"/>
@@ -88,18 +94,23 @@
               <Component id="messDetectorHintLabel" min="-2" max="-2" 
attributes="0"/>
               <EmptySpace max="-2" attributes="0"/>
               <Group type="103" groupAlignment="0" attributes="0">
+                  <Component id="messDetectorRuleSetsLabel" alignment="0" 
min="-2" max="-2" attributes="0"/>
                   <Group type="102" alignment="0" attributes="0">
                       <Component id="messDetectorRuleSetsScrollPane" min="-2" 
max="-2" attributes="0"/>
-                      <EmptySpace type="separate" min="-2" max="-2" 
attributes="0"/>
-                      <Component id="noteLabel" min="-2" max="-2" 
attributes="0"/>
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Group type="103" groupAlignment="3" attributes="0">
+                          <Component id="messDetectorRuleSetFileTextField" 
alignment="3" min="-2" max="-2" attributes="0"/>
+                          <Component id="messDetectorRuleSetFileBrowseButton" 
alignment="3" min="-2" max="-2" attributes="0"/>
+                          <Component id="messDetectorRuleSetFileLabel" 
alignment="3" min="-2" max="-2" attributes="0"/>
+                      </Group>
                   </Group>
-                  <Component id="messDetectorRuleSetsLabel" alignment="0" 
min="-2" max="-2" attributes="0"/>
               </Group>
               <EmptySpace max="-2" attributes="0"/>
+              <Component id="noteLabel" min="-2" max="-2" attributes="0"/>
+              <EmptySpace min="-2" max="-2" attributes="0"/>
               <Component id="minVersionInfoLabel" min="-2" max="-2" 
attributes="0"/>
               <EmptySpace max="-2" attributes="0"/>
               <Component id="messDetectorLearnMoreLabel" min="-2" max="-2" 
attributes="0"/>
-              <EmptySpace min="0" pref="0" max="32767" attributes="0"/>
           </Group>
       </Group>
     </DimensionLayout>
@@ -189,5 +200,32 @@
         <EventHandler event="mousePressed" 
listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" 
handler="messDetectorLearnMoreLabelMousePressed"/>
       </Events>
     </Component>
+    <Component class="javax.swing.JTextField" 
name="messDetectorRuleSetFileTextField">
+    </Component>
+    <Component class="javax.swing.JLabel" name="messDetectorRuleSetFileLabel">
+      <Properties>
+        <Property name="labelFor" type="java.awt.Component" 
editor="org.netbeans.modules.form.ComponentChooserEditor">
+          <ComponentRef name="messDetectorRuleSetFileTextField"/>
+        </Property>
+        <Property name="text" type="java.lang.String" 
editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString 
bundle="org/netbeans/modules/php/analysis/ui/options/Bundle.properties" 
key="MessDetectorOptionsPanel.messDetectorRuleSetFileLabel.text" 
replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, 
&quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+      <AccessibilityProperties>
+        <Property name="AccessibleContext.accessibleName" 
type="java.lang.String" 
editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString 
bundle="org/netbeans/modules/php/analysis/ui/options/Bundle.properties" 
key="MessDetectorOptionsPanel.messDetectorRuleSetFileLabel.AccessibleContext.accessibleName"
 replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, 
&quot;{key}&quot;)"/>
+        </Property>
+      </AccessibilityProperties>
+    </Component>
+    <Component class="javax.swing.JButton" 
name="messDetectorRuleSetFileBrowseButton">
+      <Properties>
+        <Property name="text" type="java.lang.String" 
editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
+          <ResourceString 
bundle="org/netbeans/modules/php/analysis/ui/options/Bundle.properties" 
key="MessDetectorOptionsPanel.messDetectorRuleSetFileBrowseButton.text" 
replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, 
&quot;{key}&quot;)"/>
+        </Property>
+      </Properties>
+      <Events>
+        <EventHandler event="actionPerformed" 
listener="java.awt.event.ActionListener" 
parameters="java.awt.event.ActionEvent" 
handler="messDetectorRuleSetFileBrowseButtonActionPerformed"/>
+      </Events>
+    </Component>
   </SubComponents>
 </Form>
diff --git 
a/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/options/MessDetectorOptionsPanel.java
 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/options/MessDetectorOptionsPanel.java
index e7c706a..c8f0f03 100644
--- 
a/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/options/MessDetectorOptionsPanel.java
+++ 
b/php/php.code.analysis/src/org/netbeans/modules/php/analysis/ui/options/MessDetectorOptionsPanel.java
@@ -46,6 +46,8 @@ import javax.swing.event.ListSelectionListener;
 import org.netbeans.modules.php.analysis.commands.MessDetector;
 import org.netbeans.modules.php.analysis.options.AnalysisOptions;
 import org.netbeans.modules.php.analysis.options.AnalysisOptionsValidator;
+import 
org.netbeans.modules.php.analysis.options.ValidatorMessDetectorParameter;
+import 
org.netbeans.modules.php.analysis.ui.MessDetectorRuleSetsListCellRenderer;
 import org.netbeans.modules.php.analysis.ui.MessDetectorRuleSetsListModel;
 import org.netbeans.modules.php.api.util.FileUtils;
 import org.netbeans.modules.php.api.util.UiUtils;
@@ -61,6 +63,7 @@ public class MessDetectorOptionsPanel extends 
AnalysisCategoryPanel {
 
     private static final long serialVersionUID = -2710584730175872452L;
     private static final String MESS_DETECTOR_LAST_FOLDER_SUFFIX = 
".messDetector"; // NOI18N
+    private static final String MESS_DETECTOR_RULE_SET_FILE_LAST_FOLDER_SUFFIX 
= ".messDetector.ruleSetFile"; // NOI18N
 
     private final MessDetectorRuleSetsListModel ruleSetsListModel = new 
MessDetectorRuleSetsListModel();
     private final ChangeSupport changeSupport = new ChangeSupport(this);
@@ -87,9 +90,11 @@ public class MessDetectorOptionsPanel extends 
AnalysisCategoryPanel {
         // listeners
         
messDetectorTextField.getDocument().addDocumentListener(defaultDocumentListener);
         messDetectorRuleSetsList.addListSelectionListener(new 
DefaultListSelectionListener());
+        
messDetectorRuleSetFileTextField.getDocument().addDocumentListener(defaultDocumentListener);
 
         // rulesets
         messDetectorRuleSetsList.setModel(ruleSetsListModel);
+        messDetectorRuleSetsList.setCellRenderer(new 
MessDetectorRuleSetsListCellRenderer(messDetectorRuleSetsList.getCellRenderer()));
     }
 
     public String getMessDetectorPath() {
@@ -108,6 +113,14 @@ public class MessDetectorOptionsPanel extends 
AnalysisCategoryPanel {
         selectRuleSets(ruleSets);
     }
 
+    public String getMessDetectorRuleSetFilePath() {
+        return messDetectorRuleSetFileTextField.getText().trim();
+    }
+
+    private void setMessDetectorRuleSetFilePath(String path) {
+        messDetectorRuleSetFileTextField.setText(path);
+    }
+
     public void addChangeListener(ChangeListener listener) {
         changeSupport.addChangeListener(listener);
     }
@@ -144,6 +157,7 @@ public class MessDetectorOptionsPanel extends 
AnalysisCategoryPanel {
         AnalysisOptions analysisOptions = AnalysisOptions.getInstance();
         setMessDetectorPath(analysisOptions.getMessDetectorPath());
         setMessDetectorRuleSets(analysisOptions.getMessDetectorRuleSets());
+        
setMessDetectorRuleSetFilePath(analysisOptions.getMessDetectorRuleSetFilePath());
     }
 
     @Override
@@ -151,12 +165,18 @@ public class MessDetectorOptionsPanel extends 
AnalysisCategoryPanel {
         AnalysisOptions analysisOptions = AnalysisOptions.getInstance();
         analysisOptions.setMessDetectorPath(getMessDetectorPath());
         analysisOptions.setMessDetectorRuleSets(getMessDetectorRuleSets());
+        
analysisOptions.setMessDetectorRuleSetFilePath(getMessDetectorRuleSetFilePath());
     }
     
     @Override
     public boolean isChanged() {
         String saved = AnalysisOptions.getInstance().getMessDetectorPath();
-        String current = getMessDetectorPath().trim();
+        String current = getMessDetectorRuleSetFilePath();
+        if(saved == null ? !current.isEmpty() : !saved.equals(current)) {
+            return true;
+        }
+        saved = AnalysisOptions.getInstance().getMessDetectorRuleSetFilePath();
+        current = getMessDetectorRuleSetFilePath().trim();
         if(saved == null ? !current.isEmpty() : !saved.equals(current)) {
             return true;
         }
@@ -166,7 +186,7 @@ public class MessDetectorOptionsPanel extends 
AnalysisCategoryPanel {
     @Override
     public ValidationResult getValidationResult() {
         return new AnalysisOptionsValidator()
-                .validateMessDetector(getMessDetectorPath(), 
getMessDetectorRuleSets())
+                
.validateMessDetector(ValidatorMessDetectorParameter.create(this))
                 .getResult();
     }
 
@@ -186,10 +206,13 @@ public class MessDetectorOptionsPanel extends 
AnalysisCategoryPanel {
         messDetectorHintLabel = new JLabel();
         messDetectorRuleSetsLabel = new JLabel();
         messDetectorRuleSetsScrollPane = new JScrollPane();
-        messDetectorRuleSetsList = new JList<String>();
+        messDetectorRuleSetsList = new JList<>();
         noteLabel = new JLabel();
         minVersionInfoLabel = new JLabel();
         messDetectorLearnMoreLabel = new JLabel();
+        messDetectorRuleSetFileTextField = new JTextField();
+        messDetectorRuleSetFileLabel = new JLabel();
+        messDetectorRuleSetFileBrowseButton = new JButton();
 
         messDetectorLabel.setLabelFor(messDetectorTextField);
         Mnemonics.setLocalizedText(messDetectorLabel, 
NbBundle.getMessage(MessDetectorOptionsPanel.class, 
"MessDetectorOptionsPanel.messDetectorLabel.text")); // NOI18N
@@ -228,44 +251,58 @@ public class MessDetectorOptionsPanel extends 
AnalysisCategoryPanel {
             }
         });
 
+        
messDetectorRuleSetFileLabel.setLabelFor(messDetectorRuleSetFileTextField);
+        Mnemonics.setLocalizedText(messDetectorRuleSetFileLabel, 
NbBundle.getMessage(MessDetectorOptionsPanel.class, 
"MessDetectorOptionsPanel.messDetectorRuleSetFileLabel.text")); // NOI18N
+
+        Mnemonics.setLocalizedText(messDetectorRuleSetFileBrowseButton, 
NbBundle.getMessage(MessDetectorOptionsPanel.class, 
"MessDetectorOptionsPanel.messDetectorRuleSetFileBrowseButton.text")); // NOI18N
+        messDetectorRuleSetFileBrowseButton.addActionListener(new 
ActionListener() {
+            public void actionPerformed(ActionEvent evt) {
+                messDetectorRuleSetFileBrowseButtonActionPerformed(evt);
+            }
+        });
+
         GroupLayout layout = new GroupLayout(this);
         this.setLayout(layout);
-        layout.setHorizontalGroup(
-            layout.createParallelGroup(Alignment.LEADING)
-            .addGroup(layout.createSequentialGroup()
-                .addComponent(noteLabel, GroupLayout.PREFERRED_SIZE, 
GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
-                .addGap(0, 496, Short.MAX_VALUE))
+        layout.setHorizontalGroup(layout.createParallelGroup(Alignment.LEADING)
             .addGroup(layout.createSequentialGroup()
                 .addGroup(layout.createParallelGroup(Alignment.LEADING)
                     .addComponent(messDetectorLabel)
-                    .addComponent(messDetectorRuleSetsLabel))
-                .addGap(13, 13, 13)
+                    .addComponent(messDetectorRuleSetsLabel)
+                    .addComponent(messDetectorRuleSetFileLabel))
+                .addPreferredGap(ComponentPlacement.RELATED)
                 .addGroup(layout.createParallelGroup(Alignment.LEADING)
-                    .addGroup(Alignment.TRAILING, 
layout.createSequentialGroup()
+                    .addGroup(layout.createSequentialGroup()
+                        .addComponent(messDetectorHintLabel)
+                        .addContainerGap())
+                    .addGroup(layout.createSequentialGroup()
                         
.addGroup(layout.createParallelGroup(Alignment.TRAILING)
-                            .addComponent(messDetectorRuleSetsScrollPane, 
Alignment.LEADING, GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE)
+                            .addComponent(messDetectorRuleSetFileTextField)
+                            .addComponent(messDetectorRuleSetsScrollPane, 
Alignment.LEADING)
                             .addComponent(messDetectorTextField))
                         .addPreferredGap(ComponentPlacement.RELATED)
-                        .addComponent(messDetectorBrowseButton)
-                        .addPreferredGap(ComponentPlacement.RELATED)
-                        .addComponent(messDetectorSearchButton))
-                    .addGroup(layout.createSequentialGroup()
-                        .addComponent(messDetectorHintLabel)
-                        .addGap(0, 0, Short.MAX_VALUE))))
+                        .addGroup(layout.createParallelGroup(Alignment.LEADING)
+                            .addGroup(Alignment.TRAILING, 
layout.createSequentialGroup()
+                                .addComponent(messDetectorBrowseButton)
+                                .addPreferredGap(ComponentPlacement.RELATED)
+                                .addComponent(messDetectorSearchButton))
+                            .addGroup(Alignment.TRAILING, 
layout.createSequentialGroup()
+                                
.addComponent(messDetectorRuleSetFileBrowseButton)
+                                .addGap(83, 83, 83))))))
             .addGroup(layout.createSequentialGroup()
                 .addContainerGap()
                 .addGroup(layout.createParallelGroup(Alignment.LEADING)
                     .addComponent(minVersionInfoLabel)
                     .addComponent(messDetectorLearnMoreLabel, 
GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, 
GroupLayout.PREFERRED_SIZE))
                 .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
+            .addGroup(layout.createSequentialGroup()
+                .addComponent(noteLabel, GroupLayout.PREFERRED_SIZE, 
GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
+                .addGap(0, 0, Short.MAX_VALUE))
         );
 
         layout.linkSize(SwingConstants.HORIZONTAL, new Component[] 
{messDetectorBrowseButton, messDetectorSearchButton});
 
-        layout.setVerticalGroup(
-            layout.createParallelGroup(Alignment.LEADING)
+        layout.setVerticalGroup(layout.createParallelGroup(Alignment.LEADING)
             .addGroup(layout.createSequentialGroup()
-                .addGap(0, 0, 0)
                 .addGroup(layout.createParallelGroup(Alignment.BASELINE)
                     .addComponent(messDetectorTextField, 
GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, 
GroupLayout.PREFERRED_SIZE)
                     .addComponent(messDetectorSearchButton)
@@ -275,17 +312,23 @@ public class MessDetectorOptionsPanel extends 
AnalysisCategoryPanel {
                 .addComponent(messDetectorHintLabel)
                 .addPreferredGap(ComponentPlacement.RELATED)
                 .addGroup(layout.createParallelGroup(Alignment.LEADING)
+                    .addComponent(messDetectorRuleSetsLabel)
                     .addGroup(layout.createSequentialGroup()
                         .addComponent(messDetectorRuleSetsScrollPane, 
GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, 
GroupLayout.PREFERRED_SIZE)
-                        .addGap(18, 18, 18)
-                        .addComponent(noteLabel, GroupLayout.PREFERRED_SIZE, 
GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE))
-                    .addComponent(messDetectorRuleSetsLabel))
+                        .addPreferredGap(ComponentPlacement.RELATED)
+                        
.addGroup(layout.createParallelGroup(Alignment.BASELINE)
+                            .addComponent(messDetectorRuleSetFileTextField, 
GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, 
GroupLayout.PREFERRED_SIZE)
+                            .addComponent(messDetectorRuleSetFileBrowseButton)
+                            .addComponent(messDetectorRuleSetFileLabel))))
+                .addPreferredGap(ComponentPlacement.RELATED)
+                .addComponent(noteLabel, GroupLayout.PREFERRED_SIZE, 
GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                 .addPreferredGap(ComponentPlacement.RELATED)
                 .addComponent(minVersionInfoLabel)
                 .addPreferredGap(ComponentPlacement.RELATED)
-                .addComponent(messDetectorLearnMoreLabel, 
GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, 
GroupLayout.PREFERRED_SIZE)
-                .addGap(0, 0, Short.MAX_VALUE))
+                .addComponent(messDetectorLearnMoreLabel, 
GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, 
GroupLayout.PREFERRED_SIZE))
         );
+
+        
messDetectorRuleSetFileLabel.getAccessibleContext().setAccessibleName(NbBundle.getMessage(MessDetectorOptionsPanel.class,
 
"MessDetectorOptionsPanel.messDetectorRuleSetFileLabel.AccessibleContext.accessibleName"));
 // NOI18N
     }// </editor-fold>//GEN-END:initComponents
 
     private void messDetectorLearnMoreLabelMouseEntered(MouseEvent evt) 
{//GEN-FIRST:event_messDetectorLearnMoreLabelMouseEntered
@@ -351,12 +394,25 @@ public class MessDetectorOptionsPanel extends 
AnalysisCategoryPanel {
         }
     }//GEN-LAST:event_messDetectorSearchButtonActionPerformed
 
+    
@NbBundle.Messages("MessDetectorOptionsPanel.configuration.browse.title=Select 
Mess Detector Rule Set File")
+    private void 
messDetectorRuleSetFileBrowseButtonActionPerformed(ActionEvent evt) 
{//GEN-FIRST:event_messDetectorRuleSetFileBrowseButtonActionPerformed
+         File file = new 
FileChooserBuilder(MessDetectorOptionsPanel.class.getName() + 
MESS_DETECTOR_RULE_SET_FILE_LAST_FOLDER_SUFFIX)
+                .setFilesOnly(true)
+                
.setTitle(Bundle.MessDetectorOptionsPanel_configuration_browse_title())
+                .showOpenDialog();
+        if (file != null) {
+            messDetectorRuleSetFileTextField.setText(file.getAbsolutePath());
+        }
+    }//GEN-LAST:event_messDetectorRuleSetFileBrowseButtonActionPerformed
 
     // Variables declaration - do not modify//GEN-BEGIN:variables
     private JButton messDetectorBrowseButton;
     private JLabel messDetectorHintLabel;
     private JLabel messDetectorLabel;
     private JLabel messDetectorLearnMoreLabel;
+    private JButton messDetectorRuleSetFileBrowseButton;
+    private JLabel messDetectorRuleSetFileLabel;
+    private JTextField messDetectorRuleSetFileTextField;
     private JLabel messDetectorRuleSetsLabel;
     private JList<String> messDetectorRuleSetsList;
     private JScrollPane messDetectorRuleSetsScrollPane;


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscr...@netbeans.apache.org
For additional commands, e-mail: commits-h...@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists

Reply via email to