/*******************************************************************************
* Copyright (c) 2004, 2012 BREDEX GmbH.
* 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:
* BREDEX GmbH - initial API and implementation and/or initial documentation
*******************************************************************************/
package org.eclipse.jubula.client.teststyle;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.MultiRule;
import org.eclipse.jubula.client.core.businessprocess.compcheck.ProblemPropagator;
import org.eclipse.jubula.client.core.events.DataChangedEvent;
import org.eclipse.jubula.client.core.events.DataEventDispatcher;
import org.eclipse.jubula.client.core.events.DataEventDispatcher.DataState;
import org.eclipse.jubula.client.core.events.DataEventDispatcher.IDataChangedListener;
import org.eclipse.jubula.client.core.events.DataEventDispatcher.IProjectLoadedListener;
import org.eclipse.jubula.client.core.events.DataEventDispatcher.IProjectStateListener;
import org.eclipse.jubula.client.core.events.DataEventDispatcher.ProjectState;
import org.eclipse.jubula.client.core.events.DataEventDispatcher.UpdateState;
import org.eclipse.jubula.client.core.model.IAbstractContainerPO;
import org.eclipse.jubula.client.core.model.IConditionalStatementPO;
import org.eclipse.jubula.client.core.model.INodePO;
import org.eclipse.jubula.client.core.model.IPersistentObject;
import org.eclipse.jubula.client.core.model.IProjectPO;
import org.eclipse.jubula.client.core.model.ISpecTestCasePO;
import org.eclipse.jubula.client.core.model.ITestDataCubePO;
import org.eclipse.jubula.client.core.model.ITestJobPO;
import org.eclipse.jubula.client.core.model.ITestSuitePO;
import org.eclipse.jubula.client.core.persistence.GeneralStorage;
import org.eclipse.jubula.client.core.rules.SingleJobRule;
import org.eclipse.jubula.client.core.utils.ITreeNodeOperation;
import org.eclipse.jubula.client.core.utils.TreeTraverser;
import org.eclipse.jubula.client.teststyle.checks.BaseCheck;
import org.eclipse.jubula.client.teststyle.checks.CheckCont;
import org.eclipse.jubula.client.teststyle.checks.contexts.BaseContext;
import org.eclipse.jubula.client.teststyle.gui.TeststyleProblemAdder;
import org.eclipse.jubula.client.teststyle.gui.decoration.DecoratorHandler;
import org.eclipse.jubula.client.teststyle.i18n.Messages;
import org.eclipse.jubula.client.teststyle.problems.ProblemCont;
import org.eclipse.jubula.client.ui.utils.JobUtils;
import org.eclipse.jubula.tools.internal.exception.JBException;
import org.eclipse.jubula.tools.internal.messagehandling.MessageIDs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Initialize and handles the teststyles and its definitions.
*
* @author marcell
*
*/
public final class TeststyleHandler implements IDataChangedListener,
IProjectLoadedListener, IProjectStateListener {
/**
* Job which is updating the teststyle problems in the nodes
* @author BREDEX GmbH
*
*/
class TestStyleJob extends Job {
/** the data changed events to process */
private DataChangedEvent[] m_events;
/**
*
* @param name the name of the Job
* @param events the {@link DataChangedEvent} to process
*/
public TestStyleJob(String name, DataChangedEvent... events) {
super(name);
this.m_events = events;
}
/** {@inheritDoc} */
public boolean belongsTo(Object family) {
if (family instanceof TestStyleJob) {
return true;
}
return super.belongsTo(family);
}
@Override
protected IStatus run(IProgressMonitor monitor) {
if (monitor.isCanceled()) {
return new Status(IStatus.CANCEL, Activator.PLUGIN_ID,
getName());
}
monitor.beginTask(Messages.TestStyleRunningOperation,
IProgressMonitor.UNKNOWN);
for (DataChangedEvent e : m_events) {
if (e.getUpdateState() != UpdateState.onlyInEditor) {
handleChangedPo(e.getPo(), e.getDataState(),
e.getUpdateState());
}
}
if (monitor.isCanceled()) {
return new Status(IStatus.CANCEL, Activator.PLUGIN_ID,
getName());
}
refresh();
if (monitor.isCanceled()) {
return new Status(IStatus.CANCEL, Activator.PLUGIN_ID,
getName());
}
try {
addTeststyleProblems(monitor);
} catch (JBException e1) {
// this might occur during create new version
return new Status(IStatus.WARNING, Activator.PLUGIN_ID,
getName());
}
ProblemPropagator.INSTANCE.propagate();
monitor.done();
return new Status(IStatus.OK, Activator.PLUGIN_ID, getName());
}
}
/**
* Job which is cleaning up and doing a complete teststyle check
* @author BREDEX GmbH
*/
class CompleteTestStyleCheckJob extends Job {
/**
* @param name the name of the job
*/
public CompleteTestStyleCheckJob(String name) {
super(name);
}
/** {@inheritDoc} */
public boolean belongsTo(Object family) {
if (family instanceof CompleteTestStyleCheckJob) {
return true;
}
return super.belongsTo(family);
}
/**
* {@inheritDoc}
*/
protected IStatus run(IProgressMonitor monitor) {
ProblemCont.instance.clear();
if (isEnabled()) {
// Check'em all!
for (BaseContext context : CheckCont.getContexts()) {
for (Object obj : context.getAll()) {
check(obj);
}
}
}
refresh();
try {
addTeststyleProblems(monitor);
} catch (JBException e1) {
// this might occur during create new version
return new Status(IStatus.WARNING, Activator.PLUGIN_ID,
getName());
}
return new Status(IStatus.OK, Activator.PLUGIN_ID, getName());
}
}
/** singleton */
private static TeststyleHandler instance;
/** the logger */
private static final Logger LOG = LoggerFactory
.getLogger(TeststyleHandler.class);
/**
* Private constructor of the singleton
*/
private TeststyleHandler() {
DataEventDispatcher.getInstance().addProjectLoadedListener(this, false);
addToListener();
}
/**
* @return the singleton instance
*/
public static TeststyleHandler getInstance() {
if (instance == null) {
instance = new TeststyleHandler();
}
return instance;
}
/**
* {@inheritDoc}
*/
public void handleProjectLoaded() {
if (GeneralStorage.getInstance().getProject() == null) {
return; // if there is no project, don't proceed
}
ExtensionHelper.initCheckConfiguration();
doCompleteCheck();
ProblemPropagator.INSTANCE.propagate();
}
/** {@inheritDoc} */
public void handleDataChanged(DataChangedEvent... events) {
if (!isEnabled()) {
return;
}
boolean isUpdateInEditor = true;
for (DataChangedEvent e : events) {
if (e.getUpdateState() != UpdateState.onlyInEditor) {
isUpdateInEditor = false;
break;
}
}
if (!isUpdateInEditor) {
TestStyleJob tj = new TestStyleJob("Teststyle", events); //$NON-NLS-1$
tj.setRule(
new MultiRule(new ISchedulingRule[]{
SingleJobRule.COMPLETENESSRULE,
SingleJobRule.TESTSTYLERULE}));
JobUtils.executeJob(tj, null);
for (Job job : Job.getJobManager().find(tj)) {
if (job != tj) {
job.cancel();
}
}
return;
}
}
/**
* @param po changed persistent object
* @param dataState kind of modification
* @param updateState determines the parts to update
*/
private void handleChangedPo(IPersistentObject po, DataState dataState,
UpdateState updateState) {
// FIXME mbs Need a event for closing a project
// Clean up first
switch (dataState) {
case Renamed: // fall through
case Added: // fall through
case StructureModified: // fall through
case ReuseChanged:
case Saved:
ProblemCont.instance.remove(po);
check(po);
break;
case Deleted: // fall through
ProblemCont.instance.remove(po);
default:
break;
}
// always check the project after each change
IProjectPO project = GeneralStorage.getInstance().getProject();
if (project != null) {
ProblemCont.instance.remove(project);
check(project);
}
if (po instanceof ISpecTestCasePO || po instanceof ITestSuitePO
|| po instanceof ITestJobPO
|| po instanceof IConditionalStatementPO
|| po instanceof IAbstractContainerPO) {
INodePO node = (INodePO)po;
Iterator<INodePO> iter = node.getAllNodeIter();
handleChangedPo(iter, dataState, updateState);
}
}
/**
* @param iter iterator of child node
* @param dataState kind of modification
* @param updateState determines the parts to update
*/
private void handleChangedPo(Iterator<INodePO> iter, DataState dataState,
UpdateState updateState) {
while (iter.hasNext()) {
INodePO child = iter.next();
handleChangedPo(child, dataState, updateState);
}
}
/**
* This method checks the Object obj with every check in the contexts of
* this check for violation and decorates it appropriately.
*
* @param obj
* The object that should be checked.
*/
public void check(Object obj) {
// gather all checks for this
BaseContext context = BaseContext.getFor(obj.getClass());
List<BaseCheck> checks = CheckCont.getChecksFor(context);
// Test the object!
for (BaseCheck check : checks) {
if (check.isActive(context) && check.hasError(obj)) {
if (obj instanceof ITestDataCubePO) {
ProblemCont.instance.add(
((ITestDataCubePO)obj).getId(), check);
} else {
ProblemCont.instance.add(obj, check);
}
}
}
}
/**
* Checks every element with checks of the project for CheckStyle errors.
*/
public void doCompleteCheck() {
Job checkEverythingJob =
new CompleteTestStyleCheckJob("TestStyle - complete"); //$NON-NLS-1$
checkEverythingJob.setRule(
new MultiRule(new ISchedulingRule[]{
SingleJobRule.COMPLETENESSRULE,
SingleJobRule.TESTSTYLERULE}));
JobUtils.executeJob(checkEverythingJob, null);
try {
checkEverythingJob.join();
} catch (InterruptedException e) {
LOG.warn("Error waiting for Job TestStyle job", e); //$NON-NLS-1$
}
}
/**
*
* @param monitor a monitor or null<code>null</code>
* @throws {@link JBException} if the project is
* null in the {@link GeneralStorage}
*/
private void addTeststyleProblems(IProgressMonitor monitor)
throws JBException {
IProjectPO project = GeneralStorage.getInstance().getProject();
if (project == null) {
throw new JBException("Project is null", //$NON-NLS-1$
MessageIDs.E_PROJECT_NOT_FOUND);
}
final ITreeNodeOperation<INodePO> op = new TeststyleProblemAdder();
final TreeTraverser traverser = new TreeTraverser(project, op);
if (monitor != null) {
traverser.setMonitor(monitor);
}
traverser.traverse(true);
}
/**
* Adds the handler to the important listeners.
*/
public void addToListener() {
DataEventDispatcher ded = DataEventDispatcher.getInstance();
ded.addDataChangedListener(this, true);
ded.addProjectStateListener(this);
}
/**
* Removes the handler to the important listeners.
*/
public void removeFromListener() {
DataEventDispatcher ded = DataEventDispatcher.getInstance();
ded.removeDataChangedListener(this);
ded.removeProjectStateListener(this);
}
/**
* Starts the whole routine (adds the handler and initialize the extensions
* with its definitions)
*/
public void start() {
// Fill the CheckCont with initChecks
ExtensionHelper.initChecks();
// And add the handler to the listener, so that we can use events.
if (isEnabled()) {
addToListener();
}
}
/**
* Stops the whole checkstyle routine.
*/
public void stop() {
if (isEnabled()) {
removeFromListener();
}
ProblemCont.instance.clear();
}
/**
* Refreshes the decorators so that they start decorating the available
* resources again.
*/
public void refresh() {
DecoratorHandler.refresh();
}
/**
* @return Is teststyle enabled for this project?
*/
public boolean isEnabled() {
IProjectPO project = GeneralStorage.getInstance().getProject();
if (project == null) {
return false;
}
return project.getProjectProperties().getCheckConfCont().getEnabled();
}
/** {@inheritDoc} */
public void handleProjectStateChanged(ProjectState state) {
switch (state) {
case prop_modified:
doCompleteCheck();
break;
case closed:
ProblemCont.instance.clear();
break;
case opened:
default:
break;
}
}
}