View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.tobago.context;
21  
22  import org.apache.myfaces.tobago.internal.util.ArrayUtils;
23  import org.apache.myfaces.tobago.internal.util.StringUtils;
24  import org.slf4j.Logger;
25  import org.slf4j.LoggerFactory;
26  
27  import java.io.Serializable;
28  import java.lang.invoke.MethodHandles;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.Collections;
32  import java.util.Iterator;
33  import java.util.List;
34  import java.util.Objects;
35  
36  /**
37   * <p>
38   * A markup signs a component to be rendered different from the normal.
39   * E. g. <code>markup="emphasized"</code> might be rendered bold
40   * or a <code>markup="deleted"</code> might be rendered with line-through style.
41   * The concrete rendering depends from the theme.
42   * </p>
43   * <p>
44   * The markup can also hold more than one value, e. g. <code>markup="emphasized, deleted"</code>.
45   * </p>
46   * <p>
47   * The value of the markup is unmodifiable.
48   * </p>
49   * <p>
50   * A markup must be registered for a component, before it can be used.
51   * </p>
52   * <p>
53   * A markup should only contain ASCII characters and digits.
54   * </p>
55   * <p>
56   * In JSPs the class {@link org.apache.myfaces.tobago.context.MarkupEditor} will convert the string literals.
57   * </p>
58   */
59  public final class Markup implements Serializable, Iterable<String> {
60  
61    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
62  
63    public static final Markup NULL = new Markup((String) null);
64  
65    public static final Markup ASCENDING = valueOf("ascending");
66    public static final Markup BADGE = valueOf("badge");
67    public static final Markup BIG = valueOf("big");
68    public static final Markup BOLD = valueOf("bold");
69    public static final Markup BORDERED = valueOf("bordered");
70    public static final Markup BOTTOM = valueOf("bottom");
71    public static final Markup CENTER = valueOf("center");
72    public static final Markup CLICKABLE = valueOf("clickable");
73    public static final Markup DANGER = valueOf("danger");
74    public static final Markup DARK = valueOf("dark");
75    public static final Markup DEFAULT = valueOf("default");
76    public static final Markup DELETED = valueOf("deleted");
77    public static final Markup DESCENDING = valueOf("descending");
78    public static final Markup DISABLED = valueOf("disabled");
79    public static final Markup ERROR = valueOf("error");
80    public static final Markup EVEN = valueOf("even");
81    public static final Markup EXPANDED = valueOf("expanded");
82    public static final Markup EXTRA_LARGE = valueOf("extraLarge");
83    public static final Markup FILLER = valueOf("filler");
84    public static final Markup FATAL = valueOf("fatal");
85    public static final Markup FOLDER = valueOf("folder");
86    public static final Markup HOVER = valueOf("hover");
87    public static final Markup INFO = valueOf("info");
88    public static final Markup INLINE = valueOf("inline");
89    public static final Markup ITALIC = valueOf("italic");
90    public static final Markup JUSTIFY = valueOf("justify");
91    public static final Markup LARGE = valueOf("large");
92    public static final Markup LEFT = valueOf("left");
93    public static final Markup LIGHT = valueOf("light");
94    public static final Markup MARKED = valueOf("marked");
95    public static final Markup MEDIUM = valueOf("medium");
96    public static final Markup MIDDLE = valueOf("middle");
97    public static final Markup MODAL = valueOf("modal");
98    public static final Markup NONE = valueOf("none");
99    public static final Markup NUMBER = valueOf("number");
100   public static final Markup ODD = valueOf("odd");
101   public static final Markup OUTLINE_DANGER = valueOf("outlineDanger");
102   public static final Markup OUTLINE_DARK = valueOf("outlineDark");
103   public static final Markup OUTLINE_INFO = valueOf("outlineInfo");
104   public static final Markup OUTLINE_LIGHT = valueOf("outlineLight");
105   public static final Markup OUTLINE_PRIMARY = valueOf("outlinePrimary");
106   public static final Markup OUTLINE_SECONDARY = valueOf("outlineSecondary");
107   public static final Markup OUTLINE_SUCCESS = valueOf("outlineSuccess");
108   public static final Markup OUTLINE_WARNING = valueOf("outlineWarning");
109   public static final Markup PILL = valueOf("pill");
110   public static final Markup PORTLET = valueOf("portlet");
111   public static final Markup PRIMARY = valueOf("primary");
112   public static final Markup READONLY = valueOf("readonly");
113   public static final Markup REQUIRED = valueOf("required");
114   public static final Markup RESIZABLE = valueOf("resizable");
115   public static final Markup RIGHT = valueOf("right");
116   public static final Markup SECONDARY = valueOf("secondary");
117   public static final Markup SECONDS = valueOf("seconds");
118   public static final Markup SELECTED = valueOf("selected");
119   public static final Markup SMALL = valueOf("small");
120   public static final Markup SORTABLE = valueOf("sortable");
121   public static final Markup SPREAD = valueOf("spread");
122   public static final Markup STRIPED = valueOf("striped");
123   public static final Markup STRONG = valueOf("strong");
124   public static final Markup SUCCESS = valueOf("success");
125   public static final Markup THIN = valueOf("thin");
126   public static final Markup TOGGLER_LEFT = valueOf("togglerLeft");
127   public static final Markup TOP = valueOf("top");
128   public static final Markup VERTICALLY = valueOf("vertically");
129   public static final Markup WARN = valueOf("warn");
130   public static final Markup WARNING = valueOf("warning");
131 
132   public static final String STRING_ASCENDING = "ascending";
133   public static final String STRING_BADGE = "badge";
134   public static final String STRING_BIG = "big";
135   public static final String STRING_BOLD = "bold";
136   public static final String STRING_BORDERED = "bordered";
137   public static final String STRING_BOTTOM = "bottom";
138   public static final String STRING_CENTER = "center";
139   public static final String STRING_CLICKABLE = "clickable";
140   public static final String STRING_DANGER = "danger";
141   public static final String STRING_DARK = "dark";
142   public static final String STRING_DEFAULT = "default";
143   public static final String STRING_DELETED = "deleted";
144   public static final String STRING_DESCENDING = "descending";
145   public static final String STRING_DISABLED = "disabled";
146   public static final String STRING_ERROR = "error";
147   public static final String STRING_EVEN = "even";
148   public static final String STRING_EXPANDED = "expanded";
149   public static final String STRING_EXTRA_LARGE = "extraLarge";
150   public static final String STRING_FILLER = "filler";
151   public static final String STRING_FATAL = "fatal";
152   public static final String STRING_FOLDER = "folder";
153   public static final String STRING_HOVER = "hover";
154   public static final String STRING_INFO = "info";
155   public static final String STRING_INLINE = "inline";
156   public static final String STRING_ITALIC = "italic";
157   public static final String STRING_JUSTIFY = "justify";
158   public static final String STRING_LARGE = "large";
159   public static final String STRING_LEFT = "left";
160   public static final String STRING_LIGHT = "light";
161   public static final String STRING_MARKED = "marked";
162   public static final String STRING_MEDIUM = "medium";
163   public static final String STRING_MIDDLE = "middle";
164   public static final String STRING_MODAL = "modal";
165   public static final String STRING_NONE = "none";
166   public static final String STRING_NUMBER = "number";
167   public static final String STRING_ODD = "odd";
168   public static final String STRING_OUTLINE_DANGER = "outlineDanger";
169   public static final String STRING_OUTLINE_DARK = "outlineDark";
170   public static final String STRING_OUTLINE_INFO = "outlineInfo";
171   public static final String STRING_OUTLINE_LIGHT = "outlineLight";
172   public static final String STRING_OUTLINE_PRIMARY = "outlinePrimary";
173   public static final String STRING_OUTLINE_SECONDARY = "outlineSecondary";
174   public static final String STRING_OUTLINE_SUCCESS = "outlineSuccess";
175   public static final String STRING_OUTLINE_WARNING = "outlineWarning";
176   public static final String STRING_PILL = "pill";
177   public static final String STRING_PORTLET = "portlet";
178   public static final String STRING_PRIMARY = "primary";
179   public static final String STRING_READONLY = "readonly";
180   public static final String STRING_REQUIRED = "required";
181   public static final String STRING_RESIZABLE = "resizable";
182   public static final String STRING_RIGHT = "right";
183   public static final String STRING_SECONDARY = "secondary";
184   public static final String STRING_SECONDS = "seconds";
185   public static final String STRING_SELECTED = "selected";
186   public static final String STRING_SMALL = "small";
187   public static final String STRING_SORTABLE = "sortable";
188   public static final String STRING_SPREAD = "spread";
189   public static final String STRING_STRIPED = "striped";
190   public static final String STRING_STRONG = "strong";
191   public static final String STRING_SUCCESS = "success";
192   public static final String STRING_THIN = "thin";
193   public static final String STRING_TOGGLER_LEFT = "togglerLeft";
194   public static final String STRING_TOP = "top";
195   public static final String STRING_VERTICALLY = "vertically";
196   public static final String STRING_WARN = "warn";
197   public static final String STRING_WARNING = "warning";
198 
199   /* Just one of "values" and "value" must be null */
200 
201   private final String[] values;
202   private final String value;
203 
204   private Markup(final String[] values) {
205     this.values = values != null ? filterSpecialChars(values) : null;
206     this.value = null;
207   }
208 
209   private Markup(final String value) {
210     this.values = null;
211     this.value = value != null ? filterSpecialChars(value) : null;
212   }
213 
214   private String[] filterSpecialChars(final String[] strings) {
215     for (int i = 0; i < strings.length; i++) {
216       strings[i] = filterSpecialChars(strings[i]);
217     }
218     return strings;
219   }
220 
221   private String filterSpecialChars(String string) {
222     StringBuilder stringBuilder = new StringBuilder(string.length());
223     boolean forbiddenCharFound = false;
224     for (int i = 0; i < string.length(); i++) {
225       final char c = string.charAt(i);
226       if (('0' <= c && c <= '9') || ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z')) {
227         stringBuilder.append(c);
228       } else {
229         forbiddenCharFound = true;
230       }
231     }
232     if (forbiddenCharFound) {
233       final String newString = stringBuilder.toString();
234       LOG.warn("Only numeric and alphabetic characters are allowed for markups: '{}' converted to '{}'.", string,
235           newString);
236       return newString;
237     } else {
238       return string;
239     }
240   }
241 
242   public static Markup valueOf(final String[] values) {
243     if (values == null || values.length == 0) {
244       return null;
245     } else if (values.length == 1) {
246       return valueOf(values[0]);
247     } else {
248       final String[] clonedValues = values.clone();
249       for (int i = 0; i < clonedValues.length; i++) {
250         clonedValues[i] = clonedValues[i].trim();
251       }
252       return new Markup(clonedValues);
253     }
254   }
255 
256   public static Markup valueOf(final String value) {
257     if (StringUtils.isEmpty(value)) {
258       return null;
259     }
260     if (value.contains(" ") || value.contains(",")) {
261       final String[] strings = StringUtils.split(value, ", \t\n");
262       return new Markup(strings);
263     } else {
264       return new Markup(value.trim());
265     }
266   }
267 
268   public static Markup valueOf(final Object value) {
269     if (value == null) {
270       return null;
271     }
272     if (value instanceof Markup) {
273       return (Markup) value;
274     }
275     if (value instanceof String) {
276       return valueOf((String) value);
277     }
278     if (value instanceof String[]) {
279       return valueOf((String[]) value);
280     }
281     if (value instanceof Iterable) {
282       final List<String> list = new ArrayList<>();
283       for (final Object object : (Iterable) value) {
284         list.add(object.toString());
285       }
286       return valueOf(list.toArray(new String[0]));
287     }
288     return valueOf(value.toString());
289   }
290 
291   @Override
292   public boolean equals(final Object o) {
293     if (this == o) {
294       return true;
295     }
296     if (o == null || getClass() != o.getClass()) {
297       return false;
298     }
299 
300     final Markup markup = (Markup) o;
301 
302     if (!Objects.equals(value, markup.value)) {
303       return false;
304     }
305     return Arrays.equals(values, markup.values);
306   }
307 
308   @Override
309   public int hashCode() {
310     int result = values != null ? Arrays.hashCode(values) : 0;
311     result = 31 * result + (value != null ? value.hashCode() : 0);
312     return result;
313   }
314 
315   @Override
316   public Iterator<String> iterator() {
317     if (value != null) {
318       return Collections.singleton(value).iterator();
319     }
320     if (values != null) {
321       return Arrays.asList(values).iterator();
322     }
323     return Collections.emptyIterator();
324   }
325 
326   /**
327    * Adds one markup to an other.
328    * Attention: The markup itself is not modified, you need to use the result of this operation.
329    */
330   public Markup add(final Markup markup) {
331     if (markup == null) {
332       return this;
333     }
334     if (markup == NULL) {
335       return this;
336     }
337     if (markup.value != null) {
338       return add(markup.value);
339     } else {
340       // this part is not optimized, but it will be used rarely, in the moment...
341       Markup result = this;
342       if (markup.values != null) {
343         for (final String summand : markup.values) {
344           final Markup combined = result.add(summand);
345           if (combined != null) {
346             result = combined;
347           }
348         }
349       }
350       return result;
351     }
352   }
353 
354   private Markup add(final String summand) {
355     if (summand == null) {
356       return this;
357     }
358     if (values == null) {
359       if (value == null) {
360         return valueOf(summand);
361       } else {
362         if (summand.equals(value)) {
363           return this;
364         } else {
365           return valueOf(new String[]{value, summand});
366         }
367       }
368     } else {
369       if (ArrayUtils.contains(values, summand)) {
370         return this;
371       } else {
372         final String[] strings = new String[values.length + 1];
373         System.arraycopy(values, 0, strings, 0, values.length);
374         strings[values.length] = summand;
375         return valueOf(strings);
376       }
377     }
378   }
379 
380   public Markup remove(final Markup markup) {
381     if (markup.value != null) {
382       return remove(markup.value);
383     } else {
384       // this part is not optimized, but it will be used rarely, in the moment...
385       Markup result = this;
386       for (final String summand : markup.values) {
387         final Markup removed = result.remove(summand);
388         if (removed == null) {
389           result = NULL;
390         } else {
391           result = removed;
392         }
393       }
394       return result;
395     }
396   }
397 
398   private Markup remove(final String summand) {
399     if (summand == null) {
400       return this;
401     }
402     if (values == null) {
403       if (value == null) {
404         return this;
405       } else {
406         if (summand.equals(value)) {
407           return NULL;
408         } else {
409           return this;
410         }
411       }
412     } else {
413       if (ArrayUtils.contains(values, summand)) {
414         final String[] strings = new String[values.length - 1];
415         int found = 0;
416         for (int i = 0; i < strings.length; i++) {
417           if (summand.equals(values[i])) {
418             found++;
419           }
420           strings[i] = values[i + found];
421         }
422         return valueOf(strings);
423       } else {
424         return this;
425       }
426     }
427   }
428 
429   public boolean contains(final String markup) {
430     if (markup == null) {
431       return true;
432     }
433     if (this == NULL) {
434       return this == Markup.valueOf(markup);
435     }
436     if (value != null) {
437       return value.equals(markup);
438     }
439     for (final String v : values) {
440       if (v.equals(markup)) {
441         return true;
442       }
443     }
444     return false;
445   }
446 
447   public boolean contains(final Markup markup) {
448     if (markup == null || markup == NULL) {
449       return true;
450     }
451     if (this == NULL) {
452       return this == markup;
453     }
454     if (markup.value != null) {
455       if (value != null) {
456         return value.equals(markup.value);
457       } else {
458         for (final String v : values) {
459           if (v.equals(markup.value)) {
460             return true;
461           }
462         }
463         return false;
464       }
465     } else {
466       if (value != null) {
467         return false;
468       } else {
469         for (final String markupString : markup.values) {
470           if (!contains(markupString)) {
471             return false;
472           }
473         }
474         return true;
475       }
476     }
477   }
478 
479   /**
480    * Check if there is no value set inside this markup.
481    */
482   public boolean isEmpty() {
483     return !(value != null || values != null && values.length != 0);
484   }
485 
486   @Override
487   public String toString() {
488     if (value != null) {
489       return value;
490     }
491     if (values == null) {
492       return "null";
493     }
494     return Arrays.toString(values);
495   }
496 
497 }