org.squashtest.tm.domain.testcase.TestCase.java Source code

Java tutorial

Introduction

Here is the source code for org.squashtest.tm.domain.testcase.TestCase.java

Source

/**
 *     This file is part of the Squashtest platform.
 *     Copyright (C) 2010 - 2016 Henix, henix.fr
 *
 *     See the NOTICE file distributed with this work for additional
 *     information regarding copyright ownership.
 *
 *     This is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     this software is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 *
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with this software.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.squashtest.tm.domain.testcase;

import static org.squashtest.tm.domain.testcase.TestCaseImportance.LOW;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.Lob;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.OrderColumn;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.apache.commons.lang3.StringUtils;
import org.hibernate.annotations.Type;
import org.hibernate.search.annotations.Analyze;
import org.hibernate.search.annotations.ClassBridge;
import org.hibernate.search.annotations.ClassBridges;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.FieldBridge;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.IndexedEmbedded;
import org.hibernate.search.annotations.Store;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.squashtest.tm.domain.attachment.Attachment;
import org.squashtest.tm.domain.attachment.AttachmentHolder;
import org.squashtest.tm.domain.audit.AuditableMixin;
import org.squashtest.tm.domain.customfield.BindableEntity;
import org.squashtest.tm.domain.customfield.BoundEntity;
import org.squashtest.tm.domain.infolist.InfoListItem;
import org.squashtest.tm.domain.library.NodeVisitor;
import org.squashtest.tm.domain.milestone.Milestone;
import org.squashtest.tm.domain.milestone.MilestoneHolder;
import org.squashtest.tm.domain.requirement.Requirement;
import org.squashtest.tm.domain.requirement.RequirementVersion;
import org.squashtest.tm.domain.search.CUFBridge;
import org.squashtest.tm.domain.search.CollectionSizeBridge;
import org.squashtest.tm.domain.search.InfoListItemBridge;
import org.squashtest.tm.domain.search.LevelEnumBridge;
import org.squashtest.tm.domain.testautomation.AutomatedTest;
import org.squashtest.tm.exception.NameAlreadyInUseException;
import org.squashtest.tm.exception.UnallowedTestAssociationException;
import org.squashtest.tm.exception.UnknownEntityException;
import org.squashtest.tm.exception.requirement.RequirementAlreadyVerifiedException;

/**
 * @author Gregory Fouquet
 *
 */
@Entity
@Indexed
@ClassBridges({ @ClassBridge(name = "attachments", store = Store.YES, impl = TestCaseAttachmentBridge.class),
        @ClassBridge(name = "callsteps", store = Store.YES, impl = TestCaseCallStepBridge.class),
        @ClassBridge(name = "iterations", store = Store.YES, impl = TestCaseIterationBridge.class),
        @ClassBridge(name = "executions", store = Store.YES, impl = TestCaseExecutionBridge.class),
        @ClassBridge(name = "issues", store = Store.YES, impl = TestCaseIssueBridge.class),
        @ClassBridge(name = "cufs", store = Store.YES, impl = CUFBridge.class, params = {
                @org.hibernate.search.annotations.Parameter(name = "type", value = "testcase"),
                @org.hibernate.search.annotations.Parameter(name = "inputType", value = "ALL") }),
        @ClassBridge(name = "cufs", store = Store.YES, analyze = Analyze.NO, impl = CUFBridge.class, params = {
                @org.hibernate.search.annotations.Parameter(name = "type", value = "testcase"),
                @org.hibernate.search.annotations.Parameter(name = "inputType", value = "DROPDOWN_LIST") }) })
@PrimaryKeyJoinColumn(name = "TCLN_ID")
public class TestCase extends TestCaseLibraryNode implements AttachmentHolder, BoundEntity, MilestoneHolder {
    private static final Logger LOGGER = LoggerFactory.getLogger(TestCaseLibraryNode.class);
    private static final String CLASS_NAME = "org.squashtest.tm.domain.testcase.TestCase";
    private static final String SIMPLE_CLASS_NAME = "TestCase";
    public static final int MAX_REF_SIZE = 50;
    @Column(updatable = false)
    private final int version = 1;

