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.parser;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020import static org.apache.juneau.commons.utils.StringUtils.*;
021
022import java.lang.reflect.*;
023import org.apache.juneau.marshall.*;
024
025/**
026 * Exception that indicates invalid syntax encountered during parsing.
027 *
028 * <h5 class='section'>See Also:</h5><ul>
029 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SerializersAndParsers">Serializers and Parsers</a>
030
031 * </ul>
032 *
033 * @serial exclude
034 */
035@SuppressWarnings({
036   "java:S110" // Deep inheritance inherent to the exception hierarchy
037})
038public class ParseException extends BasicRuntimeException {
039
040   private static final long serialVersionUID = 1L;
041
042   /**
043    * Creator method.
044    *
045    * <p>
046    * If the throwable is already a {@link ParseException}, we simply return that exception as-is.
047    * If the throwable is an {@link InvocationTargetException}, we unwrap the thrown exception.
048    * Otherwise we create a new {@link ParseException}.
049    *
050    * @param e The exception being wrapped or unwrapped.
051    *    <br>Can be <jk>null</jk> (a new {@link ParseException} with no cause is created).
052    * @return The specified throwable wrapped or unwrapped as a {@link ParseException} (may be the same instance if it already was one).
053    */
054   public static ParseException create(Throwable e) {
055      if (e instanceof InvocationTargetException e2)
056         e = e2.getCause();
057      if (e instanceof ParseException e3)
058         return e3;
059      return new ParseException(e);
060   }
061
062   private static String getMessage(ParserSession session, String msg, Object...args) {
063      if (args.length != 0)
064         msg = f(msg, args);
065
066      if (nn(session)) {
067         Position p = session.getPosition();
068         var sb = new StringBuilder(msg);
069
070         sb.append("\n\tAt: ").append(p);
071
072         var lastLocation = session.getLastLocation();
073         if (nn(lastLocation)) {
074            sb.append("\n\tWhile parsing into: ");
075            lastLocation.forEach((k, v) -> sb.append("\n\t\t").append(k).append(": ").append(v));
076         }
077
078         String lines = session.getInputAsString();
079         if (lines == null)
080            sb.append("\n\tUse BEAN_debug setting to display content.");
081         else {
082            int numLines = session.getDebugOutputLines();
083            int start = p.line - numLines;
084            int end = p.line + numLines;
085            sb.append("\n---start--\n").append(getNumberedLines(lines, start, end)).append("---end---");
086         }
087
088         msg = sb.toString();
089      }
090      return msg;
091   }
092
093   /**
094    * Constructor.
095    *
096    * @param session The parser session.
097    *    <br>Can be <jk>null</jk> (the message is not augmented with position/debug info).
098    * @param causedBy The inner exception.
099    *    <br>Must not be <jk>null</jk>.
100    */
101   public ParseException(ParserSession session, Exception causedBy) {
102      super(causedBy, getMessage(session, causedBy.getMessage()));
103   }
104
105   /**
106    * Constructor.
107    *
108    * @param session The parser session.
109    *    <br>Can be <jk>null</jk> (the message is not augmented with position/debug info).
110    * @param message The exception message containing {@link String#format(String, Object...) String.format}-style (<c>%s</c>) arguments.
111    * @param args Optional {@link String#format(String, Object...) String.format}-style arguments.
112    */
113   public ParseException(ParserSession session, String message, Object...args) {
114      super(getMessage(session, message, args));
115   }
116
117   /**
118    * Constructor.
119    *
120    * @param session The parser session.
121    *    <br>Can be <jk>null</jk> (the message is not augmented with position/debug info).
122    * @param causedBy The cause of this exception.
123    *    <br>Can be <jk>null</jk> (no cause is set).
124    * @param message The exception message containing {@link String#format(String, Object...) String.format}-style (<c>%s</c>) arguments.
125    * @param args Optional {@link String#format(String, Object...) String.format}-style arguments.
126    */
127   public ParseException(ParserSession session, Throwable causedBy, String message, Object...args) {
128      super(causedBy, getMessage(session, message, args));
129   }
130
131   /**
132    * Constructor.
133    *
134    * @param message The {@link String#format(String, Object...) String.format}-style message (<c>%s</c> placeholders).
135    * @param args Optional {@link String#format(String, Object...) String.format}-style arguments.
136    */
137   public ParseException(String message, Object...args) {
138      super(message, args);
139   }
140
141   /**
142    * Constructor.
143    *
144    * @param causedBy The cause of this exception.
145    *    <br>Can be <jk>null</jk> (no cause is set).
146    */
147   public ParseException(Throwable causedBy) {
148      super(causedBy);
149   }
150
151   /**
152    * Constructor.
153    *
154    * @param causedBy The cause of this exception.
155    *    <br>Can be <jk>null</jk> (no cause is set).
156    * @param message The {@link String#format(String, Object...) String.format}-style message (<c>%s</c> placeholders).
157    * @param args Optional {@link String#format(String, Object...) String.format}-style arguments.
158    */
159   public ParseException(Throwable causedBy, String message, Object...args) {
160      super(causedBy, message, args);
161   }
162
163   /**
164    * Returns the highest-level <c>ParseException</c> in the stack trace.
165    *
166    * <p>
167    * Useful for JUnit testing of error conditions.
168    *
169    * @return The root parse exception, or this exception if there isn't one.
170    */
171   public ParseException getRootCause() {
172      ParseException t = this;
173      while (! (t.getCause() == null || ! (t.getCause() instanceof ParseException t2)))
174         t = t2;
175      return t;
176   }
177
178   @Override /* Overridden from BasicRuntimeException */
179   public ParseException setMessage(String message, Object...args) {
180      super.setMessage(message, args);
181      return this;
182   }
183}