1515import java .util .Locale ;
1616import java .util .HashSet ;
1717import java .util .Map ;
18+ import java .util .Objects ;
1819import java .util .Set ;
1920
2021/**
4950public class AlertsExtension implements Parser .ParserExtension , HtmlRenderer .HtmlRendererExtension ,
5051 MarkdownRenderer .MarkdownRendererExtension {
5152
52- static final Set <String > STANDARD_TYPES = Set .of ("NOTE" , "TIP" , "IMPORTANT" , "WARNING" , "CAUTION" );
53+ /**
54+ * The standard GitHub Flavored Markdown (GFM) types that the extension
55+ * enables by default. These can be overwritten with {@link Builder#setAllowedTypes(Map)}.
56+ */
57+ public static final Map <String , String > STANDARD_TYPES = Map .ofEntries (
58+ Map .entry ("NOTE" , "Note" ),
59+ Map .entry ("TIP" , "Tip" ),
60+ Map .entry ("IMPORTANT" , "Important" ),
61+ Map .entry ("WARNING" , "Warning" ),
62+ Map .entry ("CAUTION" , "Caution" )
63+ );
5364
54- private final Map <String , String > customTypes ;
65+ /**
66+ * A map of alert marker ({@code [!TYPE]}) to the default title for that marker.
67+ */
68+ private final Map <String , String > allowedTypes ;
5569 private final boolean customTitlesAllowed ;
5670 private final boolean nestedAlertsAllowed ;
5771
5872 private AlertsExtension (Builder builder ) {
59- this .customTypes = new HashMap <>(builder .customTypes );
73+ this .allowedTypes = new HashMap <>(builder .allowedTypes );
6074 this .customTitlesAllowed = builder .customTitlesAllowed ;
6175 this .nestedAlertsAllowed = builder .nestedAlertsAllowed ;
6276 }
@@ -71,15 +85,14 @@ public static Builder builder() {
7185
7286 @ Override
7387 public void extend (Parser .Builder parserBuilder ) {
74- var allowedTypes = new HashSet <>(STANDARD_TYPES );
75- allowedTypes .addAll (customTypes .keySet ());
88+ var allowedTypesSet = new HashSet <>(allowedTypes .keySet ());
7689 parserBuilder .customBlockParserFactory (
77- new AlertBlockParser .Factory (allowedTypes , customTitlesAllowed , nestedAlertsAllowed ));
90+ new AlertBlockParser .Factory (allowedTypesSet , customTitlesAllowed , nestedAlertsAllowed ));
7891 }
7992
8093 @ Override
8194 public void extend (HtmlRenderer .Builder rendererBuilder ) {
82- rendererBuilder .nodeRendererFactory (context -> new AlertHtmlNodeRenderer (context , customTypes ));
95+ rendererBuilder .nodeRendererFactory (context -> new AlertHtmlNodeRenderer (context , allowedTypes ));
8396 }
8497
8598 @ Override
@@ -101,37 +114,57 @@ public Set<Character> getSpecialCharacters() {
101114 * Builder for configuring the alerts extension.
102115 */
103116 public static class Builder {
104- private final Map <String , String > customTypes = new HashMap <>();
117+ private Map <String , String > allowedTypes = new HashMap <>(STANDARD_TYPES );
105118 private boolean customTitlesAllowed = false ;
106119 private boolean nestedAlertsAllowed = false ;
107120
108121 /**
109- * Adds a custom alert type with a display title.
122+ * Sets which alert types will be recognized and parsed into {@link Alert} blocks,
123+ * completely overwriting any previous configuration.
110124 * <p>
111- * This can also be used to override the display title of standard GFM types
125+ * By default, {@link AlertsExtension#STANDARD_TYPES} are used.
126+ *
127+ * @param allowedTypes A map of alert type to the default title for that type.
128+ * Must not be null/empty or contain any null/empty keys or
129+ * values. Additionally, all alert types must be uppercase.
130+ * @return {@code this}
131+ * @see Builder#addCustomType(String, String)
132+ */
133+ public Builder setAllowedTypes (Map <String , String > allowedTypes ) {
134+ Objects .requireNonNull (allowedTypes , "allowedTypes must not be null" );
135+ if (allowedTypes .isEmpty ()) {
136+ throw new IllegalArgumentException ("allowedTypes must not be empty" );
137+ }
138+
139+ for (Map .Entry <String , String > entry : allowedTypes .entrySet ()) {
140+ validateTypeAndTitle (entry .getKey (), entry .getValue ());
141+ }
142+
143+ this .allowedTypes = new HashMap <>(allowedTypes );
144+ return this ;
145+ }
146+
147+ /**
148+ * Adds a custom alert type with a default title.
149+ * <p>
150+ * This can also be used to override the default title of standard GFM types
112151 * (e.g., for localization).
113152 *
114153 * @param type the alert type (must be uppercase)
115- * @param title the display title for this alert type
154+ * @param title the default title for this alert type
116155 * @return {@code this}
156+ * @see Builder#setAllowedTypes(Map)
117157 */
118158 public Builder addCustomType (String type , String title ) {
119- if (type == null || type .isEmpty ()) {
120- throw new IllegalArgumentException ("Type must not be null or empty" );
121- }
122- if (title == null || title .isEmpty ()) {
123- throw new IllegalArgumentException ("Title must not be null or empty" );
124- }
125- if (!type .equals (type .toUpperCase (Locale .ROOT ))) {
126- throw new IllegalArgumentException ("Type must be uppercase: " + type );
127- }
128- customTypes .put (type , title );
159+ validateTypeAndTitle (type , title );
160+ allowedTypes .put (type , title );
129161 return this ;
130162 }
131163
132164 /**
133165 * Allows or disallows custom titles on alerts. Inline formatting is supported
134166 * within these titles.
167+ *
135168 * @param allow Whether to allow or disallow custom titles on alerts.
136169 * @return {@code this}
137170 * @see AlertTitle
@@ -149,6 +182,7 @@ public Builder allowCustomTitles(boolean allow) {
149182 * <p>
150183 * Note that even when this is allowed, {@link Parser.Builder#maxOpenBlockParsers(int)}
151184 * will be respected.
185+ *
152186 * @param allow Whether to allow or disallow parsing alerts within non-root blocks.
153187 * @return {@code this}
154188 */
@@ -163,5 +197,29 @@ public Builder allowNestedAlerts(boolean allow) {
163197 public Extension build () {
164198 return new AlertsExtension (this );
165199 }
200+
201+ /**
202+ * Checks whether an alert type and default title are valid.
203+ *
204+ * @param type The type to validate:
205+ * <p>
206+ * - Must not be null or empty
207+ * - Must be uppercase
208+ * @param title The default title to validate. Must not be null or empty.
209+ */
210+ private void validateTypeAndTitle (String type , String title ) {
211+ Objects .requireNonNull (type , "Type must not be null" );
212+ if (type .isEmpty ()) {
213+ throw new IllegalArgumentException ("Type must not be empty" );
214+ }
215+ if (!type .equals (type .toUpperCase (Locale .ROOT ))) {
216+ throw new IllegalArgumentException ("Type must be uppercase: " + type );
217+ }
218+
219+ Objects .requireNonNull (title , "Default title must not be null: " + type );
220+ if (title .isEmpty ()) {
221+ throw new IllegalArgumentException ("Default title must not be empty: " + type );
222+ }
223+ }
166224 }
167225}
0 commit comments