    @NotNull
    @Field(analyze = Analyze.NO, store = Store.YES)
    @Size(min = 0, max = MAX_REF_SIZE)
    private String reference = "";

    @Lob
    @Type(type = "org.hibernate.type.StringClobType")
    @Field
    private String prerequisite = "";

    @NotNull
    @OneToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE, CascadeType.DETACH })
    @OrderColumn(name = "STEP_ORDER")
    @JoinTable(name = "TEST_CASE_STEPS", joinColumns = @JoinColumn(name = "TEST_CASE_ID"), inverseJoinColumns = @JoinColumn(name = "STEP_ID"))
    @FieldBridge(impl = CollectionSizeBridge.class)
    @Field(analyze = Analyze.NO, store = Store.YES)
    private List<TestStep> steps = new ArrayList<>();

    @NotNull
    @OneToMany(cascade = { CascadeType.REMOVE, CascadeType.REFRESH, CascadeType.MERGE, CascadeType.DETACH })
    @JoinColumn(name = "VERIFYING_TEST_CASE_ID")
    @FieldBridge(impl = CollectionSizeBridge.class)
    @Field(name = "requirements", analyze = Analyze.NO, store = Store.YES)
    private Set<RequirementVersionCoverage> requirementVersionCoverages = new HashSet<>(0);

    @NotNull
    @OneToMany(cascade = { CascadeType.ALL }, mappedBy = "testCase")
    @OrderBy("name")
    @Field(analyze = Analyze.NO, store = Store.YES)
    @FieldBridge(impl = CollectionSizeBridge.class)
    private Set<Parameter> parameters = new HashSet<>(0);

    @NotNull
    @OneToMany(cascade = { CascadeType.ALL }, mappedBy = "testCase")
    @OrderBy("name")
    @Field(analyze = Analyze.NO, store = Store.YES)
    @FieldBridge(impl = CollectionSizeBridge.class)
    private Set<Dataset> datasets = new HashSet<>(0);

    @NotNull
    @Enumerated(EnumType.STRING)
    @Field(analyze = Analyze.NO, store = Store.YES)
    @FieldBridge(impl = LevelEnumBridge.class)
    private TestCaseImportance importance = LOW;

    @NotNull
    @ManyToOne(cascade = CascadeType.DETACH)
    @JoinColumn(name = "TC_NATURE")
    @Field(analyze = Analyze.NO, store = Store.YES)
    @FieldBridge(impl = InfoListItemBridge.class)
    private InfoListItem nature = null;

    @NotNull
    @ManyToOne(cascade = CascadeType.DETACH)
    @JoinColumn(name = "TC_TYPE")
    @Field(analyze = Analyze.NO, store = Store.YES)
    @FieldBridge(impl = InfoListItemBridge.class)
    private InfoListItem type = null;

    @NotNull
    @Enumerated(EnumType.STRING)
    @Column(name = "TC_STATUS")
    @Field(analyze = Analyze.NO, store = Store.YES)
    @FieldBridge(impl = LevelEnumBridge.class)
    private TestCaseStatus status = TestCaseStatus.WORK_IN_PROGRESS;

    @NotNull
    @Enumerated(EnumType.STRING)
    private TestCaseExecutionMode executionMode = TestCaseExecutionMode.MANUAL;

    /**
     * Should the importance be automatically computed.
     */
    @NotNull
    private Boolean importanceAuto = Boolean.FALSE;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "TA_TEST")
    private AutomatedTest automatedTest;

    @FieldBridge(impl = CollectionSizeBridge.class)
    @Field(analyze = Analyze.NO, store = Store.YES)
    @IndexedEmbedded(includeEmbeddedObjectId = true)
    @ManyToMany(cascade = CascadeType.DETACH)
    @JoinTable(name = "MILESTONE_TEST_CASE", joinColumns = @JoinColumn(name = "TEST_CASE_ID"), inverseJoinColumns = @JoinColumn(name = "MILESTONE_ID"))
    private Set<Milestone> milestones = new HashSet<>();

    // *************************** CODE *************************************

    public TestCase(Date createdOn, String createdBy) {
        AuditableMixin audit = (AuditableMixin) this;

        audit.setCreatedOn(createdOn);
        audit.setCreatedBy(createdBy);
    }

    public TestCase() {
        super();
    }

    public int getVersion() {
        return version;
    }

    /***
     * @return the reference of the test-case
     */
    public String getReference() {
        return reference;
    }

    /**
     * @return {reference} - {name} if reference is not empty, or {name} if it is
     *
     */
    public String getFullName() {
        if (StringUtils.isBlank(reference)) {
            return getName();
        } else {
            return getReference() + " - " + getName();
        }
    }

    /***
     * Set the test-case reference
     *
     * @param reference
     */
    public void setReference(String reference) {
        this.reference = reference;
    }

    public String getPrerequisite() {
        return prerequisite;
    }

    /**
     * @see TestCase#isAutomated()
     * @return TODO either replaced by isAutomated or should be synchronized with isAutomated
     */
    public TestCaseExecutionMode getExecutionMode() {
        return executionMode;
    }

    public void setExecutionMode(@NotNull TestCaseExecutionMode executionMode) {
        this.executionMode = executionMode;
    }

    public List<TestStep> getSteps() {
        return steps;
    }

    // TODO : best would be to have a smarter subclass of List that would override #add(...) methods for this purpose
    private void notifyStepBelongsToMe(TestStep step) {
        step.setTestCase(this);
    }

    public void addStep(@NotNull TestStep step) {
        getSteps().add(step);
        notifyStepBelongsToMe(step);
    }

    public void addStep(int index, @NotNull TestStep step) {
        getSteps().add(index, step);
        notifyStepBelongsToMe(step);
    }

    public void moveStep(int stepIndex, int newIndex) {
        if (stepIndex == newIndex) {
            return;
        }
        TestStep step = getSteps().get(stepIndex);
        getSteps().remove(stepIndex);
        getSteps().add(newIndex, step);
    }

    /**
     * Will move a list of steps to a new position.
     *
     * @param newIndex
     *            the position we want the first element of movedSteps to be once the operation is complete
     * @param movedSteps
     *            the list of steps to move, sorted by rank among each others.
     */
    public void moveSteps(int newIndex, List<TestStep> movedSteps) {
        if (!steps.isEmpty()) {
            steps.removeAll(movedSteps);
            steps.addAll(newIndex, movedSteps);
        }
    }

    @Override
    public void accept(TestCaseLibraryNodeVisitor visitor) {
        visitor.visit(this);

    }

    /**
     * Will create a copy from this instance. <br>
     * Will copy all properties, steps, automated scripts, parameters, datasets.<br>
     * ! Will not copy {@link RequirementVersionCoverage}s !
     *
     * @return a copy of this {@link TestCase}
     */
    @Override
    public TestCase createCopy() {
        TestCase copy = new TestCase();
        copy.setSimplePropertiesUsing(this);
        copy.addCopiesOfSteps(this);
        copy.addCopiesOfAttachments(this);
        copy.addCopiesOfParametersAndDatasets(this);
        copy.notifyAssociatedWithProject(this.getProject());
        copy.bindSameMilestones(this);
        if (this.automatedTest != null) {
            try {
                copy.setAutomatedTest(this.automatedTest);
            } catch (UnallowedTestAssociationException e) {
                LOGGER.error(
                        "data inconsistancy : this test case (#{}) has a script even if it's project isn't test automation enabled",
                        this.getId());
            }
        }
        return copy;
    }

    /**
     * will add to this parameters, datasets and dataParamValues copied from the given source.
     *
     * @param source
     *            : the source test case to copy the params, datasets and dataparamvalues from.
     */
    private void addCopiesOfParametersAndDatasets(TestCase source) {
        // create copy of parameters and remember the association original/copy
        Map<Parameter, Parameter> copyByOriginalParam = new HashMap<>(source.getParameters().size());
        for (Parameter parameter : source.getParameters()) {
            Parameter paramCopy = parameter.detachedCopy();
            paramCopy.setTestCase(this);
            copyByOriginalParam.put(parameter, paramCopy);
        }
        addCopiesOfDatasets(source, copyByOriginalParam);
    }

    private void addCopiesOfDatasets(TestCase source, Map<Parameter, Parameter> copyByOriginalParam) {
        for (Dataset dataset : source.getDatasets()) {
            Dataset datasetCopy = new Dataset(dataset.getName(), this);
            // create copy of datasetParamValues and link the copies to the rightful parameters
            for (DatasetParamValue datasetParamValue : dataset.getParameterValues()) {
                Parameter datasetParamValueCopyParam = getParameterToLinkedTheCopiedDatasetParamValueTo(source,
                        copyByOriginalParam, datasetParamValue);
                String datasetParamValueCopyParamValue = datasetParamValue.getParamValue();
                new DatasetParamValue(datasetParamValueCopyParam, datasetCopy, datasetParamValueCopyParamValue);
            }
        }
    }

    private Parameter getParameterToLinkedTheCopiedDatasetParamValueTo(TestCase source,
            Map<Parameter, Parameter> copyByOriginalParam, DatasetParamValue datasetParamValue) {
        Parameter datasetParamValueCopyParam;
        if (datasetParamValue.getParameter().getTestCase().getId().equals(source.getId())) {
            // if the parameter associated to the datasetParamValue is from this test case we need to link the
            // copied paramValue to the copy of the parameter
            datasetParamValueCopyParam = copyByOriginalParam.get(datasetParamValue.getParameter());
        } else {
            // if the parameter associated to the datasetParamValue belongs to a called test-case we link the
            // copied paramValue to the same parameter it's source is.
            datasetParamValueCopyParam = datasetParamValue.getParameter();
        }
        return datasetParamValueCopyParam;
    }

    private void addCopiesOfAttachments(TestCase source) {
        for (Attachment tcAttach : source.getAttachmentList().getAllAttachments()) {
            Attachment atCopy = tcAttach.hardCopy();
            this.getAttachmentList().addAttachment(atCopy);
        }
    }

    private void addCopiesOfSteps(TestCase source) {
        for (TestStep testStep : source.getSteps()) {
            this.addStep(testStep.createCopy());
        }
    }

    private void bindSameMilestones(TestCase src) {
        for (Milestone m : src.getMilestones()) {
            this.bindMilestone(m);
        }
    }

    private void setSimplePropertiesUsing(TestCase source) {
        this.setName(source.getName());
        this.setDescription(source.getDescription());
        this.setPrerequisite(source.getPrerequisite());
        this.executionMode = source.getExecutionMode();
        this.importance = source.getImportance();
        this.nature = source.getNature();
        this.type = source.getType();
        this.status = source.getStatus();
        this.reference = source.getReference();
        this.importanceAuto = source.isImportanceAuto();
    }

    /**
     * Will compare id of test-case steps with given id and return the index of the matching step. Otherwise throw an
     * exception.
     *
     * @param stepId
     * @return the step index (starting at 0)
     * @throws UnknownEntityException
     */
    public int getPositionOfStep(long stepId) throws UnknownEntityException {
        for (int i = 0; i < getSteps().size(); i++) {
            if (getSteps().get(i).getId() == stepId) {
                return i;
            }
        }

        throw new UnknownEntityException(stepId, TestStep.class);
    }

    @Override
    public String getClassSimpleName() {
        return TestCase.SIMPLE_CLASS_NAME;
    }

    @Override
    public String getClassName() {
        return TestCase.CLASS_NAME;
    }

    /**
     * @return the weight
     */
    public TestCaseImportance getImportance() {
        return importance;
    }

    /**
     * @param weight
     *            the weight to set
     */
    public void setImportance(@NotNull TestCaseImportance weight) {
        this.importance = weight;
    }

    public InfoListItem getNature() {
        return nature;
    }

    public void setNature(@NotNull InfoListItem nature) {
        this.nature = nature;
    }

    public InfoListItem getType() {
        return type;
    }

    public void setType(@NotNull InfoListItem type) {
        this.type = type;
    }

    public TestCaseStatus getStatus() {
        return status;
    }

    public void setStatus(@NotNull TestCaseStatus status) {
        this.status = status;
    }

    /**
     * @param prerequisite
     *            the prerequisite to set
     */
    public void setPrerequisite(@NotNull String prerequisite) {
        this.prerequisite = prerequisite;
    }

    /**
     * @return the weightAuto
     */
    public Boolean isImportanceAuto() {
        return importanceAuto;
    }

    /**
     * @param importanceAuto
     *            the importanceAuto to set
     */
    public void setImportanceAuto(@NotNull Boolean importanceAuto) {
        this.importanceAuto = importanceAuto;
    }

    // *************** test automation section ******************

    public AutomatedTest getAutomatedTest() {
        return automatedTest;
    }

    public void setAutomatedTest(AutomatedTest testAutomationTest) {
        if (getProject().isBoundToTestAutomationProject(testAutomationTest.getProject())) {
            this.automatedTest = testAutomationTest;
        } else {
            throw new UnallowedTestAssociationException();
        }
    }

    public void removeAutomatedScript() {
        this.automatedTest = null;
    }

    public boolean isAutomated() {
        return automatedTest != null && getProject().isTestAutomationEnabled();
    }

    // ***************** (detached) custom field section *************

    @Override
    public Long getBoundEntityId() {
        return getId();
    }

    @Override
    public BindableEntity getBoundEntityType() {
        return BindableEntity.TEST_CASE;
    }

    @Override
    public void accept(NodeVisitor visitor) {
        visitor.visit(this);
    }

    /**
     *
     * @return the list of {@link ActionTestStep} or empty list
     */
    public List<ActionTestStep> getActionSteps() {
        List<ActionTestStep> result = new ArrayList<>();
        ActionStepRetreiver retriever = new ActionStepRetreiver(result);
        for (TestStep step : this.getSteps()) {
            step.accept(retriever);
        }
        return retriever.getResult();

    }

    private static final class ActionStepRetreiver implements TestStepVisitor {

        private List<ActionTestStep> result;

        private List<ActionTestStep> getResult() {
            return result;
        }

        private ActionStepRetreiver(List<ActionTestStep> result) {
            this.result = result;
        }

        @Override
        public void visit(ActionTestStep visited) {
            result.add(visited);

        }

        @Override
        public void visit(CallTestStep visited) {
            // noop
        }

    }

    // =====================Requirement verifying section====================

    /**
     *
     * @return UNMODIFIABLE VIEW of verified requirements.
     */
    public Set<RequirementVersion> getVerifiedRequirementVersions() {
        Set<RequirementVersion> verified = new HashSet<>();
        for (RequirementVersionCoverage coverage : requirementVersionCoverages) {
            verified.add(coverage.getVerifiedRequirementVersion());
        }
        return Collections.unmodifiableSet(verified);
    }

    /**
     *
     * Checks if the given version is already verified, avoiding to look at the given requirementVersionCoverage.
     *
     * @param requirementVersionCoverage
     * @param version
     * @throws RequirementAlreadyVerifiedException
     */
    public void checkRequirementNotVerified(RequirementVersionCoverage requirementVersionCoverage,
            RequirementVersion version) throws RequirementAlreadyVerifiedException {
        Requirement req = version.getRequirement();
        for (RequirementVersionCoverage coverage : this.requirementVersionCoverages) {
            if (coverage != requirementVersionCoverage) {
                RequirementVersion verified = coverage.getVerifiedRequirementVersion();
                if (verified != null && req.equals(verified.getRequirement())) {
                    throw new RequirementAlreadyVerifiedException(version, this);
                }
            }
        }

    }

    /**
     * Set the verifying test case as this, and add the coverage the the this.requirementVersionCoverage
     *
     * @param requirementVersionCoverage
     */
    public void addRequirementCoverage(RequirementVersionCoverage requirementVersionCoverage) {
        this.requirementVersionCoverages.add(requirementVersionCoverage);
    }

    /**
     * Copy this.requirementVersionCoverages . All {@link RequirementVersionCoverage} having for verifying test case the
     * copy param.
     *
     * @param copy
     *            : the {@link TestCase} that will verify the copied coverages
     * @return : the copied {@link RequirementVersionCoverage}s
     */
    public List<RequirementVersionCoverage> createRequirementVersionCoveragesForCopy(TestCase copy) {
        List<RequirementVersionCoverage> createdCoverages = new ArrayList<>();
        for (RequirementVersionCoverage coverage : this.requirementVersionCoverages) {
            RequirementVersionCoverage covCopy = coverage.copyForTestCase(copy);
            if (covCopy != null) {
                createdCoverages.add(covCopy);
            }

        }
        return createdCoverages;
    }

    /**
     * Returns true if a step of the same id is found in this.steps.
     *
     * @param step
     *            : the step to check
     * @return true if this {@link TestCase} has the given step.
     */
    public boolean hasStep(TestStep step) {
        for (TestStep step2 : steps) {
            if (step2.getId().equals(step.getId())) {
                return true;
            }
        }
        return false;
    }

    /**
     * Simply remove the RequirementVersionCoverage from this.requirementVersionCoverages.
     *
     * @param requirementVersionCoverage
     *            : the entity to remove from this test case's {@link RequirementVersionCoverage}s list.
     *
     * @deprecated does not seem to be used in 1.14 - to be removed
     */
    @Deprecated
    public void removeRequirementVersionCoverage(RequirementVersionCoverage requirementVersionCoverage) {
        this.requirementVersionCoverages.remove(requirementVersionCoverage);

    }

    /***
     *
     * @return an unmodifiable set of the test case {@link RequirementVersionCoverage}s
     */
    public Set<RequirementVersionCoverage> getRequirementVersionCoverages() {
        return requirementVersionCoverages;
    }

    /**
     * @param calledVersion
     * @return true if this {@link TestCase} verifies the {@link RequirementVersion}
     */
    public boolean verifies(RequirementVersion rVersion) {
        for (RequirementVersionCoverage coverage : this.requirementVersionCoverages) {
            if (coverage.getVerifiedRequirementVersion().getId().equals(rVersion.getId())) {
                return true;
            }
        }
        return false;
    }

    /**
     * checks if the given version is already verified.
     *
     * @param version
     * @throws RequirementAlreadyVerifiedException
     */
    public void checkRequirementNotVerified(RequirementVersion version) throws RequirementAlreadyVerifiedException {
        Requirement req = version.getRequirement();
        for (RequirementVersion verified : this.getVerifiedRequirementVersions()) {
            if (verified != null && req.equals(verified.getRequirement())) {
                throw new RequirementAlreadyVerifiedException(version, this);
            }
        }

    }

    // =====================Parameter Section====================
    public Set<Parameter> getParameters() {
        return parameters;
    }

    /**
     * If the given parameter doesn't already exists in this.parameters, and, if the given parameter's name is not found
     * in this.parmeters : will add the given parameter to this.parameters.
     *
     * @throws NameAlreadyInUseException
     * @param parameter
     */
    protected void addParameter(@NotNull Parameter parameter) {
        Parameter homonyme = findParameterByName(parameter.getName());
        if (homonyme != null && !homonyme.equals(parameter)) {
            throw new NameAlreadyInUseException(Parameter.class.getSimpleName(), parameter.getName());
        }
        this.parameters.add(parameter);

    }

    public Set<Dataset> getDatasets() {
        return datasets;
    }

    public void addDataset(@NotNull Dataset dataset) {
        this.datasets.add(dataset);
    }

    public void removeDataset(@NotNull Dataset dataset) {
        this.datasets.remove(dataset);
    }

    @Override
    public Set<Milestone> getMilestones() {
        return milestones;
    }

    public Set<Milestone> getAllMilestones() {
        Set<Milestone> allMilestones = new HashSet<>();
        allMilestones.addAll(milestones);
        for (RequirementVersionCoverage coverage : requirementVersionCoverages) {
            allMilestones.addAll(coverage.getVerifiedRequirementVersion().getMilestones());
        }
        return allMilestones;
    }

    @Override
    public void bindMilestone(Milestone milestone) {
        milestones.add(milestone);
    }

    @Override
    public void unbindMilestone(Milestone milestone) {
        unbindMilestone(milestone.getId());
    }

    @Override
    public void unbindMilestone(Long milestoneId) {
        Iterator<Milestone> iter = milestones.iterator();

        while (iter.hasNext()) {
            Milestone m = iter.next();
            if (m.getId().equals(milestoneId)) {
                iter.remove();
                break;
            }
        }
    }

    public void clearMilestones() {
        milestones.clear();
    }

    @Override
    public boolean isMemberOf(Milestone milestone) {
        return milestones.contains(milestone);
    }

    /**
     * Will go through this.parameters and return the Parameter matching the given name
     *
     * @param name
     *            : the name of the parameter to return
     * @return the parameter matching the given name or <code>null</code>
     */
    public Parameter findParameterByName(String name) {
        for (Parameter parameter : this.parameters) {
            if (parameter.getName().equals(name)) {
                return parameter;
            }
        }
        return null;
    }

    /**
     * Will find the names of all parameters used in this test case's steps.
     *
     * @return a Set of Sting empty or containing all used parameter names in this steps.
     */
    public Set<String> findUsedParamsNamesInSteps() {
        Set<String> result = new HashSet<>();
        for (ActionTestStep step : this.getActionSteps()) {
            result.addAll(step.findUsedParametersNames());
        }
        return result;
    }

    /**
     * Creates a test case which non-collection, non-primitive type fields are set to null.
     *
     * @return
     */
    public static TestCase createBlankTestCase() {
        TestCase res = new TestCase();

        res.importanceAuto = null;
        res.prerequisite = null;
        res.reference = null;
        res.nature = null;
        res.type = null;
        res.importance = null;
        res.status = null;

        return res;
    }

    /**
     * Same as {@link #isImportanceAuto()}, required as per javabean spec since it returns a Boolean instead of a
     * boolean.
     *
     */
    public Boolean getImportanceAuto() {
        return isImportanceAuto();
    }

    /**
     * @see org.squashtest.tm.domain.milestone.MilestoneHolder#unbindAllMilestones()
     */
    @Override
    public void unbindAllMilestones() {
        milestones.clear();
    }

    public boolean isModifiable() {
        return milestonesAllowEdit();
    }

    private boolean milestonesAllowEdit() {
        for (Milestone m : getAllMilestones()) {
            if (!m.getStatus().isAllowObjectModification()) {
                return false;
            }
        }
        return true;
    }

    @Override
    public Boolean doMilestonesAllowCreation() {
        return Milestone.allowsCreationOrDeletion(getAllMilestones());
    }

    @Override
    public Boolean doMilestonesAllowEdition() {
        return Milestone.allowsEdition(getAllMilestones());
    };

    /**
     * @param findBindable
     */
    public void bindAllMilsetones(List<Milestone> ms) {
        milestones.addAll(ms);

    }

}