/*******************************************************************************
* Copyright (c) 2009, 2011 Alena Laskavaia
* 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:
* 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.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.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 Iterable<IChecker>, 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 EXTENSION_POINT_NAME = "checkers"; //$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<IChecker>();
private static CheckersRegistry instance;
private static boolean initialized = false;
private HashMap<Object, IProblemProfile> profiles = new HashMap<Object, IProblemProfile>();
private HashMap<IChecker, Collection<IProblem>> problemList = new HashMap<IChecker, Collection<IProblem>>();
private Map<String, IChecker> problemCheckerMapping = new HashMap<String, IChecker>();
private CheckersRegistry() {
instance = this;
profiles.put(DEFAULT, new ProblemProfile(DEFAULT));
readCheckersRegistry();
initialized = true;
}
private void readCheckersRegistry() {
IExtensionPoint ep = Platform.getExtensionRegistry().getExtensionPoint(CodanCorePlugin.PLUGIN_ID, 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"); //$NON-NLS-1$
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;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.cdt.codan.core.model.ICheckersRegistry#iterator()
*/
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 == false)
throw new IllegalStateException("Registry is not initialized"); //$NON-NLS-1$
return instance;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.cdt.codan.core.model.ICheckersRegistry#addChecker(org.eclipse
* .cdt.codan.core.model.IChecker)
*/
public void addChecker(IChecker checker) {
checkers.add(checker);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.cdt.codan.core.model.ICheckersRegistry#addProblem(org.eclipse
* .cdt.codan.core.model.IProblem, java.lang.String)
*/
public void addProblem(IProblem p, String category) {
IProblemCategory cat = getDefaultProfile().findCategory(category);
if (cat == null)
cat = getDefaultProfile().getRoot();
((ProblemProfile) getDefaultProfile()).addProblem(p, cat);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.cdt.codan.core.model.ICheckersRegistry#addCategory(org.eclipse
* .cdt.codan.core.model.IProblemCategory, java.lang.String)
*/
public void addCategory(IProblemCategory p, String category) {
IProblemCategory cat = getDefaultProfile().findCategory(category);
if (cat == null)
cat = getDefaultProfile().getRoot();
((ProblemProfile) getDefaultProfile()).addCategory(p, cat);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.cdt.codan.core.model.ICheckersRegistry#addRefProblem(org.
* eclipse.cdt.codan.core.model.IChecker,
* org.eclipse.cdt.codan.core.model.IProblem)
*/
public void addRefProblem(IChecker c, IProblem p) {
Collection<IProblem> plist = problemList.get(c);
if (plist == null) {
plist = new ArrayList<IProblem>();
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
*/
public Collection<IProblem> getRefProblems(IChecker checker) {
return problemList.get(checker);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.cdt.codan.core.model.ICheckersRegistry#getDefaultProfile()
*/
public IProblemProfile getDefaultProfile() {
return profiles.get(DEFAULT);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.cdt.codan.core.model.ICheckersRegistry#getWorkspaceProfile()
*/
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;
}
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);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.cdt.codan.core.model.ICheckersRegistry#getResourceProfile
* (org.eclipse.core.resources.IResource)
*/
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);
boolean useWorkspace = projectNode.getBoolean(PreferenceConstants.P_USE_PARENT, true);
if (!useWorkspace) {
loader.load(projectNode);
}
profiles.put(element, prof);
} else if (element.getParent() != null) {
prof = getResourceProfile(element.getParent());
} else {
prof = getResourceProfile(element.getProject());
}
}
return prof;
}
/*
* (non-Javadoc)
*
* @seeorg.eclipse.cdt.codan.core.model.ICheckersRegistry#
* getResourceProfileWorkingCopy(org.eclipse.core.resources.IResource)
*/
public IProblemProfile getResourceProfileWorkingCopy(IResource element) {
IProblemProfile prof = (IProblemProfile) getResourceProfile(element).clone();
return prof;
}
/**
* Tests if a checker is enabled (needs to be run) or not. Checker is
* enabled
* if at least one problem it reports is enabled.
*
* @param checker
* @param resource
* @return <code>true</code> if the checker is enabled
*/
public boolean isCheckerEnabled(IChecker checker, IResource resource) {
IProblemProfile resourceProfile = getResourceProfile(resource);
Collection<IProblem> refProblems = getRefProblems(checker);
for (Iterator<IProblem> iterator = refProblems.iterator(); iterator.hasNext();) {
IProblem p = iterator.next();
// we need to check problem enablement in particular profile
IProblem problem = resourceProfile.findProblem(p.getId());
if (problem == null)
throw new IllegalArgumentException("Id is not registered"); //$NON-NLS-1$
if (problem.isEnabled())
return true;
}
// no problem is enabled for this checker, skip the checker
return false;
}
/**
* 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 isCheckerEnabledForLaunchMode(IChecker checker, IResource resource, CheckerLaunchMode mode) {
IProblemProfile resourceProfile = getResourceProfile(resource);
Collection<IProblem> refProblems = getRefProblems(checker);
boolean enabled = false;
for (Iterator<IProblem> iterator = refProblems.iterator(); iterator.hasNext();) {
IProblem p = iterator.next();
// we need to check problem enablement in particular profile
IProblem problem = resourceProfile.findProblem(p.getId());
if (problem == null)
throw new IllegalArgumentException("Id is not registered"); //$NON-NLS-1$
if (checker instanceof AbstractCheckerWithProblemPreferences) {
LaunchModeProblemPreference pref = ((AbstractCheckerWithProblemPreferences) checker).getLaunchModePreference(problem);
if (pref.isRunningInMode(mode)) {
enabled = true;
break;
}
}
}
return enabled;
}
/**
* @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 (Exception e) {
// well...
}
if (max < num)
max = num;
}
}
max++;
return prefix + max;
}
}