001/* 002 * SPDX-License-Identifier: Apache-2.0 003 * 004 * Copyright 2025-2026 The Enola <https://enola.dev> Authors 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package dev.enola.tool.todo; 019 020import com.fasterxml.jackson.annotation.JsonIgnore; 021import com.google.auto.value.AutoBuilder; 022import com.google.common.collect.ImmutableList; 023import com.google.common.collect.ImmutableMap; 024 025import dev.enola.data.id.UUID_IRI; 026 027import org.jspecify.annotations.Nullable; 028 029import java.net.URI; 030import java.time.Instant; 031import java.util.List; 032import java.util.Map; 033import java.util.Optional; 034 035public record ToDo( 036 // TODO implements Identifiable<URI> 037 URI id, 038 String title, 039 Optional<String> description, 040 ImmutableList<String> tags, 041 ImmutableMap<String, String> attributes, 042 Optional<Instant> created, 043 Optional<Instant> completed, 044 Optional<Byte> priority, 045 046 /** Assignee URI; e.g. <code>mailto:user@example.org</code>. */ 047 Optional<URI> assignee, 048 049 /** Parent ToDo ID; e.g. a "Project". */ 050 // TODO Add other more flexible relationships, e.g. "depends on", "related to" etc. 051 // TODO public Set<URI> children = new HashSet<>(); 052 Optional<URI> parent 053 054 // TODO Discussion, like in a bug tracker, with comments by users etc. 055 056 // TODO public Set<URI> attachments = new HashSet<>(); 057 ) { 058 059 public ToDo { 060 if (id == null) throw new IllegalArgumentException("id is required"); 061 if (title == null) throw new IllegalArgumentException("title is required"); 062 if (description == null) description = Optional.empty(); 063 if (tags == null) tags = ImmutableList.of(); 064 if (attributes == null) attributes = ImmutableMap.of(); 065 if (created == null) created = Optional.empty(); 066 if (completed == null) completed = Optional.empty(); 067 if (priority == null) priority = Optional.empty(); 068 if (assignee == null) assignee = Optional.empty(); 069 if (parent == null) parent = Optional.empty(); 070 } 071 072 @JsonIgnore 073 public boolean isCompleted() { 074 return completed.isPresent(); 075 } 076 077 public static Builder builder() { 078 return new AutoBuilder_ToDo_Builder(); 079 } 080 081 public Builder toBuilder() { 082 return builder().from(this); 083 } 084 085 @AutoBuilder(callMethod = "create") 086 public abstract static class Builder { 087 public abstract Builder id(URI id); 088 089 public abstract Builder title(String title); 090 091 public abstract Builder description(String description); 092 093 public abstract Builder tags(List<String> tags); 094 095 public abstract Builder attributes(Map<String, String> attributes); 096 097 public abstract Builder created(Instant created); 098 099 public abstract Builder completed(Instant completed); 100 101 public abstract Builder priority(Byte priority); 102 103 public abstract Builder assignee(URI assignee); 104 105 public abstract Builder parent(URI parent); 106 107 public Builder from(ToDo todo) { 108 id(todo.id()); 109 title(todo.title()); 110 todo.description().ifPresent(this::description); 111 tags(todo.tags()); 112 attributes(todo.attributes()); 113 todo.created().ifPresent(this::created); 114 todo.completed().ifPresent(this::completed); 115 todo.priority().ifPresent(this::priority); 116 todo.assignee().ifPresent(this::assignee); 117 todo.parent().ifPresent(this::parent); 118 return this; 119 } 120 121 public abstract ToDo build(); 122 } 123 124 static ToDo create( 125 @Nullable URI id, 126 String title, 127 @Nullable Optional<String> description, 128 @Nullable List<String> tags, 129 @Nullable Map<String, String> attributes, 130 @Nullable Optional<Instant> created, 131 @Nullable Optional<Instant> completed, 132 @Nullable Optional<Byte> priority, 133 @Nullable Optional<URI> assignee, 134 @Nullable Optional<URI> parent) { 135 136 // TODO Generate a Multihash from content... 137 if (id == null) id = new UUID_IRI().toURI(); 138 139 if (created == null || created.isEmpty()) { 140 created = Optional.of(Instant.now()); 141 } 142 143 return new ToDo( 144 id, 145 title, 146 description == null ? Optional.empty() : description, 147 tags == null ? ImmutableList.of() : ImmutableList.copyOf(tags), 148 attributes == null ? ImmutableMap.of() : ImmutableMap.copyOf(attributes), 149 created, 150 completed == null ? Optional.empty() : completed, 151 priority == null ? Optional.empty() : priority, 152 assignee == null ? Optional.empty() : assignee, 153 parent == null ? Optional.empty() : parent); 154 } 155}