GanttTaskTableColumn.java
/* ========================================================================
* PlantUML : a free UML diagram generator
* ========================================================================
*
* (C) Copyright 2009-2024, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://plantuml.com/paypal
*
* This file is part of PlantUML.
*
* PlantUML is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PlantUML 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 General Public
* License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA.
*
*
* Original Author: Arnaud Roques
*
*
*/
package net.sourceforge.plantuml.gantt;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Locale;
import com.plantuml.ubrex.builder.UBrexLeaf;
import com.plantuml.ubrex.builder.UBrexOr;
import com.plantuml.ubrex.builder.UBrexPart;
import net.sourceforge.plantuml.gantt.core.Task;
import net.sourceforge.plantuml.gantt.time.TimePoint;
/**
* Describes a single column of the textual task table drawn on the left side of
* a Gantt diagram. Each constant knows both how to label its header and how to
* extract the matching value from a {@link Task}, so that
* {@link GanttTaskTable} only has to iterate over the columns it wants to
* display.
*/
public enum GanttTaskTableColumn {
TASK {
@Override
String header(Locale locale) {
return GanttI18n.task(locale);
}
@Override
String valueOf(Task task, Context context) {
return task.getCode().getDisplay();
}
},
START {
@Override
String header(Locale locale) {
return GanttI18n.start(locale);
}
@Override
String valueOf(Task task, Context context) {
return context.formatDayWithTime(task.getStart());
}
},
END {
@Override
String header(Locale locale) {
return GanttI18n.end(locale);
}
@Override
String valueOf(Task task, Context context) {
return context.formatEnd(task.getEnd());
}
},
DURATION {
@Override
String header(Locale locale) {
return GanttI18n.duration(locale);
}
@Override
String valueOf(Task task, Context context) {
final LocalDateTime start = task.getStart().toLocalDateTime();
final LocalDateTime end = task.getEnd().toLocalDateTime();
final Duration duration = Duration.between(start, end);
return GanttI18n.durationHumanReadable(context.getLocale(), duration);
}
};
static final long SECONDS_IN_A_DAY = ChronoUnit.DAYS.getDuration().getSeconds(); // 86400
/**
* Localized header label shown on the first row of the table.
*/
abstract String header(Locale locale);
/**
* Textual value of this column for the given task.
*/
abstract String valueOf(Task task, Context context);
/**
* Carries the table-wide state that columns need to format their values (locale
* and absolute-vs-relative day rendering), keeping the enum itself free of any
* reference to the surrounding drawing code.
*/
static final class Context {
private final Locale locale;
private final boolean relativeMode;
private final LocalDate minDay;
Context(Locale locale, boolean relativeMode, LocalDate minDay) {
this.locale = locale;
this.relativeMode = relativeMode;
this.minDay = minDay;
}
Locale getLocale() {
return locale;
}
String formatDay(TimePoint point) {
if (relativeMode)
return GanttI18n.dayNumber(locale, relativeDayNum(point));
return point.toStringShort(locale);
}
// Same as formatDay, but appends the time of day when the point does not fall
// exactly at midnight. Seconds are shown only when they are not zero, so a
// point at 14:30:00 renders as "14:30" while 14:30:45 renders as "14:30:45".
String formatDayWithTime(TimePoint point) {
final String day = formatDay(point);
final LocalDateTime dateTime = point.toLocalDateTime();
final int hour = dateTime.getHour();
final int minute = dateTime.getMinute();
final int second = dateTime.getSecond();
if (hour == 0 && minute == 0 && second == 0)
return day;
final String time;
if (second == 0)
time = String.format("%02d:%02d", hour, minute);
else
time = String.format("%02d:%02d:%02d", hour, minute, second);
return day + " " + time;
}
// The task end is stored as an exclusive bound. When it falls exactly at
// midnight the task spans whole days, so we step back one second to render
// the last included day (e.g. an end at "6 00:00:00" shows as "5"). When the
// end carries an explicit time of day, it is already the point to display and
// must be shown as-is, down to the minute or second.
String formatEnd(TimePoint end) {
final LocalDateTime dateTime = end.toLocalDateTime();
final boolean atMidnight = dateTime.getHour() == 0 && dateTime.getMinute() == 0
&& dateTime.getSecond() == 0;
if (atMidnight)
return formatDay(end.minusOneSecond());
return formatDayWithTime(end);
}
private int relativeDayNum(TimePoint point) {
final int minAbs = TimePoint.ofStartOfDay(minDay).getAbsoluteDayNum();
return point.getAbsoluteDayNum() - minAbs + 1;
}
}
public static GanttTaskTableColumn of(String what) {
try {
return valueOf(what.toUpperCase());
} catch (Exception e) {
return null;
}
}
public static UBrexPart getUbrex() {
final Collection<UBrexPart> definition = new ArrayList<>();
for (GanttTaskTableColumn column : GanttTaskTableColumn.values()) {
definition.add(new UBrexLeaf(column.toString()));
definition.add(new UBrexLeaf(column.toString().toLowerCase()));
}
return new UBrexOr(definition);
}
}