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.common.utils.StringUtils.*;
020
021import java.lang.reflect.*;
022import java.text.*;
023
024import org.apache.juneau.*;
025import org.apache.juneau.collections.*;
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)
055         e = ((InvocationTargetException)e).getCause();
056      if (e instanceof ParseException)
057         return (ParseException)e;
058      return new ParseException(e);
059   }
060
061   /**
062    * Constructor.
063    *
064    * @param message The {@link MessageFormat}-style message.
065    * @param args Optional {@link MessageFormat}-style arguments.
066    */
067   public ParseException(String message, Object...args) {
068      super(message, args);
069   }
070
071   /**
072    * Constructor.
073    *
074    * @param causedBy The cause of this exception.
075    * @param message The {@link MessageFormat}-style message.
076    * @param args Optional {@link MessageFormat}-style arguments.
077    */
078   public ParseException(Throwable causedBy, String message, Object...args) {
079      super(causedBy, message, args);
080   }
081
082   /**
083    * Constructor.
084    *
085    * @param causedBy The cause of this exception.
086    */
087   public ParseException(Throwable causedBy) {
088      super(causedBy);
089   }
090
091   /**
092    * Constructor.
093    *
094    * @param session The parser session.
095    * @param message The exception message containing {@link MessageFormat}-style arguments.
096    * @param args Optional {@link MessageFormat}-style arguments.
097    */
098   public ParseException(ParserSession session, String message, Object...args) {
099      super(getMessage(session, message, args));
100   }
101
102   /**
103    * Constructor.
104    *
105    * @param session The parser session.
106    * @param causedBy The cause of this exception.
107    * @param message The exception message containing {@link MessageFormat}-style arguments.
108    * @param args Optional {@link MessageFormat}-style arguments.
109    */
110   public ParseException(ParserSession session, Throwable causedBy, String message, Object...args) {
111      super(causedBy, getMessage(session, message, args));
112   }
113
114   /**
115    * Constructor.
116    *
117    * @param session The parser session.
118    * @param causedBy The inner exception.
119    */
120   public ParseException(ParserSession session, Exception causedBy) {
121      super(causedBy, getMessage(session, causedBy.getMessage()));
122   }
123
124
125   private static String getMessage(ParserSession session, String msg, Object... args) {
126      if (args.length != 0)
127         msg = format(msg, args);
128
129      if (session != null) {
130         Position p = session.getPosition();
131         StringBuilder sb = new StringBuilder(msg);
132
133         sb.append("\n\tAt: ").append(p);
134
135         JsonMap lastLocation = session.getLastLocation();
136         if (lastLocation != null) {
137            sb.append("\n\tWhile parsing into: ");
138            lastLocation.forEach((k,v) -> sb.append("\n\t\t").append(k).append(": ").append(v));
139         }
140
141         String lines = session.getInputAsString();
142         if (lines == null)
143            sb.append("\n\tUse BEAN_debug setting to display content.");
144         else {
145            int numLines = session.getDebugOutputLines();
146            int start = p.line - numLines, end = p.line + numLines;
147            sb.append("\n---start--\n").append(getNumberedLines(lines, start, end)).append("---end---");
148         }
149
150         msg = sb.toString();
151      }
152      return msg;
153   }
154
155   /**
156    * Returns the highest-level <c>ParseException</c> in the stack trace.
157    *
158    * <p>
159    * Useful for JUnit testing of error conditions.
160    *
161    * @return The root parse exception, or this exception if there isn't one.
162    */
163   public ParseException getRootCause() {
164      ParseException t = this;
165      while (! (t.getCause() == null || ! (t.getCause() instanceof ParseException)))
166         t = (ParseException)t.getCause();
167      return t;
168   }
169}