From dbbdbd8f95d06a6f008c5f066d1161bfc4e86a62 Mon Sep 17 00:00:00 2001 From: Andrew Gvozdev Date: Fri, 11 Sep 2009 02:09:38 +0000 Subject: [PATCH] bug 109139: Generic Error parser (RegexErrorParser) --- .../org.eclipse.cdt.make.ui/plugin.properties | 5 + build/org.eclipse.cdt.make.ui/plugin.xml | 21 +- .../BuildSettingsPreferencePage.java | 48 ++ .../MakefilePreferencesMessages.properties | 4 +- .../errorparsers/tests/ErrorParserTests.java | 1 + .../tests/RegexErrorParserTests.java | 689 ++++++++++++++++ core/org.eclipse.cdt.core.tests/plugin.xml | 17 + core/org.eclipse.cdt.core/plugin.properties | 1 + core/org.eclipse.cdt.core/plugin.xml | 19 + .../schema/ErrorParser.exsd | 102 ++- .../src/org/eclipse/cdt/core/CCorePlugin.java | 56 +- .../eclipse/cdt/core/ErrorParserManager.java | 118 ++- .../eclipse/cdt/core/IErrorParserNamed.java | 40 + .../errorparsers/ErrorParserNamedWrapper.java | 109 +++ .../core/errorparsers/RegexErrorParser.java | 179 +++++ .../core/errorparsers/RegexErrorPattern.java | 385 +++++++++ .../ErrorParserExtensionManager.java | 726 +++++++++++++++++ .../ui/dialogs/IInputStatusValidator.java | 34 + .../ui/dialogs/InputStatusDialog.java | 208 +++++ .../cdt/ui/dialogs/DialogsMessages.java | 46 ++ .../cdt/ui/dialogs/DialogsMessages.properties | 23 + .../dialogs/RegexErrorParserOptionPage.java | 752 ++++++++++++++++++ .../RegularExpressionStatusDialog.java | 106 +++ .../eclipse/cdt/ui/newui/AbstractPage.java | 2 +- .../eclipse/cdt/ui/newui/ErrorParsTab.java | 620 +++++++++++---- .../cdt/ui/newui/PluginResources.properties | 14 +- 26 files changed, 4130 insertions(+), 195 deletions(-) create mode 100644 build/org.eclipse.cdt.make.ui/src/org/eclipse/cdt/make/internal/ui/preferences/BuildSettingsPreferencePage.java create mode 100644 core/org.eclipse.cdt.core.tests/misc/org/eclipse/cdt/core/internal/errorparsers/tests/RegexErrorParserTests.java create mode 100644 core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/IErrorParserNamed.java create mode 100644 core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/errorparsers/ErrorParserNamedWrapper.java create mode 100644 core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/errorparsers/RegexErrorParser.java create mode 100644 core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/errorparsers/RegexErrorPattern.java create mode 100644 core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/errorparsers/ErrorParserExtensionManager.java create mode 100644 core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/dialogs/IInputStatusValidator.java create mode 100644 core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/dialogs/InputStatusDialog.java create mode 100644 core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/dialogs/RegexErrorParserOptionPage.java create mode 100644 core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/dialogs/RegularExpressionStatusDialog.java diff --git a/build/org.eclipse.cdt.make.ui/plugin.properties b/build/org.eclipse.cdt.make.ui/plugin.properties index cdb06649eb5..47e69fcd6a0 100644 --- a/build/org.eclipse.cdt.make.ui/plugin.properties +++ b/build/org.eclipse.cdt.make.ui/plugin.properties @@ -35,6 +35,11 @@ CommandTargetBuild.description=Invoke a make target build for the selected conta CommandTargetCreate.name=Create Make Target CommandTargetCreate.description=Create a new make build target for the selected container. +# Build Settings Preference page +PreferenceBuildSettings.name=Build Settings +ErrorParsersTab.name=Error Parsers +ErrorParsersTab.tooltip=Error Parsers scan build output and report errors in Problems view + PreferenceMakeProject.name=New Make Projects PreferenceMake.name=Make PreferenceMakefileEditor.name=Makefile Editor diff --git a/build/org.eclipse.cdt.make.ui/plugin.xml b/build/org.eclipse.cdt.make.ui/plugin.xml index fd55394ef24..e7cd5111b79 100644 --- a/build/org.eclipse.cdt.make.ui/plugin.xml +++ b/build/org.eclipse.cdt.make.ui/plugin.xml @@ -175,6 +175,12 @@ + + - + - + + + diff --git a/build/org.eclipse.cdt.make.ui/src/org/eclipse/cdt/make/internal/ui/preferences/BuildSettingsPreferencePage.java b/build/org.eclipse.cdt.make.ui/src/org/eclipse/cdt/make/internal/ui/preferences/BuildSettingsPreferencePage.java new file mode 100644 index 00000000000..13b34a81a09 --- /dev/null +++ b/build/org.eclipse.cdt.make.ui/src/org/eclipse/cdt/make/internal/ui/preferences/BuildSettingsPreferencePage.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * Copyright (c) 2009 Andrew Gvozdev and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Andrew Gvozdev - Initial API and implementation + *******************************************************************************/ + +package org.eclipse.cdt.make.internal.ui.preferences; + +import org.eclipse.cdt.core.settings.model.ICResourceDescription; +import org.eclipse.cdt.ui.newui.AbstractPrefPage; +import org.eclipse.cdt.ui.newui.ICPropertyTab; + +/** + * Preference page for Build Settings. + * + */ +public class BuildSettingsPreferencePage extends AbstractPrefPage { + + @Override + protected String getHeader() { + return MakefilePreferencesMessages.getString("BuildPreferencePage.description"); //$NON-NLS-1$ + } + + /* + * All affected settings are stored in preferences. Tabs are responsible for + * saving, after OK signal. No need to affect Project Description somehow. + */ + @Override + public boolean performOk() { + forEach(ICPropertyTab.OK, null); + return true; + } + + @Override + public ICResourceDescription getResDesc() { + return null; + } + + @Override + protected boolean isSingle() { + return false; + } +} diff --git a/build/org.eclipse.cdt.make.ui/src/org/eclipse/cdt/make/internal/ui/preferences/MakefilePreferencesMessages.properties b/build/org.eclipse.cdt.make.ui/src/org/eclipse/cdt/make/internal/ui/preferences/MakefilePreferencesMessages.properties index 11342c616c6..64b7d046363 100644 --- a/build/org.eclipse.cdt.make.ui/src/org/eclipse/cdt/make/internal/ui/preferences/MakefilePreferencesMessages.properties +++ b/build/org.eclipse.cdt.make.ui/src/org/eclipse/cdt/make/internal/ui/preferences/MakefilePreferencesMessages.properties @@ -1,5 +1,5 @@ ############################################################################### -# Copyright (c) 2000, 2006 IBM Corporation and others. +# Copyright (c) 2000, 2009 IBM Corporation and others. # All rights reserved. This program and the accompanying materials # are made available under the terms of the Eclipse Public License v1.0 # which accompanies this distribution, and is available at @@ -9,6 +9,8 @@ # IBM Corporation - initial API and implementation ############################################################################### +BuildPreferencePage.description=These settings are global to the entire workspace. They are overridden by project-specific settings. + MakefileEditorPreferencePage.description=Makefile Editor settings: MakefileEditorPreferencePage.invalid_input_print_margin= Invalid print margin column specified MakefileEditorPreferencePage.empty_input_print_margin= No print margin column specified diff --git a/core/org.eclipse.cdt.core.tests/misc/org/eclipse/cdt/core/internal/errorparsers/tests/ErrorParserTests.java b/core/org.eclipse.cdt.core.tests/misc/org/eclipse/cdt/core/internal/errorparsers/tests/ErrorParserTests.java index 50cc51a9a25..6c62eef8370 100644 --- a/core/org.eclipse.cdt.core.tests/misc/org/eclipse/cdt/core/internal/errorparsers/tests/ErrorParserTests.java +++ b/core/org.eclipse.cdt.core.tests/misc/org/eclipse/cdt/core/internal/errorparsers/tests/ErrorParserTests.java @@ -26,6 +26,7 @@ public class ErrorParserTests { suite.addTest(ErrorParserManagerTest.suite()); suite.addTest(ErrorParserFileMatchingTest.suite()); suite.addTest(ErrorParserEfsFileMatchingTest.suite()); + suite.addTest(RegexErrorParserTests.suite()); return suite; } diff --git a/core/org.eclipse.cdt.core.tests/misc/org/eclipse/cdt/core/internal/errorparsers/tests/RegexErrorParserTests.java b/core/org.eclipse.cdt.core.tests/misc/org/eclipse/cdt/core/internal/errorparsers/tests/RegexErrorParserTests.java new file mode 100644 index 00000000000..cbdfc98d7af --- /dev/null +++ b/core/org.eclipse.cdt.core.tests/misc/org/eclipse/cdt/core/internal/errorparsers/tests/RegexErrorParserTests.java @@ -0,0 +1,689 @@ +/******************************************************************************* + * Copyright (c) 2009 Andrew Gvozdev and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Andrew Gvozdev - Initial API and implementation + *******************************************************************************/ + +package org.eclipse.cdt.core.internal.errorparsers.tests; + +import java.util.ArrayList; + +import junit.framework.TestCase; +import junit.framework.TestSuite; + +import org.eclipse.cdt.core.CCorePlugin; +import org.eclipse.cdt.core.ErrorParserManager; +import org.eclipse.cdt.core.IErrorParser; +import org.eclipse.cdt.core.IErrorParserNamed; +import org.eclipse.cdt.core.IMarkerGenerator; +import org.eclipse.cdt.core.ProblemMarkerInfo; +import org.eclipse.cdt.core.errorparsers.ErrorParserNamedWrapper; +import org.eclipse.cdt.core.errorparsers.RegexErrorParser; +import org.eclipse.cdt.core.errorparsers.RegexErrorPattern; +import org.eclipse.cdt.internal.errorparsers.ErrorParserExtensionManager; +import org.eclipse.cdt.internal.errorparsers.GCCErrorParser; +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; + +/** + * Test cases testing RegexErrorParser functionality + */ +public class RegexErrorParserTests extends TestCase { + // These should match id and name of extension point defined in plugin.xml + private static final String REGEX_ERRORPARSER_ID = "org.eclipse.cdt.core.tests.RegexErrorParserId"; + private static final String REGEX_ERRORPARSER_NAME = "Test Plugin RegexErrorParser"; + private static final String GCC_ERRORPARSER_ID = "org.eclipse.cdt.core.GCCErrorParser"; + + private static final String TEST_PROJECT_NAME = "RegexErrorParserTests"; + + private IProject fProject = null; + private ArrayList errorList; + + private final IMarkerGenerator markerGenerator = new IMarkerGenerator() { + // deprecated + public void addMarker(IResource file, int lineNumber, String errorDesc, int severity, String errorVar) {} + + public void addMarker(ProblemMarkerInfo problemMarkerInfo) { + errorList.add(problemMarkerInfo); + } + }; + + /** + * Dummy error parser + */ + public static class DummyErrorParser implements IErrorParser { + /** + * Constructor + */ + public DummyErrorParser() { + } + + public boolean processLine(String line, ErrorParserManager eoParser) { + return false; + } + } + + /** + * Constructor. + * @param name - name of the test. + */ + public RegexErrorParserTests(String name) { + super(name); + + } + + @Override + protected void setUp() throws Exception { + fProject = ResourceHelper.createCDTProject(TEST_PROJECT_NAME); + assertNotNull(fProject); + errorList = new ArrayList(); + } + + @Override + protected void tearDown() throws Exception { + ResourceHelper.cleanUp(); + fProject = null; + + ErrorParserManager.setUserDefinedErrorParsers(null); + } + + /** + * @return - new TestSuite. + */ + public static TestSuite suite() { + return new TestSuite(RegexErrorParserTests.class); + } + + /** + * main function of the class. + * + * @param args - arguments + */ + public static void main(String[] args) { + junit.textui.TestRunner.run(suite()); + } + + /** + * Check if error pattern can be added/deleted. + * + * @throws Exception... + */ + public void testRegexErrorParserAddDeletePattern() throws Exception { + RegexErrorParser regexErrorParser = new RegexErrorParser(); + regexErrorParser.addPattern(new RegexErrorPattern("pattern 1", + null, null, null, null, RegexErrorPattern.SEVERITY_SKIP, true)); + regexErrorParser.addPattern(new RegexErrorPattern("delete me", + null, null, null, null, RegexErrorPattern.SEVERITY_SKIP, true)); + regexErrorParser.addPattern(new RegexErrorPattern("pattern 3", + null, null, null, null, RegexErrorPattern.SEVERITY_SKIP, true)); + + // adding patterns + RegexErrorPattern[] patternsBefore = regexErrorParser.getPatterns(); + assertEquals(3, patternsBefore.length); + assertEquals("delete me", patternsBefore[1].getPattern()); + RegexErrorPattern next = patternsBefore[2]; + + // delete pattern test + regexErrorParser.removePattern(patternsBefore[1]); + + RegexErrorPattern[] patternsAfter = regexErrorParser.getPatterns(); + assertEquals(2, patternsAfter.length); + assertEquals(next, patternsAfter[1]); + } + + /** + * Make sure the order of patterns is preserved. + * + * @throws Exception... + */ + public void testRegexErrorParserPatternOrder() throws Exception { + final int ERR=IMarkerGenerator.SEVERITY_ERROR_RESOURCE; + RegexErrorParser regexErrorParser = new RegexErrorParser(); + RegexErrorPattern removable = new RegexErrorPattern("CCC", null, null, null, null, ERR, true); + regexErrorParser.addPattern(new RegexErrorPattern("AAA", null, null, null, null, ERR, true)); + regexErrorParser.addPattern(new RegexErrorPattern("BBB", null, null, null, null, ERR, true)); + regexErrorParser.addPattern(removable); + regexErrorParser.addPattern(new RegexErrorPattern("DDD", null, null, null, null, ERR, true)); + regexErrorParser.addPattern(new RegexErrorPattern("ZZZ", null, null, null, null, ERR, true)); + + { + RegexErrorPattern[] patterns = regexErrorParser.getPatterns(); + assertEquals("AAA", patterns[0].getPattern()); + assertEquals("BBB", patterns[1].getPattern()); + assertEquals("CCC", patterns[2].getPattern()); + assertEquals("DDD", patterns[3].getPattern()); + assertEquals("ZZZ", patterns[4].getPattern()); + } + + regexErrorParser.removePattern(removable); + + { + RegexErrorPattern[] patterns = regexErrorParser.getPatterns(); + assertEquals("AAA", patterns[0].getPattern()); + assertEquals("BBB", patterns[1].getPattern()); + assertEquals("DDD", patterns[2].getPattern()); + assertEquals("ZZZ", patterns[3].getPattern()); + } + } + + /** + * Check how RegexErrorParser parses output. + * + * @throws Exception... + */ + public void testRegexErrorParserParseOutput() throws Exception { + RegexErrorParser regexErrorParser = new RegexErrorParser(); + regexErrorParser.addPattern(new RegexErrorPattern("(.*)#(.*)#(.*)#(.*)", + "$1", "$2", "$3 $4", "var=$4", IMarkerGenerator.SEVERITY_ERROR_RESOURCE, true)); + regexErrorParser.addPattern(new RegexErrorPattern("(.*)!(skip me)!(.*)!(.*)", + null, null, null, null, RegexErrorPattern.SEVERITY_SKIP, true)); + regexErrorParser.addPattern(new RegexErrorPattern("(.*)!(Description)!(.*)!(.*)", + "$4", "$3", "$2", "$1", IMarkerGenerator.SEVERITY_WARNING, /*eat-line*/ false)); + // broken pattern + regexErrorParser.addPattern(new RegexErrorPattern("(.*)!(.*)", + "$6", "$7", "$8", "$9", IMarkerGenerator.SEVERITY_WARNING, true)); + regexErrorParser.addPattern(new RegexErrorPattern("(.*)!(.*)!(.*)!(.*)", + null, null, null, null, IMarkerGenerator.SEVERITY_INFO, true)); + + String fileName = "RegexErrorParser.c"; + ResourceHelper.createFile(fProject, fileName); + + ErrorParserManager epManager = new ErrorParserManager(fProject, markerGenerator, new String[0]); + boolean result; + ProblemMarkerInfo problemMarkerInfo; + + // Regular pattern + regexErrorParser.processLine(fileName+"#10#Description#Variable", epManager); + // This should get ignored + regexErrorParser.processLine("Variable!skip me!10!"+fileName, epManager); + // Eat-line=false + qualifying next pattern (nulls), i.e. generates 2 problems + regexErrorParser.processLine("Variable!Description!10!"+fileName, epManager); + + errorList.clear(); + epManager.reportProblems(); + assertEquals(3, errorList.size()); + + // Regular + problemMarkerInfo = errorList.get(0); + assertEquals(IMarkerGenerator.SEVERITY_ERROR_RESOURCE, problemMarkerInfo.severity); + assertEquals("L/"+TEST_PROJECT_NAME+"/"+fileName, problemMarkerInfo.file.toString()); + assertEquals(fileName, problemMarkerInfo.file.getName()); + assertEquals(10, problemMarkerInfo.lineNumber); + assertEquals("Description Variable",problemMarkerInfo.description); + assertEquals("var=Variable",problemMarkerInfo.variableName); + + // Eat-line + problemMarkerInfo = errorList.get(1); + assertEquals(IMarkerGenerator.SEVERITY_WARNING, problemMarkerInfo.severity); + assertEquals("L/"+TEST_PROJECT_NAME+"/"+fileName, problemMarkerInfo.file.toString()); + assertEquals(fileName, problemMarkerInfo.file.getName()); + assertEquals(10, problemMarkerInfo.lineNumber); + assertEquals("Description",problemMarkerInfo.description); + assertEquals("Variable",problemMarkerInfo.variableName); + + // Nulls + problemMarkerInfo = errorList.get(2); + assertEquals(IMarkerGenerator.SEVERITY_INFO, problemMarkerInfo.severity); + assertEquals("P/"+TEST_PROJECT_NAME, problemMarkerInfo.file.toString()); + assertEquals(0, problemMarkerInfo.lineNumber); + assertEquals("",problemMarkerInfo.description); + assertEquals("",problemMarkerInfo.variableName); + + // clone & equals + RegexErrorParser cloned = (RegexErrorParser)regexErrorParser.clone(); + assertTrue(cloned!=regexErrorParser); + assertEquals(regexErrorParser, cloned); + assertTrue(cloned.getPatterns()!=regexErrorParser.getPatterns()); + assertEquals(cloned.getPatterns().length, regexErrorParser.getPatterns().length); + for (int i=0; i0); + final String firstId = ErrorParserManager.getErrorParserAvailableIds()[0]; + final IErrorParserNamed firstErrorParser = ErrorParserManager.getErrorParserCopy(firstId); + assertNotNull(firstErrorParser); + assertEquals(firstId, firstErrorParser.getId()); + final String firstName = firstErrorParser.getName(); + // Preconditions + { + String all = ErrorParserManager.toDelimitedString(ErrorParserManager.getErrorParserAvailableIds()); + assertEquals(false, all.contains(TESTING_ID)); + assertEquals(true, all.contains(firstId)); + + assertNull(ErrorParserManager.getErrorParserCopy(TESTING_ID)); + + IErrorParserNamed retrieved2 = ErrorParserManager.getErrorParserCopy(firstId); + assertNotNull(retrieved2); + assertEquals(firstErrorParser, retrieved2); + } + + // set available parsers + { + IErrorParser dummy1 = new DummyErrorParser(); + IErrorParser dummy2 = new DummyErrorParser(); + ErrorParserManager.setUserDefinedErrorParsers(new IErrorParserNamed[] { + // add brand new one + new ErrorParserNamedWrapper(TESTING_ID, TESTING_NAME, dummy1), + // override extension with another one + new ErrorParserNamedWrapper(firstId, firstName, dummy2), + }); + String all = ErrorParserManager.toDelimitedString(ErrorParserManager.getErrorParserAvailableIds()); + assertEquals(true, all.contains(TESTING_ID)); + assertEquals(true, all.contains(firstId)); + + IErrorParserNamed retrieved1 = ErrorParserManager.getErrorParserCopy(TESTING_ID); + assertNotNull(retrieved1); + assertEquals(TESTING_NAME, retrieved1.getName()); + assertTrue(retrieved1 instanceof ErrorParserNamedWrapper); + assertEquals(dummy1, ((ErrorParserNamedWrapper)retrieved1).getErrorParser()); + + IErrorParserNamed retrieved2 = ErrorParserManager.getErrorParserCopy(firstId); + assertNotNull(retrieved2); + assertEquals(firstName, retrieved2.getName()); + assertTrue(retrieved2 instanceof ErrorParserNamedWrapper); + assertEquals(dummy2, ((ErrorParserNamedWrapper)retrieved2).getErrorParser()); + } + // reset available parsers + { + ErrorParserManager.setUserDefinedErrorParsers(null); + + String all = ErrorParserManager.toDelimitedString(ErrorParserManager.getErrorParserAvailableIds()); + assertEquals(false, all.contains(TESTING_ID)); + assertEquals(true, all.contains(firstId)); + + assertNull(ErrorParserManager.getErrorParserCopy(TESTING_ID)); + + IErrorParserNamed retrieved2 = ErrorParserManager.getErrorParserCopy(firstId); + assertNotNull(retrieved2); + assertEquals(firstErrorParser, retrieved2); + } + } + + /** + * Test setting/retrieval of user defined error parsers. + * + * @throws Exception... + */ + public void testUserDefinedErrorParsers() throws Exception { + final String TESTING_ID = "org.eclipse.cdt.core.test.errorparser"; + final String TESTING_NAME = "An error parser"; + // reset parsers + { + ErrorParserManager.setUserDefinedErrorParsers(null); + String all = ErrorParserManager.toDelimitedString(ErrorParserManager.getErrorParserAvailableIds()); + String extensions = ErrorParserManager.toDelimitedString(ErrorParserManager.getErrorParserExtensionIds()); + assertEquals(all, extensions); + } + { + ErrorParserManager.setUserDefinedErrorParsers(new IErrorParserNamed[] { + new ErrorParserNamedWrapper(TESTING_ID, TESTING_NAME, new DummyErrorParser()), + }); + String all = ErrorParserManager.toDelimitedString(ErrorParserManager.getErrorParserAvailableIds()); + String extensions = ErrorParserManager.toDelimitedString(ErrorParserManager.getErrorParserExtensionIds()); + assertFalse(all.equals(extensions)); + } + } + + /** + * Test setting/retrieval of default error parser IDs preferences. + * + * @throws Exception... + */ + public void testDefaultErrorParserIds() throws Exception { + final String[] availableParserIds = ErrorParserManager.getErrorParserAvailableIds(); + assertNotNull(availableParserIds); + final String[] initialDefaultErrorParserIds = ErrorParserManager.getDefaultErrorParserIds(); + + // preconditions + { + String[] defaultErrorParserIds = ErrorParserManager.getDefaultErrorParserIds(); + assertNotNull(defaultErrorParserIds); + assertEquals(ErrorParserManager.toDelimitedString(availableParserIds), + ErrorParserManager.toDelimitedString(defaultErrorParserIds)); + } + // setDefaultErrorParserIds + { + String[] newDefaultErrorParserIds = { + "org.eclipse.cdt.core.test.errorparser0", + "org.eclipse.cdt.core.test.errorparser1", + "org.eclipse.cdt.core.test.errorparser2", + }; + ErrorParserManager.setDefaultErrorParserIds(newDefaultErrorParserIds); + String[] defaultErrorParserIds = ErrorParserManager.getDefaultErrorParserIds(); + assertNotNull(defaultErrorParserIds); + assertEquals(ErrorParserManager.toDelimitedString(newDefaultErrorParserIds), + ErrorParserManager.toDelimitedString(defaultErrorParserIds)); + } + + // reset + { + ErrorParserManager.setDefaultErrorParserIds(null); + String[] defaultErrorParserIds = ErrorParserManager.getDefaultErrorParserIds(); + assertNotNull(defaultErrorParserIds); + assertEquals(ErrorParserManager.toDelimitedString(availableParserIds), + ErrorParserManager.toDelimitedString(defaultErrorParserIds)); + } + } + + /** + * Test serialization of user defined error parsers. + * + * @throws Exception... + */ + public void testSerializeErrorParser() throws Exception { + final String TESTING_ID = "org.eclipse.cdt.core.test.errorparser"; + final String TESTING_NAME = "An error parser"; + + { + // Create error parser + IErrorParser errorParser = new GCCErrorParser(); + // Add to available parsers + ErrorParserExtensionManager.setUserDefinedErrorParsersInternal(new IErrorParserNamed[] {new ErrorParserNamedWrapper(TESTING_ID, TESTING_NAME, errorParser)}); + assertNotNull(ErrorParserManager.getErrorParserCopy(TESTING_ID)); + assertEquals(TESTING_NAME, ErrorParserManager.getErrorParserCopy(TESTING_ID).getName()); + // Serialize in persistent storage + ErrorParserExtensionManager.serializeUserDefinedErrorParsers(); + } + { + // Remove from available parsers + ErrorParserExtensionManager.setUserDefinedErrorParsersInternal(null); + assertNull(ErrorParserManager.getErrorParserCopy(TESTING_ID)); + } + + { + // Re-load from persistent storage and check it out + ErrorParserExtensionManager.loadUserDefinedErrorParsers(); + IErrorParserNamed errorParser = ErrorParserManager.getErrorParserCopy(TESTING_ID); + assertNotNull(errorParser); + assertEquals(TESTING_NAME, errorParser.getName()); + assertTrue(errorParser instanceof ErrorParserNamedWrapper); + assertTrue(((ErrorParserNamedWrapper)errorParser).getErrorParser() instanceof GCCErrorParser); + } + { + // Remove from available parsers as clean-up + ErrorParserExtensionManager.setUserDefinedErrorParsersInternal(null); + assertNull(ErrorParserManager.getErrorParserCopy(TESTING_ID)); + } + } + + /** + * Test serialization of user defined RegexErrorParser. + * + * @throws Exception... + */ + public void testSerializeRegexErrorParser() throws Exception { + + final String TESTING_ID = "org.eclipse.cdt.core.test.regexerrorparser"; + final String TESTING_NAME = "Regex Error Parser"; + final String ALL_IDS = ErrorParserManager.toDelimitedString(ErrorParserManager.getErrorParserAvailableIds()); + { + // Create error parser with the same id as in eclipse registry + RegexErrorParser regexErrorParser = new RegexErrorParser(TESTING_ID, TESTING_NAME); + regexErrorParser.addPattern(new RegexErrorPattern("Pattern-Y", + "line-Y", "file-Y", "description-Y", null, IMarkerGenerator.SEVERITY_WARNING, false)); + + // Add to available parsers + ErrorParserExtensionManager.setUserDefinedErrorParsersInternal(new IErrorParserNamed[] {regexErrorParser}); + assertNotNull(ErrorParserManager.getErrorParserCopy(TESTING_ID)); + // And serialize in persistent storage + ErrorParserExtensionManager.serializeUserDefinedErrorParsers(); + } + + { + // Remove from available parsers + ErrorParserExtensionManager.setUserDefinedErrorParsersInternal(null); + assertNull(ErrorParserManager.getErrorParserCopy(TESTING_ID)); + } + + { + // Re-load from persistent storage and check it out + ErrorParserExtensionManager.loadUserDefinedErrorParsers(); + String all = ErrorParserManager.toDelimitedString(ErrorParserManager.getErrorParserAvailableIds()); + assertTrue(all.contains(TESTING_ID)); + + IErrorParser errorParser = ErrorParserManager.getErrorParserCopy(TESTING_ID); + assertNotNull(errorParser); + assertTrue(errorParser instanceof RegexErrorParser); + RegexErrorParser regexErrorParser = (RegexErrorParser)errorParser; + assertEquals(TESTING_ID, regexErrorParser.getId()); + assertEquals(TESTING_NAME, regexErrorParser.getName()); + + RegexErrorPattern[] errorPatterns = regexErrorParser.getPatterns(); + assertEquals(1, errorPatterns.length); + assertEquals("Pattern-Y", errorPatterns[0].getPattern()); + } + + { + // Remove from available parsers and serialize + ErrorParserExtensionManager.setUserDefinedErrorParsersInternal(null); + ErrorParserExtensionManager.serializeUserDefinedErrorParsers(); + + // Re-load from persistent storage and check it out + ErrorParserExtensionManager.loadUserDefinedErrorParsers(); + String all = ErrorParserManager.toDelimitedString(ErrorParserManager.getErrorParserAvailableIds()); + assertEquals(ALL_IDS, all); + } + } + + /** + * Make sure special characters are serialized properly. + * + * @throws Exception... + */ + public void testSerializeRegexErrorParserSpecialCharacters() throws Exception { + + final String TESTING_ID = "org.eclipse.cdt.core.test.regexerrorparser"; + final String TESTING_NAME = "<>\"'\\& Error Parser"; + final String TESTING_REGEX = "Pattern-<>\"'\\&"; + final String ALL_IDS = ErrorParserManager.toDelimitedString(ErrorParserManager.getErrorParserAvailableIds()); + { + // Create error parser with the same id as in eclipse registry + RegexErrorParser regexErrorParser = new RegexErrorParser(TESTING_ID, TESTING_NAME); + regexErrorParser.addPattern(new RegexErrorPattern(TESTING_REGEX, + "line-<>\"'\\&", "file-<>\"'\\&", "description-<>\"'\\&", null, IMarkerGenerator.SEVERITY_WARNING, false)); + + // Add to available parsers + ErrorParserExtensionManager.setUserDefinedErrorParsersInternal(new IErrorParserNamed[] {regexErrorParser}); + assertNotNull(ErrorParserManager.getErrorParserCopy(TESTING_ID)); + // And serialize in persistent storage + ErrorParserExtensionManager.serializeUserDefinedErrorParsers(); + } + + { + // Re-load from persistent storage and check it out + ErrorParserExtensionManager.loadUserDefinedErrorParsers(); + String all = ErrorParserManager.toDelimitedString(ErrorParserManager.getErrorParserAvailableIds()); + assertTrue(all.contains(TESTING_ID)); + + IErrorParser errorParser = ErrorParserManager.getErrorParserCopy(TESTING_ID); + assertNotNull(errorParser); + assertTrue(errorParser instanceof RegexErrorParser); + RegexErrorParser regexErrorParser = (RegexErrorParser)errorParser; + assertEquals(TESTING_ID, regexErrorParser.getId()); + assertEquals(TESTING_NAME, regexErrorParser.getName()); + + RegexErrorPattern[] errorPatterns = regexErrorParser.getPatterns(); + assertEquals(1, errorPatterns.length); + assertEquals(TESTING_REGEX, errorPatterns[0].getPattern()); + } + } + + /** + * Check that default parser IDs are stored properly. + * + * @throws Exception... + */ + public void testSerializeDefaultErrorParserIds() throws Exception { + final String[] testingDefaultErrorParserIds = { + "org.eclipse.cdt.core.test.errorparser0", + "org.eclipse.cdt.core.test.errorparser1", + "org.eclipse.cdt.core.test.errorparser2", + }; + final String TESTING_IDS = ErrorParserManager.toDelimitedString(testingDefaultErrorParserIds); + final String DEFAULT_IDS = ErrorParserManager.toDelimitedString(ErrorParserManager.getDefaultErrorParserIds()); + + { + // setDefaultErrorParserIds + ErrorParserExtensionManager.setDefaultErrorParserIdsInternal(testingDefaultErrorParserIds); + + String[] defaultErrorParserIds = ErrorParserManager.getDefaultErrorParserIds(); + assertNotNull(defaultErrorParserIds); + assertEquals(TESTING_IDS, ErrorParserManager.toDelimitedString(defaultErrorParserIds)); + + // serialize them + ErrorParserExtensionManager.serializeDefaultErrorParserIds(); + } + + { + // Remove from internal list + ErrorParserExtensionManager.setDefaultErrorParserIdsInternal(null); + assertEquals(DEFAULT_IDS, ErrorParserManager.toDelimitedString(ErrorParserManager.getDefaultErrorParserIds())); + } + + { + // Re-load from persistent storage and check it out + ErrorParserExtensionManager.loadDefaultErrorParserIds(); + + String[] defaultErrorParserIds = ErrorParserManager.getDefaultErrorParserIds(); + assertNotNull(defaultErrorParserIds); + assertEquals(TESTING_IDS, ErrorParserManager.toDelimitedString(defaultErrorParserIds)); + } + + { + // Reset IDs and serialize + ErrorParserExtensionManager.setDefaultErrorParserIdsInternal(null); + ErrorParserExtensionManager.serializeDefaultErrorParserIds(); + + // Check that default IDs are loaded + ErrorParserExtensionManager.loadDefaultErrorParserIds(); + String[] defaultErrorParserIds = ErrorParserManager.getDefaultErrorParserIds(); + assertNotNull(defaultErrorParserIds); + assertEquals(DEFAULT_IDS, ErrorParserManager.toDelimitedString(defaultErrorParserIds)); + } + } + + /** + * Test retrieval of error parser, clone() and equals(). + * + * @throws Exception... + */ + public void testGetErrorParserCopy() throws Exception { + { + IErrorParserNamed clone1 = ErrorParserManager.getErrorParserCopy(REGEX_ERRORPARSER_ID); + IErrorParserNamed clone2 = ErrorParserManager.getErrorParserCopy(REGEX_ERRORPARSER_ID); + assertEquals(clone1, clone2); + assertNotSame(clone1, clone2); + } + { + IErrorParserNamed clone1 = ErrorParserManager.getErrorParserCopy(GCC_ERRORPARSER_ID); + IErrorParserNamed clone2 = ErrorParserManager.getErrorParserCopy(GCC_ERRORPARSER_ID); + assertEquals(clone1, clone2); + assertNotSame(clone1, clone2); + + assertTrue(clone1 instanceof ErrorParserNamedWrapper); + assertTrue(clone2 instanceof ErrorParserNamedWrapper); + IErrorParser gccClone1 = ((ErrorParserNamedWrapper)clone1).getErrorParser(); + IErrorParser gccClone2 = ((ErrorParserNamedWrapper)clone2).getErrorParser(); + assertNotSame(clone1, clone2); + } + } +} diff --git a/core/org.eclipse.cdt.core.tests/plugin.xml b/core/org.eclipse.cdt.core.tests/plugin.xml index 2ea4018d2dc..757b5f248e3 100644 --- a/core/org.eclipse.cdt.core.tests/plugin.xml +++ b/core/org.eclipse.cdt.core.tests/plugin.xml @@ -159,4 +159,21 @@ + + + + + + diff --git a/core/org.eclipse.cdt.core/plugin.properties b/core/org.eclipse.cdt.core/plugin.properties index 02fe04af25c..63f637feb3b 100644 --- a/core/org.eclipse.cdt.core/plugin.properties +++ b/core/org.eclipse.cdt.core/plugin.properties @@ -48,6 +48,7 @@ CDTGNUAssemblerErrorParser.name=CDT GNU Assembler Error Parser CDTGNULinkerErrorParser.name=CDT GNU Linker Error Parser CDTGNUMakeErrorParser.name=CDT GNU Make Error Parser CDTVisualCErrorParser.name=CDT Visual C Error Parser +CDTRegexErrorParser.name=CDT Regular Expression Error Parser PathEntryContainerInitializer=Path Entry Container Initializer diff --git a/core/org.eclipse.cdt.core/plugin.xml b/core/org.eclipse.cdt.core/plugin.xml index 6174b57e379..1af203884e5 100644 --- a/core/org.eclipse.cdt.core/plugin.xml +++ b/core/org.eclipse.cdt.core/plugin.xml @@ -144,6 +144,8 @@ name="%CDTGNUMakeErrorParser.name" point="org.eclipse.cdt.core.ErrorParser"> @@ -152,6 +154,8 @@ name="%CDTGNUCErrorParser.name" point="org.eclipse.cdt.core.ErrorParser"> @@ -160,6 +164,8 @@ name="%CDTGNUAssemblerErrorParser.name" point="org.eclipse.cdt.core.ErrorParser"> @@ -168,6 +174,8 @@ name="%CDTGNULinkerErrorParser.name" point="org.eclipse.cdt.core.ErrorParser"> @@ -176,9 +184,20 @@ name="%CDTVisualCErrorParser.name" point="org.eclipse.cdt.core.ErrorParser"> + + + + diff --git a/core/org.eclipse.cdt.core/schema/ErrorParser.exsd b/core/org.eclipse.cdt.core/schema/ErrorParser.exsd index a4ce450e13c..0ab67eae5a2 100644 --- a/core/org.eclipse.cdt.core/schema/ErrorParser.exsd +++ b/core/org.eclipse.cdt.core/schema/ErrorParser.exsd @@ -23,14 +23,14 @@ - + ID of the extension point (Simple ID) - + Name of the extension point @@ -46,19 +46,107 @@ - + + + + + + + ID of the error parser. If attribute is missing error parser ID is constructed appending Simple ID of extension to plugin ID. + + + + + + + Name of the error parser. If this attribute is missing extension name is taken. + + + + a fully qualified name of the Java class that implements <samp>org.eclipse.cdt.core.IErrorParser</samp> interface. - + + + + + Use element "pattern" to configure RegexErrorParser. + + + + + + + Attribute "severity" specifies which severity should be used to display the marker in Problems View. There are 3 levels of severity, "Error", "Warning" and "Info". "Ignore" lets stop evaluating the line by the rest of patterns without showing up in Problems View. + + + + + + + + + + + + + + + + + + + Java regular expression to define capturing groups for file-expr, line-expr and description-expr. + + + + + + + "Replacement" expression composed from capturing groups defined in regex to define the file. + + + + + + + "Replacement" expression composed from capturing groups defined in regex to define the line in file. + + + + + + + "Replacement" expression composed from capturing groups defined in regex to define the description (i.e. "$1: $2"). It is possible to specify more than one capturing group in such expression. + + + + + + + "Replacement" expression composed from capturing groups defined in regex to define variable. The value will be assigned to marker attributes but is not used by CDT currently. + + + + + + + The attribute defines if a line matched by the pattern is prevented or allowed to be processed by the rest of patterns. "No" allows several patterns to evaluate one line. + + + + + + @@ -107,7 +195,8 @@ public class SampleErrorParser extends AbstractErrorParser {<br/> Plug-ins that want to extend this extension point must implement <samp>org.eclipse.cdt.core.IErrorParser</samp> interface. <br/> -It is recommended to extend <samp>org.eclipse.cdt.core.errorparsers.AbstractErrorParser</samp> for most cases. +For most cases it is sufficient to configure RegexErrorParser which is provided by default. +Another good choice is to extend <samp>org.eclipse.cdt.core.errorparsers.AbstractErrorParser</samp> as done in the example. <br/> ErrorParsers dealing with multi-line messages should implement <samp>org.eclipse.cdt.core.IErrorParser2</samp> interface. @@ -132,8 +221,7 @@ All rights reserved. This program and the accompanying materials<br/> are made available under the terms of the Eclipse Public License v1.0<br/> which accompanies this distribution, and is available at<br/> http://www.eclipse.org/legal/epl-v10.html<br/> - - \ No newline at end of file + diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/CCorePlugin.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/CCorePlugin.java index e73c0eefb19..b3627d36a99 100644 --- a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/CCorePlugin.java +++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/CCorePlugin.java @@ -19,7 +19,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.MissingResourceException; import java.util.ResourceBundle; @@ -60,6 +59,7 @@ import org.eclipse.cdt.internal.core.pdom.PDOMManager; import org.eclipse.cdt.internal.core.resources.ResourceLookup; import org.eclipse.cdt.internal.core.settings.model.CProjectDescriptionManager; import org.eclipse.cdt.internal.core.settings.model.ExceptionFactory; +import org.eclipse.cdt.internal.errorparsers.ErrorParserExtensionManager; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProjectDescription; import org.eclipse.core.resources.IResource; @@ -118,7 +118,13 @@ public class CCorePlugin extends Plugin { public static final String PREF_INDEXER = "indexer"; //$NON-NLS-1$ public static final String DEFAULT_INDEXER = IPDOMManager.ID_FAST_INDEXER; + /** + * Name of the extension point for contributing an error parser + */ public final static String ERROR_PARSER_SIMPLE_ID = "ErrorParser"; //$NON-NLS-1$ + /** + * Full unique name of the extension point for contributing an error parser + */ public final static String ERROR_PARSER_UNIQ_ID = PLUGIN_ID + "." + ERROR_PARSER_SIMPLE_ID; //$NON-NLS-1$ // default store for pathentry @@ -886,41 +892,29 @@ public class CCorePlugin extends Plugin { } /** - * Array of error parsers ids. + * @deprecated since CDT 6.1. Use {@link ErrorParserManager#getErrorParserAvailableIds()} instead + * @return array of error parsers ids */ + @Deprecated public String[] getAllErrorParsersIDs() { - IExtensionPoint extension = Platform.getExtensionRegistry().getExtensionPoint(CCorePlugin.PLUGIN_ID, ERROR_PARSER_SIMPLE_ID); - String[] empty = new String[0]; - if (extension != null) { - IExtension[] extensions = extension.getExtensions(); - ArrayList list = new ArrayList(extensions.length); - for (IExtension e : extensions) - list.add(e.getUniqueIdentifier()); - return list.toArray(empty); - } - return empty; + ErrorParserExtensionManager.loadErrorParserExtensions(); + return ErrorParserExtensionManager.getErrorParserAvailableIds(); } - + + /** + * @deprecated since CDT 6.1. Use {@link ErrorParserManager#getErrorParserCopy(String)} instead + * @param id - id of error parser + * @return array of error parsers + */ + @Deprecated public IErrorParser[] getErrorParser(String id) { - IErrorParser[] empty = new IErrorParser[0]; - try { - IExtensionPoint extension = Platform.getExtensionRegistry().getExtensionPoint(CCorePlugin.PLUGIN_ID, ERROR_PARSER_SIMPLE_ID); - if (extension != null) { - IExtension[] extensions = extension.getExtensions(); - List list = new ArrayList(extensions.length); - for (IExtension e : extensions) { - String parserID = e.getUniqueIdentifier(); - if ((id == null && parserID != null) || (id != null && id.equals(parserID))) { - for (IConfigurationElement ce : e.getConfigurationElements()) - list.add((IErrorParser)ce.createExecutableExtension("class")); //$NON-NLS-1$ - } - } - return list.toArray(empty); - } - } catch (CoreException e) { - log(e); + ErrorParserExtensionManager.loadErrorParserExtensions(); + IErrorParser errorParser = ErrorParserExtensionManager.getErrorParserInternal(id); + if (errorParser == null) { + return new IErrorParser[] {}; + } else { + return new IErrorParser[] { errorParser }; } - return empty; } public IScannerInfoProvider getScannerInfoProvider(IProject project) { diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/ErrorParserManager.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/ErrorParserManager.java index 115752d03ae..bbf08b37f46 100644 --- a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/ErrorParserManager.java +++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/ErrorParserManager.java @@ -23,8 +23,10 @@ import java.util.List; import java.util.Map; import java.util.Vector; +import org.eclipse.cdt.core.errorparsers.ErrorParserNamedWrapper; import org.eclipse.cdt.core.resources.ACBuilder; import org.eclipse.cdt.internal.core.resources.ResourceLookup; +import org.eclipse.cdt.internal.errorparsers.ErrorParserExtensionManager; import org.eclipse.cdt.utils.CygPath; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; @@ -36,6 +38,7 @@ import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.URIUtil; +import org.osgi.service.prefs.BackingStoreException; /** * The purpose of ErrorParserManager is to delegate the work of error parsing @@ -46,10 +49,22 @@ import org.eclipse.core.runtime.URIUtil; * @noextend This class is not intended to be subclassed by clients. */ public class ErrorParserManager extends OutputStream { + /** + * The list of error parsers stored in .project for 3.X projects + * as key/value pair with key="org.eclipse.cdt.core.errorOutputParser" + * @deprecated since CDT 4.0. + */ + @Deprecated + public final static String PREF_ERROR_PARSER = CCorePlugin.PLUGIN_ID + ".errorOutputParser"; //$NON-NLS-1$ + + /** + * Delimiter for error parsers presented in one string. + * @since 5.2 + */ + public final static char ERROR_PARSER_DELIMITER = ';'; private int nOpens; - - public final static String PREF_ERROR_PARSER = CCorePlugin.PLUGIN_ID + ".errorOutputParser"; //$NON-NLS-1$ + private int lineCounter=0; private final IProject fProject; private final IMarkerGenerator fMarkerGenerator; @@ -143,12 +158,14 @@ public class ErrorParserManager extends OutputStream { private void enableErrorParsers(String[] parsersIDs) { if (parsersIDs == null) { - parsersIDs = CCorePlugin.getDefault().getAllErrorParsersIDs(); + parsersIDs = ErrorParserExtensionManager.getDefaultErrorParserIds(); } fErrorParsers = new LinkedHashMap(parsersIDs.length); for (String parsersID : parsersIDs) { - IErrorParser[] parsers = CCorePlugin.getDefault().getErrorParser(parsersID); - fErrorParsers.put(parsersID, parsers); + IErrorParser errorParser = ErrorParserExtensionManager.getErrorParserCopy(parsersID); + if (errorParser!=null) { + fErrorParsers.put(parsersID, new IErrorParser[] {errorParser} ); + } } } @@ -169,7 +186,7 @@ public class ErrorParserManager extends OutputStream { } /** - * Return the current URI location where the build is being performed + * @return the current URI location where the build is being performed * @since 5.1 */ public URI getWorkingDirectoryURI() { @@ -292,9 +309,14 @@ public class ErrorParserManager extends OutputStream { String lineTrimmed = line.trim(); + lineCounter++; for (IErrorParser[] parsers : fErrorParsers.values()) { - for (IErrorParser curr : parsers) { + for (IErrorParser parser : parsers) { + IErrorParser curr = parser; + if (parser instanceof ErrorParserNamedWrapper) { + curr = ((ErrorParserNamedWrapper)parser).getErrorParser(); + } int types = IErrorParser2.NONE; if (curr instanceof IErrorParser2) { types = ((IErrorParser2) curr).getProcessLineBehaviour(); @@ -324,6 +346,14 @@ public class ErrorParserManager extends OutputStream { } } } + + /** + * @return counter counting processed lines of output + * @since 5.2 + */ + public int getLineCounter() { + return lineCounter; + } /** * Returns the file with the given (partial) location if that file can be uniquely identified. @@ -693,4 +723,78 @@ public class ErrorParserManager extends OutputStream { public boolean hasErrors() { return hasErrors; } + + /** + * Set and store in workspace area user defined error parsers. + * + * @param errorParsers - array of user defined error parsers + * @throws CoreException in case of problems + * @since 5.2 + */ + public static void setUserDefinedErrorParsers(IErrorParserNamed[] errorParsers) throws CoreException { + ErrorParserExtensionManager.setUserDefinedErrorParsers(errorParsers); + } + + /** + * @return available error parsers IDs which include contributed through extension + user defined ones + * from workspace + * @since 5.2 + */ + public static String[] getErrorParserAvailableIds() { + return ErrorParserExtensionManager.getErrorParserAvailableIds(); + } + + /** + * @return IDs of error parsers contributed through error parser extension point. + * @since 5.2 + */ + public static String[] getErrorParserExtensionIds() { + return ErrorParserExtensionManager.getErrorParserExtensionIds(); + } + + /** + * Set and store default error parsers IDs to be used if error parser list is empty. + * + * @param ids - default error parsers IDs + * @throws BackingStoreException in case of problem with storing + * @since 5.2 + */ + public static void setDefaultErrorParserIds(String[] ids) throws BackingStoreException { + ErrorParserExtensionManager.setDefaultErrorParserIds(ids); + } + + /** + * @return default error parsers IDs to be used if error parser list is empty. + * @since 5.2 + */ + public static String[] getDefaultErrorParserIds() { + return ErrorParserExtensionManager.getDefaultErrorParserIds(); + } + + /** + * @param id - ID of error parser + * @return cloned copy of error parser. Note that {@link ErrorParserNamedWrapper} returns + * shallow copy with the same instance of underlying error parser. + * @since 5.2 + */ + public static IErrorParserNamed getErrorParserCopy(String id) { + return ErrorParserExtensionManager.getErrorParserCopy(id); + } + + /** + * @param ids - array of error parser IDs + * @return error parser IDs delimited with error parser delimiter ";" + * @since 5.2 + */ + public static String toDelimitedString(String[] ids) { + String result=""; //$NON-NLS-1$ + for (String id : ids) { + if (result.length()==0) { + result = id; + } else { + result += ERROR_PARSER_DELIMITER + id; + } + } + return result; + } } diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/IErrorParserNamed.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/IErrorParserNamed.java new file mode 100644 index 00000000000..462d58b4281 --- /dev/null +++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/IErrorParserNamed.java @@ -0,0 +1,40 @@ +/******************************************************************************* + * Copyright (c) 2009 Andrew Gvozdev (Quoin Inc.) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Andrew Gvozdev (Quoin Inc.) - Initial API and implementation + *******************************************************************************/ +package org.eclipse.cdt.core; + +/** + * Extension of IErrorParser interface to attach id and names to an error parser. + * Clients must implement {@link Object#clone} and {@link Object#equals} methods to avoid slicing. + * @since 5.2 + */ +public interface IErrorParserNamed extends IErrorParser, Cloneable { + /** + * Set error parser ID. + * @param id of error parser + */ + public void setId(String id); + + /** + * Set error parser name. + * @param name of error parser + */ + public void setName(String name); + + /** + * @return id of error parser + */ + public String getId(); + + /** + * @return name of error parser + */ + public String getName(); +} diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/errorparsers/ErrorParserNamedWrapper.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/errorparsers/ErrorParserNamedWrapper.java new file mode 100644 index 00000000000..bf43ffb47a6 --- /dev/null +++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/errorparsers/ErrorParserNamedWrapper.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (c) 2009, 2009 Andrew Gvozdev (Quoin Inc.) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Andrew Gvozdev (Quoin Inc.) - initial API and implementation + *******************************************************************************/ + +package org.eclipse.cdt.core.errorparsers; + +import org.eclipse.cdt.core.ErrorParserManager; +import org.eclipse.cdt.core.IErrorParser; +import org.eclipse.cdt.core.IErrorParserNamed; +import org.eclipse.core.runtime.Assert; + +/** + * Class to wrap any {@link IErrorParser} to {@link IErrorParserNamed}. + * @since 5.2 + */ +public class ErrorParserNamedWrapper implements IErrorParserNamed { + private String fId; + private String fName; + private final IErrorParser fErrorParser; + + /** + * Constructor. + * + * @param id - assigned ID + * @param name - assigned name. + * @param errorParser - error parser to assign name and ID. + */ + public ErrorParserNamedWrapper(String id, String name, IErrorParser errorParser) { + Assert.isNotNull(errorParser); + + this.fId = id; + this.fName = name; + this.fErrorParser = errorParser; + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.core.IErrorParser#processLine(java.lang.String, org.eclipse.cdt.core.ErrorParserManager) + */ + public boolean processLine(String line, ErrorParserManager epm) { + return fErrorParser.processLine(line, epm); + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.core.IErrorParserNamed#getId() + */ + public String getId() { + return fId; + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.core.IErrorParserNamed#getName() + */ + public String getName() { + return fName; + } + + /** + * @return original error parser which is being wrapped + */ + public IErrorParser getErrorParser() { + return fErrorParser; + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.core.IErrorParserNamed#setId(java.lang.String) + */ + public void setId(String id) { + this.fId = id; + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.core.IErrorParserNamed#setName(java.lang.String) + */ + public void setName(String name) { + this.fName = name; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object o) { + if (o instanceof ErrorParserNamedWrapper) { + ErrorParserNamedWrapper that = (ErrorParserNamedWrapper)o; + return this.fId.equals(that.fId) + && this.fName.equals(that.fName) + // can't be more specific than that since IErrorParser may not implement equals()... + && this.getClass()==that.getClass(); + } + return false; + + } + + /* (non-Javadoc) + * @see java.lang.Object#clone() + */ + @Override + public Object clone() throws CloneNotSupportedException { + // shallow copy since IErrorParser is not {@link Cloneable} in general. + return new ErrorParserNamedWrapper(fId, fName, fErrorParser); + } +} diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/errorparsers/RegexErrorParser.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/errorparsers/RegexErrorParser.java new file mode 100644 index 00000000000..e1852d7e7fd --- /dev/null +++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/errorparsers/RegexErrorParser.java @@ -0,0 +1,179 @@ +/******************************************************************************* + * Copyright (c) 2009, 2009 Andrew Gvozdev (Quoin Inc.) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Andrew Gvozdev (Quoin Inc.) - initial API and implementation + *******************************************************************************/ + +package org.eclipse.cdt.core.errorparsers; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.cdt.core.CCorePlugin; +import org.eclipse.cdt.core.ErrorParserManager; +import org.eclipse.cdt.core.IErrorParser; +import org.eclipse.cdt.core.IErrorParserNamed; + +/** + * {@code RegexerrorParser} is an error parser designed to use regular expressions in order + * to parse build output to produce Errors, Warnings or Infos in Problems View. + * + * Clients may extend this class. As it implements {@link Cloneable} interface those clients + * must implement {@link Object#clone} and {@link Object#equals} methods to avoid slicing. + * Hint to implementers: if you want to extend it with customized {@link RegexErrorPattern} + * it is possible to inject it in {@link #addPattern(RegexErrorPattern)}. + * + * @see IErrorParser + * @since 5.2 + */ +public class RegexErrorParser implements IErrorParserNamed, Cloneable { + private String fId; + private String fName; + private final List fPatterns= new ArrayList(); + + /** + * Default constructor will initialize the error parser with the name of the class + * using reflection mechanism. + */ + public RegexErrorParser() { + fName = this.getClass().getSimpleName(); + fId = this.getClass().getCanonicalName(); + } + + /** + * Constructor to initialize ID and name of the error parser. + * + * @param id - ID of the error parser. + * @param name - name of the error parser. + */ + public RegexErrorParser(String id, String name) { + fName = name; + fId = id; + } + + /** + * Set error parser ID. + * + * @param id of error parser + */ + public void setId(String id) { + fId = id; + } + + /** + * Set error parser name. + * + * @param name of error parser + */ + public void setName(String name) { + fName = name; + } + + /** + * Add new {@link RegexErrorPattern}. + * + * @param pattern - new pattern + */ + public void addPattern(RegexErrorPattern pattern) { + fPatterns.add(pattern); + } + + /** + * Remove error pattern from processing. + * + * @param pattern - error pattern to remove + */ + public void removePattern(RegexErrorPattern pattern) { + fPatterns.remove(pattern); + } + + /** + * Remove all error patterns. + */ + public void clearPatterns() { + fPatterns.clear(); + } + + /** + * Method toString() for debugging purposes. + */ + @Override + public String toString() { + return "id="+fId+", name="+fName; //$NON-NLS-1$//$NON-NLS-2$ + } + + /** + * @return id of error parser + */ + public String getId() { + return fId; + } + + /** + * @return name of error parser + */ + public String getName() { + return fName; + } + + /** + * @return array of error patterns of this error parser. + */ + public RegexErrorPattern[] getPatterns() { + return fPatterns.toArray(new RegexErrorPattern[0]); + } + + + /** + * Parse a line of build output and register errors/warnings/infos for + * Problems view in internal list of {@link ErrorParserManager}. + * + * @param line - line of the input + * @param epManager - error parsers manager + * @return true if error parser recognized and accepted line, false otherwise + */ + public boolean processLine(String line, ErrorParserManager epManager) { + for (RegexErrorPattern pattern : fPatterns) + try { + if (pattern.processLine(line, epManager)) + return true; + } catch (Exception e){ + String message = "Error parsing line [" + line + "]"; //$NON-NLS-1$//$NON-NLS-2$ + CCorePlugin.log(message, e); + } + + return false; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object o) { + if (o instanceof RegexErrorParser) { + RegexErrorParser that = (RegexErrorParser)o; + return this.fId.equals(that.fId) + && this.fName.equals(that.fName) + && this.fPatterns.equals(that.fPatterns); + } + return false; + + } + + /* (non-Javadoc) + * @see java.lang.Object#clone() + */ + @Override + public Object clone() throws CloneNotSupportedException { + RegexErrorParser that = new RegexErrorParser(fId, fName); + for (RegexErrorPattern pattern : fPatterns) { + that.addPattern((RegexErrorPattern)pattern.clone()); + } + return that; + } +} diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/errorparsers/RegexErrorPattern.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/errorparsers/RegexErrorPattern.java new file mode 100644 index 00000000000..95cc485fea7 --- /dev/null +++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/core/errorparsers/RegexErrorPattern.java @@ -0,0 +1,385 @@ +/******************************************************************************* + * Copyright (c) 2009, 2009 Andrew Gvozdev (Quoin Inc.) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Andrew Gvozdev (Quoin Inc.) - initial API and implementation + *******************************************************************************/ + +package org.eclipse.cdt.core.errorparsers; + +import java.io.File; +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.eclipse.cdt.core.ErrorParserManager; +import org.eclipse.cdt.core.IMarkerGenerator; +import org.eclipse.cdt.utils.CygPath; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.Path; + +/** + *

