From 0b9a58bdccce001ea30ea481254c7e7b973f4ded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=93scar=20Gonz=C3=A1lez=20Fern=C3=A1ndez?= Date: Wed, 11 Nov 2009 19:48:25 +0100 Subject: [PATCH] ItEr34S12CUCreacionUnidadesPlanificacionItEr33S14: Adding SchedulingState class and accompanying test. --- .../orders/entities/SchedulingState.java | 262 +++++++++++++++++ .../orders/entities/SchedulingStateTest.java | 277 ++++++++++++++++++ 2 files changed, 539 insertions(+) create mode 100644 navalplanner-business/src/main/java/org/navalplanner/business/orders/entities/SchedulingState.java create mode 100644 navalplanner-business/src/test/java/org/navalplanner/business/test/orders/entities/SchedulingStateTest.java diff --git a/navalplanner-business/src/main/java/org/navalplanner/business/orders/entities/SchedulingState.java b/navalplanner-business/src/main/java/org/navalplanner/business/orders/entities/SchedulingState.java new file mode 100644 index 000000000..d5dd30ffe --- /dev/null +++ b/navalplanner-business/src/main/java/org/navalplanner/business/orders/entities/SchedulingState.java @@ -0,0 +1,262 @@ +/* + * This file is part of ###PROJECT_NAME### + * + * Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e + * Desenvolvemento Tecnolóxico de Galicia + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.navalplanner.business.orders.entities; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang.Validate; +import org.apache.commons.lang.builder.ToStringBuilder; + +/** + * @author Óscar González Fernández + * + */ +public class SchedulingState { + + public enum Type { + SCHEDULING_POINT { + @Override + public boolean belongsToSchedulingPoint() { + return true; + } + + @Override + public boolean isCompletelyScheduled() { + return true; + } + + @Override + public boolean isPartiallyScheduled() { + return false; + } + }, + SCHEDULED_SUBELEMENT { + @Override + public boolean belongsToSchedulingPoint() { + return true; + } + + @Override + public boolean isCompletelyScheduled() { + return true; + } + + @Override + public boolean isPartiallyScheduled() { + return false; + } + }, + PARTIALY_SCHEDULED_SUPERELEMENT { + @Override + public boolean belongsToSchedulingPoint() { + return false; + } + + @Override + public boolean isCompletelyScheduled() { + return false; + } + + @Override + public boolean isPartiallyScheduled() { + return true; + } + }, + COMPLETELY_SCHEDULED_SUPERELEMENT { + @Override + public boolean belongsToSchedulingPoint() { + return false; + } + + @Override + public boolean isCompletelyScheduled() { + return true; + } + + @Override + public boolean isPartiallyScheduled() { + return false; + } + }, + NO_SCHEDULED { + @Override + public boolean belongsToSchedulingPoint() { + return false; + } + + @Override + public boolean isCompletelyScheduled() { + return false; + } + + @Override + public boolean isPartiallyScheduled() { + return false; + } + }; + + public abstract boolean belongsToSchedulingPoint(); + + public abstract boolean isPartiallyScheduled(); + + public abstract boolean isCompletelyScheduled(); + + public boolean isSomewhatScheduled() { + return isPartiallyScheduled() || isCompletelyScheduled(); + } + } + + private Type type = Type.NO_SCHEDULED; + private SchedulingState parent; + + private List children = new ArrayList(); + + public SchedulingState() { + } + + public SchedulingState(List children) { + for (SchedulingState each : children) { + if (!each.isRoot()) { + throw new IllegalArgumentException(each + + " is already child of another " + + SchedulingState.class.getSimpleName()); + } + add(each); + } + } + + public SchedulingState(Type type) { + this.type = type; + } + + public Type getType() { + return type; + } + + public void add(SchedulingState child) { + child.parent = this; + children.add(child); + setType(calculateTypeFromChildren()); + } + + public SchedulingState getParent() { + return parent; + } + + public boolean isRoot() { + return parent == null; + } + + public boolean canBeScheduled() { + return type == Type.NO_SCHEDULED; + } + + public void schedule() { + if (type.isSomewhatScheduled()) { + throw new IllegalStateException("it's already somewhat scheduled"); + } + setType(Type.SCHEDULING_POINT); + for (SchedulingState schedulingState : getDescendants()) { + schedulingState.setType(Type.SCHEDULED_SUBELEMENT); + } + } + + private void setType(Type type) { + if (this.type == type) { + return; + } + this.type = type; + notifyParentOfTypeChange(); + } + + private void notifyParentOfTypeChange() { + if (isRoot()) { + return; + } + parent.typeChangedOnChild(this); + } + + private void typeChangedOnChild(SchedulingState child) { + if (getType().belongsToSchedulingPoint()) { + return; + } + setType(calculateTypeFromChildren()); + } + + private Type calculateTypeFromChildren() { + Validate.isTrue(!children.isEmpty()); + boolean allScheduled = true; + boolean someScheduled = false; + for (SchedulingState each : children) { + someScheduled = someScheduled + || each.getType().isSomewhatScheduled(); + allScheduled = allScheduled + && each.getType().isCompletelyScheduled(); + } + if (allScheduled) { + return Type.COMPLETELY_SCHEDULED_SUPERELEMENT; + } else if (someScheduled) { + return Type.PARTIALY_SCHEDULED_SUPERELEMENT; + } else { + return Type.NO_SCHEDULED; + } + } + + private List getDescendants() { + List result = new ArrayList(); + addDescendants(result); + return result; + } + + private void addDescendants(List result) { + for (SchedulingState each : children) { + result.add(each); + each.addDescendants(result); + } + } + + public boolean isCompletelyScheduled() { + return type.isCompletelyScheduled(); + } + + public boolean isPartiallyScheduled() { + return type.isPartiallyScheduled(); + } + + public boolean isSomewhatScheduled() { + return type.isSomewhatScheduled(); + } + + public void removeChild(SchedulingState schedulingState) { + boolean removed = children.remove(schedulingState); + if (removed) { + schedulingState.parent = null; + } + setType(calculateTypeFromChildren()); + } + + @Override + public String toString() { + return new ToStringBuilder(this).append(type).toString(); + } + + +} diff --git a/navalplanner-business/src/test/java/org/navalplanner/business/test/orders/entities/SchedulingStateTest.java b/navalplanner-business/src/test/java/org/navalplanner/business/test/orders/entities/SchedulingStateTest.java new file mode 100644 index 000000000..51472c5d1 --- /dev/null +++ b/navalplanner-business/src/test/java/org/navalplanner/business/test/orders/entities/SchedulingStateTest.java @@ -0,0 +1,277 @@ +/* + * This file is part of ###PROJECT_NAME### + * + * Copyright (C) 2009 Fundación para o Fomento da Calidade Industrial e + * Desenvolvemento Tecnolóxico de Galicia + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.navalplanner.business.test.orders.entities; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.matchers.JUnitMatchers.each; + +import java.util.Arrays; +import java.util.List; + +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.junit.Before; +import org.junit.Test; +import org.navalplanner.business.orders.entities.SchedulingState; +import org.navalplanner.business.orders.entities.SchedulingState.Type; + +/** + * @author Óscar González Fernández + * + */ +public class SchedulingStateTest { + + private SchedulingState root; + + private SchedulingState childA; + + private SchedulingState childB; + + private SchedulingState grandChildA1; + + private SchedulingState grandChildA2; + + private SchedulingState grandChildB1; + + private SchedulingState grandChildB2; + + @Before + public void setUp() { + root = new SchedulingState(); + root.add(childA = new SchedulingState()); + childA.add(grandChildA1 = new SchedulingState()); + childA.add(grandChildA2 = new SchedulingState()); + root.add(childB = new SchedulingState()); + childB.add(grandChildB1 = new SchedulingState()); + childB.add(grandChildB2 = new SchedulingState()); + } + + private List all(){ + return Arrays.asList(root,childA, childB, grandChildA1, grandChildA2, + grandChildB1, grandChildB2); + } + + private List allRootDescendants() { + return Arrays.asList(childA, childB, grandChildA1, grandChildA2, + grandChildB1, grandChildB2); + } + + @Test + public void aNewlyCreatedSchedulingStateIsNoScheduled() { + SchedulingState schedulingState = new SchedulingState(); + assertThat(schedulingState.getType(), + equalTo(Type.NO_SCHEDULED)); + } + + @Test(expected = IllegalArgumentException.class) + public void cannotCreateASchedulingStateWithChildrenAlreadyAssigned() { + new SchedulingState(Arrays.asList(childA)); + } + + @Test + public void anAddedSchedulingStateHasAParent() { + assertThat(childA.getParent(), equalTo(root)); + } + + @Test + public void theRootOfASchedulingStateTreeHasNoParent() { + assertNull(root.getParent()); + } + + @Test + public void ifHasNoParentItsRoot() { + assertTrue(root.isRoot()); + } + + @Test + public void whenSchedulingAElementItTursIntoASchedulingPoint() { + grandChildA1.schedule(); + assertThat(grandChildA1.getType(), equalTo(Type.SCHEDULING_POINT)); + } + + @Test + public void whenSchedulingAElementItTurnsAllItsDescendantsIntoScheduledSubelements() { + root.schedule(); + assertThat(allRootDescendants(), + each(hasType(Type.SCHEDULED_SUBELEMENT))); + } + + @Test + public void aScheduledElementIsCompletelyScheduled() { + root.schedule(); + assertThat(all(), each(completelyScheduled())); + } + + @Test + public void aSomewhatScheduledElemenetCannotBeScheduled() { + grandChildA1.schedule(); + grandChildB1.schedule(); + for (SchedulingState schedulingState : all()) { + if (schedulingState == grandChildA2 + || schedulingState == grandChildB2) { + // can be scheduled + continue; + } + try { + schedulingState.schedule(); + fail("must send " + IllegalStateException.class); + } catch (IllegalStateException e) { + // ok + } + } + } + + @Test + public void scheduledSubelementsCantBeScheduled() { + root.schedule(); + assertThat(allRootDescendants(), each(not(canBeScheduled()))); + } + + @Test + public void aNoScheduledElementCanBeScheduled() { + assertThat(all(), each(canBeScheduled())); + } + + @Test + public void aSchedulingPointCantBeScheduled() { + root.schedule(); + assertFalse(root.canBeScheduled()); + } + + @Test + public void rootIsPartiallyScheduledWhenSchedulingOneOfTheChildren() { + childA.schedule(); + assertThat(root, hasType(Type.PARTIALY_SCHEDULED_SUPERELEMENT)); + } + + @Test + public void rootIsCompletelyScheduledWhenSchedulingAllOfTheChildren() { + childA.schedule(); + childB.schedule(); + assertThat(root, hasType(Type.COMPLETELY_SCHEDULED_SUPERELEMENT)); + } + + @Test + public void whenSchedulingAGrandChildrenTheRootIsPartiallyScheduled() { + grandChildA1.schedule(); + assertThat(root, hasType(Type.PARTIALY_SCHEDULED_SUPERELEMENT)); + } + + @Test + public void ifSchedulingAllGrandChildrenTheRootIsCompletelyScheduled() { + grandChildA1.schedule(); + grandChildA2.schedule(); + grandChildB1.schedule(); + grandChildB2.schedule(); + assertThat(root, hasType(Type.COMPLETELY_SCHEDULED_SUPERELEMENT)); + } + + @Test + public void addingANewChildToACompletelyScheduled() { + childA.schedule(); + childB.schedule(); + root.add(new SchedulingState()); + assertThat(root, hasType(Type.PARTIALY_SCHEDULED_SUPERELEMENT)); + } + + @Test + public void removingTheOnlyNoScheduled() { + childA.schedule(); + root.removeChild(childB); + assertThat(root, hasType(Type.COMPLETELY_SCHEDULED_SUPERELEMENT)); + } + + @Test + public void removingAChildMakesItHasNoParent() { + childA.schedule(); + root.removeChild(childB); + assertThat(childB.getParent(), nullValue()); + } + + abstract static class SchedulingStateMatcher extends + BaseMatcher { + @Override + public boolean matches(Object object) { + if (object instanceof SchedulingState) { + return matches((SchedulingState) object); + } else { + return false; + } + } + + protected abstract boolean matches(SchedulingState schedulingState); + } + + private Matcher hasType(final Type type) { + return new SchedulingStateMatcher() { + + @Override + public boolean matches(SchedulingState state) { + return state.getType() == type; + } + + @Override + public void describeTo(Description description) { + description + .appendText("the type of the SchedulingState must be: " + + type); + } + }; + } + + private Matcher completelyScheduled() { + return new SchedulingStateMatcher() { + + @Override + public void describeTo(Description description) { + description.appendText("completely scheduled"); + } + + @Override + protected boolean matches(SchedulingState schedulingState) { + return schedulingState.isCompletelyScheduled(); + } + }; + } + + private Matcher canBeScheduled() { + return new SchedulingStateMatcher() { + + @Override + public boolean matches(SchedulingState state) { + return state.canBeScheduled(); + } + + @Override + public void describeTo(Description description) { + description.appendText("can be scheduled"); + } + }; + } + +}