From 3cb6805185c876194d00e607ec530eb74d3dd30f Mon Sep 17 00:00:00 2001 From: Zihlu Wang Date: Sun, 6 Aug 2023 18:32:16 +0800 Subject: [PATCH] feat(webcal): Finished main features of webcal module --- pom.xml | 1 + webcal/pom.xml | 37 +++ .../org/codecrafters/webcal/WebCalendar.java | 166 +++++++++++ .../codecrafters/webcal/WebCalendarEvent.java | 265 ++++++++++++++++ .../codecrafters/webcal/WebCalendarNode.java | 282 ++++++++++++++++++ .../webcal/config/Classification.java | 43 +++ .../webcal/test/TestWebCalendar.java | 56 ++++ 7 files changed, 850 insertions(+) create mode 100644 webcal/pom.xml create mode 100644 webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendar.java create mode 100644 webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendarEvent.java create mode 100644 webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendarNode.java create mode 100644 webcal/src/main/java/cn/org/codecrafters/webcal/config/Classification.java create mode 100644 webcal/src/test/java/cn/org/codecrafters/webcal/test/TestWebCalendar.java diff --git a/pom.xml b/pom.xml index 9c1141b..98e423b 100644 --- a/pom.xml +++ b/pom.xml @@ -20,6 +20,7 @@ devkit-core devkit-utils guid + webcal simple-jwt-facade simple-jwt-authzero simple-jwt-jjwt diff --git a/webcal/pom.xml b/webcal/pom.xml new file mode 100644 index 0000000..a11b1ff --- /dev/null +++ b/webcal/pom.xml @@ -0,0 +1,37 @@ + + + + + 4.0.0 + + cn.org.codecrafters + jdevkit + 1.0.0 + + + webcal + + + 17 + 17 + UTF-8 + + + \ No newline at end of file diff --git a/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendar.java b/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendar.java new file mode 100644 index 0000000..c96e64a --- /dev/null +++ b/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendar.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2023 CodeCraftersCN. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.org.codecrafters.webcal; + +import java.util.ArrayList; +import java.util.List; + +/** + * WebCalendar + * + * @author Zihlu Wang + */ +public final class WebCalendar { + + // + // Constants + // + + // Tag + private final static String TAG = "VCALENDAR"; + + // + // Fields + // + + private String name; + + /** + * Company name. This value is to specify the {@code productIdentifier} + * property. + */ + private String companyName; + + /** + * Product name. This value is to specify the {@code productIdentifier} + * property. + */ + private String productName; + + private String domainName; + + /** + * Calendar scale, referenced from RFC 5545 - 3.7.1. Calendar Scale. + */ + private final String scale = "GREGORIAN"; + + /** + * Method, referenced from RFC 5545 - 3.7.2. Method. + */ + private String method; + + private final String version = "2.0"; + + private List nodes; + + // + // Constructors + // + public WebCalendar() { + this.nodes = new ArrayList<>(); + } + + // + // Methods + // + + /** + * Set the name for this calendar. + * + * @param name The name for the calendar. + * @return The calendar instance. + */ + public WebCalendar setName(String name) { + this.name = name; + return this; + } + + /** + * Set the company name for this calendar. + * + * @param companyName The company name for the calendar. + * @return The calendar instance. + */ + public WebCalendar setCompanyName(String companyName) { + this.companyName = companyName; + return this; + } + + public WebCalendar setDomainName(String domainName) { + this.domainName = domainName; + return this; + } + + /** + * Set the product name for this calendar. + * + * @param productName The product name for the calendar. + * @return The calendar instance. + */ + public WebCalendar setProductName(String productName) { + this.productName = productName; + return this; + } + + /** + * Set the method for this calendar. + * + * @param method The product name for the calendar. + * @return The calendar instance. + */ + public WebCalendar setMethod(String method) { + this.method = method; + return this; + } + + /** + * Add a calendar node to this calendar. + * + * @param node Any calendar node. + * @return The calendar instance. + */ + public WebCalendar addEvent(WebCalendarNode node) { + this.nodes.add(node); + return this; + } + + /** + * Resolve the calendar instance to a text that implements RFC-5545. + * + * @return A string includes all events in this calendar. + */ + public String resolve() { + var events = new StringBuilder(); + if (nodes != null && !nodes.isEmpty()) { + nodes.forEach(item -> + events.append(item.setDomainName(domainName) + .resolve())); + } + + return "BEGIN:" + TAG + "\n" + + "PRODID:-//" + companyName + "//" + productName + "//EN\n" + + "VERSION:" + version + "\n" + + "X-WR-CALNAME:" + name + "\n" + + events + "\n" + + "END:" + TAG; + } + +} + diff --git a/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendarEvent.java b/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendarEvent.java new file mode 100644 index 0000000..0c94f0a --- /dev/null +++ b/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendarEvent.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2023 CodeCraftersCN. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.org.codecrafters.webcal; + +import cn.org.codecrafters.webcal.config.Classification; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Arrays; +import java.util.Collection; +import java.util.Optional; +import java.util.UUID; + +/** + * WebCalendarEvent + * + * @author Zihlu Wang + */ +public final class WebCalendarEvent extends WebCalendarNode { + + private final static String TAG = "VEVENT"; + + /** + * Add a batch of categories. + * + * @param categories A batch of categories. + * @return The Event instance. + */ + public WebCalendarEvent addCategories(String... categories) { + this.categories.addAll(Arrays.asList(categories)); + return this; + } + + /** + * Add a batch of categories. + * + * @param categories A batch of categories. + * @return The Event instance. + */ + public WebCalendarEvent addCategories(Collection categories) { + this.categories.addAll(categories); + return this; + } + + /** + * Add a category. + * + * @param category A category. + * @return The Event instance. + */ + public WebCalendarEvent addCategory(String category) { + this.categories.add(category); + return this; + } + + /** + * Set the classification. + * + * @param classification The specified classification value. + * @return The Event instance. + */ + public WebCalendarEvent setClassification(Classification classification) { + this.classification = classification; + return this; + } + + /** + * Set the comment. + * + * @param comment The comment. + * @return The Event instance. + */ + public WebCalendarEvent setComment(String comment) { + this.comment = comment; + return this; + } + + /** + * Set the description. + * + * @param description The description. + * @return The Event instance. + */ + public WebCalendarEvent setDescription(String description) { + this.description = description; + return this; + } + + /** + * Set the location. + * + * @param location The location. + * @return The Event instance. + */ + public WebCalendarEvent setLocation(String location) { + this.location = location; + return this; + } + + /** + * Set the percent complete value. + * + * @param percentComplete The percent complete value. + * @return The Event instance. + */ + public WebCalendarEvent setPercentComplete(Integer percentComplete) { + if (percentComplete < 0 || percentComplete > 100) { + throw new IllegalArgumentException("Percent out of range (0 ~ 100)"); + } + this.percentComplete = percentComplete; + return this; + } + + /** + * Set a priority. + * + * @param priority The priority to be set. + * @return The Event instance. + */ + public WebCalendarEvent setPriority(Integer priority) { + if (priority < 0 || priority > 9) { + throw new IllegalArgumentException("The priority you provide is out of range (0 ~ 9)."); + } + this.priority = priority; + return this; + } + + /** + * Set the summary. + * + * @param summary The summary (you can also call it as a title). + * @return The Event instance. + */ + public WebCalendarEvent setSummary(String summary) { + this.summary = summary; + return this; + } + + /** + * Set the end of this node. + * + * @param end The end time of this event. + * @return The Event instance. + */ + public WebCalendarEvent setEnd(LocalDateTime end) { + if (this.duration != null) { + throw new IllegalStateException("You have set the field DURATION before, please remove it or remove setEnd."); + } + this.end = end; + return this; + } + + /** + * Set the start of this event. + * + * @param start The date time specify the start time of this event. + * @return The Event instance. + */ + public WebCalendarEvent setStart(LocalDateTime start) { + this.start = start; + return this; + } + + /** + * Set the duration of this event. + * + * @param duration The duration of this event. + * @return The Event instance. + */ + public WebCalendarEvent setDuration(Duration duration) { + if (this.end != null) { + throw new IllegalStateException("You have set the field END before, please remove it or remove setDuration."); + } + this.duration = duration; + return this; + } + + /** + * Set the URL. + * + * @param url The URL. + * @return The Event instance. + */ + public WebCalendarEvent setUrl(String url) { + this.url = url; + return this; + } + + /** + * Set the uid of this event. + * + * @param uid The uid. + * @return The Event instance. + */ + public WebCalendarEvent setUid(String uid) { + this.uid = uid; + return this; + } + + /** + * Set the domain name of this event. + * + * @param domainName The domain name. + * @return The Event instance. + */ + public WebCalendarEvent setDomainName(String domainName) { + this.domainName = domainName; + return this; + } + + /** + * Set the timezone of this event. + * + * @param timezone The time zone to set. + * @return The Event instance. + */ + public WebCalendarEvent setTimezone(String timezone) { + this.timezone = timezone; + return this; + } + + @Override + public String resolve() { + return "\nBEGIN:" + TAG + "\n" + + "UID:" + Optional.ofNullable(uid).orElse(UUID.randomUUID().toString()) + "@" + domainName + "\n" + + Optional.ofNullable(summary).map((item) -> "SUMMARY:" + item + "\n").orElse("") + + "DTSTART" + Optional.ofNullable(timezone).map(item -> ";TZID=" + item).orElse("") + ":" + start.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + "\n" + + Optional.ofNullable(categories) + .map((item) -> { + if (!item.isEmpty()) { + return "CATEGORIES:" + resolveCategories() + "\n"; + } + return null; + }).orElse("") + + Optional.ofNullable(duration) + .map((item) -> "DURATION:PT" + item.getSeconds() + "S\n").orElse("") + + Optional.ofNullable(end) + .map((item) -> "DTEND:" + end.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + "\n").orElse("") + + Optional.ofNullable(classification) + .map((item) -> "CLASS:" + item.getClassification() + "\n").orElse("") + + Optional.ofNullable(comment).map((item) -> "COMMENT:" + item + "\n").orElse("") + + Optional.ofNullable(description).map((item) -> "DESCRIPTION:" + item + "\n").orElse("") + + Optional.ofNullable(location).map((item) -> "LOCATION:" + item + "\n").orElse("") + + Optional.ofNullable(percentComplete).map((item) -> "PERCENT-COMPLETE:" + item + "\n").orElse("") + + Optional.ofNullable(priority).map((item) -> "PRIORITY:" + item + "\n").orElse("") + + "END:" + TAG + "\n"; + } + +} diff --git a/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendarNode.java b/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendarNode.java new file mode 100644 index 0000000..a1090cf --- /dev/null +++ b/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendarNode.java @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2023 CodeCraftersCN. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.org.codecrafters.webcal; + +import cn.org.codecrafters.webcal.config.Classification; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +/** + * WebCalendarNode + * + * @author Zihlu Wang + */ +public abstract sealed class WebCalendarNode + permits WebCalendarEvent { + + /** + *

+ * Categories, is used to specify categories or subtypes of the calendar component. The categories are useful in + * searching for a calendar component of a particular type and category. + *

+ * + * Template: + *
CATEGORIES:APPOINTMENT,EDUCATION
+ *
CATEGORIES:MEETING
+ * + *

Referenced from RFC-5545 - 3.8.1.2. Categories

+ */ + protected List categories; + + /** + *

+ * Classification, An access classification is only one component of the general security system within a calendar + * application. It provides a method of capturing the scope of the access the calendar owner intends for information + * within an individual calendar entry. + *

+ * + * Template: + *
CLASS:protected
+ * + *

Referenced from RFC-5545 - 3.8.1.3. Classification

+ */ + protected Classification classification; + + /** + *

+ * Comment, is used to specify a comment to the calendar user. + *

+ * + * Template: + *
+     * COMMENT:The meeting really needs to include both ourselves
+     * and the customer. We can't hold this meeting without them.
+     * As a matter of fact\, the venue for the meeting ought to be at
+     * their site. - - John
+     * 
+ * + *

Referenced from RFC-5545 - 3.8.1.4. Comment

+ */ + protected String comment; + + /** + *

+ * Description is used in the {@link WebCalendarEvent} and to-do to capture lengthy extual descriptions associated with + * the activity. + *

+ *

+ * Description is used in the Journal calendar component to capture one or more textual journal entries. + *

+ *

+ * Description is used in the Alarm calendar component to capture the display text for a DISPLAY category of + * alarm, and to capture the body text for an EMAIL category of alarm. + *

+ * + * Template: + *
+     * DESCRIPTION:Meeting to provide technical review for "Phoenix"
+     * design.\nHappy Face Conference Room. Phoenix design team
+     * MUST attend this meeting.\nRSVP to team leader.
+     * 
+ * + *

Referenced from RFC-5545 - 3.8.1.5. Description

+ */ + protected String description; + + + + /** + *

+ * Location, Specific venues such as conference or meeting rooms may be explicitly specified using this property. + *

+ * + *

+ * Note
+ * This location has not implement the URI of the location. + *

+ * + * Template: + *
LOCATION:Conference Room - F123\, Bldg. 002
+ *
LOCATION;ALTREP="http://xyzcorp.com/conf-rooms/f123.vcf":
+     *      Conference Room - F123\, Bldg. 002
+ * + *

Referenced from RFC-5545 - 3.8.1.7. Location

+ */ + protected String location; + + + + /** + *

+ * Percent Complete, is a positive integer between 0 and 100. A value of "0" indicates the to-do has not yet been + * started. A value of "100" indicates that the to-do has been completed. Integer values in between indicate the + * percent partially complete. + *

+ * + * Template: + *
PERCENT-COMPLETE:39
+ * + *

Referenced from RFC-5545 - 3.8.1.8. Percent Complete

+ */ + protected Integer percentComplete; + + + + /** + *

+ * Priority, is specified as an integer in the range 0 to 9. A value of 0 specifies an undefined priority. A value + * of 1 is the highest priority. A value of 2 is the second-highest priority. Subsequent numbers specify a + * decreasing ordinal priority. A value of 9 is the lowest priority. + *

+ * + * Example: + *
    + *
  • + * The following is an example of a property with the highest priority: + *
    PRIORITY:1
    + *
  • + *
  • + * The following is an example of a property with a next highest priority: + *
    PRIORITY:2
    + *
  • + *
+ */ + protected Integer priority; + + + + /** + *

+ * Summary, is used to capture a short, one-line summary about the activity or journal entry. + *

+ * + * Example: + *
SUMMARY:Department Party
+ */ + protected String summary; + + + + // /** + // * Completed defines the date and time that a to-do was actually completed. + // * + // *

+ // * Example: + // *

COMPLETED:19960401T150000Z
+ // *

+ // */ + // protected DateTime completed; + // + // /** + // * Set the specific time of the completion of this to-do event. + // * + // * @param completed The complete time. + // */ + // protected void setCompleted(DateTime completed) { + // this.completed = completed; + // } + + /** + * End defines the date and time by which the event ends. + * + * Example: + *
DTEND:19960401T150000Z
+ *
DTEND;VALUE=DATE:19980704
+ */ + protected LocalDateTime end; + + + + /** + * Start defines the start date and time for the event. + * + * Example: + *
DTSTART:19980118T073000Z
+ *
DTSTART;VALUE=DATE:19980118
+ */ + protected LocalDateTime start; + + + + /** + * Duration may be used to specify a duration of the event, instead of an explicit end DATE-TIME. + * + * Example: + *
DURATION:PT100000S
+ */ + protected Duration duration; + + + + /** + * URL may be used in a calendar component to convey a location where a more dynamic rendition of the calendar + * information associated with the calendar component can be found. + * + * Example: + *
URL:http://example.com/pub/calendars/jsmith/mytime.ics
+ */ + protected String url; + + protected String uid; + + protected String domainName; + + protected String timezone; + + + + // + // Constructors + // + protected WebCalendarNode() { + this.categories = new ArrayList<>(); + } + + public WebCalendarNode setDomainName(String domainName) { + this.domainName = domainName; + return this; + } + + // + // Protected methods + // + protected String resolveCategories() { + var builder = new StringBuilder(); + if (categories != null && !categories.isEmpty()) { + categories.forEach(item -> builder.append(item).append(",")); + return builder.substring(0, builder.length() - 1); + } + return builder.toString(); + } + + // + // Abstract methods + // + public abstract String resolve(); + +} + diff --git a/webcal/src/main/java/cn/org/codecrafters/webcal/config/Classification.java b/webcal/src/main/java/cn/org/codecrafters/webcal/config/Classification.java new file mode 100644 index 0000000..94a5588 --- /dev/null +++ b/webcal/src/main/java/cn/org/codecrafters/webcal/config/Classification.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 CodeCraftersCN. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.org.codecrafters.webcal.config; + +/** + * WebCalClassification + * + * @author Zihlu Wang + */ +public enum Classification { + + PUBLIC("PUBLIC"), + + PRIVATE("PRIVATE"), + + CONFIDENTIAL("CONFIDENTIAL"), + ; + + private final String classification; + + Classification(String classification) { + this.classification = classification; + } + + public String getClassification() { + return classification; + } +} diff --git a/webcal/src/test/java/cn/org/codecrafters/webcal/test/TestWebCalendar.java b/webcal/src/test/java/cn/org/codecrafters/webcal/test/TestWebCalendar.java new file mode 100644 index 0000000..c9224b6 --- /dev/null +++ b/webcal/src/test/java/cn/org/codecrafters/webcal/test/TestWebCalendar.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2023 CodeCraftersCN. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.org.codecrafters.webcal.test; + +import cn.org.codecrafters.webcal.WebCalendar; +import cn.org.codecrafters.webcal.WebCalendarEvent; +import cn.org.codecrafters.webcal.config.Classification; +import lombok.extern.slf4j.Slf4j; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * TestWebCalendar + * + * @author Zihlu Wang + */ +@Slf4j +public class TestWebCalendar { + + @Test + void testWebCalendar() { + var calendar = new WebCalendar(); + calendar.setCompanyName("Code Crafters") + .setDomainName("codecrafters.org.cn") + .setName("Code Crafters SPECIAL EVENT") + .setProductName("Code Crafters SPECIAL EVENT"); + + calendar.addEvent(new WebCalendarEvent() + .setClassification(Classification.PUBLIC) + .setStart(LocalDateTime.of(2023, 8, 6, 0, 0, 0)) + .setEnd(LocalDateTime.of(2023, 8, 6, 8, 0, 0)) + .setLocation("湖南省长沙市天心区碧云路60号") + .setUid(UUID.randomUUID().toString()) + .setTimezone("Asia/Hong_Kong")); + + System.out.println(calendar.resolve()); + } + +}