001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.marshall.markdown;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import org.apache.juneau.commons.collections.*;
022
023/**
024 * Parses Markdown document-mode output (headings + tables) back to POJOs.
025 *
026 * <h5 class='topic'>Media types</h5>
027 * <p>
028 * Handles <c>Content-Type</c> types:  <bc>text/markdown, text/x-markdown</bc>
029 *
030 * <h5 class='topic'>Description</h5>
031 * <p>
032 * Extends {@link MarkdownParser} to parse Markdown documents produced by {@link MarkdownDocSerializer}.
033 * Heading sections are mapped back to nested bean properties based on heading text matching property names.
034 *
035 * <h5 class='section'>Example:</h5>
036 * <p class='bjava'>
037 *    <jc>// Parse a Markdown document back to a bean</jc>
038 *    String <jv>md</jv> = <js>"# Person\n\n| Property | Value |\n|---|---|\n| name | Alice |\n\n## address\n\n| Property | Value |\n|---|---|\n| city | Boston |"</js>;
039 *    Person <jv>p</jv> = MarkdownDocParser.<jsf>DEFAULT</jsf>.read(<jv>md</jv>, Person.<jk>class</jk>);
040 * </p>
041 * <p class='bjava'>
042 *    <jc>// Round-trip example</jc>
043 *    String <jv>md</jv> = MarkdownDocSerializer.<jsf>DEFAULT</jsf>.write(<jv>myBean</jv>);
044 *    MyBean <jv>parsed</jv> = MarkdownDocParser.<jsf>DEFAULT</jsf>.read(<jv>md</jv>, MyBean.<jk>class</jk>);
045 * </p>
046 *
047 * <h5 class='section'>Limitations:</h5><ul>
048 *    <li class='note'>Heading text must match bean property names exactly (case-sensitive).
049 *    <li class='note'>Heading levels must be consistent with serializer output; manually edited heading levels may not parse correctly.
050 *    <li class='note'>A heading followed by a bulleted list is treated as a {@code List&lt;String&gt;} property.
051 *    <li class='note'>A heading followed by a multi-column table is treated as a {@code List&lt;Bean&gt;} property.
052 *    <li class='note'>The top-level heading (title) is ignored; only sub-headings map to properties.
053 *    <li class='note'>This class is thread safe and reusable.
054 * </ul>
055 *
056 * <h5 class='section'>See Also:</h5><ul>
057 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/Markdown">Markdown</a>
058 * </ul>
059 */
060@SuppressWarnings({
061   "java:S110", // Inheritance depth acceptable for this class hierarchy
062   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
063   "java:S9149" // Per-format static factories intentionally shadow the parent's.
064})
065public class MarkdownDocParser extends MarkdownParser {
066
067   /**
068    * Builder class.
069    */
070   public static class Builder extends MarkdownParser.Builder<Builder> {
071
072      private static final Cache<HashKey,MarkdownDocParser> CACHE = Cache.of(HashKey.class, MarkdownDocParser.class).build();
073
074      int headingLevel;
075
076      /**
077       * Constructor, default settings.
078       */
079      protected Builder() {
080         headingLevel = 1;
081         type(MarkdownDocParser.class);
082      }
083
084      /**
085       * Copy constructor.
086       *
087       * @param copyFrom The builder to copy from.
088       *    <br>Cannot be <jk>null</jk>.
089       */
090      protected Builder(Builder copyFrom) {
091         super(reqnn("copyFrom", copyFrom));
092         headingLevel = copyFrom.headingLevel;
093      }
094
095      /**
096       * Copy constructor.
097       *
098       * @param copyFrom The bean to copy from.
099       *    <br>Cannot be <jk>null</jk>.
100       */
101      protected Builder(MarkdownDocParser copyFrom) {
102         super(reqnn("copyFrom", copyFrom));
103         headingLevel = copyFrom.headingLevel;
104      }
105
106      /**
107       * The expected starting heading level for the document.
108       *
109       * <p>
110       * Default is 1 ({@code # Heading}). Must match the level used by the serializer.
111       *
112       * @param value The heading level.
113       * @return This object.
114       */
115      public Builder headingLevel(int value) {
116         headingLevel = Math.min(6, Math.max(1, value));
117         return this;
118      }
119
120      @Override /* Overridden from Context.Builder<?> */
121      public HashKey hashKey() {
122         return HashKey.of(super.hashKey(), headingLevel);
123      }
124
125      @Override /* Overridden from Context.Builder<?> */
126      public MarkdownDocParser build() {
127         return cache(CACHE).build(MarkdownDocParser.class);
128      }
129
130      @Override /* Overridden from Context.Builder<?> */
131      public Builder copy() {
132         return new Builder(this);
133      }
134
135   }
136
137   /**
138    * Default parser, all default settings.
139    */
140   public static final MarkdownDocParser DEFAULT = new MarkdownDocParser(create());
141
142   /**
143    * Creates a new builder for this object.
144    *
145    * @return A new builder.
146    */
147   public static Builder create() {
148      return new Builder();
149   }
150
151   final int headingLevel;
152
153   /**
154    * Constructor.
155    *
156    * @param builder The builder for this object.
157    */
158   public MarkdownDocParser(Builder builder) {
159      super(builder);
160      headingLevel = builder.headingLevel;
161   }
162
163   @Override /* Overridden from Context */
164   public Builder copy() {
165      return new Builder(this);
166   }
167
168   @Override /* Overridden from Context */
169   public MarkdownDocParserSession.Builder createSession() {
170      return MarkdownDocParserSession.create(this);
171   }
172
173   @Override /* Overridden from Context */
174   public MarkdownDocParserSession getSession() { return createSession().build(); }
175}