From 0ff97e5959a939d90abca9f07b6f392ea9b31f78 Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Sun, 29 Sep 2019 14:59:55 +0200 Subject: [PATCH] fix:[core] Wrong deprecation warnings for unused XPath attributes * Improve integration tests in pmd-dist to detect warnings about deprecated attributes. * Wrap the attribute value in a singleton list, to be able to distinguish between no value (null in the list) and value not determined yet (list is null). * Add integration test for apex. * Updated release notes fixes #2020 --- docs/pages/release_notes.md | 1 + .../pmd/lang/ast/xpath/Attribute.java | 22 +- .../pmd/it/BinaryDistributionIT.java | 30 +- .../net/sourceforge/pmd/it/CpdExecutor.java | 4 +- .../sourceforge/pmd/it/ExecutionResult.java | 88 ++++- .../net/sourceforge/pmd/it/PMDExecutor.java | 83 +++- .../src/test/resources/rulesets/all-apex.xml | 18 + .../src/test/resources/rulesets/all-java.xml | 18 + .../apex/TableGridController.cls | 371 ++++++++++++++++++ .../{ => java}/JumbledIncrementer.java | 0 10 files changed, 594 insertions(+), 41 deletions(-) create mode 100644 pmd-dist/src/test/resources/rulesets/all-apex.xml create mode 100644 pmd-dist/src/test/resources/rulesets/all-java.xml create mode 100644 pmd-dist/src/test/resources/sample-source/apex/TableGridController.cls rename pmd-dist/src/test/resources/sample-source/{ => java}/JumbledIncrementer.java (100%) diff --git a/docs/pages/release_notes.md b/docs/pages/release_notes.md index d9f11046c3..6042f54d10 100644 --- a/docs/pages/release_notes.md +++ b/docs/pages/release_notes.md @@ -32,6 +32,7 @@ This is a {{ site.pmd.release_type }} release. * core * [#2014](https://github.com/pmd/pmd/issues/2014): \[core] Making add(SourceCode sourceCode) public for alternative file systems + * [#2020](https://github.com/pmd/pmd/issues/2020): \[core] Wrong deprecation warnings for unused XPath attributes * [#2036](https://github.com/pmd/pmd/issues/2036): \[core] Wrong include/exclude patterns are silently ignored * java * [#2042](https://github.com/pmd/pmd/issues/2042): \[java] PMD crashes with ClassFormatError: Absent Code attribute... diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/Attribute.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/Attribute.java index f37ca7e215..85cca22010 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/Attribute.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/ast/xpath/Attribute.java @@ -6,6 +6,8 @@ package net.sourceforge.pmd.lang.ast.xpath; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.Collections; +import java.util.List; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -36,7 +38,7 @@ public class Attribute { private final Node parent; private final String name; private Method method; - private Object value; + private List value; private String stringValue; /** Creates a new attribute belonging to the given node using its accessor. */ @@ -50,7 +52,7 @@ public class Attribute { public Attribute(Node parent, String name, String value) { this.parent = parent; this.name = name; - this.value = value; + this.value = Collections.singletonList(value); this.stringValue = value; } @@ -71,20 +73,20 @@ public class Attribute { } public Object getValue() { - if (value != null) { // TODO if the method returned null we'll call it again... - return value; + if (value != null) { + return value.get(0); } if (method.isAnnotationPresent(Deprecated.class) && LOG.isLoggable(Level.WARNING) && DETECTED_DEPRECATED_ATTRIBUTES.putIfAbsent(getLoggableAttributeName(), Boolean.TRUE) == null) { - // this message needs to be kept in sync with PMDCoverageTest + // this message needs to be kept in sync with PMDCoverageTest / BinaryDistributionIT LOG.warning("Use of deprecated attribute '" + getLoggableAttributeName() + "' in XPath query"); } // this lazy loading reduces calls to Method.invoke() by about 90% try { - value = method.invoke(parent, EMPTY_OBJ_ARRAY); - return value; + value = Collections.singletonList(method.invoke(parent, EMPTY_OBJ_ARRAY)); + return value.get(0); } catch (IllegalAccessException | InvocationTargetException iae) { iae.printStackTrace(); } @@ -95,10 +97,8 @@ public class Attribute { if (stringValue != null) { return stringValue; } - Object v = this.value; - if (this.value == null) { - v = getValue(); - } + Object v = getValue(); + stringValue = v == null ? "" : String.valueOf(v); return stringValue; } diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java index cb687cf304..7eb1a92497 100644 --- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java +++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java @@ -90,7 +90,7 @@ public class BinaryDistributionIT { @Test public void runPMD() throws Exception { - String srcDir = new File(".", "src/test/resources/sample-source/").getAbsolutePath(); + String srcDir = new File(".", "src/test/resources/sample-source/java/").getAbsolutePath(); ExecutionResult result; @@ -98,12 +98,38 @@ public class BinaryDistributionIT { result.assertExecutionResult(0, "apex, ecmascript, java, jsp, plsql, pom, scala, vf, vm, wsdl, xml, xsl"); result = PMDExecutor.runPMDRules(tempDir, srcDir, "src/test/resources/rulesets/sample-ruleset.xml"); - result.assertExecutionResult(4, "JumbledIncrementer.java:8:"); + result.assertExecutionResult(4, "", "JumbledIncrementer.java:8:"); result = PMDExecutor.runPMDRules(tempDir, srcDir, "rulesets/java/quickstart.xml"); result.assertExecutionResult(4, ""); } + @Test + public void testAllJavaRules() throws Exception { + String srcDir = new File(".", "src/test/resources/sample-source/java/").getAbsolutePath(); + + ExecutionResult result = PMDExecutor.runPMDRules(tempDir, srcDir, "src/test/resources/rulesets/all-java.xml"); + assertDefaultExecutionResult(result); + } + + @Test + public void testAllApexRules() throws Exception { + String srcDir = new File(".", "src/test/resources/sample-source/apex/").getAbsolutePath(); + + ExecutionResult result = PMDExecutor.runPMDRules(tempDir, srcDir, "src/test/resources/rulesets/all-apex.xml"); + assertDefaultExecutionResult(result); + } + + private static void assertDefaultExecutionResult(ExecutionResult result) { + result.assertExecutionResult(4, ""); + + result.assertNoError("Exception applying rule"); + result.assertNoError("Ruleset not found"); + result.assertNoError("Use of deprecated attribute"); + result.assertNoErrorInReport("Error while processing"); + result.assertNoErrorInReport("Error while parsing"); + } + @Test public void runCPD() throws Exception { String srcDir = new File(".", "src/test/resources/sample-source-cpd/").getAbsolutePath(); diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/CpdExecutor.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/CpdExecutor.java index 42e80430ac..b7c25f023e 100644 --- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/CpdExecutor.java +++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/CpdExecutor.java @@ -34,7 +34,7 @@ public class CpdExecutor { String output = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8); int result = process.waitFor(); - return new ExecutionResult(result, output); + return new ExecutionResult(result, output, null, null); } private static ExecutionResult runCpdWindows(Path tempDir, String ... arguments) throws Exception { @@ -46,7 +46,7 @@ public class CpdExecutor { String output = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8); int result = process.waitFor(); - return new ExecutionResult(result, output); + return new ExecutionResult(result, output, null, null); } /** diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/ExecutionResult.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/ExecutionResult.java index 4e7c8134f1..04efb3971b 100644 --- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/ExecutionResult.java +++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/ExecutionResult.java @@ -5,7 +5,9 @@ package net.sourceforge.pmd.it; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import net.sourceforge.pmd.PMD; @@ -18,10 +20,14 @@ import net.sourceforge.pmd.PMD; public class ExecutionResult { private final int exitCode; private final String output; + private final String errorOutput; + private final String report; - ExecutionResult(int theExitCode, String theOutput) { + ExecutionResult(int theExitCode, String theOutput, String theErrorOutput, String theReport) { this.exitCode = theExitCode; this.output = theOutput; + this.errorOutput = theErrorOutput; + this.report = theReport; } @Override @@ -30,7 +36,9 @@ public class ExecutionResult { sb.append("ExecutionResult:") .append(PMD.EOL) .append(" exit code: ").append(exitCode).append(PMD.EOL) - .append(" output:").append(PMD.EOL).append(output).append(PMD.EOL); + .append(" output:").append(PMD.EOL).append(output).append(PMD.EOL) + .append(" errorOutput:").append(PMD.EOL).append(errorOutput).append(PMD.EOL) + .append(" report:").append(PMD.EOL).append(report).append(PMD.EOL); return sb.toString(); } @@ -42,10 +50,80 @@ public class ExecutionResult { * @param expectedOutput the output to search for */ public void assertExecutionResult(int expectedExitCode, String expectedOutput) { - assertEquals("Command exited with wrong code", expectedExitCode, exitCode); + assertExecutionResult(expectedExitCode, expectedOutput, null); + } + + /** + * Asserts that the command exited with the expected exit code and that the given expected + * output is contained in the actual command output and the given expected report is in the + * generated report. + * + * @param expectedExitCode the exit code, e.g. 0 if no rule violations are expected, or 4 if violations are found + * @param expectedOutput the output to search for + * @param expectedReport the string to search for tin the report + */ + public void assertExecutionResult(int expectedExitCode, String expectedOutput, String expectedReport) { + assertEquals("Command exited with wrong code.\nComplete result:\n\n" + this, expectedExitCode, exitCode); assertNotNull("No output found", output); - if (!output.contains(expectedOutput)) { - fail("Expected output '" + expectedOutput + "' not present.\nComplete output:\n\n" + output); + if (expectedOutput != null && !expectedOutput.isEmpty()) { + if (!output.contains(expectedOutput)) { + fail("Expected output '" + expectedOutput + "' not present.\nComplete result:\n\n" + this); + } + } else { + assertTrue("The output should have been empty.\nComplete result:\n\n" + this, output.isEmpty()); + } + if (expectedReport != null && !expectedReport.isEmpty()) { + assertTrue("Expected report '" + expectedReport + "'.\nComplete result:\n\n" + this, + report.contains(expectedReport)); + } + } + + /** + * Asserts that the given error message is not in the error output. + * @param errorMessage the error message to search for + */ + public void assertNoError(String errorMessage) { + assertFalse("Found error message: " + errorMessage + ".\nComplete result:\n\n" + this, + errorOutput.contains(errorMessage)); + } + + /** + * Asserts that the given error message is not in the report. + * @param errorMessage the error message to search for + */ + public void assertNoErrorInReport(String errorMessage) { + assertFalse("Found error message in report: " + errorMessage + ".\nComplete result:\n\n" + this, + report.contains(errorMessage)); + } + + static class Builder { + private int exitCode; + private String output; + private String errorOutput; + private String report; + + Builder withExitCode(int exitCode) { + this.exitCode = exitCode; + return this; + } + + Builder withOutput(String output) { + this.output = output; + return this; + } + + Builder withErrorOutput(String errorOutput) { + this.errorOutput = errorOutput; + return this; + } + + Builder withReport(String report) { + this.report = report; + return this; + } + + ExecutionResult build() { + return new ExecutionResult(exitCode, output, errorOutput, report); } } } diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java index 1e127a1f0f..05bd0a9755 100644 --- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java +++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/PMDExecutor.java @@ -4,9 +4,13 @@ package net.sourceforge.pmd.it; +import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.SystemUtils; @@ -24,33 +28,65 @@ public class PMDExecutor { private static final String RULESET_FLAG = "-R"; private static final String FORMAT_FLAG = "-f"; private static final String FORMATTER = "text"; + private static final String REPORTFILE_FLAG = "-r"; private PMDExecutor() { // this is a helper class only } - private static ExecutionResult runPMDUnix(Path tempDir, String ... arguments) throws Exception { + private static ExecutionResult runPMDUnix(Path tempDir, Path reportFile, String ... arguments) throws Exception { String cmd = tempDir.resolve(PMD_BIN_PREFIX + PMDVersion.VERSION + "/bin/run.sh").toAbsolutePath().toString(); - ProcessBuilder pb = new ProcessBuilder(cmd, "pmd"); - pb.command().addAll(Arrays.asList(arguments)); - pb.redirectErrorStream(true); - Process process = pb.start(); - String output = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8); - - int result = process.waitFor(); - return new ExecutionResult(result, output); + return runPMD(cmd, Arrays.asList(arguments), reportFile); } - private static ExecutionResult runPMDWindows(Path tempDir, String ... arguments) throws Exception { + private static ExecutionResult runPMDWindows(Path tempDir, Path reportFile, String ... arguments) throws Exception { String cmd = tempDir.resolve(PMD_BIN_PREFIX + PMDVersion.VERSION + "/bin/pmd.bat").toAbsolutePath().toString(); - ProcessBuilder pb = new ProcessBuilder(cmd); - pb.command().addAll(Arrays.asList(arguments)); - pb.redirectErrorStream(true); - Process process = pb.start(); - String output = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8); + return runPMD(cmd, Arrays.asList(arguments), reportFile); + } - int result = process.waitFor(); - return new ExecutionResult(result, output); + private static ExecutionResult runPMD(String cmd, List arguments, Path reportFile) throws Exception { + ProcessBuilder pb = new ProcessBuilder(cmd, "pmd"); + pb.command().addAll(arguments); + pb.redirectErrorStream(false); + final Process process = pb.start(); + final ExecutionResult.Builder result = new ExecutionResult.Builder(); + + Thread outputReader = new Thread(new Runnable() { + @Override + public void run() { + String output; + try { + output = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8); + result.withOutput(output); + } catch (IOException e) { + result.withOutput("Exception occurred: " + e.toString()); + } + } + }); + outputReader.start(); + Thread errorReader = new Thread(new Runnable() { + @Override + public void run() { + String error; + try { + error = IOUtils.toString(process.getErrorStream(), StandardCharsets.UTF_8); + result.withErrorOutput(error); + } catch (IOException e) { + result.withErrorOutput("Exception occurred: " + e.toString()); + } + } + }); + errorReader.start(); + + int exitCode = process.waitFor(); + outputReader.join(TimeUnit.MINUTES.toMillis(5)); + errorReader.join(TimeUnit.MINUTES.toMillis(5)); + + String report = null; + if (reportFile != null) { + report = IOUtils.toString(reportFile.toUri(), StandardCharsets.UTF_8); + } + return result.withExitCode(exitCode).withReport(report).build(); } /** @@ -63,10 +99,15 @@ public class PMDExecutor { * @throws Exception if the execution fails for any reason (executable not found, ...) */ public static ExecutionResult runPMDRules(Path tempDir, String sourceDirectory, String ruleset) throws Exception { + Path reportFile = Files.createTempFile("pmd-it-report", "txt"); + reportFile.toFile().deleteOnExit(); + if (SystemUtils.IS_OS_WINDOWS) { - return runPMDWindows(tempDir, SOURCE_DIRECTORY_FLAG, sourceDirectory, RULESET_FLAG, ruleset, FORMAT_FLAG, FORMATTER); + return runPMDWindows(tempDir, reportFile, SOURCE_DIRECTORY_FLAG, sourceDirectory, RULESET_FLAG, ruleset, + FORMAT_FLAG, FORMATTER, REPORTFILE_FLAG, reportFile.toAbsolutePath().toString()); } else { - return runPMDUnix(tempDir, SOURCE_DIRECTORY_FLAG, sourceDirectory, RULESET_FLAG, ruleset, FORMAT_FLAG, FORMATTER); + return runPMDUnix(tempDir, reportFile, SOURCE_DIRECTORY_FLAG, sourceDirectory, RULESET_FLAG, ruleset, + FORMAT_FLAG, FORMATTER, REPORTFILE_FLAG, reportFile.toAbsolutePath().toString()); } } @@ -79,9 +120,9 @@ public class PMDExecutor { */ public static ExecutionResult runPMD(Path tempDir, String ... arguments) throws Exception { if (SystemUtils.IS_OS_WINDOWS) { - return runPMDWindows(tempDir, arguments); + return runPMDWindows(tempDir, null, arguments); } else { - return runPMDUnix(tempDir, arguments); + return runPMDUnix(tempDir, null, arguments); } } } diff --git a/pmd-dist/src/test/resources/rulesets/all-apex.xml b/pmd-dist/src/test/resources/rulesets/all-apex.xml new file mode 100644 index 0000000000..92fe0705e8 --- /dev/null +++ b/pmd-dist/src/test/resources/rulesets/all-apex.xml @@ -0,0 +1,18 @@ + + + + Every Apex Rule in PMD + + + + + + + + + + + diff --git a/pmd-dist/src/test/resources/rulesets/all-java.xml b/pmd-dist/src/test/resources/rulesets/all-java.xml new file mode 100644 index 0000000000..a091ea021b --- /dev/null +++ b/pmd-dist/src/test/resources/rulesets/all-java.xml @@ -0,0 +1,18 @@ + + + + Every Java Rule in PMD + + + + + + + + + + + diff --git a/pmd-dist/src/test/resources/sample-source/apex/TableGridController.cls b/pmd-dist/src/test/resources/sample-source/apex/TableGridController.cls new file mode 100644 index 0000000000..a84b10ef72 --- /dev/null +++ b/pmd-dist/src/test/resources/sample-source/apex/TableGridController.cls @@ -0,0 +1,371 @@ +/* +Copyright (c) 2013 Up2Go International LLC +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * DOCUMENTATION MISSING //TODO + * + * @author Robert Soesemann (robert.soesemann@up2go.com) + */ +public with sharing class TableGridController { + + // COMPONENT ATTRIBUTES + public String typeParam { get; set; } + public String fieldsParam { get; set; } + public String filterParam { get; set; } + public Boolean sortDescParam { get; set; } + public Integer pageSizeParam { get; set; } + public String modeParam { get; set; } + public String sortByParam { get; set; } + public List selectedIds { get; set; } + + public String gridIdParam { get; set; } + public Boolean saveSettingsParam { + get { + return (saveSettingsParam == null) ? false : saveSettingsParam; + } + set; + } + + public String lookupFieldValue { get; set; } + + + // MEMBER VARIABLES + public SoqlQuery soqlQuery { get; set; } + public RowManager rowManager { get; set; } + public Boolean isInitialized { get; private set; } + public String previousSortField {get; set;} + public String objectLabel { get; private set; } + public String objectLabelPlural { get; private set; } + public Boolean noneSelected { get; private set; } + public Boolean allSelected { get; set; } + public String parentFrameUrl { get; set; } + public String currentMode { get; set; } + public String currentPageUrl { + get { + PageReference currentPage = ApexPages.currentPage(); + currentPage.getParameters().remove('AJAXREQUEST'); + return currentPage.getUrl(); + } + private set; + } + public Boolean isPageLoaded { get; private set; } + + private TableGridState__c settings; + + + // COMPONENT INIT JOEL DIETZ STYLE + + public void getInit() { + isPageLoaded = false; + + typeParam = TableGridUtils.normalize(typeParam); + gridIdParam = TableGridUtils.normalize(gridIdParam); + + try { + // Try to load user's saved settings for this grid instance + settings = getSettings(); + + // Init from saved settings if it exists + if(settings.Id != null) { + String fieldNames = settings.txtl_FieldNames__c; + soqlQuery = new SoqlQuery(typeParam, fieldNames); + + // Always use the components initial filter + if(filterParam != null) { + soqlQuery.filter(filterParam); + } + // Recreate additional filter statements + String filterStatements = settings.txtl_FilterStatements__c; + + if(filterStatements != null) { + for(String statement : filterStatements.split(';')) { + String[] fragments = statement.split(','); + + try { + FilterStatement newStatement = new FilterStatement(typeParam, fragments[0], fragments[1], fragments[2]) ; + soqlQuery.filterStatements.add( newStatement ); + } + catch(Exception initException) { + //Just do not add Filter + } + } + } + // Order By + if(settings.txt_SortBy__c != null) { + soqlQuery.orderBy(settings.txt_SortBy__c, settings.chk_SortDescending__c == null ? false : settings.chk_SortDescending__c); + } + + currentMode = settings.txt_Mode__c; + } + // Otherwise init from component attributes + else { + fieldsParam = TableGridUtils.normalize(fieldsParam); + + soqlQuery = new SoqlQuery(typeParam, fieldsParam); + if(filterParam != null) { + soqlQuery.filter(filterParam); + } + if(sortByParam != null) { + soqlQuery.orderBy(sortByParam, sortDescParam == null ? false : sortDescParam); + } + currentMode = modeParam; + } + objectLabel = SchemaCache.objectDescribe(typeParam).getLabel(); + objectLabelPlural = SchemaCache.objectDescribe(typeParam).getLabelPlural(); + noneSelected = true; + + // Add lookup filter as editable statement + if(currentMode == 'singleselect' && lookupFieldValue != null) { + FilterStatement newStatement = new FilterStatement(typeParam, 'Name', 'contains', lookupFieldValue) ; + + // If Statement does not already exists + Boolean alreadyExists = false; + for(FilterStatement fs : soqlQuery.filterStatements) { + if(fs.hashcode.equals(newStatement.hashcode)) { + alreadyExists = true; + break; + } + } + // Add it to SOQLQuery + if(!alreadyExists) { + soqlQuery.filterStatements.add( newStatement ); + } + } + } + catch(Exception ex) { + showMessage(ApexPages.Severity.FATAL, 'TableGrid Initialization Error: ' + ex.getMessage() + ' (Please contact your administrator)'); + return; + } + + // Create RowManager from query + rowManager = new RowManager(soqlQuery, settings); + + previousSortField = soqlQuery.sortFieldName; + + // Mark initialisation as successful + isInitialized = true; + + allSelected = false; + } + + + // ACTIONS + + public void doSort() { + // Flip sort direction if sort field is unchanged + if(soqlQuery.sortFieldName == previousSortField) { + soqlQuery.sortDescending = !soqlQuery.sortDescending; + } + else { + soqlQuery.sortDescending = true; + } + + // Refetch rows + rowManager.fetchRows(soqlQuery); + previousSortField = soqlQuery.sortFieldName; + + // Save settings + saveSettings(); + } + + public PageReference doEditNew() { + String typePrefix = SchemaCache.objectDescribe(typeParam).getKeyPrefix(); + PageReference page = new PageReference('/' + typePrefix + '/e'); + + page.getParameters().put('retURL', parentFrameUrl); + page.getParameters().put('cancelURL', parentFrameUrl); + page.getParameters().put('saveURL', parentFrameUrl); + + return page; + } + + public void doSaveSelected() { + try { + List toUpdate = new List(); + + for(SObjectRow row: rowManager.rows) { + if(row.isSelected) { + toUpdate.add(row.delegate); + } + } + if(!toUpdate.isEmpty()) { + update toUpdate; + showMessage(ApexPages.Severity.INFO, 'Successfully updated changed records'); + } + } + catch(Exception ex){ + showMessage(ApexPages.Severity.ERROR, ex.getMessage()); + } + + doRefresh(); + } + + + public void doDeleteSelected() { + try { + List toDelete = new List(); + + for(SObjectRow row: rowManager.rows) { + if(row.isSelected) { + toDelete.add(row.delegate); + } + } + delete toDelete; + } + catch(Exception ex){ + showMessage(ApexPages.Severity.ERROR, ex.getMessage()); + } + + doRefresh(); + } + + public void doRefresh() { + // Update field names and refetchs rows + soqlQuery.updateFieldNames(); + rowManager.fetchRows(soqlQuery); + + saveSettings(); + } + + public void doLoadDefaults() { + if(settings.Id != null) { + delete settings; + } + getInit(); + } + + public void doChangeMode() { + // Mode set already by apex:param that called this action + + saveSettings(); + } + + + public void doHandleSelection() { + noneSelected = true; + if(selectedIds!=null) + selectedIds.clear(); + else + selectedIds = new List(); + + for(SObjectRow row : rowManager.rows) { + if(row.isSelected) { + noneSelected = false; + + if(currentMode=='select') { + selectedIds.add(row.delegate.Id); + } + } + } + } + + + /** + * Save settings override into users custom settings + */ + private void saveSettings() { + if(saveSettingsParam == true) { + // SELECT + settings.txtl_FieldNames__c = soqlQuery.fieldNames; + + // WHERE + String serializedStatements = ''; + Iterator iter = soqlQuery.filterStatements.iterator(); + while(iter.hasNext()) { + serializedStatements += iter.next().asStringSetting(); + serializedStatements += iter.hasNext() ? ';' : ''; + } + settings.txtl_FilterStatements__c = serializedStatements; + + // ORDER BY + settings.txt_SortBy__c = previousSortField; + settings.chk_SortDescending__c = soqlQuery.sortDescending; + + // PAGINATION + settings.num_PageSize__c = rowManager.pageSize; + settings.num_PageNumber__c = rowManager.pageNumber; + + // MODE + settings.txt_Mode__c = currentMode; + + upsert settings; + } + } + + /** + * Returns existing (in the db) or new Settings object for this tablegrid + */ + private TableGridState__c getSettings() { + TableGridState__c settings; + String currentPage; + String uniqueGridId; + + // Set instance and page id dependant on calling context + if(Test.isRunningTest()) { + currentPage = 'calledWithoutPage'; + uniqueGridId = 'uniqueGridId'; + } + else { + String wholeUrl = ApexPages.currentPage().getUrl(); + Integer firstPos = wholeUrl.lastIndexOf('/'); + Integer lastPos = wholeUrl.indexOf('?', firstPos); + if(lastPos == -1) { + lastPos = wholeUrl.length(); + } + currentPage = wholeUrl.substring(firstPos, lastPos); + uniqueGridId = gridIdParam; + } + + settings = new TableGridState__c(lkp_User__c = UserInfo.getUserId(), + txt_PageUrl__c = currentPage, + txt_GridId__c = gridIdParam, + num_PageSize__c = pageSizeParam); + + if(saveSettingsParam == true) { + // Check if settings are saved in the database + List fromDatabase = [SELECT txtl_FieldNames__c, txtl_FilterStatements__c, txt_SortBy__c, chk_SortDescending__c, num_PageSize__c, num_PageNumber__c, txt_Mode__c + FROM TableGridState__c + WHERE txt_GridId__c = :uniqueGridId + AND txt_PageUrl__c = :currentPage + AND lkp_User__c = :UserInfo.getUserId() + LIMIT 1]; + + if(fromDatabase != null && !fromDatabase.isEmpty()) { + settings = fromDatabase[0]; + } + } + + return settings; + } + + + private void showMessage(ApexPages.Severity severity, String messageText) { + ApexPages.Message message = new ApexPages.Message(severity, messageText); + ApexPages.addMessage(message); + } +} diff --git a/pmd-dist/src/test/resources/sample-source/JumbledIncrementer.java b/pmd-dist/src/test/resources/sample-source/java/JumbledIncrementer.java similarity index 100% rename from pmd-dist/src/test/resources/sample-source/JumbledIncrementer.java rename to pmd-dist/src/test/resources/sample-source/java/JumbledIncrementer.java