| /******************************************************************************* |
| * Copyright (c) 2009, 2013 Alena Laskavaia |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Alena Laskavaia - initial API and implementation |
| * Sergey Prigogin (Google) |
| *******************************************************************************/ |
| package org.eclipse.cdt.codan.internal.core; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.cdt.codan.core.CodanCorePlugin; |
| import org.eclipse.cdt.codan.core.PreferenceConstants; |
| import org.eclipse.cdt.codan.core.model.AbstractCheckerWithProblemPreferences; |
| import org.eclipse.cdt.codan.core.model.CheckerLaunchMode; |
| import org.eclipse.cdt.codan.core.model.Checkers; |
| import org.eclipse.cdt.codan.core.model.CodanSeverity; |
| import org.eclipse.cdt.codan.core.model.IChecker; |
| import org.eclipse.cdt.codan.core.model.ICheckerWithPreferences; |
| import org.eclipse.cdt.codan.core.model.ICheckersRegistry; |
| import org.eclipse.cdt.codan.core.model.IProblem; |
| import org.eclipse.cdt.codan.core.model.IProblemCategory; |
| import org.eclipse.cdt.codan.core.model.IProblemProfile; |
| import org.eclipse.cdt.codan.core.model.IProblemWorkingCopy; |
| import org.eclipse.cdt.codan.core.param.LaunchModeProblemPreference; |
| import org.eclipse.cdt.codan.internal.core.model.CodanProblem; |
| import org.eclipse.cdt.codan.internal.core.model.CodanProblemCategory; |
| import org.eclipse.cdt.codan.internal.core.model.ProblemProfile; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.ResourcesPlugin; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IConfigurationElement; |
| import org.eclipse.core.runtime.IExtensionPoint; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.preferences.ConfigurationScope; |
| import org.eclipse.core.runtime.preferences.DefaultScope; |
| import org.eclipse.core.runtime.preferences.InstanceScope; |
| import org.osgi.service.prefs.Preferences; |
| |
| /** |
| * Implementation of checker registry interface |
| */ |
| public class CheckersRegistry implements ICheckersRegistry { |
| private static final String NAME_ATTR = "name"; //$NON-NLS-1$ |
| private static final String ID_ATTR = "id"; //$NON-NLS-1$ |
| private static final String CLASS_ATTR = "class"; //$NON-NLS-1$ |
| private static final String CHECKERS_EXTENSION_POINT_NAME = "checkers"; //$NON-NLS-1$ |
| private static final String CHECKER_ENABLEMENT_EXTENSION_POINT_NAME = "checkerEnablement"; //$NON-NLS-1$ |
| private static final String CHECKER_ELEMENT = "checker"; //$NON-NLS-1$ |
| private static final String PROBLEM_ELEMENT = "problem"; //$NON-NLS-1$ |
| private static final String CATEGORY_ELEMENT = "category"; //$NON-NLS-1$ |
| private static final Object DEFAULT = "DEFAULT"; //$NON-NLS-1$ |
| public static final String CLONE_SUFFIX = ".COPY"; //$NON-NLS-1$ |
| private Collection<IChecker> checkers = new ArrayList<>(); |
| private static CheckersRegistry instance; |
| private static boolean initialized = false; |
| private final Map<Object, IProblemProfile> profiles = new HashMap<>(); |
| private final Map<IChecker, Collection<IProblem>> problemList = new HashMap<>(); |
| private final Map<String, IChecker> problemCheckerMapping = new HashMap<>(); |
| private final List<ICheckerEnablementVerifier> checkerEnablementVerifiers = new ArrayList<>(); |
| |
| private CheckersRegistry() { |
| instance = this; |
| profiles.put(DEFAULT, new ProblemProfile(DEFAULT)); |
| readCheckersRegistry(); |
| readCheckerEnablementVerifier(); |
| initialized = true; |
| } |
| |
| private void readCheckersRegistry() { |
| IExtensionPoint ep = getExtensionPoint(CHECKERS_EXTENSION_POINT_NAME); |
| if (ep == null) |
| return; |
| IConfigurationElement[] elements = ep.getConfigurationElements(); |
| // process categories |
| for (int i = 0; i < elements.length; i++) { |
| IConfigurationElement configurationElement = elements[i]; |
| processCategories(configurationElement); |
| } |
| // process shared problems |
| for (int i = 0; i < elements.length; i++) { |
| IConfigurationElement configurationElement = elements[i]; |
| processProblem(configurationElement); |
| } |
| // process checkers |
| for (int i = 0; i < elements.length; i++) { |
| IConfigurationElement configurationElement = elements[i]; |
| processChecker(configurationElement); |
| } |
| // init parameters for checkers with parameters |
| for (Iterator<IChecker> iterator = problemList.keySet().iterator(); iterator.hasNext();) { |
| IChecker c = iterator.next(); |
| if (c instanceof ICheckerWithPreferences) { |
| Collection<IProblem> list = problemList.get(c); |
| for (Iterator<IProblem> iterator2 = list.iterator(); iterator2.hasNext();) { |
| IProblem p = iterator2.next(); |
| if (p instanceof IProblemWorkingCopy) { |
| try { |
| ((ICheckerWithPreferences) c).initPreferences((IProblemWorkingCopy) p); |
| } catch (Throwable t) { |
| CodanCorePlugin.log(t); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * @param configurationElement |
| */ |
| private void processCategories(IConfigurationElement configurationElement) { |
| if (configurationElement.getName().equals(CATEGORY_ELEMENT)) { |
| String id = getAtt(configurationElement, ID_ATTR); |
| if (id == null) |
| return; |
| String name = getAtt(configurationElement, NAME_ATTR); |
| if (name == null) |
| return; |
| CodanProblemCategory cat = new CodanProblemCategory(id, name); |
| String category = getAtt(configurationElement, "parentCategory", false); //$NON-NLS-1$ |
| addCategory(cat, category); |
| } |
| } |
| |
| /** |
| * @param configurationElement |
| */ |
| private void processChecker(IConfigurationElement configurationElement) { |
| try { |
| if (configurationElement.getName().equals(CHECKER_ELEMENT)) { |
| String id = getAtt(configurationElement, ID_ATTR); |
| if (id == null) |
| return; |
| String name = getAtt(configurationElement, NAME_ATTR, false); |
| if (name == null) |
| name = id; |
| IChecker checkerObj = null; |
| try { |
| Object checker = configurationElement.createExecutableExtension(CLASS_ATTR); |
| checkerObj = (IChecker) checker; |
| addChecker(checkerObj); |
| } catch (CoreException e) { |
| CodanCorePlugin.log(e); |
| return; |
| } |
| boolean hasRef = false; |
| IConfigurationElement[] children2 = configurationElement.getChildren(PROBLEM_ELEMENT); |
| if (children2 != null) { |
| for (IConfigurationElement ref : children2) { |
| IProblem p = processProblem(ref); |
| addRefProblem(checkerObj, p); |
| hasRef = true; |
| } |
| } |
| IConfigurationElement[] children1 = configurationElement.getChildren("problemRef"); //$NON-NLS-1$ |
| if (children1 != null) { |
| for (IConfigurationElement ref : children1) { |
| hasRef = true; |
| IProblem p = getDefaultProfile().findProblem(ref.getAttribute("refId")); //$NON-NLS-1$ |
| addRefProblem(checkerObj, p); |
| } |
| } |
| if (!hasRef) { |
| CodanProblem p = new CodanProblem(id, name); |
| addProblem(p, null); |
| addRefProblem(checkerObj, p); |
| } |
| } |
| } catch (Throwable e) { |
| CodanCorePlugin.log(e); |
| } |
| } |
| |
| /** |
| * @param configurationElement |
| * @return |
| */ |
| private CodanProblem processProblem(IConfigurationElement configurationElement) { |
| if (configurationElement.getName().equals(PROBLEM_ELEMENT)) { |
| String id = getAtt(configurationElement, ID_ATTR); |
| if (id == null) |
| return null; |
| String name = getAtt(configurationElement, NAME_ATTR); |
| if (name == null) |
| name = id; |
| CodanProblem p = new CodanProblem(id, name); |
| String category = getAtt(configurationElement, "category", false); //$NON-NLS-1$ |
| if (category == null) |
| category = "org.eclipse.cdt.codan.core.categories.ProgrammingProblems"; //$NON-NLS-1$ |
| String enab = getAtt(configurationElement, "defaultEnabled", false); //$NON-NLS-1$ |
| String sev = getAtt(configurationElement, "defaultSeverity", false); //$NON-NLS-1$ |
| String patt = getAtt(configurationElement, "messagePattern", false); //$NON-NLS-1$ |
| String desc = getAtt(configurationElement, "description", false); //$NON-NLS-1$ |
| String markerType = getAtt(configurationElement, "markerType", false); //$NON-NLS-1$ |
| String smultiple = getAtt(configurationElement, "multiple", false); //$NON-NLS-1$ |
| if (enab != null) { |
| p.setEnabled(Boolean.valueOf(enab)); |
| } |
| if (sev != null) { |
| CodanSeverity cSev = CodanSeverity.valueOf(sev); |
| if (cSev != null) |
| p.setSeverity(cSev); |
| } |
| if (patt != null) { |
| p.setMessagePattern(patt); |
| } |
| if (markerType != null) { |
| p.setMarkerType(markerType); |
| } |
| p.setDescription(desc); |
| if (smultiple != null) { |
| p.setMultiple(Boolean.valueOf(smultiple)); |
| } |
| addProblem(p, category); |
| return p; |
| } |
| return null; |
| } |
| |
| private static String getAtt(IConfigurationElement configurationElement, String name) { |
| return getAtt(configurationElement, name, true); |
| } |
| |
| private static String getAtt(IConfigurationElement configurationElement, String name, boolean req) { |
| String elementValue = configurationElement.getAttribute(name); |
| if (elementValue == null && req) |
| CodanCorePlugin.log("Extension " //$NON-NLS-1$ |
| + configurationElement.getDeclaringExtension().getUniqueIdentifier() |
| + " missing required attribute: " + configurationElement.getName() //$NON-NLS-1$ |
| + "." + name); //$NON-NLS-1$ |
| return elementValue; |
| } |
| |
| private void readCheckerEnablementVerifier() { |
| IExtensionPoint ep = getExtensionPoint(CHECKER_ENABLEMENT_EXTENSION_POINT_NAME); |
| for (IConfigurationElement ce : ep.getConfigurationElements()) { |
| try { |
| checkerEnablementVerifiers.add((ICheckerEnablementVerifier) ce.createExecutableExtension(CLASS_ATTR)); |
| } catch (CoreException e) { |
| CodanCorePlugin.log(e); |
| } |
| } |
| } |
| |
| private IExtensionPoint getExtensionPoint(String extensionPointName) { |
| return Platform.getExtensionRegistry().getExtensionPoint(CodanCorePlugin.PLUGIN_ID, extensionPointName); |
| } |
| |
| @Override |
| public Iterator<IChecker> iterator() { |
| return checkers.iterator(); |
| } |
| |
| /** |
| * @return the singleton checkers registry |
| */ |
| public static synchronized CheckersRegistry getInstance() { |
| if (instance == null) |
| return new CheckersRegistry(); |
| if (!initialized) |
| throw new IllegalStateException("Registry is not initialized"); //$NON-NLS-1$ |
| return instance; |
| } |
| |
| @Override |
| public void addChecker(IChecker checker) { |
| checkers.add(checker); |
| } |
| |
| @Override |
| public void addProblem(IProblem p, String category) { |
| IProblemCategory cat = getDefaultProfile().findCategory(category); |
| if (cat == null) |
| cat = getDefaultProfile().getRoot(); |
| ((ProblemProfile) getDefaultProfile()).addProblem(p, cat); |
| } |
| |
| @Override |
| public void addCategory(IProblemCategory p, String category) { |
| IProblemCategory cat = getDefaultProfile().findCategory(category); |
| if (cat == null) |
| cat = getDefaultProfile().getRoot(); |
| ((ProblemProfile) getDefaultProfile()).addCategory(p, cat); |
| } |
| |
| @Override |
| public void addRefProblem(IChecker c, IProblem p) { |
| Collection<IProblem> plist = problemList.get(c); |
| if (plist == null) { |
| plist = new ArrayList<>(); |
| problemList.put(c, plist); |
| } |
| plist.add(p); |
| problemCheckerMapping.put(p.getId(), c); |
| } |
| |
| /** |
| * Returns the checker associated with a problem. |
| * @param problem the given problem. |
| * @return the checker associated with a problem. |
| */ |
| public IChecker getCheckerForProblem(IProblem problem) { |
| return problemCheckerMapping.get(problem.getId()); |
| } |
| |
| /** |
| * Returns list of problems registered for given checker |
| * |
| * @return collection of problems or null |
| */ |
| @Override |
| public Collection<IProblem> getRefProblems(IChecker checker) { |
| return problemList.get(checker); |
| } |
| |
| @Override |
| public IProblemProfile getDefaultProfile() { |
| return profiles.get(DEFAULT); |
| } |
| |
| @Override |
| public IProblemProfile getWorkspaceProfile() { |
| IProblemProfile wp = profiles.get(ResourcesPlugin.getWorkspace()); |
| if (wp == null) { |
| wp = (IProblemProfile) getDefaultProfile().clone(); |
| ((ProblemProfile) wp).setResource(ResourcesPlugin.getWorkspace()); |
| // load default values |
| CodanPreferencesLoader loader = new CodanPreferencesLoader(wp); |
| Preferences[] preferences = { InstanceScope.INSTANCE.getNode(CodanCorePlugin.PLUGIN_ID), |
| ConfigurationScope.INSTANCE.getNode(CodanCorePlugin.PLUGIN_ID), |
| DefaultScope.INSTANCE.getNode(CodanCorePlugin.PLUGIN_ID), }; |
| loader.load(preferences); |
| profiles.put(ResourcesPlugin.getWorkspace(), wp); |
| } |
| return wp; |
| } |
| |
| @Override |
| public void updateProfile(IResource element, IProblemProfile profile) { |
| // Updating profile can invalidate all cached profiles |
| IProblemProfile defaultProfile = getDefaultProfile(); |
| profiles.clear(); |
| profiles.put(DEFAULT, defaultProfile); |
| if (profile != null && element != null) |
| profiles.put(element, profile); |
| } |
| |
| @Override |
| public IProblemProfile getResourceProfile(IResource element) { |
| IProblemProfile prof = profiles.get(element); |
| if (prof == null) { |
| if (element instanceof IProject) { |
| prof = (IProblemProfile) getWorkspaceProfile().clone(); |
| ((ProblemProfile) prof).setResource(element); |
| // Load default values |
| CodanPreferencesLoader loader = new CodanPreferencesLoader(prof); |
| Preferences projectNode = CodanPreferencesLoader.getProjectNode((IProject) element); |
| if (projectNode != null && !projectNode.getBoolean(PreferenceConstants.P_USE_PARENT, true)) { |
| loader.load(projectNode); |
| } |
| profiles.put(element, prof); |
| } else if (element.getParent() != null) { |
| prof = getResourceProfile(element.getParent()); |
| } else { |
| prof = getResourceProfile(element.getProject()); |
| } |
| } |
| return prof; |
| } |
| |
| @Override |
| public IProblemProfile getResourceProfileWorkingCopy(IResource element) { |
| IProblemProfile prof = (IProblemProfile) getResourceProfile(element).clone(); |
| return prof; |
| } |
| |
| /** |
| * Tests if a checker needs to run in a specific launch mode. |
| * |
| * @param checker |
| * @param resource |
| * @param mode |
| * @return <code>true</code> if the checker should run. |
| */ |
| public boolean isCheckerEnabled(IChecker checker, IResource resource, CheckerLaunchMode mode) { |
| if (resource.getType() != IResource.FILE) { |
| return false; |
| } |
| |
| if (mode == CheckerLaunchMode.RUN_AS_YOU_TYPE && !Checkers.canCheckerRunAsYouType(checker)) { |
| return false; |
| } |
| for (ICheckerEnablementVerifier verifier : checkerEnablementVerifiers) { |
| if (!verifier.isCheckerEnabled(checker, resource, mode)) { |
| return false; |
| } |
| } |
| |
| IProblemProfile resourceProfile = getResourceProfile(resource); |
| Collection<IProblem> refProblems = getRefProblems(checker); |
| for (IProblem p : refProblems) { |
| // We need to check problem enablement in a particular profile. |
| IProblem problem = resourceProfile.findProblem(p.getId()); |
| if (problem == null) |
| throw new IllegalArgumentException(p.getId() + " is not registered"); //$NON-NLS-1$ |
| if (problem.isEnabled() && checker instanceof AbstractCheckerWithProblemPreferences) { |
| LaunchModeProblemPreference pref = ((AbstractCheckerWithProblemPreferences) checker) |
| .getLaunchModePreference(problem); |
| if (pref.isRunningInMode(mode)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * @return the number of checkers |
| */ |
| public int getCheckersSize() { |
| return checkers.size(); |
| } |
| |
| /** |
| * Create a replicated problem - it has same check and same initial values |
| * as original but user can modify it further |
| * |
| * @param problem |
| * @param profile |
| */ |
| public void replicateProblem(IProblem problem, IProblemProfile profile) { |
| CodanProblem x = (CodanProblem) problem.clone(); |
| x.setId(getNextCloneId(problem, profile)); |
| ((ProblemProfile) profile).addProblem(x, problem.getParentCategory()); |
| } |
| |
| /** |
| * @param problem |
| * @param profile |
| * @return |
| */ |
| private String getNextCloneId(IProblem problem, IProblemProfile profile) { |
| IProblem[] problems = profile.getProblems(); |
| String prefix = problem.getId() + CLONE_SUFFIX; |
| int max = 0; |
| for (int i = 0; i < problems.length; i++) { |
| IProblem x = problems[i]; |
| if (x.getId().startsWith(prefix)) { |
| int num = 0; |
| try { |
| num = Integer.parseInt(x.getId().substring(prefix.length())); |
| } catch (NumberFormatException e) { |
| // well... |
| } |
| if (max < num) |
| max = num; |
| } |
| } |
| max++; |
| return prefix + max; |
| } |
| } |