/*
* 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-2012 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.zkoss.ganttz;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.collections.CollectionUtils;
import org.joda.time.LocalDate;
import org.zkoss.ganttz.adapters.DomainDependency;
import org.zkoss.ganttz.adapters.IAdapterToTaskFundamentalProperties;
import org.zkoss.ganttz.adapters.IDomainAndBeansMapper;
import org.zkoss.ganttz.adapters.IStructureNavigator;
import org.zkoss.ganttz.adapters.PlannerConfiguration;
import org.zkoss.ganttz.data.Dependency;
import org.zkoss.ganttz.data.DependencyType;
import org.zkoss.ganttz.data.GanttDiagramGraph;
import org.zkoss.ganttz.data.GanttDiagramGraph.GanttZKDiagramGraph;
import org.zkoss.ganttz.data.ITaskFundamentalProperties;
import org.zkoss.ganttz.data.Milestone;
import org.zkoss.ganttz.data.Position;
import org.zkoss.ganttz.data.Task;
import org.zkoss.ganttz.data.TaskContainer;
import org.zkoss.ganttz.data.TaskLeaf;
import org.zkoss.ganttz.data.criticalpath.CriticalPathCalculator;
import org.zkoss.ganttz.extensions.IContext;
import org.zkoss.ganttz.timetracker.TimeTracker;
import org.zkoss.ganttz.timetracker.zoom.IDetailItemModificator;
import org.zkoss.ganttz.timetracker.zoom.TimeTrackerState;
import org.zkoss.ganttz.util.Interval;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zul.Button;
import org.zkoss.zul.Checkbox;
import org.zkoss.zul.Window;
public class FunctionalityExposedForExtensions<T> implements IContext<T> {
private static class OneToOneMapper<T> implements IDomainAndBeansMapper<T> {
private Map<T, Task> fromDomainToTask = new HashMap<T, Task>();
private Map<Task, T> fromTaskToDomain = new HashMap<Task, T>();
private Map<Task, TaskContainer> fromTaskToParent = new HashMap<Task, TaskContainer>();
private List<Task> topLevel = new ArrayList<Task>();
@Override
public Task findAssociatedBean(T domainObject)
throws IllegalArgumentException {
if (domainObject == null) {
throw new IllegalArgumentException("domainObject is null");
}
if (!fromDomainToTask.containsKey(domainObject)) {
throw new IllegalArgumentException("not found " + domainObject);
}
return fromDomainToTask.get(domainObject);
}
public Map<T, Task> getMapDomainToTask() {
return fromDomainToTask;
}
/**
* Registers the task at the provided position.
*/
void register(Position position, Task task, T domainObject) {
fromDomainToTask.put(domainObject, task);
fromTaskToDomain.put(task, domainObject);
if (position.isAppendToTop()) {
topLevel.add(task);
} else if (position.isAtTop()) {
topLevel.add(position.getInsertionPosition(), task);
} else {
fromTaskToParent.put(task, position.getParent());
}
}
void remove(T domainObject) {
Task toBeRemoved = findAssociatedBean(domainObject);
fromDomainToTask.remove(domainObject);
fromTaskToDomain.remove(toBeRemoved);
TaskContainer parent = fromTaskToParent.get(toBeRemoved);
if (parent != null) {
parent.remove(toBeRemoved);
}
fromTaskToParent.remove(toBeRemoved);
topLevel.remove(toBeRemoved);
}
@Override
public T findAssociatedDomainObject(Task task)
throws IllegalArgumentException {
if (task == null) {
throw new IllegalArgumentException("taskBean is null");
}
if (!fromTaskToDomain.containsKey(task)) {
throw new IllegalArgumentException();
}
return fromTaskToDomain.get(task);
}
@Override
public Position findPositionFor(Task task) {
List<TaskContainer> ancestors = ancestorsOf(task);
if (ancestors.isEmpty()) {
return Position.createAtTopPosition(topLevel.indexOf(task));
}
TaskContainer parent = ancestors.get(0);
return Position.createPosition(ancestors, parent.getTasks()
.indexOf(task));
}
@Override
public Position findPositionFor(T domainObject) {
return findPositionFor(findAssociatedBean(domainObject));
}
private List<TaskContainer> ancestorsOf(Task task) {
ArrayList<TaskContainer> result = new ArrayList<TaskContainer>();
TaskContainer taskContainer = fromTaskToParent.get(task);
while (taskContainer != null) {
result.add(taskContainer);
taskContainer = fromTaskToParent.get(taskContainer);
}
return result;
}
@Override
public List<? extends TaskContainer> getParents(Task task) {
Position position = findPositionFor(task);
return position.getAncestors();
}
}
private final Planner planner;
private final IAdapterToTaskFundamentalProperties<T> adapter;
private final IStructureNavigator<T> navigator;
private final OneToOneMapper<T> mapper = new OneToOneMapper<T>();
private final GanttZKDiagramGraph diagramGraph;
private TimeTracker timeTracker;
private final PlannerConfiguration<T> configuration;
public FunctionalityExposedForExtensions(Planner planner,
PlannerConfiguration<T> configuration,
GanttZKDiagramGraph diagramGraph) {
this.planner = planner;
this.configuration = configuration;
this.adapter = configuration.getAdapter();
this.navigator = configuration.getNavigator();
this.diagramGraph = diagramGraph;
final IDetailItemModificator firstLevelModificators = configuration
.getFirstLevelModificators();
final IDetailItemModificator secondLevelModificators = configuration
.getSecondLevelModificators();
Calendar calendarRightNow = Calendar.getInstance();
LocalDate localDateRightNow = LocalDate.fromCalendarFields(calendarRightNow);
LocalDate initDate = localDateRightNow.minusYears(1);
LocalDate endDate = localDateRightNow.plusYears(5);
this.timeTracker = new TimeTracker(new Interval(
TimeTrackerState.year(initDate.getYear()),
TimeTrackerState.year(endDate.getYear())),
planner.getZoomLevel(), firstLevelModificators,
secondLevelModificators, planner);
}
/**
* @param insertionPosition
* the position in which to register the task at top level. It
* can be <code>null</code>
* @param accumulatedDependencies
* @param data
* @param parent
* @return
*/
private Task buildAndRegister(Position position,
List<DomainDependency<T>> accumulatedDependencies, T data) {
accumulatedDependencies.addAll(adapter.getOutcomingDependencies(data));
accumulatedDependencies.addAll(adapter.getIncomingDependencies(data));
final Task result = build(data);
if (!navigator.isLeaf(data)) {
TaskContainer container = (TaskContainer) result;
int i = 0;
for (T child : navigator.getChildren(data)) {
container.add(buildAndRegister(position.down(container, i),
accumulatedDependencies, child));
i++;
}
} else if (navigator.isMilestone(data)) {
Milestone milestone = (Milestone) result;
milestone.setOwner(position.getParent());
}
result.setShowingReportedHours(planner.showReportedHoursRightNow());
result.setShowingMoneyCostBar(planner.showMoneyCostBarRightNow());
result.setShowingAdvances(planner.showAdvancesRightNow());
mapper.register(position, result, data);
return result;
}
private Task build(T data) {
ITaskFundamentalProperties adapted = adapter.adapt(data);
if (navigator.isMilestone(data)) {
return new Milestone(adapted);
} else if (navigator.isLeaf(data)) {
return new TaskLeaf(adapted);
} else {
return new TaskContainer(adapted,
planner.areContainersExpandedByDefault());
}
}
public void add(Position position, Collection<? extends T> domainObjects) {
List<DomainDependency<T>> totalDependencies = new ArrayList<DomainDependency<T>>();
List<Task> tasksCreated = new ArrayList<Task>();
for (T object : domainObjects) {
Task task = buildAndRegister(position, totalDependencies, object);
tasksCreated.add(task);
}
updateTimeTracker(tasksCreated);
if (position.isAppendToTop() || position.isAtTop()) {
this.diagramGraph.addTopLevel(tasksCreated);
} else {
this.diagramGraph.addTasks(tasksCreated);
TaskContainer parent = position.getParent();
parent.addAll(position.getInsertionPosition(), tasksCreated);
this.diagramGraph.childrenAddedTo(parent);
}
for (Dependency dependency : DomainDependency.toDependencies(mapper,
totalDependencies)) {
this.diagramGraph.addWithoutEnforcingConstraints(dependency);
}
this.diagramGraph.enforceAllRestrictions();
this.planner.addTasks(position, tasksCreated);
}
private void updateTimeTracker(List<Task> tasksCreated) {
for (Task task : tasksCreated) {
timeTracker.trackPosition(task);
if (task.isContainer()) {
TaskContainer container = (TaskContainer) task;
updateTimeTracker(container.getTasks());
}
}
}
public void add(Collection<? extends T> domainObjects) {
add(Position.createAppendToTopPosition(), domainObjects);
}
@Override
public void add(T domainObject) {
add(Position.createAppendToTopPosition(), domainObject);
}
@Override
public void add(Position position, T domainObject) {
add(position, Collections.singletonList(domainObject));
}
public IDomainAndBeansMapper<T> getMapper() {
return mapper;
}
@Override
public void reload(PlannerConfiguration<?> configuration) {
planner.setConfiguration(configuration);
}
@Override
public Position remove(T domainObject) {
Task task = mapper.findAssociatedBean(domainObject);
Position position = mapper.findPositionFor(task);
adapter.doRemovalOf(mapper.findAssociatedDomainObject(task));
mapper.remove(domainObject);
diagramGraph.remove(task);
task.removed();
planner.removeTask(task);
return position;
}
@Override
public Component getRelativeTo() {
return planner;
}
@Override
public void replace(T oldDomainObject, T newDomainObject) {
Position position = remove(oldDomainObject);
add(position, newDomainObject);
}
public GanttZKDiagramGraph getDiagramGraph() {
return diagramGraph;
}
private DomainDependency<T> toDomainDependency(Dependency bean) {
T source = mapper.findAssociatedDomainObject(bean.getSource());
T destination = mapper
.findAssociatedDomainObject(bean.getDestination());
DomainDependency<T> dep = DomainDependency.createDependency(source,
destination, bean.getType());
return dep;
}
public void addDependency(Dependency dependency) {
if (!canAddDependency(dependency)) {
return;
}
diagramGraph.add(dependency);
getDependencyList().addDependencyComponent(
getTaskList().asDependencyComponent(dependency));
adapter.addDependency(toDomainDependency(dependency));
}
private TaskList getTaskList() {
return planner.getTaskList();
}
private boolean canAddDependency(Dependency dependency) {
return diagramGraph.canAddDependency(dependency)
&& adapter.canAddDependency(toDomainDependency(dependency));
}
private DependencyList getDependencyList() {
return planner.getDependencyList();
}
public void removeDependency(Dependency dependency) {
adapter.removeDependency(toDomainDependency(dependency));
diagramGraph.removeDependency(dependency);
getDependencyList().remove(dependency);
}
/**
* Substitutes the dependency for a new one with the same source and
* destination but with the specified type. If the new dependency cannot be
* added, the old one remains.
* @param dependency
* @param type
* the new type
* @return true only if the new dependency can be added.
*/
public boolean changeType(Dependency dependency, DependencyType type) {
Dependency newDependency = dependency.createWithType(type);
boolean canAddDependency = diagramGraph.canAddDependency(newDependency);
if (canAddDependency) {
removeDependency(dependency);
addDependency(newDependency);
}
return canAddDependency;
}
@Override
public TimeTracker getTimeTracker() {
return timeTracker;
}
@Override
public void recalculatePosition(T domainObject) {
Task associatedTask = mapper.findAssociatedBean(domainObject);
diagramGraph.enforceRestrictions(associatedTask);
}
@Override
public void showCriticalPath() {
CriticalPathCalculator<Task, Dependency> criticalPathCalculator = CriticalPathCalculator
.create(configuration.isDependenciesConstraintsHavePriority());
List<Task> criticalPath = criticalPathCalculator
.calculateCriticalPath(diagramGraph);
for (Task task : diagramGraph.getTasks()) {
task.setInCriticalPath(isInCriticalPath(criticalPath, task));
}
}
private boolean isInCriticalPath(List<Task> criticalPath, Task task) {
if (task.isContainer()) {
List<Task> allTaskLeafs = ((TaskContainer) task).getAllTaskLeafs();
return CollectionUtils.containsAny(criticalPath, allTaskLeafs);
} else {
return criticalPath.contains(task);
}
}
@Override
public List<T> getCriticalPath() {
List<T> result = new ArrayList<T>();
CriticalPathCalculator<Task, Dependency> criticalPathCalculator = CriticalPathCalculator
.create(configuration.isDependenciesConstraintsHavePriority());
for (Task each : criticalPathCalculator
.calculateCriticalPath(diagramGraph)) {
result.add(mapper.findAssociatedDomainObject(each));
}
return result;
}
@Override
public void hideCriticalPath() {
for (Task task : diagramGraph.getTasks()) {
task.setInCriticalPath(false);
}
}
@Override
public void showAdvances() {
for (Task task : diagramGraph.getTasks()) {
task.setShowingAdvances(true);
}
}
@Override
public void hideAdvances() {
for (Task task : diagramGraph.getTasks()) {
task.setShowingAdvances(false);
}
}
@Override
public void showReportedHours() {
for (Task task : diagramGraph.getTasks()) {
task.setShowingReportedHours(true);
}
}
@Override
public void hideReportedHours() {
for (Task task : diagramGraph.getTasks()) {
task.setShowingReportedHours(false);
}
}
@Override
public void showMoneyCostBar() {
for (Task task : diagramGraph.getTasks()) {
task.setShowingMoneyCostBar(true);
}
}
@Override
public void hideMoneyCostBar() {
for (Task task : diagramGraph.getTasks()) {
task.setShowingMoneyCostBar(false);
}
}
@Override
public void reloadCharts() {
configuration.reloadCharts();
}
public boolean isPrintEnabled() {
return configuration.isPrintEnabled();
}
private HashMap<String, String> buildParameters(Component parent) {
HashMap<String, String> parameters = new HashMap<String, String>();
Checkbox expanded = (Checkbox) parent.getFellow("print_expanded");
Checkbox resources = (Checkbox) parent.getFellow("print_resources");
Checkbox labels = (Checkbox) parent.getFellow("print_labels");
Checkbox advances = (Checkbox) parent.getFellow("print_advances");
Checkbox reportedHours = (Checkbox) parent
.getFellow("print_reported_hours");
Checkbox moneyCostBar = (Checkbox) parent
.getFellow("print_money_cost_bar");
parameters.put("extension", ".png");
if (expanded.isChecked() == true) {
parameters.put("expanded", "all");
}
if (labels.isChecked() == true) {
parameters.put("labels", "all");
}
if (advances.isChecked() == true) {
parameters.put("advances", "all");
}
if (reportedHours.isChecked() == true) {
parameters.put("reportedHours", "all");
}
if (moneyCostBar.isChecked() == true) {
parameters.put("moneyCostBar", "all");
}
if (resources.isChecked() == true) {
parameters.put("resources", "all");
}
parameters.put("zoom", planner.getZoomLevel().getInternalName());
return parameters;
}
public void print() {
if (!isPrintEnabled()) {
throw new UnsupportedOperationException("print is not supported");
}
final Window printProperties = (Window) Executions.createComponents(
"/planner/print_configuration.zul", planner, null);
Button printButton = (Button) printProperties.getFellow("printButton");
printButton.addEventListener(Events.ON_CLICK, new EventListener() {
@Override
public void onEvent(Event event) {
printProperties.detach();
configuration.print(buildParameters(printProperties),planner);
}
});
printButton.setParent(printProperties);
try {
printProperties.doModal();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public List<Task> getTasksOrderedByStartDate() {
List<Task> tasks = diagramGraph.getTasks();
Collections.sort(tasks, new Comparator<Task>() {
@Override
public int compare(Task o1, Task o2) {
return o1.getBeginDate().compareTo(o2.getBeginDate());
}
});
return tasks;
}
public void expandAll() {
setExpandAll(true, getTasksOrderedByStartDate());
}
public void collapseAll() {
setExpandAll(false, getTasksOrderedByStartDate());
}
private void setExpandAll(boolean expand, List<Task> tasks) {
for (Task task : tasks) {
if (task instanceof TaskContainer) {
((TaskContainer) task).setExpanded(expand);
}
}
}
@Override
public GanttDiagramGraph<Task, Dependency> getGanttDiagramGraph() {
return diagramGraph;
}
}