/*
* This file is part of LibrePlan
*
* Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
* Desenvolvemento Tecnolóxico de Galicia
* Copyright (C) 2010-2011 Igalia, S.L.
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.libreplan.business.orders.entities;
import static org.libreplan.business.i18n.I18nHelper._;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.ToStringBuilder;
/**
* @author Óscar González Fernández <ogonzalez@igalia.com>
*
*/
public class SchedulingState {
public enum Type {
SCHEDULING_POINT {
@Override
public boolean belongsToOrIsSchedulingPoint() {
return true;
}
@Override
public boolean isCompletelyScheduled() {
return true;
}
@Override
public boolean isPartiallyScheduled() {
return false;
}
},
SCHEDULED_SUBELEMENT {
@Override
public boolean belongsToOrIsSchedulingPoint() {
return true;
}
@Override
public boolean isCompletelyScheduled() {
return true;
}
@Override
public boolean isPartiallyScheduled() {
return false;
}
},
PARTIALY_SCHEDULED_SUPERELEMENT {
@Override
public boolean belongsToOrIsSchedulingPoint() {
return false;
}
@Override
public boolean isCompletelyScheduled() {
return false;
}
@Override
public boolean isPartiallyScheduled() {
return true;
}
},
COMPLETELY_SCHEDULED_SUPERELEMENT {
@Override
public boolean belongsToOrIsSchedulingPoint() {
return false;
}
@Override
public boolean isCompletelyScheduled() {
return true;
}
@Override
public boolean isPartiallyScheduled() {
return false;
}
},
NO_SCHEDULED {
@Override
public boolean belongsToOrIsSchedulingPoint() {
return false;
}
@Override
public boolean isCompletelyScheduled() {
return false;
}
@Override
public boolean isPartiallyScheduled() {
return false;
}
};
public abstract boolean belongsToOrIsSchedulingPoint();
public abstract boolean isPartiallyScheduled();
public abstract boolean isCompletelyScheduled();
public final Type newTypeWhenDetachedFromParent() {
return this == SCHEDULED_SUBELEMENT ? NO_SCHEDULED : this;
}
public boolean isSomewhatScheduled() {
return isPartiallyScheduled() || isCompletelyScheduled();
}
}
public interface ITypeChangedListener {
public void typeChanged(Type newType);
}
public static SchedulingState createSchedulingState(Type initialType,
List<SchedulingState> childrenStates,
ITypeChangedListener typeListener) {
SchedulingState result = new SchedulingState(initialType,
childrenStates);
Type newType = result.getType();
if (newType != initialType) {
typeListener.typeChanged(newType);
}
result.addTypeChangeListener(typeListener);
return result;
}
private Type type = Type.NO_SCHEDULED;
private SchedulingState parent;
private Set<SchedulingState> children = new LinkedHashSet<SchedulingState>();
private List<ITypeChangedListener> listeners = new ArrayList<ITypeChangedListener>();
public SchedulingState() {
}
public SchedulingState(Type type, List<SchedulingState> children) {
this(type);
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) {
children.add(child);
child.changingParentTo(this);
setType(calculateTypeFromChildren());
}
private void changingParentTo(SchedulingState parent) {
this.parent = parent;
if (parent.getType().belongsToOrIsSchedulingPoint()) {
setTypeWithoutNotifyingParent(Type.SCHEDULED_SUBELEMENT);
for (SchedulingState each : getDescendants()) {
each.setTypeWithoutNotifyingParent(Type.SCHEDULED_SUBELEMENT);
}
}
}
public SchedulingState getParent() {
return parent;
}
public boolean isRoot() {
return parent == null;
}
public boolean canBeScheduled() {
return type == Type.NO_SCHEDULED;
}
public void schedule() {
if (!canBeScheduled()) {
throw new IllegalStateException("It is already somewhat scheduled");
}
setType(Type.SCHEDULING_POINT);
for (SchedulingState schedulingState : getDescendants()) {
schedulingState.setType(Type.SCHEDULED_SUBELEMENT);
}
}
public boolean canBeUnscheduled() {
return getType() == Type.SCHEDULING_POINT;
}
public void unschedule() {
if (!canBeUnscheduled()) {
throw new IllegalStateException("It cannot be unscheduled");
}
setType(Type.NO_SCHEDULED);
markDescendantsAsNoScheduled();
}
private void markDescendantsAsNoScheduled() {
for (SchedulingState each : children) {
each.ancestorUnscheduled();
each.markDescendantsAsNoScheduled();
}
}
private void ancestorUnscheduled() {
Validate.isTrue(type == Type.SCHEDULED_SUBELEMENT);
setTypeWithoutNotifyingParent(Type.NO_SCHEDULED);
}
private void setType(Type type) {
if (this.type == type) {
return;
}
this.type = type;
notifyParentOfTypeChange();
fireTypeChanged();
}
private void setTypeWithoutNotifyingParent(Type type) {
if (this.type == type) {
return;
}
this.type = type;
fireTypeChanged();
}
private void notifyParentOfTypeChange() {
if (isRoot()) {
return;
}
parent.typeChangedOnChild(this);
}
private void typeChangedOnChild(SchedulingState child) {
setType(calculateTypeFromChildren());
}
private Type calculateTypeFromChildren() {
if (getType().belongsToOrIsSchedulingPoint()) {
return getType();
}
if (children.isEmpty()) {
return Type.NO_SCHEDULED;
}
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<SchedulingState> getDescendants() {
List<SchedulingState> result = new ArrayList<SchedulingState>();
addDescendants(result);
return result;
}
private void addDescendants(List<SchedulingState> 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 isNoScheduled() {
return type == Type.NO_SCHEDULED;
}
public boolean isSomewhatScheduled() {
return type.isSomewhatScheduled();
}
public void removeChild(SchedulingState schedulingState) {
boolean removed = children.remove(schedulingState);
if (removed) {
schedulingState.detachFromParent();
}
setType(calculateTypeFromChildren());
}
private void detachFromParent() {
this.parent = null;
setType(type.newTypeWhenDetachedFromParent());
}
@Override
public String toString() {
return new ToStringBuilder(this).append(type).toString();
}
private void fireTypeChanged() {
for (ITypeChangedListener listener : listeners) {
listener.typeChanged(type);
}
}
public void addTypeChangeListener(ITypeChangedListener listener) {
Validate.notNull(listener);
listeners.add(listener);
}
public void removeTypeChangeListener(ITypeChangedListener listener) {
listeners.remove(listener);
}
public int getChildrenNumber() {
return children.size();
}
public String getStateName() {
if (isCompletelyScheduled()) {
return _("Fully scheduled");
} else if (isPartiallyScheduled()) {
return _("Partially scheduled");
} else {
return _("Unscheduled");
}
}
public String getStateAbbreviation() {
if (isCompletelyScheduled()) {
return _("F");
} else if (isPartiallyScheduled()) {
return _("P");
} else {
return _("U");
}
}
public String getCssClass() {
String cssclass = "not-scheduled";
if (isCompletelyScheduled()) {
cssclass = "completely-scheduled";
} else if (isPartiallyScheduled()) {
cssclass = "partially-scheduled";
}
return cssclass;
}
}