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.parser;
018
019import static org.apache.juneau.commons.utils.StringUtils.*;
020import static org.apache.juneau.commons.utils.Utils.*;
021
022import java.lang.reflect.*;
023import java.text.*;
024
025import org.apache.juneau.*;
026import org.apache.juneau.serializer.*;
027
028/**
029 * Exception that indicates invalid syntax encountered during parsing.
030 *
031 * <h5 class='section'>See Also:</h5><ul>
032 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SerializersAndParsers">Serializers and Parsers</a>
033
034 * </ul>
035 *
036 * @serial exclude
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    * @return A new {@link SerializeException}.
052    */
053   public static ParseException create(Throwable e) {
054      if (e instanceof InvocationTargetException e2)
055         e = e2.getCause();
056      if (e instanceof ParseException e3)
057         return e3;
058      return new ParseException(e);
059   }
060
061   private static String getMessage(ParserSession session, String msg, Object...args) {
062      if (args.length != 0)
063         msg = f(msg, args);
064
065      if (nn(session)) {
066         Position p = session.getPosition();
067         var sb = new StringBuilder(msg);
068
069         sb.append("\n\tAt: ").append(p);
070
071         var lastLocation = session.getLastLocation();
072         if (nn(lastLocation)) {
073            sb.append("\n\tWhile parsing into: ");
074            lastLocation.forEach((k, v) -> sb.append("\n\t\t").append(k).append(": ").append(v));
075         }
076
077         String lines = session.getInputAsString();
078         if (lines == null)
079            sb.append("\n\tUse BEAN_debug setting to display content.");
080         else {
081            int numLines = session.getDebugOutputLines();
082            int start = p.line - numLines, end = p.line + numLines;
083            sb.append("\n---start--\n").append(getNumberedLines(lines, start, end)).append("---end---");
084         }
085
086         msg = sb.toString();
087      }
088      return msg;
089   }
090
091   /**
092    * Constructor.
093    *
094    * @param session The parser session.
095    * @param causedBy The inner exception.
096    */
097   public ParseException(ParserSession session, Exception causedBy) {
098      super(causedBy, getMessage(session, causedBy.getMessage()));
099   }
100
101   /**
102    * Constructor.
103    *
104    * @param session The parser session.
105    * @param message The exception message containing {@link MessageFormat}-style arguments.
106    * @param args Optional {@link MessageFormat}-style arguments.
107    */
108   public ParseException(ParserSession session, String message, Object...args) {
109      super(getMessage(session, message, args));
110   }
111
112   /**
113    * Constructor.
114    *
115    * @param session The parser session.
116    * @param causedBy The cause of this exception.
117    * @param message The exception message containing {@link MessageFormat}-style arguments.
118    * @param args Optional {@link MessageFormat}-style arguments.
119    */
120   public ParseException(ParserSession session, Throwable causedBy, String message, Object...args) {
121      super(causedBy, getMessage(session, message, args));
122   }
123
124   /**
125    * Constructor.
126    *
127    * @param message The {@link MessageFormat}-style message.
128    * @param args Optional {@link MessageFormat}-style arguments.
129    */
130   public ParseException(String message, Object...args) {
131      super(message, args);
132   }
133
134   /**
135    * Constructor.
136    *
137    * @param causedBy The cause of this exception.
138    */
139   public ParseException(Throwable causedBy) {
140      super(causedBy);
141   }
142
143   /**
144    * Constructor.
145    *
146    * @param causedBy The cause of this exception.
147    * @param message The {@link MessageFormat}-style message.
148    * @param args Optional {@link MessageFormat}-style arguments.
149    */
150   public ParseException(Throwable causedBy, String message, Object...args) {
151      super(causedBy, message, args);
152   }
153
154   /**
155    * Returns the highest-level <c>ParseException</c> in the stack trace.
156    *
157    * <p>
158    * Useful for JUnit testing of error conditions.
159    *
160    * @return The root parse exception, or this exception if there isn't one.
161    */
162   public ParseException getRootCause() {
163      ParseException t = this;
164      while (! (t.getCause() == null || ! (t.getCause() instanceof ParseException)))
165         t = (ParseException)t.getCause();
166      return t;
167   }
168
169   @Override /* Overridden from BasicRuntimeException */
170   public ParseException setMessage(String message, Object...args) {
171      super.setMessage(message, args);
172      return this;
173   }
174}