RegexErrorPattern specifies a regular expression and rules how to create markers for + * Problems View. It is used by {@link RegexErrorParser} to process build output. + * + *

Regex pattern used by this class is Java regular expression and defines capturing groups. + * Those capturing groups are used in file, line, description expressions to get the values. + *

For example: pattern "(../../..) (.*):(\d*): (Error:.*)" could go along with + * file-expression "$2", line-expression "$3" and description-expression "$1 $4". + * + *

Note: variable name is being stored in marker tag. However currently it is not being used. + * + *

Severity could be one of: + *
- {@link IMarkerGenerator#SEVERITY_INFO}, + *
- {@link IMarkerGenerator#SEVERITY_WARNING}, + *
- {@link IMarkerGenerator#SEVERITY_ERROR_RESOURCE}, + *
- {@link IMarkerGenerator#SEVERITY_ERROR_BUILD} + *
- {@link RegexErrorPattern#SEVERITY_SKIP} + *
{@code SEVERITY_SKIP} means that output line is checked to match the pattern + * but won't be parsed to create a marker. It is useful with conjunction with + * {@code eatProcessedLine=true} to filter out certain lines. + * + *

{@code eatProcessedLine} specifies if the current output line is being passed + * to the rest of patterns for further processing or consumed by the pattern. + * + *

Clients may extend this class. As it implements {@link Cloneable} interface those clients + * must implement {@link Object#clone} and {@link Object#equals} methods to avoid slicing. + * @since 5.2 + */ +public class RegexErrorPattern implements Cloneable { + /** + * Additional "severity" flag which tells if qualified output line should be ignored. + */ + public static final int SEVERITY_SKIP = -1; + private static final String EMPTY_STR=""; //$NON-NLS-1$ + + private Pattern pattern; + private String fileExpression; + private String lineExpression; + private String descriptionExpression; + private String varNameExpression; + private int severity; + private boolean eatProcessedLine; + + private static boolean isCygwin = true; + + /** + * Constructor. + * + * @param pattern - regular expression describing the capturing groups + * @param fileExpression - capturing group expression defining file name + * @param lineExpression - capturing group expression defining line number + * @param descriptionExpression - capturing group expression defining description + * @param varNameExpression -capturing group expression defining variable name + * @param severity - severity, one of + *
{@link IMarkerGenerator#SEVERITY_INFO}, + *
{@link IMarkerGenerator#SEVERITY_WARNING}, + *
{@link IMarkerGenerator#SEVERITY_ERROR_RESOURCE}, + *
{@link IMarkerGenerator#SEVERITY_ERROR_BUILD} + *
{@link RegexErrorPattern#SEVERITY_SKIP} + * @param eat - defines whether to consume output line avoiding further processing by other patterns + * + *

See general description for this class {@link RegexErrorPattern} for more details. + */ + public RegexErrorPattern(String pattern, + String fileExpression, + String lineExpression, + String descriptionExpression, + String varNameExpression, + int severity, + boolean eat) { + this.pattern = Pattern.compile(pattern!=null ? pattern : EMPTY_STR); + this.fileExpression = fileExpression!=null ? fileExpression : EMPTY_STR; + this.lineExpression = lineExpression!=null ? lineExpression : EMPTY_STR; + this.descriptionExpression = descriptionExpression!=null ? descriptionExpression : EMPTY_STR; + this.varNameExpression = varNameExpression!=null ? varNameExpression : EMPTY_STR; + this.severity = severity; + this.eatProcessedLine = eat; + } + + /** + * @return regular expression pattern + */ + public String getPattern() { + return pattern.toString(); + } + + /** + * @return expression defining file name + */ + public String getFileExpression() { + return fileExpression; + } + + /** + * @return expression defining line number + */ + public String getLineExpression() { + return lineExpression; + } + + /** + * @return expression defining description + */ + public String getDescriptionExpression() { + return descriptionExpression; + } + + /** + * @return expression defining variable name + */ + public String getVarNameExpression() { + return varNameExpression; + } + + /** + * @return severity of the marker, one of: + *
{@link IMarkerGenerator#SEVERITY_INFO}, + *
{@link IMarkerGenerator#SEVERITY_WARNING}, + *
{@link IMarkerGenerator#SEVERITY_ERROR_RESOURCE}, + *
{@link IMarkerGenerator#SEVERITY_ERROR_BUILD} + */ + public int getSeverity() { + return severity; + } + + /** + * @return whether output line is consumed and not processed further by other patterns + */ + public boolean isEatProcessedLine() { + return eatProcessedLine; + } + + /** + * @param pattern - regular expression pattern describing the capturing groups + */ + public void setPattern(String pattern) { + this.pattern = Pattern.compile(pattern); + } + + /** + * @param fileExpression - capturing group expression defining file name + */ + public void setFileExpression(String fileExpression) { + this.fileExpression = fileExpression; + } + + /** + * @param lineExpression - capturing group expression defining line number + */ + public void setLineExpression(String lineExpression) { + this.lineExpression = lineExpression; + } + + /** + * @param descriptionExpression - capturing group expression defining description + */ + public void setDescriptionExpression(String descriptionExpression) { + this.descriptionExpression = descriptionExpression; + } + + /** + * @param varNameExpression -capturing group expression defining variable name + */ + public void setVarNameExpression(String varNameExpression) { + this.varNameExpression = varNameExpression; + } + + /** + * @param severity - severity, one of + *
{@link IMarkerGenerator#SEVERITY_INFO}, + *
{@link IMarkerGenerator#SEVERITY_WARNING}, + *
{@link IMarkerGenerator#SEVERITY_ERROR_RESOURCE}, + *
{@link IMarkerGenerator#SEVERITY_ERROR_BUILD} + *
{@link RegexErrorPattern#SEVERITY_SKIP} + */ + public void setSeverity(int severity) { + this.severity = severity; + } + + /** + * @param eatProcessedLine - whether to consume output line avoiding further processing by other patterns + */ + public void setEatProcessedLine(boolean eatProcessedLine) { + this.eatProcessedLine = eatProcessedLine; + } + + /** + * @param input - input line. + * @return matcher to interpret the input line. + */ + private Matcher getMatcher(CharSequence input) { + return pattern.matcher(input); + } + + private String parseStr(Matcher matcher, String str) { + if (str!=null) + return matcher.replaceAll(str); + return null; + } + /** + * @param matcher - matcher to parse the input line. + * @return parsed file name or {@code null}. + */ + protected String getFileName(Matcher matcher) { + return parseStr(matcher, fileExpression); + } + + /** + * @param matcher - matcher to parse the input line. + * @return parsed line number or {@code 0}. + */ + protected int getLineNum(Matcher matcher) { + if (lineExpression != null) + try { + return Integer.valueOf(matcher.replaceAll(lineExpression)).intValue(); + } catch (NumberFormatException e) { + } + return 0; + } + + /** + * @param matcher - matcher to parse the input line. + * @return parsed description or {@code null}. + */ + protected String getDesc(Matcher matcher) { + return parseStr(matcher, descriptionExpression); + } + + /** + * @param matcher - matcher to parse the input line. + * @return parsed variable name or {@code null}. + */ + protected String getVarName(Matcher matcher) { + return parseStr(matcher, varNameExpression); + } + + /** + * @param matcher - matcher to parse the input line. + * @return severity of the problem. + */ + protected int getSeverity(Matcher matcher) { + return severity; + } + + /** + * Parse a line of build output and register error/warning for + * Problems view. + * + * @param line - one line of output. + * @param eoParser - {@link ErrorParserManager}. + * @return {@code true} if error/warning/info problem was found. + */ + public boolean processLine(String line, ErrorParserManager eoParser) { + Matcher matcher = getMatcher(line); + if (!matcher.find()) + return false; + + recordError(matcher, eoParser); + return eatProcessedLine; + } + + /** + * Register the error in {@link ErrorParserManager}. + * + * @param matcher - matcher to parse the input line. + * @param eoParser - {@link ErrorParserManager}. + * @return {@code true} indicating that error was found. + */ + protected boolean recordError(Matcher matcher, ErrorParserManager eoParser) { + int severity = getSeverity(matcher); + if (severity == SEVERITY_SKIP) + return true; + + String fileName = getFileName(matcher); + int lineNum = getLineNum(matcher); + String desc = getDesc(matcher); + String varName = getVarName(matcher); + IPath externalPath = null ; + + IResource file = null; + if (fileName != null) { + file = eoParser.findFileName(fileName); + + if (file == null) { + // If the file is not found in the workspace we attach the problem to the project + // and add the external path to the file. + file = eoParser.getProject(); + externalPath = getLocation(fileName); + } + } + + eoParser.generateExternalMarker(file, lineNum, desc, severity, varName, externalPath); + return true; + } + + /** + * If the file designated by filename exists, return the IPath representation of the filename + * If it does not exist, try cygpath translation + * + * @param filename - file name + * @return location (outside of the workspace). + */ + private IPath getLocation(String filename) { + IPath path = new Path(filename); + File file = path.toFile() ; + if (!file.exists() && isCygwin && path.isAbsolute()) { + CygPath cygpath = null ; + try { + cygpath = new CygPath("cygpath"); //$NON-NLS-1$ + String cygfilename = cygpath.getFileName(filename); + IPath convertedPath = new Path(cygfilename); + file = convertedPath.toFile() ; + if (file.exists()) { + path = convertedPath; + } + } catch (UnsupportedOperationException e) { + isCygwin = false; + } catch (IOException e) { + } finally { + if (null!=cygpath) { + cygpath.dispose(); + } + } + } + return path ; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object o) { + if (o instanceof RegexErrorPattern) { + RegexErrorPattern that = (RegexErrorPattern)o; + return this.pattern.toString().equals(that.pattern.toString()) + && this.fileExpression.equals(that.fileExpression) + && this.lineExpression.equals(that.lineExpression) + && this.descriptionExpression.equals(that.descriptionExpression) + && this.varNameExpression.equals(that.varNameExpression) + && this.severity==that.severity + && this.eatProcessedLine==that.eatProcessedLine; + } + return false; + + } + + /* (non-Javadoc) + * @see java.lang.Object#clone() + */ + @Override + public Object clone() throws CloneNotSupportedException { + return new RegexErrorPattern(pattern.toString(), + fileExpression, + lineExpression, + descriptionExpression, + varNameExpression, + severity, + eatProcessedLine); + } +} diff --git a/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/errorparsers/ErrorParserExtensionManager.java b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/errorparsers/ErrorParserExtensionManager.java new file mode 100644 index 00000000000..d2e4ba56b9b --- /dev/null +++ b/core/org.eclipse.cdt.core/src/org/eclipse/cdt/internal/errorparsers/ErrorParserExtensionManager.java @@ -0,0 +1,726 @@ +/******************************************************************************* + * Copyright (c) 2009, 2009 Andrew Gvozdev (Quoin Inc.) and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Andrew Gvozdev (Quoin Inc.) - initial API and implementation + *******************************************************************************/ + +package org.eclipse.cdt.internal.errorparsers; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.Map.Entry; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.eclipse.cdt.core.CCorePlugin; +import org.eclipse.cdt.core.ErrorParserManager; +import org.eclipse.cdt.core.IErrorParser; +import org.eclipse.cdt.core.IErrorParserNamed; +import org.eclipse.cdt.core.IMarkerGenerator; +import org.eclipse.cdt.core.errorparsers.ErrorParserNamedWrapper; +import org.eclipse.cdt.core.errorparsers.RegexErrorParser; +import org.eclipse.cdt.core.errorparsers.RegexErrorPattern; +import org.eclipse.cdt.internal.core.XmlUtil; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IExtension; +import org.eclipse.core.runtime.IExtensionPoint; +import org.eclipse.core.runtime.IExtensionRegistry; +import org.eclipse.core.runtime.IPath; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.preferences.IEclipsePreferences; +import org.eclipse.core.runtime.preferences.InstanceScope; +import org.osgi.service.prefs.BackingStoreException; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +/** + * ErrorParserExtensionManager manages error parser extensions, serialization and preferences + * + */ +public class ErrorParserExtensionManager { + private static final String STORAGE_ERRORPARSER_EXTENSIONS = "model.extensions.xml"; //$NON-NLS-1$ + private static final String PREFERENCE_ERRORPARSER_DEFAULT_IDS = "errorparser.default.ids"; //$NON-NLS-1$ + private static final String NONE = ""; //$NON-NLS-1$ + + private static final String EXTENSION_POINT_ERROR_PARSER = "org.eclipse.cdt.core.ErrorParser"; //$NON-NLS-1$ + private static final String ELEM_PLUGIN = "plugin"; //$NON-NLS-1$ + private static final String ELEM_EXTENSION = "extension"; //$NON-NLS-1$ + private static final String ELEM_ERRORPARSER = "errorparser"; //$NON-NLS-1$ + private static final String ELEM_PATTERN = "pattern"; //$NON-NLS-1$ + private static final String ATTR_CLASS = "class"; //$NON-NLS-1$ + private static final String ATTR_ID = "id"; //$NON-NLS-1$ + private static final String ATTR_NAME = "name"; //$NON-NLS-1$ + private static final String ATTR_POINT = "point"; //$NON-NLS-1$ + + private static final String ATTR_REGEX = "regex"; //$NON-NLS-1$ + private static final String ATTR_SEVERITY = "severity"; //$NON-NLS-1$ + private static final String ATTR_FILE = "file-expr"; //$NON-NLS-1$ + private static final String ATTR_LINE = "line-expr"; //$NON-NLS-1$ + private static final String ATTR_DESCRIPTION = "description-expr"; //$NON-NLS-1$ + private static final String ATTR_VARIABLE = "variable-expr"; //$NON-NLS-1$ + private static final String ATTR_EAT_LINE = "eat-processed-line"; //$NON-NLS-1$ + + private static final String ATTR_VALUE_WARNING = "Warning"; //$NON-NLS-1$ + private static final String ATTR_VALUE_ERROR = "Error"; //$NON-NLS-1$ + private static final String ATTR_VALUE_INFO = "Info"; //$NON-NLS-1$ + private static final String ATTR_VALUE_IGNORE = "Ignore"; //$NON-NLS-1$ + + private static final LinkedHashMap fExtensionErrorParsers = new LinkedHashMap(); + private static final LinkedHashMap fAvailableErrorParsers = new LinkedHashMap(); + private static LinkedHashMap fUserDefinedErrorParsers = null; + private static List fDefaultErrorParserIds = null; + + static { + loadUserDefinedErrorParsers(); + loadDefaultErrorParserIds(); + loadErrorParserExtensions(); + } + + /** + * Load user defined error parsers from workspace preference storage. + * + * @noreference This method is not intended to be referenced by clients. + */ + synchronized public static void loadUserDefinedErrorParsers() { + fUserDefinedErrorParsers = null; + Document doc = null; + try { + doc = loadXml(getStoreLocation(STORAGE_ERRORPARSER_EXTENSIONS)); + } catch (Exception e) { + CCorePlugin.log("Can't load preferences from file "+STORAGE_ERRORPARSER_EXTENSIONS, e); //$NON-NLS-1$ + } + + if (doc!=null) { + Set errorParsers = new LinkedHashSet(); + loadErrorParserExtensions(doc, errorParsers); + + if (errorParsers.size()>0) { + fUserDefinedErrorParsers = new LinkedHashMap(); + for (IErrorParserNamed errorParser : errorParsers) { + fUserDefinedErrorParsers.put(errorParser.getId(), errorParser); + } + } + } + recalculateAvailableErrorParsers(); + } + + /** + * Load XML from file to DOM Document. + * + * @param location - location of XML file + * @return new loaded XML Document or {@code null} if file does not exist + * @throws ParserConfigurationException + * @throws SAXException + * @throws IOException + */ + private static Document loadXml(IPath location) throws ParserConfigurationException, SAXException, IOException { + java.io.File storeFile = location.toFile(); + if (storeFile.exists()) { + InputStream xmlStream = new FileInputStream(storeFile); + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + return builder.parse(xmlStream); + } + return null; + } + + /** + * Parse error parser contributed extensions from XML document. + * + * @param doc - source XML + * @param errorParsers - resulting list of error parsers + */ + private static void loadErrorParserExtensions(Document doc, Set errorParsers) { + errorParsers.clear(); + NodeList extentionNodes = doc.getElementsByTagName(ELEM_EXTENSION); + for (int iext=0;iext sortedErrorParsers = new TreeSet(new Comparator() { + public int compare(IErrorParserNamed errorParser1, IErrorParserNamed errorParser2) { + return errorParser1.getName().compareTo(errorParser2.getName()); + } + }); + + loadErrorParserExtensions(Platform.getExtensionRegistry(), sortedErrorParsers); + + fExtensionErrorParsers.clear(); + for (IErrorParserNamed errorParser : sortedErrorParsers) { + fExtensionErrorParsers.put(errorParser.getId(), errorParser); + } + recalculateAvailableErrorParsers(); + } + + /** + * Load error parser contributed extensions from extension registry. + * + * @param registry - extension registry + * @param errorParsers - resulting set of error parsers + */ + private static void loadErrorParserExtensions(IExtensionRegistry registry, Set errorParsers) { + errorParsers.clear(); + IExtensionPoint extension = registry.getExtensionPoint(CCorePlugin.PLUGIN_ID, CCorePlugin.ERROR_PARSER_SIMPLE_ID); + if (extension != null) { + IExtension[] extensions = extension.getExtensions(); + for (IExtension ext : extensions) { + try { + String extensionID = ext.getUniqueIdentifier(); + String oldStyleId = extensionID; + String oldStyleName = ext.getLabel(); + for (IConfigurationElement cfgEl : ext.getConfigurationElements()) { + if (cfgEl.getName().equals(ELEM_ERRORPARSER)) { + IErrorParserNamed errorParser = createErrorParserCarcass(oldStyleId, oldStyleName, cfgEl); + if (errorParser!=null) { + configureErrorParser(errorParser, cfgEl); + errorParsers.add(errorParser); + } + } + } + } catch (Exception e) { + CCorePlugin.log("Cannot load ErrorParser extension " + ext.getUniqueIdentifier(), e); //$NON-NLS-1$ + } + } + } + } + + /** + * Populate the list of available error parsers where workspace level user defined parsers + * overwrite contributed through error parser extension point. + */ + private static void recalculateAvailableErrorParsers() { + fAvailableErrorParsers.clear(); + if (fUserDefinedErrorParsers!=null) { + fAvailableErrorParsers.putAll(fUserDefinedErrorParsers); + } + for (IErrorParserNamed errorParser : fExtensionErrorParsers.values()) { + String id = errorParser.getId(); + if (!fAvailableErrorParsers.containsKey(id)) { + fAvailableErrorParsers.put(id, errorParser); + } + } + } + + /** + * Serialize error parsers in workspace level storage. + * + * @throws CoreException if something goes wrong + */ + public static void serializeUserDefinedErrorParsers() throws CoreException { + try { + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + Document doc = builder.newDocument(); + Element elementPlugin = doc.createElement(ELEM_PLUGIN); + doc.appendChild(elementPlugin); + + if (fUserDefinedErrorParsers!=null) { + for (Entry entry: fUserDefinedErrorParsers.entrySet()) { + IErrorParserNamed errorParser = entry.getValue(); + addErrorParserExtension(elementPlugin, errorParser); + } + } + + serializeXml(doc, getStoreLocation(STORAGE_ERRORPARSER_EXTENSIONS)); + + } catch (Exception e) { + throw new CoreException(new Status(IStatus.ERROR, "Failed serializing to file " + STORAGE_ERRORPARSER_EXTENSIONS, CCorePlugin.PLUGIN_ID, e)); //$NON-NLS-1$ + } + } + + /** + * Utility method to convert severity to string for the purpose of serializing in XML. + * + * @param severity - severity + * @return string representation + */ + private static String severityToString(int severity) { + switch (severity) { + case IMarkerGenerator.SEVERITY_INFO: + return ATTR_VALUE_INFO; + case IMarkerGenerator.SEVERITY_WARNING: + return ATTR_VALUE_WARNING; + case IMarkerGenerator.SEVERITY_ERROR_BUILD: + case IMarkerGenerator.SEVERITY_ERROR_RESOURCE: + return ATTR_VALUE_ERROR; + } + return ATTR_VALUE_IGNORE; + } + + /** + * Utility method to de-serialize severity from XML. + * + * @param attrSeverity - string representation of the severity + * @return severity + */ + private static int stringToSeverity(String attrSeverity) { + if (ATTR_VALUE_ERROR.equals(attrSeverity)) + return IMarkerGenerator.SEVERITY_ERROR_RESOURCE; + if (ATTR_VALUE_WARNING.equals(attrSeverity)) + return IMarkerGenerator.SEVERITY_WARNING; + if (ATTR_VALUE_INFO.equals(attrSeverity)) + return IMarkerGenerator.SEVERITY_INFO; + + return RegexErrorPattern.SEVERITY_SKIP; + } + + /** + * Add error parser extension to XML fragment, normally under element. + * + * @param elementPlugin - element where to add error parser extension + * @param errorParserNamed - error parser to add + */ + private static void addErrorParserExtension(Element elementPlugin, IErrorParserNamed errorParserNamed) { + String id = errorParserNamed.getId(); + String name = errorParserNamed.getName(); + String simpleId = getSimpleId(id); + + IErrorParser errorParser = errorParserNamed; + if (errorParser instanceof ErrorParserNamedWrapper) + errorParser = ((ErrorParserNamedWrapper)errorParser).getErrorParser(); + + Document doc = elementPlugin.getOwnerDocument(); + + // + Element elementExtension = doc.createElement(ELEM_EXTENSION); + elementExtension.setAttribute(ATTR_ID, simpleId); + elementExtension.setAttribute(ATTR_NAME, name); + elementExtension.setAttribute(ATTR_POINT, EXTENSION_POINT_ERROR_PARSER); + + elementPlugin.appendChild(elementExtension); + + // + Element elementErrorParser = doc.createElement(ELEM_ERRORPARSER); + elementErrorParser.setAttribute(ATTR_ID, id); + elementErrorParser.setAttribute(ATTR_NAME, name); + elementErrorParser.setAttribute(ATTR_CLASS, errorParser.getClass().getCanonicalName()); + + elementExtension.appendChild(elementErrorParser); + + if (errorParserNamed instanceof RegexErrorParser) { + RegexErrorParser regexErrorParser = (RegexErrorParser)errorParserNamed; + RegexErrorPattern[] patterns = regexErrorParser.getPatterns(); + + for (RegexErrorPattern pattern : patterns) { + // + Element elementPattern = doc.createElement(ELEM_PATTERN); + elementPattern.setAttribute(ATTR_SEVERITY, severityToString(pattern.getSeverity())); + elementPattern.setAttribute(ATTR_REGEX, pattern.getPattern()); + elementPattern.setAttribute(ATTR_FILE, pattern.getFileExpression()); + elementPattern.setAttribute(ATTR_LINE, pattern.getLineExpression()); + elementPattern.setAttribute(ATTR_DESCRIPTION, pattern.getDescriptionExpression()); + elementPattern.setAttribute(ATTR_EAT_LINE, String.valueOf(pattern.isEatProcessedLine())); + + elementErrorParser.appendChild(elementPattern); + } + + } + } + + /** + * Determine simple ID of error parser as last segment of full or unique ID. + * + * @param uniqueId - full ID of error parser + * @return simple ID of error parser + */ + private static String getSimpleId(String uniqueId) { + String simpleId = uniqueId; + int dot = uniqueId.lastIndexOf('.'); + if (dot>=0) { + simpleId = uniqueId.substring(dot+1); + } + return simpleId; + } + + /** + * Serialize XML Document in a file. + * + * @param doc - XML to serialize + * @param location - location of the file + * @throws IOException in case of problems with file I/O + * @throws TransformerException in case of problems with XML output + */ + synchronized private static void serializeXml(Document doc, IPath location) throws IOException, TransformerException { + + java.io.File storeFile = location.toFile(); + if (!storeFile.exists()) { + storeFile.createNewFile(); + } + OutputStream fileStream = new FileOutputStream(storeFile); + + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$ + transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); //$NON-NLS-1$ + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$ + + XmlUtil.prettyFormat(doc); + DOMSource source = new DOMSource(doc); + StreamResult result = new StreamResult(new FileOutputStream(storeFile)); + transformer.transform(source, result); + + fileStream.close(); + } + + /** + * Save the list of default error parsers in preferences. + * + * @throws BackingStoreException in case of problem storing + */ + public static void serializeDefaultErrorParserIds() throws BackingStoreException { + IEclipsePreferences preferences = new InstanceScope().getNode(CCorePlugin.PLUGIN_ID); + String ids = NONE; + if (fDefaultErrorParserIds!=null) { + ids = ErrorParserManager.toDelimitedString(fDefaultErrorParserIds.toArray(new String[0])); + } + + preferences.put(PREFERENCE_ERRORPARSER_DEFAULT_IDS, ids); + preferences.flush(); + } + + /** + * @param store - name of the store + * @return location of the store in the plug-in state area + */ + private static IPath getStoreLocation(String store) { + return CCorePlugin.getDefault().getStateLocation().append(store); + } + + /** + * Creates empty non-configured error parser from extension point definition looking at "class" attribute. + * ID and name of error parser are assigned from first extension point encountered. + * + * @param className - full qualified class name of error parser. + * @param registry - extension registry + * @return new non-configured error parser + */ + private static IErrorParserNamed createErrorParserCarcass(String className, IExtensionRegistry registry) { + if (className==null || className.length()==0 || className.equals(RegexErrorParser.class.getName())) + return new RegexErrorParser(); + + try { + IExtensionPoint extension = registry.getExtensionPoint(CCorePlugin.PLUGIN_ID, CCorePlugin.ERROR_PARSER_SIMPLE_ID); + if (extension != null) { + IExtension[] extensions = extension.getExtensions(); + for (IExtension ext : extensions) { + String extensionID = ext.getUniqueIdentifier(); + String oldStyleId = extensionID; + String oldStyleName = ext.getLabel(); + for (IConfigurationElement cfgEl : ext.getConfigurationElements()) { + if (cfgEl.getName().equals(ELEM_ERRORPARSER) && className.equals(cfgEl.getAttribute(ATTR_CLASS))) { + return createErrorParserCarcass(oldStyleId, oldStyleName, cfgEl); + } + } + } + } + } catch (Exception e) { + CCorePlugin.log("Error creating error parser", e); //$NON-NLS-1$ + } + return null; + } + + /** + * Creates empty non-configured error parser as executable extension from extension point definition. + * If "class" attribute is empty RegexErrorParser is created. + * + * @param initialId - nominal ID of error parser + * @param initialName - nominal name of error parser + * @param ce - configuration element with error parser definition + * @return new non-configured error parser + * @throws CoreException in case of failure + */ + private static IErrorParserNamed createErrorParserCarcass(String initialId, String initialName, IConfigurationElement ce) throws CoreException { + IErrorParserNamed errorParser = null; + if (ce.getAttribute(ATTR_CLASS)!=null) { + IErrorParser ep = (IErrorParser)ce.createExecutableExtension(ATTR_CLASS); + if (ep instanceof IErrorParserNamed) { + errorParser = (IErrorParserNamed)ep; + errorParser.setId(initialId); + errorParser.setName(initialName); + } else if (ep!=null) { + errorParser = new ErrorParserNamedWrapper(initialId, initialName, ep); + } + } + if (errorParser==null) { + errorParser = new RegexErrorParser(initialId, initialName); + } + return errorParser; + } + + /** + * Configure error parser from XML error parser node. + * + * @param errorParser - error parser to configure + * @param errorparserNode - XML error parser node + */ + private static void configureErrorParser(IErrorParserNamed errorParser, Node errorparserNode) { + NamedNodeMap errorParserAttributes = errorparserNode.getAttributes(); + String id = determineNodeValue(errorParserAttributes.getNamedItem(ATTR_ID)); + String name = determineNodeValue(errorParserAttributes.getNamedItem(ATTR_NAME)); + errorParser.setId(id); + errorParser.setName(name); + if (errorParser instanceof RegexErrorParser) { + RegexErrorParser regexErrorParser = (RegexErrorParser)errorParser; + + NodeList patternNodes = errorparserNode.getChildNodes(); + for (int ipat=0;ipat0) + errorParser.setId(id); + String name = cfgEl.getAttribute(ATTR_NAME); + if (name!=null && name.length()>0) + errorParser.setName(name); + + if (errorParser instanceof RegexErrorParser) { + RegexErrorParser regexErrorParser = (RegexErrorParser)errorParser; + + for (IConfigurationElement cepat : cfgEl.getChildren()) { + if (cepat.getName().equals(ELEM_PATTERN)) { + + boolean eat = ! Boolean.FALSE.toString().equals(cepat.getAttribute(ATTR_EAT_LINE)); + regexErrorParser.addPattern(new RegexErrorPattern(cepat.getAttribute(ATTR_REGEX), + cepat.getAttribute(ATTR_FILE), + cepat.getAttribute(ATTR_LINE), + cepat.getAttribute(ATTR_DESCRIPTION), + cepat.getAttribute(ATTR_VARIABLE), + stringToSeverity(cepat.getAttribute(ATTR_SEVERITY)), + eat)); + } + } + } + } + + /** + * Return error parser as stored in internal list. + * + * @noreference This method is not intended to be referenced by clients. + * Use {@link #getErrorParserCopy(String)} instead. + * + * @param id - ID of error parser + * @return internal instance of error parser + */ + public static IErrorParser getErrorParserInternal(String id) { + IErrorParserNamed errorParser = fAvailableErrorParsers.get(id); + if (errorParser instanceof ErrorParserNamedWrapper) + return ((ErrorParserNamedWrapper)errorParser).getErrorParser(); + return errorParser; + } + + /** + * Set and store in workspace area user defined error parsers. + * + * @param errorParsers - array of user defined error parsers + * @throws CoreException in case of problems + */ + public static void setUserDefinedErrorParsers(IErrorParserNamed[] errorParsers) throws CoreException { + setUserDefinedErrorParsersInternal(errorParsers); + serializeUserDefinedErrorParsers(); + } + + /** + * Internal method to set user defined error parsers in memory. + * + * @noreference This method is not intended to be referenced by clients. + * Use {@link #setUserDefinedErrorParsers(IErrorParserNamed[])}. + * + * @param errorParsers - array of user defined error parsers + */ + public static void setUserDefinedErrorParsersInternal(IErrorParserNamed[] errorParsers) { + if (errorParsers==null) { + fUserDefinedErrorParsers = null; + } else { + fUserDefinedErrorParsers= new LinkedHashMap(); + // set customized list + for (IErrorParserNamed errorParser : errorParsers) { + fUserDefinedErrorParsers.put(errorParser.getId(), errorParser); + } + } + recalculateAvailableErrorParsers(); + } + + /** + * @return available error parsers IDs which include contributed through extension + user defined ones + * from workspace + */ + public static String[] getErrorParserAvailableIds() { + return fAvailableErrorParsers.keySet().toArray(new String[0]); + } + + /** + * @return IDs of error parsers contributed through error parser extension point. + */ + public static String[] getErrorParserExtensionIds() { + return fExtensionErrorParsers.keySet().toArray(new String[0]); + } + + /** + * Set and store default error parsers IDs to be used if error parser list is empty. + * + * @param ids - default error parsers IDs + * @throws BackingStoreException in case of problem with storing + */ + public static void setDefaultErrorParserIds(String[] ids) throws BackingStoreException { + setDefaultErrorParserIdsInternal(ids); + serializeDefaultErrorParserIds(); + } + + /** + * Set default error parsers IDs in internal list. + * + * @noreference This method is not intended to be referenced by clients. + * Use {@link #setDefaultErrorParserIds(String[])}. + * + * @param ids - default error parsers IDs + */ + public static void setDefaultErrorParserIdsInternal(String[] ids) { + if (ids==null) { + fDefaultErrorParserIds = null; + } else { + fDefaultErrorParserIds = new ArrayList(Arrays.asList(ids)); + } + } + + /** + * @return default error parsers IDs to be used if error parser list is empty. + */ + public static String[] getDefaultErrorParserIds() { + if (fDefaultErrorParserIds==null) { + return fAvailableErrorParsers.keySet().toArray(new String[0]); + } + return fDefaultErrorParserIds.toArray(new String[0]); + } + + /** + * @param id - ID of error parser + * @return cloned copy of error parser. Note that {@link ErrorParserNamedWrapper} returns + * shallow copy with the same instance of underlying error parser. + */ + public static IErrorParserNamed getErrorParserCopy(String id) { + IErrorParserNamed errorParser = fAvailableErrorParsers.get(id); + + try { + if (errorParser instanceof RegexErrorParser) { + return (RegexErrorParser) ((RegexErrorParser)errorParser).clone(); + } else if (errorParser instanceof ErrorParserNamedWrapper) { + return (ErrorParserNamedWrapper) ((ErrorParserNamedWrapper)errorParser).clone(); + } + } catch (CloneNotSupportedException e) { + CCorePlugin.log(e); + } + return errorParser; + } + + +} diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/dialogs/IInputStatusValidator.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/dialogs/IInputStatusValidator.java new file mode 100644 index 00000000000..57c100929ef --- /dev/null +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/dialogs/IInputStatusValidator.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2009, 2009 Andrew Gvozdev and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Andrew Gvozdev - initial API and implementation + *******************************************************************************/ +package org.eclipse.cdt.internal.ui.dialogs; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; + +/** + * The IInputStatusValidator is the interface for IStatus validators. + */ +public interface IInputStatusValidator { + /** + * Validates the given string. Returns the status with an error/warning/info message to display if the new + * text fails validation. + * + * @param newText + * the text to check for validity + * + * @return {@link IStatus} object. For the purpose of validation severity and message are considered. + *

  • {@link Status#OK_STATUS} or any {@link IStatus#OK} to indicate no error. + *
  • {@link IStatus#ERROR} indicates an error. + *
  • {@link IStatus#WARNING} indicates a warning. + *
  • {@link IStatus#INFO} indicates an informational message. + */ + public IStatus isValid(String newText); +} diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/dialogs/InputStatusDialog.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/dialogs/InputStatusDialog.java new file mode 100644 index 00000000000..1c5d84b5079 --- /dev/null +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/internal/ui/dialogs/InputStatusDialog.java @@ -0,0 +1,208 @@ +/******************************************************************************* + * Copyright (c) 2009, 2009 Andrew Gvozdev and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Andrew Gvozdev - initial API and implementation + *******************************************************************************/ +package org.eclipse.cdt.internal.ui.dialogs; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.dialogs.StatusDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** + * An input dialog for soliciting an input string from the user. + * The string can be validated. In case of problem error/warning/info message + * is shown in status line and decorated with appropriate status icon. + *

    + * This concrete dialog class can be instantiated as is, or further subclassed as required. + *

    + */ +public class InputStatusDialog extends StatusDialog { + /** + * The title of the dialog. + */ + private String title; + + /** + * The message to display, or null if none. + */ + private String message; + + /** + * The input value; the empty string by default. + */ + private String value = "";//$NON-NLS-1$ + + /** + * The input validator, or null if none. + */ + private IInputStatusValidator validator; + + /** + * Input text widget. + */ + private Text text; + + /** + * Creates an input dialog with OK and Cancel buttons. Note that the dialog + * will have no visual representation (no widgets) until it is told to open. + *

    + * Note that the open method blocks for input dialogs. + *

    + * + * @param parentShell + * the parent shell, or null to create a top-level + * shell + * @param dialogTitle + * the dialog title, or null if none + * @param dialogMessage + * the dialog message, or null if none + * @param initialValue + * the initial input value, or null if none + * (equivalent to the empty string) + * @param validator + * an input validator, or null if none + * For a validator, following return statuses are recognized: + *
  • {@link Status#OK_STATUS} or any {@link IStatus#OK} to indicate no error. + *
  • {@link IStatus#ERROR} indicates an error. + *
  • {@link IStatus#WARNING} indicates a warning. + *
  • {@link IStatus#INFO} indicates an informational message + */ + public InputStatusDialog(Shell parentShell, String dialogTitle, String dialogMessage, + String initialValue, IInputStatusValidator validator) { + super(parentShell); + this.title = dialogTitle; + if (dialogMessage == null) { + this.message = ""; //$NON-NLS-1$ + } else { + this.message = dialogMessage; + } + if (initialValue == null) { + this.value = ""; //$NON-NLS-1$ + } else { + this.value = initialValue; + } + this.validator = validator; + } + + /* + * (non-Javadoc) Method declared on Dialog. + */ + @Override + protected void buttonPressed(int buttonId) { + if (buttonId == IDialogConstants.OK_ID) { + value = text.getText(); + } else { + value = null; + } + super.buttonPressed(buttonId); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.window.Window#configureShell(org.eclipse.swt.widgets.Shell) + */ + @Override + protected void configureShell(Shell shell) { + super.configureShell(shell); + if (title != null) { + shell.setText(title); + } + } + + @Override + protected Control createDialogArea(Composite parent) { + Composite composite = (Composite) super.createDialogArea(parent); + + Label label = new Label(composite, SWT.WRAP); + label.setText(message); + GridData data = new GridData(GridData.GRAB_HORIZONTAL | GridData.GRAB_VERTICAL + | GridData.HORIZONTAL_ALIGN_FILL | GridData.VERTICAL_ALIGN_CENTER); + data.widthHint = convertHorizontalDLUsToPixels(IDialogConstants.MINIMUM_MESSAGE_AREA_WIDTH); + label.setLayoutData(data); + label.setFont(parent.getFont()); + + text = new Text(composite, getInputTextStyle()); + text.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL | GridData.HORIZONTAL_ALIGN_FILL)); + text.addModifyListener(new ModifyListener() { + public void modifyText(ModifyEvent e) { + validateInput(); + } + }); + text.setFocus(); + if (value != null) { + text.setText(value); + text.selectAll(); + } + + applyDialogFont(composite); + + return composite; + } + + /** + * Returns the text area. + * + * @return the text area + */ + protected Text getText() { + return text; + } + + /** + * Returns the validator. + * + * @return the validator + */ + protected IInputStatusValidator getValidator() { + return validator; + } + + public String getValue() { + return value; + } + + /** + * Validates the input. + *

    + * The default implementation of this framework method delegates the request to the supplied input + * validator object; if it finds the input invalid, the error message is displayed in the dialog's message + * line. This hook method is called whenever the text changes in the input field. + *

    + */ + protected void validateInput() { + IStatus status = Status.OK_STATUS; + if (validator != null) { + status = validator.isValid(text.getText()); + } + updateStatus(status); + } + + /** + * Returns the style bits that should be used for the input text field. Defaults to a single line entry. + * Subclasses may override. + * + * @return the integer style bits that should be used when creating the input text + * + */ + protected int getInputTextStyle() { + return SWT.SINGLE | SWT.BORDER; + } + +} diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/dialogs/DialogsMessages.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/dialogs/DialogsMessages.java index 80ac136bd43..73b8abf0b45 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/dialogs/DialogsMessages.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/dialogs/DialogsMessages.java @@ -57,6 +57,52 @@ public class DialogsMessages extends NLS { public static String DocCommentOwnerBlock_SelectDocToolDescription; public static String DocCommentOwnerCombo_None; public static String DocCommentOwnerComposite_DocumentationToolGroupTitle; + /** @since 5.2 */ + public static String RegexErrorParserOptionPage_ConsumeNo; + /** @since 5.2 */ + public static String RegexErrorParserOptionPage_ConsumeYes; + /** @since 5.2 */ + public static String RegexErrorParserOptionPage_DescriptionColumn; + /** @since 5.2 */ + public static String RegexErrorParserOptionPage_EatColumn; + /** @since 5.2 */ + public static String RegexErrorParserOptionPage_FileColumn; + /** @since 5.2 */ + public static String RegexErrorParserOptionPage_LineColumn; + /** @since 5.2 */ + public static String RegexErrorParserOptionPage_LinkToPreferencesMessage; + /** @since 5.2 */ + public static String RegexErrorParserOptionPage_Pattern_Column; + /** @since 5.2 */ + public static String RegexErrorParserOptionPage_SeverityColumn; + /** @since 5.2 */ + public static String RegexErrorParserOptionPage_SeverityError; + /** @since 5.2 */ + public static String RegexErrorParserOptionPage_SeverityIgnore; + /** @since 5.2 */ + public static String RegexErrorParserOptionPage_SeverityInfo; + /** @since 5.2 */ + public static String RegexErrorParserOptionPage_SeverityWarning; + /** @since 5.2 */ + public static String RegexErrorParserOptionPage_Title; + /** @since 5.2 */ + public static String RegexErrorParserOptionPage_TooltipConsume; + /** @since 5.2 */ + public static String RegexErrorParserOptionPage_TooltipDescription; + /** @since 5.2 */ + public static String RegexErrorParserOptionPage_TooltipFile; + /** @since 5.2 */ + public static String RegexErrorParserOptionPage_TooltipLine; + /** @since 5.2 */ + public static String RegexErrorParserOptionPage_TooltipPattern; + /** @since 5.2 */ + public static String RegexErrorParserOptionPage_TooltipSeverity; + /** @since 5.2 */ + public static String RegularExpression_EmptyPattern; + /** @since 5.2 */ + public static String RegularExpression_Validate; + /** @since 5.2 */ + public static String RegularExpression_Enter; static { // initialize resource bundle diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/dialogs/DialogsMessages.properties b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/dialogs/DialogsMessages.properties index f415fe0cdc9..7511ed91e5d 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/dialogs/DialogsMessages.properties +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/dialogs/DialogsMessages.properties @@ -41,3 +41,26 @@ IndexerStrategyBlock_immediateUpdate=Update index immediately after every file-s IndexerStrategyBlock_buildConfigGroup=Build configuration for the indexer IndexerStrategyBlock_activeBuildConfig=Use active build configuration IndexerStrategyBlock_specificBuildConfig=Use the build configuration specified in the project's indexer settings +RegexErrorParserOptionPage_ConsumeNo=No +RegexErrorParserOptionPage_ConsumeYes= +RegexErrorParserOptionPage_DescriptionColumn=Description +RegexErrorParserOptionPage_EatColumn=Consume +RegexErrorParserOptionPage_FileColumn=File +RegexErrorParserOptionPage_LineColumn=Line +RegexErrorParserOptionPage_LinkToPreferencesMessage=These options are workspace-wide and can be changed in Workspace Settings. +RegexErrorParserOptionPage_Pattern_Column=Pattern +RegexErrorParserOptionPage_SeverityColumn=Severity +RegexErrorParserOptionPage_SeverityError=Error +RegexErrorParserOptionPage_SeverityIgnore=Ignore +RegexErrorParserOptionPage_SeverityInfo=Info +RegexErrorParserOptionPage_SeverityWarning=Warning +RegexErrorParserOptionPage_Title=Error Parser Options +RegexErrorParserOptionPage_TooltipConsume=Use ''{0}'' to allow to re-process matching input line by other patterns +RegexErrorParserOptionPage_TooltipDescription=Description as replacement expression (i.e. $3) +RegexErrorParserOptionPage_TooltipFile=File as replacement expression (i.e. $1) +RegexErrorParserOptionPage_TooltipLine=Line as replacement expression (i.e. $2) +RegexErrorParserOptionPage_TooltipPattern=Java regular expression specifying capturing groups for 'File', 'Line' and 'Description' +RegexErrorParserOptionPage_TooltipSeverity=Severity of marker +RegularExpression_EmptyPattern=Empty pattern +RegularExpression_Validate=Validate Regular Expression +RegularExpression_Enter=Enter regular expression (use Ctrl+Space for content assist): diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/dialogs/RegexErrorParserOptionPage.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/dialogs/RegexErrorParserOptionPage.java new file mode 100644 index 00000000000..adb46b9b5b3 --- /dev/null +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/dialogs/RegexErrorParserOptionPage.java @@ -0,0 +1,752 @@ +/******************************************************************************* + * Copyright (c) 2009, 2009 Andrew Gvozdev and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Andrew Gvozdev - Initial API and implementation + *******************************************************************************/ + +package org.eclipse.cdt.ui.dialogs; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.text.FindReplaceDocumentAdapterContentProposalProvider; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.CellEditor; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.ColumnWeightData; +import org.eclipse.jface.viewers.ComboBoxCellEditor; +import org.eclipse.jface.viewers.EditingSupport; +import org.eclipse.jface.viewers.TableLayout; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.TextCellEditor; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Link; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableItem; +import org.eclipse.ui.ISharedImages; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.dialogs.PreferencesUtil; +import org.eclipse.ui.fieldassist.ContentAssistCommandAdapter; + +import com.ibm.icu.text.MessageFormat; + +import org.eclipse.cdt.core.ErrorParserManager; +import org.eclipse.cdt.core.IErrorParserNamed; +import org.eclipse.cdt.core.IMarkerGenerator; +import org.eclipse.cdt.core.errorparsers.RegexErrorParser; +import org.eclipse.cdt.core.errorparsers.RegexErrorPattern; +import org.eclipse.cdt.ui.newui.AbstractCPropertyTab; + +import org.eclipse.cdt.internal.ui.dialogs.MessageLine; +import org.eclipse.cdt.internal.ui.util.PixelConverter; +import org.eclipse.cdt.internal.ui.util.SWTUtil; +import org.eclipse.cdt.internal.ui.util.TableLayoutComposite; + +/** + * Options page for RegexErrorParser in Error Parsers Tab of properties/preferences. + * + * @noextend This class is not intended to be subclassed by clients. + * @since 5.2 + */ +public final class RegexErrorParserOptionPage extends AbstractCOptionPage { + + private static final String WORKSPACE_PREFERENCE_PAGE = "org.eclipse.cdt.make.ui.preferences.BuildSettings"; //$NON-NLS-1$ + + private static final int BUTTON_ADD = 0; + private static final int BUTTON_DELETE = 1; + private static final int BUTTON_MOVEUP = 2; + private static final int BUTTON_MOVEDOWN = 3; + + private static final String[] BUTTONS = new String[] { + AbstractCPropertyTab.ADD_STR, + AbstractCPropertyTab.DEL_STR, + AbstractCPropertyTab.MOVEUP_STR, + AbstractCPropertyTab.MOVEDOWN_STR, + }; + + private static final String OOPS = "OOPS"; //$NON-NLS-1$ + + private Table fTable; + private TableViewer fTableViewer; + private Button[] fButtons = null; + + private RegexErrorParser fErrorParser; + private boolean fEditable; + + /** + * Provides generic implementation for overridden methods. + * One purpose is to make it easier for subclasses to operate with {@link RegexErrorPattern}, + * another is to provide content assist. + * + */ + private abstract class RegexPatternEditingSupport extends EditingSupport { + private final TableViewer tableViewer; + + /** + * Constructor. + * + * @param viewer - table viewer where to provide editing support. + * @param isFindStyle - if "find" or "replace" style for potential content assist, + * see {@link FindReplaceDocumentAdapterContentProposalProvider}. + */ + public RegexPatternEditingSupport(TableViewer viewer, boolean isFindStyle) { + super(viewer); + tableViewer = viewer; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.EditingSupport#canEdit(java.lang.Object) + */ + @Override + protected boolean canEdit(Object element) { + return fEditable; + } + + /** + * The intention of RegexPatternEditingSupport is to provide Regex content assist + * during in-table editing. However having problems with mouse selection and + * {@link ContentAssistCommandAdapter} using {@link FindReplaceDocumentAdapterContentProposalProvider} + * is removed for time being. See bug 288982 for more details. + * + * @see org.eclipse.jface.viewers.EditingSupport#getCellEditor(java.lang.Object) + */ + @Override + protected CellEditor getCellEditor(Object element) { + CellEditor editor = new TextCellEditor(tableViewer.getTable()); + return editor; + } + + /** + * Get value from {@link RegexErrorPattern}. This is column-specific value. + * + * @param regexErrorPattern - pattern to query. + * @return retrieved value + */ + abstract protected Object getFromPattern(RegexErrorPattern regexErrorPattern); + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.EditingSupport#getValue(java.lang.Object) + */ + @Override + protected Object getValue(Object element) { + if (element instanceof RegexErrorPattern) { + RegexErrorPattern regexErrorPattern = (RegexErrorPattern) element; + return getFromPattern(regexErrorPattern); + } + return OOPS; + } + + /** + * Set value into one of the pattern's field. Which field - it's column-specific. + * + * @param regexErrorPattern - pattern to set the field + * @param value - value to set + */ + abstract protected void setToPattern(RegexErrorPattern regexErrorPattern, String value); + + /* (non-Javadoc) + * @see org.eclipse.jface.viewers.EditingSupport#setValue(java.lang.Object, java.lang.Object) + */ + @Override + protected void setValue(Object element, Object value) { + if (element instanceof RegexErrorPattern && (value instanceof String)) { + String stringValue = (String) value; + RegexErrorPattern errorPattern = (RegexErrorPattern) element; + setToPattern(errorPattern, stringValue); + tableViewer.update(element, null); + } + } + } + + /** + * Constructor. + * + * @param errorparser - regex error parser for which to display options. + * @param editable - if error parser is editable and allowed to change its patterns + */ + public RegexErrorParserOptionPage(RegexErrorParser errorparser, boolean editable) { + fErrorParser = errorparser; + fEditable = editable; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.dialogs.IDialogPage#createControl(org.eclipse.swt.widgets.Composite) + */ + @Override + public void createControl(Composite parent) { + Group group = new Group(parent, SWT.SHADOW_ETCHED_IN); + group.setText(DialogsMessages.RegexErrorParserOptionPage_Title); + + GridLayout gridLayout = new GridLayout(2, true); + gridLayout.makeColumnsEqualWidth = false; + gridLayout.marginRight = -10; + gridLayout.marginLeft = -4; + group.setLayout(gridLayout); + group.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + + Composite composite = new Composite(group, SWT.NONE); + GridLayout layout = new GridLayout(); + layout.numColumns = 2; + layout.marginWidth = 1; + layout.marginHeight = 1; + layout.marginRight = 1; + composite.setLayout(layout); + composite.setLayoutData(new GridData(GridData.FILL_BOTH)); + Dialog.applyDialogFont(composite); + + if (!fEditable) + createLinkToPreferences(composite); + + createPatternsTable(group, composite); + + if (fEditable) { + createButtons(composite); + + // TODO: remove the warning before 6.1 + MessageLine errorImageLabel = new MessageLine(composite, SWT.NONE); + errorImageLabel.setImage(JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_WARNING)); + errorImageLabel.setText("Error Parser Options user entries may be lost on CDT update as persistence will be finalized with CDT 6.1 final release."); + } + + setControl(group); + group.update(); + } + + private void createLinkToPreferences(final Composite parent) { + // must not be editable as error parser gets desynchronized with ErrorParsTab + Assert.isTrue(!fEditable); + + Link link = new Link(parent, SWT.NONE); + link.setText(DialogsMessages.RegexErrorParserOptionPage_LinkToPreferencesMessage); + + link.addListener(SWT.Selection, new Listener() { + public void handleEvent(Event event) { + // Use event.text to tell which link was used + PreferencesUtil.createPreferenceDialogOn(parent.getShell(), WORKSPACE_PREFERENCE_PAGE, null, null).open(); + + IErrorParserNamed errorParser = ErrorParserManager.getErrorParserCopy(fErrorParser.getId()); + if (errorParser instanceof RegexErrorParser) + fErrorParser = (RegexErrorParser) errorParser; + else + fErrorParser = null; + + initializeTable(); + } + }); + + GridData gridData = new GridData(SWT.FILL, SWT.BOTTOM, true, false); + gridData.horizontalSpan = 2; + link.setLayoutData(gridData); + } + + private static RegexErrorPattern newDummyPattern() { + return new RegexErrorPattern(null, null, null, null, null, IMarker.SEVERITY_ERROR, true); + } + + private static String severityToString(int severity) { + switch (severity) { + case IMarkerGenerator.SEVERITY_INFO: + return DialogsMessages.RegexErrorParserOptionPage_SeverityInfo; + case IMarkerGenerator.SEVERITY_WARNING: + return DialogsMessages.RegexErrorParserOptionPage_SeverityWarning; + case IMarkerGenerator.SEVERITY_ERROR_BUILD: + case IMarkerGenerator.SEVERITY_ERROR_RESOURCE: + return DialogsMessages.RegexErrorParserOptionPage_SeverityError; + } + return DialogsMessages.RegexErrorParserOptionPage_SeverityIgnore; + } + + private void initializeTable() { + RegexErrorPattern[] errorParserPatterns = fErrorParser!=null + ? errorParserPatterns = fErrorParser.getPatterns() + : new RegexErrorPattern[0]; + + int len = errorParserPatterns.length; + int newLen = len; + + RegexErrorPattern[] tablePatterns = new RegexErrorPattern[newLen]; + System.arraycopy(errorParserPatterns, 0, tablePatterns, 0, len); + + fTableViewer.setInput(tablePatterns); + fTableViewer.refresh(); + } + + private void createPatternsTable(Group group, Composite parent) { + TableLayoutComposite tableLayouter = new TableLayoutComposite(parent, SWT.NONE); + tableLayouter.addColumnData(new ColumnWeightData(10, true)); // severity + tableLayouter.addColumnData(new ColumnWeightData(40, true)); // pattern + tableLayouter.addColumnData(new ColumnWeightData(10, true)); // file + tableLayouter.addColumnData(new ColumnWeightData(10, true)); // line + tableLayouter.addColumnData(new ColumnWeightData(15, true)); // description + tableLayouter.addColumnData(new ColumnWeightData(10, true)); // eat line + + int style= SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER; + if (fEditable) { + style = style | SWT.FULL_SELECTION; + } + fTable = new Table(tableLayouter, style); + fTable.setHeaderVisible(true); + fTable.setLinesVisible(true); + fTable.setEnabled(fEditable); + + GridData gridData = new GridData(GridData.FILL_BOTH); + gridData.heightHint = SWTUtil.getTableHeightHint(fTable, 10); + gridData.widthHint = new PixelConverter(group).convertWidthInCharsToPixels(100); + tableLayouter.setLayoutData(gridData); + fTable.setLayout(new TableLayout()); + + fTable.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + updateButtons(); + } + }); + + fTableViewer = new TableViewer(fTable); + fTableViewer.setUseHashlookup(true); + fTableViewer.setContentProvider(new ArrayContentProvider()); + + createSeverityColumn(); + createPatternColumn(); + createFileColumn(); + createLineColumn(); + createDescriptionColumn(); + createEatLineColumn(); + + initializeTable(); + } + + private void createSeverityColumn() { + TableViewerColumn columnViewer = new TableViewerColumn(fTableViewer, SWT.NONE); + columnViewer.getColumn().setText(DialogsMessages.RegexErrorParserOptionPage_SeverityColumn); + columnViewer.getColumn().setResizable(true); + columnViewer.getColumn().setToolTipText(DialogsMessages.RegexErrorParserOptionPage_TooltipSeverity); + columnViewer.setLabelProvider(new ColumnLabelProvider() { + final ISharedImages images = PlatformUI.getWorkbench().getSharedImages(); + + @Override + public Image getImage(Object element) { + if (element instanceof RegexErrorPattern) { + RegexErrorPattern regexErrorPattern = (RegexErrorPattern) element; + switch (regexErrorPattern.getSeverity()) { + case IMarkerGenerator.SEVERITY_INFO: + return images.getImage(ISharedImages.IMG_OBJS_INFO_TSK); + case IMarkerGenerator.SEVERITY_WARNING: + return images.getImage(ISharedImages.IMG_OBJS_WARN_TSK); + case IMarkerGenerator.SEVERITY_ERROR_BUILD: + case IMarkerGenerator.SEVERITY_ERROR_RESOURCE: + return images.getImage(ISharedImages.IMG_OBJS_ERROR_TSK); + case RegexErrorPattern.SEVERITY_SKIP: + return images.getImage(ISharedImages.IMG_ELCL_REMOVE_DISABLED); + } + } + return null; + } + + @Override + public String getText(Object element) { + if (element instanceof RegexErrorPattern) { + RegexErrorPattern regex = (RegexErrorPattern) element; + return severityToString(regex.getSeverity()); + } + return severityToString(RegexErrorPattern.SEVERITY_SKIP); + } + }); + columnViewer.setEditingSupport(new EditingSupport(fTableViewer) { + final String[] severityComboBoxArray = new String[] { + severityToString(IMarkerGenerator.SEVERITY_ERROR_RESOURCE), + severityToString(IMarkerGenerator.SEVERITY_WARNING), + severityToString(IMarkerGenerator.SEVERITY_INFO), + severityToString(RegexErrorPattern.SEVERITY_SKIP), + }; + + private int severityToIndex(int severity) { + String strSeverity = severityToString(severity); + for (int i = 0; i < severityComboBoxArray.length; i++) { + if (strSeverity.equals(severityComboBoxArray[i])) + return i; + } + return 0; + } + + private int indexToSeverity(int index) { + String strCombo = severityComboBoxArray[index]; + for (int i = 0; i < severityComboBoxArray.length; i++) { + if (severityToString(i).equals(strCombo)) + return i; + } + return RegexErrorPattern.SEVERITY_SKIP; + } + + @Override + protected boolean canEdit(Object element) { + return (element instanceof RegexErrorPattern) && fEditable; + } + + @Override + protected CellEditor getCellEditor(Object element) { + return new ComboBoxCellEditor(fTableViewer.getTable(), severityComboBoxArray, SWT.READ_ONLY); + } + + @Override + protected Object getValue(Object element) { + if (element instanceof RegexErrorPattern) + return severityToIndex(((RegexErrorPattern) element).getSeverity()); + return RegexErrorPattern.SEVERITY_SKIP; + } + + @Override + protected void setValue(Object element, Object value) { + if (element instanceof RegexErrorPattern && (value instanceof Integer)) { + ((RegexErrorPattern) element).setSeverity(indexToSeverity(((Integer) value).intValue())); + fTableViewer.update(element, null); + } + } + + }); + } + + private void createPatternColumn() { + TableViewerColumn columnViewer = new TableViewerColumn(fTableViewer, SWT.NONE); + columnViewer.getColumn().setText(DialogsMessages.RegexErrorParserOptionPage_Pattern_Column); + columnViewer.getColumn().setResizable(true); + columnViewer.getColumn().setToolTipText(DialogsMessages.RegexErrorParserOptionPage_TooltipPattern); + columnViewer.setLabelProvider(new ColumnLabelProvider() { + + @Override + public String getText(Object element) { + if (element instanceof RegexErrorPattern) { + RegexErrorPattern regex = (RegexErrorPattern) element; + String pattern = regex.getPattern(); + return pattern; + } + return OOPS; + } + }); + + columnViewer.setEditingSupport(new RegexPatternEditingSupport(fTableViewer, true) { + @Override + protected Object getFromPattern(RegexErrorPattern regexErrorPattern) { + return regexErrorPattern.getPattern(); + } + + @Override + protected void setToPattern(RegexErrorPattern regexErrorPattern, String value) { + if (!fEditable) + return; + try{ + regexErrorPattern.setPattern(value); + } catch (Exception e) { + // to avoid recursive edits. the dialog is needed to ensure valid pattern on losing focus. + // this looks ugly and likely incorrect + fEditable = false; + RegularExpressionStatusDialog dialog= new RegularExpressionStatusDialog(getShell(), value); + if (dialog.open() == Window.OK) { + regexErrorPattern.setPattern(dialog.getValue()); + } + fEditable = true; + } + } + }); + } + + private void createFileColumn() { + TableViewerColumn columnViewer = new TableViewerColumn(fTableViewer, SWT.NONE); + columnViewer.getColumn().setText(DialogsMessages.RegexErrorParserOptionPage_FileColumn); + columnViewer.getColumn().setToolTipText(DialogsMessages.RegexErrorParserOptionPage_TooltipFile); + columnViewer.getColumn().setResizable(true); + columnViewer.setLabelProvider(new ColumnLabelProvider() { + + @Override + public String getText(Object element) { + if (element instanceof RegexErrorPattern) { + RegexErrorPattern regex = (RegexErrorPattern) element; + return regex.getFileExpression(); + } + return OOPS; + } + }); + columnViewer.setEditingSupport(new RegexPatternEditingSupport(fTableViewer, false) { + @Override + protected Object getFromPattern(RegexErrorPattern regexErrorPattern) { + return regexErrorPattern.getFileExpression(); + } + + @Override + protected void setToPattern(RegexErrorPattern regexErrorPattern, String value) { + regexErrorPattern.setFileExpression(value); + } + }); + } + + private void createLineColumn() { + TableViewerColumn columnViewer = new TableViewerColumn(fTableViewer, SWT.NONE); + columnViewer.getColumn().setText(DialogsMessages.RegexErrorParserOptionPage_LineColumn); + columnViewer.getColumn().setResizable(true); + columnViewer.getColumn().setToolTipText(DialogsMessages.RegexErrorParserOptionPage_TooltipLine); + columnViewer.setLabelProvider(new ColumnLabelProvider() { + + @Override + public String getText(Object element) { + if (element instanceof RegexErrorPattern) { + RegexErrorPattern regex = (RegexErrorPattern) element; + return regex.getLineExpression(); + } + return OOPS; + } + }); + columnViewer.setEditingSupport(new RegexPatternEditingSupport(fTableViewer, false) { + @Override + protected Object getFromPattern(RegexErrorPattern regexErrorPattern) { + return regexErrorPattern.getLineExpression(); + } + + @Override + protected void setToPattern(RegexErrorPattern regexErrorPattern, String value) { + regexErrorPattern.setLineExpression(value); + } + }); + } + + private void createDescriptionColumn() { + TableViewerColumn columnViewer = new TableViewerColumn(fTableViewer, SWT.NONE); + columnViewer.getColumn().setText(DialogsMessages.RegexErrorParserOptionPage_DescriptionColumn); + columnViewer.getColumn().setResizable(true); + columnViewer.getColumn().setToolTipText(DialogsMessages.RegexErrorParserOptionPage_TooltipDescription); + columnViewer.setLabelProvider(new ColumnLabelProvider() { + + @Override + public String getText(Object element) { + if (element instanceof RegexErrorPattern) { + RegexErrorPattern regex = (RegexErrorPattern) element; + return regex.getDescriptionExpression(); + } + return OOPS; + } + }); + columnViewer.setEditingSupport(new RegexPatternEditingSupport(fTableViewer, false) { + @Override + protected Object getFromPattern(RegexErrorPattern regexErrorPattern) { + return regexErrorPattern.getDescriptionExpression(); + } + + @Override + protected void setToPattern(RegexErrorPattern regexErrorPattern, String value) { + regexErrorPattern.setDescriptionExpression(value); + } + }); + } + + private void createEatLineColumn() { + final String EAT_NO = DialogsMessages.RegexErrorParserOptionPage_ConsumeNo; + final String EAT_YES = DialogsMessages.RegexErrorParserOptionPage_ConsumeYes; + + final String[] eatLineComboBoxArray = new String[] { EAT_YES, EAT_NO, }; + final int EAT_YES_INDEX = 0; + final int EAT_NO_INDEX = 1; + + TableViewerColumn columnViewer = new TableViewerColumn(fTableViewer, SWT.NONE); + columnViewer.getColumn().setText(DialogsMessages.RegexErrorParserOptionPage_EatColumn); + columnViewer.getColumn().setResizable(true); + + String message = MessageFormat.format(DialogsMessages.RegexErrorParserOptionPage_TooltipConsume, new Object[] { EAT_NO }); + columnViewer.getColumn().setToolTipText(message); + columnViewer.setLabelProvider(new ColumnLabelProvider() { + + @Override + public String getText(Object element) { + if (element instanceof RegexErrorPattern) { + RegexErrorPattern regex = (RegexErrorPattern) element; + if (!regex.isEatProcessedLine()) + return EAT_NO; + } + return EAT_YES; + } + }); + columnViewer.setEditingSupport(new EditingSupport(fTableViewer) { + @Override + protected boolean canEdit(Object element) { + return (element instanceof RegexErrorPattern) && fEditable; + } + + @Override + protected CellEditor getCellEditor(Object element) { + return new ComboBoxCellEditor(fTableViewer.getTable(), eatLineComboBoxArray, SWT.READ_ONLY); + } + + @Override + protected Object getValue(Object element) { + if (element instanceof RegexErrorPattern) + if (!((RegexErrorPattern) element).isEatProcessedLine()) + return EAT_NO_INDEX; + return EAT_YES_INDEX; + } + + @Override + protected void setValue(Object element, Object value) { + if ((element instanceof RegexErrorPattern) && (value instanceof Integer)) { + ((RegexErrorPattern) element).setEatProcessedLine((Integer) value != EAT_NO_INDEX); + fTableViewer.update(element, null); + } + } + + }); + } + + private void createButtons(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + composite.setLayoutData(new GridData(GridData.FILL_VERTICAL)); + composite.setLayout(new GridLayout(1, false)); + + fButtons = new Button[BUTTONS.length]; + for (int i = 0; i < BUTTONS.length; i++) { + fButtons[i] = new Button(composite, SWT.PUSH); + GridData gridData = new GridData(SWT.FILL, SWT.CENTER, false, false); + gridData.minimumWidth = 80; + + if (BUTTONS[i] != null) { + fButtons[i].setText(BUTTONS[i]); + fButtons[i].setEnabled(false); + } else { + // no button, but placeholder + fButtons[i].setVisible(false); + fButtons[i].setEnabled(false); + gridData.heightHint = 10; + } + + fButtons[i].setLayoutData(gridData); + fButtons[i].addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + for (int i=0; i=0 && pos<=last; + + fButtons[BUTTON_ADD].setEnabled(true); + fButtons[BUTTON_DELETE].setEnabled(selected); + fButtons[BUTTON_MOVEUP].setEnabled(selected && pos != 0); + fButtons[BUTTON_MOVEDOWN].setEnabled(selected && pos != last); + } + } + + private void buttonPressed (int button) { + switch (button) { + case BUTTON_ADD: + addErrorPattern(); + break; + case BUTTON_DELETE: + deleteErrorPattern(); + break; + case BUTTON_MOVEUP: + moveItem(true); + break; + case BUTTON_MOVEDOWN: + moveItem(false); + break; + default: + break; + } + updateButtons(); + } + + private void addErrorPattern() { + int pos = fTable.getSelectionIndex(); + int last = fTable.getItemCount()-1; + if (pos<0 || pos>last) + pos = last; + + int newPos = pos + 1; + fTableViewer.insert(newDummyPattern(), newPos); + fTable.setSelection(newPos); + } + + private void deleteErrorPattern() { + int pos = fTable.getSelectionIndex(); + int last = fTable.getItemCount()-1; + + if (pos>=0 && pos<=last) { + fTableViewer.remove(fTableViewer.getElementAt(pos)); + fTable.setSelection(pos); + } + } + + private void moveItem(boolean up) { + int pos = fTable.getSelectionIndex(); + int count = fTable.getItemCount(); + int last = count-1; + boolean selected = pos>=0 && pos<=last; + + if (!selected || (up && pos==0) || (!up && pos==last)) + return; + + Object item = fTableViewer.getElementAt(pos); + fTableViewer.remove(item); + int newPos = up ? pos-1 : pos+1; + fTableViewer.insert(item, newPos); + fTable.setSelection(newPos); + } + /* + * (non-Javadoc) + * + * @see org.eclipse.cdt.ui.dialogs.ICOptionPage#performApply(org.eclipse.core.runtime.IProgressMonitor) + */ + @Override + public void performApply(IProgressMonitor monitor) throws CoreException { + if (fErrorParser!=null && fEditable) { + fErrorParser.clearPatterns(); + for (TableItem tableItem : fTable.getItems()) { + Object item = tableItem.getData(); + if (item instanceof RegexErrorPattern) { + fErrorParser.addPattern((RegexErrorPattern)item); + } + } + } + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.cdt.ui.dialogs.ICOptionPage#performDefaults() + */ + @Override + public void performDefaults() { + // ErrorParsTas.performDefaults() will do all the work + } +} diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/dialogs/RegularExpressionStatusDialog.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/dialogs/RegularExpressionStatusDialog.java new file mode 100644 index 00000000000..9b816b4469d --- /dev/null +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/dialogs/RegularExpressionStatusDialog.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * Copyright (c) 2009,2009 Andrew Gvozdev and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Andrew Gvozdev - Initial API and implementation + *******************************************************************************/ + +package org.eclipse.cdt.ui.dialogs; + +import java.util.regex.Pattern; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.jface.fieldassist.TextContentAdapter; +import org.eclipse.jface.text.FindReplaceDocumentAdapterContentProposalProvider; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.fieldassist.ContentAssistCommandAdapter; + +import org.eclipse.cdt.internal.ui.dialogs.IInputStatusValidator; +import org.eclipse.cdt.internal.ui.dialogs.InputStatusDialog; +import org.eclipse.cdt.internal.ui.dialogs.StatusInfo; + +/** + * Input Dialog for validating regular expression syntax. + * + * @since 5.2 + */ +public class RegularExpressionStatusDialog extends InputStatusDialog { + private static final IInputStatusValidator fValidator = new IInputStatusValidator() { + public IStatus isValid(String newText) { + StatusInfo status = new StatusInfo(); + if (newText.length() == 0) { + status.setWarning(DialogsMessages.RegularExpression_EmptyPattern); + } else { + try { + Pattern.compile(newText); + } catch (Exception e) { + // only first line of PatternSyntaxException is really descriptive + status.setError(e.getMessage().split("[\n\r]", 2)[0]); //$NON-NLS-1$ + } + } + return status; + } + }; + + /** + * Constructor + * + * @param shell - the parent shell, or null to create a top-level shell + * @param initialValue the initial input value, or null if none + * (equivalent to the empty string) + */ + public RegularExpressionStatusDialog(Shell shell, String initialValue) { + super(shell, DialogsMessages.RegularExpression_Validate, + DialogsMessages.RegularExpression_Enter, + initialValue, fValidator); + } + + /** + * Constructor + * + * @param shell - the parent shell, or null to create a top-level shell + * @param dialogTitle - the dialog title, or null if none + * @param dialogMessage - the dialog message, or null if none + * @param initialValue the initial input value, or null if none + * (equivalent to the empty string) + */ + public RegularExpressionStatusDialog(Shell shell, String dialogTitle, String dialogMessage, String initialValue) { + super(shell, dialogTitle, dialogMessage, initialValue, fValidator); + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.internal.ui.dialogs.InputStatusDialog#createDialogArea(org.eclipse.swt.widgets.Composite) + */ + @Override + protected Control createDialogArea(Composite parent) { + Control control = super.createDialogArea(parent); + + new ContentAssistCommandAdapter( + getText(), + new TextContentAdapter(), + new FindReplaceDocumentAdapterContentProposalProvider(true), + null, + null, + true); + + + setHelpAvailable(false); + return control; + } + + /* (non-Javadoc) + * @see org.eclipse.jface.dialogs.StatusDialog#create() + */ + @Override + public void create() { + super.create(); + if (getValue().length()>0) + validateInput(); + } +} diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/newui/AbstractPage.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/newui/AbstractPage.java index 1fb30db8027..92627095854 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/newui/AbstractPage.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/newui/AbstractPage.java @@ -339,7 +339,7 @@ implements GridData gd; parentComposite = new Composite(c, SWT.NONE); parentComposite.setLayoutData(gd= new GridData(GridData.FILL_BOTH)); - gd.widthHint= 200; + gd.widthHint= 800; itabs.clear(); if (!isSingle()) { parentComposite.setLayout(new FillLayout()); diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/newui/ErrorParsTab.java b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/newui/ErrorParsTab.java index 6dd2faa2f63..95e86c3b8b6 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/newui/ErrorParsTab.java +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/newui/ErrorParsTab.java @@ -7,234 +7,568 @@ * * Contributors: * Intel Corporation - Initial API and implementation + * Andrew Gvozdev (Quoin Inc.) - Regular expression error parsers *******************************************************************************/ package org.eclipse.cdt.ui.newui; -import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.HashMap; -import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; -import org.eclipse.core.runtime.IExtension; -import org.eclipse.core.runtime.IExtensionPoint; -import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Assert; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.viewers.CheckStateChangedEvent; import org.eclipse.jface.viewers.CheckboxTableViewer; import org.eclipse.jface.viewers.ICheckStateListener; import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableItem; +import org.eclipse.ui.PlatformUI; +import org.osgi.service.prefs.BackingStoreException; -import org.eclipse.cdt.core.CCorePlugin; +import com.ibm.icu.text.MessageFormat; + +import org.eclipse.cdt.core.ErrorParserManager; +import org.eclipse.cdt.core.IErrorParserNamed; +import org.eclipse.cdt.core.errorparsers.RegexErrorParser; import org.eclipse.cdt.core.settings.model.ICConfigurationDescription; import org.eclipse.cdt.core.settings.model.ICMultiConfigDescription; import org.eclipse.cdt.core.settings.model.ICResourceDescription; +import org.eclipse.cdt.ui.CUIPlugin; +import org.eclipse.cdt.ui.dialogs.ICOptionPage; +import org.eclipse.cdt.ui.dialogs.RegexErrorParserOptionPage; +import org.eclipse.cdt.utils.ui.controls.TabFolderLayout; + +import org.eclipse.cdt.internal.ui.ICHelpContextIds; +import org.eclipse.cdt.internal.ui.dialogs.IInputStatusValidator; +import org.eclipse.cdt.internal.ui.dialogs.InputStatusDialog; +import org.eclipse.cdt.internal.ui.dialogs.StatusInfo; +import org.eclipse.cdt.internal.ui.util.PixelConverter; /** + * This class represents Error Parser Tab in Project Properties or workspace Preferences + * * @noextend This class is not intended to be subclassed by clients. */ public class ErrorParsTab extends AbstractCPropertyTab { - private HashMap mapParsers = new HashMap(); - private Table table; - private CheckboxTableViewer tv; - private ICConfigurationDescription cfgd; - + private static final int DEFAULT_HEIGHT = 130; + private static final int BUTTON_ADD = 0; + private static final int BUTTON_EDIT = 1; + private static final int BUTTON_DELETE = 2; + // there is a separator instead of button = 3 + private static final int BUTTON_MOVEUP = 4; + private static final int BUTTON_MOVEDOWN = 5; + + private static final String[] BUTTONS = new String[] { + ADD_STR, + EDIT_STR, + DEL_STR, + null, + MOVEUP_STR, + MOVEDOWN_STR, + }; + + private static final String OOPS = "OOPS"; //$NON-NLS-1$ + + private Table fTable; + private CheckboxTableViewer fTableViewer; + private ICConfigurationDescription fCfgDesc; + + private final Map fAvailableErrorParsers = new LinkedHashMap(); + private final Map fOptionsPageMap = new HashMap(); + private ICOptionPage fCurrentOptionsPage = null; + + private Composite fCompositeForOptionsPage; + + /* (non-Javadoc) + * @see org.eclipse.cdt.ui.newui.AbstractCPropertyTab#createControls(org.eclipse.swt.widgets.Composite) + */ @Override public void createControls(Composite parent) { + super.createControls(parent); - usercomp.setLayout(new FillLayout()); - table = new Table(usercomp, SWT.BORDER | SWT.CHECK | SWT.SINGLE); - table.addSelectionListener(new SelectionAdapter() { + PlatformUI.getWorkbench().getHelpSystem().setHelp(usercomp, ICHelpContextIds.ERROR_PARSERS_PAGE); + + usercomp.setLayout(new GridLayout(1, false)); + + // SashForm + SashForm sashForm = new SashForm(usercomp, SWT.NONE); + sashForm.setBackground(sashForm.getDisplay().getSystemColor(SWT.COLOR_GRAY)); + sashForm.setOrientation(SWT.VERTICAL); + sashForm.setLayoutData(new GridData(GridData.FILL_BOTH)); + + GridLayout layout = new GridLayout(2, false); + layout.marginHeight = 5; + sashForm.setLayout(layout); + + // table + Composite compositeSashForm = new Composite(sashForm, SWT.NONE); + compositeSashForm.setLayout(new GridLayout(2, false)); + fTable = new Table(compositeSashForm, SWT.BORDER | SWT.CHECK | SWT.SINGLE); + fTable.setLayoutData(new GridData(GridData.FILL_BOTH)); + fTable.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { + displaySelectedOptionPage(); updateButtons(); }}); - tv = new CheckboxTableViewer(table); - tv.setContentProvider(new IStructuredContentProvider() { + fTableViewer = new CheckboxTableViewer(fTable); + fTableViewer.setContentProvider(new IStructuredContentProvider() { public Object[] getElements(Object inputElement) { return (Object[])inputElement; } public void dispose() {} public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {} }); - - tv.addCheckStateListener(new ICheckStateListener() { - public void checkStateChanged(CheckStateChangedEvent e) { - saveChecked(); + fTableViewer.setLabelProvider(new LabelProvider() { + @Override + public String getText(Object element) { + if (element instanceof String) { + String id = (String)element; + IErrorParserNamed errorParser = fAvailableErrorParsers.get(id); + if (errorParser!=null) { + String name = errorParser.getName(); + if (name!=null && name.length()>0) { + return name; + } + } + return UIMessages.getFormattedString("ErrorParsTab.error.NonAccessibleID", id); //$NON-NLS-1$ + } + return OOPS; } }); - - initButtons(new String[] { - MOVEUP_STR, MOVEDOWN_STR, null, - UIMessages.getString("ErrorParsTab.0"), //$NON-NLS-1$ - UIMessages.getString("ErrorParsTab.1") //$NON-NLS-1$ - }); + + fTableViewer.addCheckStateListener(new ICheckStateListener() { + public void checkStateChanged(CheckStateChangedEvent e) { + saveChecked(); + }}); + + // Buttons + Composite compositeButtons = new Composite(compositeSashForm, SWT.NONE); + compositeButtons.setLayoutData(new GridData(GridData.END)); + initButtons(compositeButtons, BUTTONS); + + fCompositeForOptionsPage = new Composite(sashForm, SWT.NULL); + GridData gd = new GridData(); + fCompositeForOptionsPage.setLayout(new TabFolderLayout()); + + PixelConverter converter = new PixelConverter(parent); + gd.heightHint = converter.convertHorizontalDLUsToPixels(DEFAULT_HEIGHT); + + gd.horizontalAlignment = GridData.FILL; + gd.grabExcessHorizontalSpace = true; + gd.grabExcessVerticalSpace = true; + gd.horizontalSpan = 2; + fCompositeForOptionsPage.setLayoutData(gd); + + sashForm.setWeights(new int[] {50, 50}); + + // init data + ICResourceDescription resDecs = getResDesc(); + fCfgDesc = resDecs!=null ? resDecs.getConfiguration() : null; initMapParsers(); + updateData(getResDesc()); } - - protected void initMapParsers() { - mapParsers.clear(); - IExtensionPoint point = Platform.getExtensionRegistry().getExtensionPoint( - CCorePlugin.PLUGIN_ID, - CCorePlugin.ERROR_PARSER_SIMPLE_ID - ); - if (point != null) { - IExtension[] exts = point.getExtensions(); - for (IExtension ext : exts) { - if (ext.getConfigurationElements().length > 0) { - mapParsers.put(ext.getUniqueIdentifier(), ext.getLabel()); + + private void initMapParsers() { + fAvailableErrorParsers.clear(); + fOptionsPageMap.clear(); + for (String id : ErrorParserManager.getErrorParserAvailableIds()) { + IErrorParserNamed errorParser = ErrorParserManager.getErrorParserCopy(id); + fAvailableErrorParsers.put(id, errorParser); + initializeOptionsPage(id); + } + + String ids[]; + if (fCfgDesc!=null) { + ICConfigurationDescription srcCfgDesc = fCfgDesc.getConfiguration(); + if (srcCfgDesc instanceof ICMultiConfigDescription) { + String[][] ss = ((ICMultiConfigDescription)srcCfgDesc).getErrorParserIDs(); + ids = CDTPrefUtil.getStrListForDisplay(ss); + } else { + ids = srcCfgDesc.getBuildSetting().getErrorParserIDs(); + } + Set setIds = new LinkedHashSet(Arrays.asList(ids)); + setIds.addAll(fAvailableErrorParsers.keySet()); + fTableViewer.setInput(setIds.toArray(new String[0])); + } else { + fTableViewer.setInput(fAvailableErrorParsers.keySet().toArray(new String[0])); + ids = ErrorParserManager.getDefaultErrorParserIds(); + } + fTableViewer.setCheckedElements(ids); + + displaySelectedOptionPage(); + } + + private void initializeOptionsPage(String id) { + IErrorParserNamed errorParser = fAvailableErrorParsers.get(id); + if (errorParser!=null) { + String name = errorParser.getName(); + if (name!=null && name.length()>0) { + // RegexErrorParser has an Options page + if (errorParser instanceof RegexErrorParser) { + // allow to edit only for Build Settings Preference Page (where cfgd==null) + RegexErrorParserOptionPage optionsPage = new RegexErrorParserOptionPage((RegexErrorParser) errorParser, isErrorParsersEditable()); + fOptionsPageMap.put(id, optionsPage); + optionsPage.setContainer(page); + optionsPage.createControl(fCompositeForOptionsPage); + optionsPage.setVisible(false); + fCompositeForOptionsPage.layout(true); } } } } - + + private void displaySelectedOptionPage() { + if (fCurrentOptionsPage != null) + fCurrentOptionsPage.setVisible(false); + + int pos = fTable.getSelectionIndex(); + if (pos<0) + return; + + String parserId = (String)fTable.getItem(pos).getData(); + ICOptionPage optionsPage = fOptionsPageMap.get(parserId); + if (optionsPage != null) { + optionsPage.setVisible(true); + } + fCurrentOptionsPage = optionsPage; + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.ui.newui.AbstractCPropertyTab#buttonPressed(int) + */ @Override public void buttonPressed (int n) { switch (n) { - case 0: // up + case BUTTON_ADD: + addErrorParser(); + break; + case BUTTON_EDIT: + editErrorParser(); + break; + case BUTTON_DELETE: + deleteErrorParser(); + break; + case BUTTON_MOVEUP: moveItem(true); break; - case 1: // down + case BUTTON_MOVEDOWN: moveItem(false); break; - case 2: // do nothing - it's not a button - break; - - case 3: // check all - tv.setAllChecked(true); - saveChecked(); - break; - case 4: // uncheck all - tv.setAllChecked(false); - saveChecked(); - break; default: break; } + updateButtons(); } // Move item up / down private void moveItem(boolean up) { - int n = table.getSelectionIndex(); - if (n < 0 || - (up && n == 0) || - (!up && n+1 == table.getItemCount())) + int n = fTable.getSelectionIndex(); + if (n < 0 || (up && n == 0) || (!up && n+1 == fTable.getItemCount())) return; - TableData d = (TableData)tv.getElementAt(n); - boolean checked = tv.getChecked(d); - tv.remove(d); - n = up ? n - 1 : n + 1; - tv.insert(d, n); - tv.setChecked(d, checked); - table.setSelection(n); + + String id = (String)fTableViewer.getElementAt(n); + boolean checked = fTableViewer.getChecked(id); + fTableViewer.remove(id); + n = up ? n-1 : n+1; + fTableViewer.insert(id, n); + fTableViewer.setChecked(id, checked); + fTable.setSelection(n); + saveChecked(); } - - class TableData { - String key; - String value; - public TableData (String _key, String _value) { - key = _key; - value = _value; - } - @Override - public String toString() { return value; } - } - - @Override - public void updateData(ICResourceDescription _cfgd) { - cfgd = _cfgd.getConfiguration(); - if (mapParsers == null) return; - String[] ss = null; - if (page.isMultiCfg()) { - String[][] ids = ((ICMultiConfigDescription)cfgd).getErrorParserIDs(); - ss = CDTPrefUtil.getStrListForDisplay(ids); - } else { - ss = cfgd.getBuildSetting().getErrorParserIDs(); - } - - ArrayList data = new ArrayList(mapParsers.size()); - ArrayList checked = new ArrayList(ss.length); - HashMap cloneMap = new HashMap(mapParsers); - // add checked elements - for (String element : ss) { - String s = cloneMap.get(element); - if (s != null) { - TableData d = new TableData(element,s); - data.add(d); - checked.add(d); - cloneMap.remove(element); + private String makeId(String name) { + return CUIPlugin.PLUGIN_ID+'.'+name; + } + + private void addErrorParser() { + IInputStatusValidator inputValidator = new IInputStatusValidator() { + public IStatus isValid(String newText) { + StatusInfo status = new StatusInfo(); + if (newText.trim().length() == 0) { + status.setError(UIMessages.getString("ErrorParsTab.error.NonEmptyName")); //$NON-NLS-1$ + } else if (newText.indexOf(ErrorParserManager.ERROR_PARSER_DELIMITER)>=0) { + String message = MessageFormat.format( UIMessages.getString("ErrorParsTab.error.IllegalCharacter"), //$NON-NLS-1$ + new Object[] { ErrorParserManager.ERROR_PARSER_DELIMITER }); + status.setError(message); + } else if (fAvailableErrorParsers.containsKey(makeId(newText))) { + status.setError(UIMessages.getString("ErrorParsTab.error.NonUniqueID")); //$NON-NLS-1$ + } + return status; } + + }; + InputStatusDialog addDialog = new InputStatusDialog(usercomp.getShell(), + UIMessages.getString("ErrorParsTab.title.Add"), //$NON-NLS-1$ + UIMessages.getString("ErrorParsTab.label.EnterName"), //$NON-NLS-1$ + UIMessages.getString("ErrorParsTab.label.DefaultRegexErrorParserName"), //$NON-NLS-1$ + inputValidator); + addDialog.setHelpAvailable(false); + + if (addDialog.open() == Window.OK) { + String newName = addDialog.getValue(); + String newId = makeId(newName); + IErrorParserNamed errorParser = new RegexErrorParser(newId, newName); + fAvailableErrorParsers.put(newId, errorParser); + + fTableViewer.add(newId); + fTableViewer.setChecked(newId, true); + fTable.setSelection(fTable.getItemCount()-1); + + initializeOptionsPage(newId); + displaySelectedOptionPage(); + updateButtons(); } - // add remaining parsers (unchecked) - Iterator it = cloneMap.keySet().iterator(); - while (it.hasNext()) { - String s = it.next(); - data.add(new TableData(s, cloneMap.get(s))); + } + + private void editErrorParser() { + int n = fTable.getSelectionIndex(); + Assert.isTrue(n>=0); + + String id = (String)fTableViewer.getElementAt(n); + IErrorParserNamed errorParser = fAvailableErrorParsers.get(id); + + IInputStatusValidator inputValidator = new IInputStatusValidator() { + public IStatus isValid(String newText) { + StatusInfo status = new StatusInfo(); + if (newText.trim().length() == 0) { + status.setError(UIMessages.getString("ErrorParsTab.error.NonEmptyName")); //$NON-NLS-1$ + } else if (newText.indexOf(ErrorParserManager.ERROR_PARSER_DELIMITER)>=0) { + String message = MessageFormat.format( UIMessages.getString("ErrorParsTab.error.IllegalCharacter"), //$NON-NLS-1$ + new Object[] { ErrorParserManager.ERROR_PARSER_DELIMITER }); + status.setError(message); + } + return status; + } + + }; + InputStatusDialog addDialog = new InputStatusDialog(usercomp.getShell(), + UIMessages.getString("ErrorParsTab.title.Edit"), //$NON-NLS-1$ + UIMessages.getString("ErrorParsTab.label.EnterName"), //$NON-NLS-1$ + errorParser.getName(), + inputValidator); + addDialog.setHelpAvailable(false); + + if (addDialog.open() == Window.OK) { + errorParser.setName(addDialog.getValue()); + fTableViewer.refresh(id); } - tv.setInput(data.toArray()); - tv.setCheckedElements(checked.toArray()); + } + + private void deleteErrorParser() { + int n = fTable.getSelectionIndex(); + if (n < 0) + return; + + fTableViewer.remove(fTableViewer.getElementAt(n)); + + int last = fTable.getItemCount() - 1; + if (n>last) + n = last; + if (n>=0) + fTable.setSelection(n); + + saveChecked(); + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.ui.newui.AbstractCPropertyTab#updateData(org.eclipse.cdt.core.settings.model.ICResourceDescription) + */ + @Override + public void updateData(ICResourceDescription resDecs) { + ICConfigurationDescription oldCfgDesc = fCfgDesc; + fCfgDesc = resDecs!=null ? resDecs.getConfiguration() : null; + if (oldCfgDesc!=fCfgDesc) { + initMapParsers(); + } + displaySelectedOptionPage(); updateButtons(); } + private static boolean isExtensionId(String id) { + for (String extId : ErrorParserManager.getErrorParserExtensionIds()) { + if (extId.equals(id)) { + return true; + } + } + return false; + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.ui.newui.AbstractCPropertyTab#updateButtons() + */ @Override public void updateButtons() { - int cnt = table.getItemCount(); - int pos = table.getSelectionIndex(); - buttonSetEnabled(0, pos > 0); - buttonSetEnabled(1, pos != -1 && pos < (cnt - 1)); - buttonSetEnabled(3, cnt > 0); - buttonSetEnabled(4, cnt > 0); + int pos = fTable.getSelectionIndex(); + int count = fTable.getItemCount(); + int last = count - 1; + boolean selected = pos >= 0 && pos <= last; + String id = (String)fTableViewer.getElementAt(pos); + + buttonSetEnabled(BUTTON_ADD, isErrorParsersEditable()); + buttonSetEnabled(BUTTON_EDIT, isErrorParsersEditable() && selected); + buttonSetEnabled(BUTTON_DELETE, isErrorParsersEditable() && selected && !isExtensionId(id)); + buttonSetEnabled(BUTTON_MOVEUP, selected && pos != 0); + buttonSetEnabled(BUTTON_MOVEDOWN, selected && pos != last); } - + + + /* (non-Javadoc) + * @see org.eclipse.cdt.ui.newui.AbstractCPropertyTab#performApply(org.eclipse.cdt.core.settings.model.ICResourceDescription, org.eclipse.cdt.core.settings.model.ICResourceDescription) + */ @Override protected void performApply(ICResourceDescription src, ICResourceDescription dst) { - ICConfigurationDescription sd = src.getConfiguration(); - ICConfigurationDescription dd = dst.getConfiguration(); - String[] s = null; - if (sd instanceof ICMultiConfigDescription) { - String[][] ss = ((ICMultiConfigDescription)sd).getErrorParserIDs(); - s = CDTPrefUtil.getStrListForDisplay(ss); - } else { - s = sd.getBuildSetting().getErrorParserIDs(); + performOK(); + + if (!page.isForPrefs()) { + ICConfigurationDescription sd = src.getConfiguration(); + ICConfigurationDescription dd = dst.getConfiguration(); + String[] s = null; + if (sd instanceof ICMultiConfigDescription) { + String[][] ss = ((ICMultiConfigDescription)sd).getErrorParserIDs(); + s = CDTPrefUtil.getStrListForDisplay(ss); + } else { + s = sd.getBuildSetting().getErrorParserIDs(); + } + if (dd instanceof ICMultiConfigDescription) + ((ICMultiConfigDescription)dd).setErrorParserIDs(s); + else + dd.getBuildSetting().setErrorParserIDs(s); + initMapParsers(); } - if (dd instanceof ICMultiConfigDescription) - ((ICMultiConfigDescription)dd).setErrorParserIDs(s); - else - dd.getBuildSetting().setErrorParserIDs(s); } + + /* (non-Javadoc) + * @see org.eclipse.cdt.ui.newui.AbstractCPropertyTab#performOK() + */ + @Override + protected void performOK() { + informPages(true); + + if (page.isForPrefs()) { + if (fCfgDesc==null) { + // Build Settings page + try { + IErrorParserNamed[] errorParsers = new IErrorParserNamed[fTable.getItemCount()]; + int i=0; + for (TableItem item : fTable.getItems()) { + if (item.getData() instanceof String) { + String id = (String) item.getData(); + errorParsers[i] = fAvailableErrorParsers.get(id); + i++; + } + } - private void saveChecked() { - Object[] objs = tv.getCheckedElements(); - ArrayList lst = new ArrayList(); - if (objs != null) { - for (Object ob : objs) - lst.add(((TableData)ob).key); + Object[] checkedElements = fTableViewer.getCheckedElements(); + String[] checkedErrorParserIds = new String[checkedElements.length]; + System.arraycopy(checkedElements, 0, checkedErrorParserIds, 0, checkedElements.length); + + ErrorParserManager.setUserDefinedErrorParsers(errorParsers); + ErrorParserManager.setDefaultErrorParserIds(checkedErrorParserIds); + } catch (BackingStoreException e) { + CUIPlugin.log(UIMessages.getString("ErrorParsTab.error.OnApplyingSettings"), e); //$NON-NLS-1$ + } catch (CoreException e) { + CUIPlugin.log(UIMessages.getString("ErrorParsTab.error.OnApplyingSettings"), e); //$NON-NLS-1$ + } + } + initMapParsers(); } - String[] s = lst.toArray(new String[lst.size()]); - if (cfgd instanceof ICMultiConfigDescription) - ((ICMultiConfigDescription)cfgd).setErrorParserIDs(s); - else - cfgd.getBuildSetting().setErrorParserIDs(s); } - // This page can be displayed for project only + + private void saveChecked() { + if (fCfgDesc!=null) { + Object[] objs = fTableViewer.getCheckedElements(); + String[] ids = new String[objs.length]; + System.arraycopy(objs, 0, ids, 0, objs.length); + + if (fCfgDesc instanceof ICMultiConfigDescription) + ((ICMultiConfigDescription)fCfgDesc).setErrorParserIDs(ids); + else + fCfgDesc.getBuildSetting().setErrorParserIDs(ids); + } + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.ui.newui.AbstractCPropertyTab#canBeVisible() + */ @Override public boolean canBeVisible() { return page.isForProject() || page.isForPrefs(); } + /** + * @return {@code true} if the error parsers are allowed to be editable, + * i.e. Add/Edit/Delete buttons are enabled and Options page edits enabled. + * This will evaluate to {@code true} for Preference Build Settings page but + * not for Preference New CDT Project Wizard/Makefile Project. + */ + private boolean isErrorParsersEditable() { + return fCfgDesc==null; + } + + /* (non-Javadoc) + * @see org.eclipse.cdt.ui.newui.AbstractCPropertyTab#performDefaults() + */ @Override protected void performDefaults() { - if (cfgd instanceof ICMultiConfigDescription) - ((ICMultiConfigDescription)cfgd).setErrorParserIDs(null); - else - cfgd.getBuildSetting().setErrorParserIDs(null); - updateData(getResDesc()); - } + if (isErrorParsersEditable()) { + // Must be Build Settings Preference Page + if (MessageDialog.openQuestion(usercomp.getShell(), + UIMessages.getString(UIMessages.getString("ErrorParsTab.title.ConfirmReset")), //$NON-NLS-1$ + UIMessages.getString(UIMessages.getString("ErrorParsTab.message.ConfirmReset")))) { //$NON-NLS-1$ + + try { + ErrorParserManager.setUserDefinedErrorParsers(null); + ErrorParserManager.setDefaultErrorParserIds(null); + } catch (BackingStoreException e) { + CUIPlugin.log(UIMessages.getString("ErrorParsTab.error.OnRestoring"), e); //$NON-NLS-1$ + } catch (CoreException e) { + CUIPlugin.log(UIMessages.getString("ErrorParsTab.error.OnRestoring"), e); //$NON-NLS-1$ + } + } + } else { + if (fCfgDesc instanceof ICMultiConfigDescription) + ((ICMultiConfigDescription) fCfgDesc).setErrorParserIDs(null); + else + fCfgDesc.getBuildSetting().setErrorParserIDs(null); + } + initMapParsers(); + updateButtons(); + } + + private void informPages(boolean apply) { + Collection pages = fOptionsPageMap.values(); + for (ICOptionPage dynamicPage : pages) { + if (dynamicPage!=null && dynamicPage.isValid() && dynamicPage.getControl() != null) { + try { + if (apply) + dynamicPage.performApply(new NullProgressMonitor()); + else + dynamicPage.performDefaults(); + } catch (CoreException e) { + CUIPlugin.log(UIMessages.getString("ErrorParsTab.error.OnApplyingSettings"), e); //$NON-NLS-1$ + } + } + } + } } diff --git a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/newui/PluginResources.properties b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/newui/PluginResources.properties index 024be6f9eba..2f330529b4c 100644 --- a/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/newui/PluginResources.properties +++ b/core/org.eclipse.cdt.ui/src/org/eclipse/cdt/ui/newui/PluginResources.properties @@ -443,8 +443,18 @@ ConfigDescriptionTab.0=Project Description ConfigDescriptionTab.1=Configuration Description ConfigDescriptionTab.2=Resource Description BinaryParsTab.0=Binary parser: -ErrorParsTab.0=Check all -ErrorParsTab.1=Uncheck all +ErrorParsTab.error.NonEmptyName=Specify non empty name +ErrorParsTab.error.NonUniqueID=Error parser ID is not unique, specify different name +ErrorParsTab.error.OnApplyingSettings=Error applying Error Parser Tab settings +ErrorParsTab.error.OnRestoring=Error restoring default Error Parser Tab settings +ErrorParsTab.error.NonAccessibleID=[ Not accessible id={0} ] +ErrorParsTab.error.IllegalCharacter=Special character ''{0}'' is not allowed +ErrorParsTab.label.EnterName=Enter name of new error parser: +ErrorParsTab.label.DefaultRegexErrorParserName=Regex Error Parser +ErrorParsTab.message.ConfirmReset=Are you sure you want to delete all customized error parsers? +ErrorParsTab.title.Add=Add Regex Error Parser +ErrorParsTab.title.ConfirmReset=Confirm Resetting Error Parsers +ErrorParsTab.title.Edit=Edit Regex Error Parser name StructureTreeTab.0=Level: StructureTreeTab.1=Maximal tree nesting StructureTreeTab.2=Long