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.commons.svl;
018
019/**
020 * Thrown when an SVL <c>#{...}</c> expression cannot be lexed or parsed.
021 *
022 * <p>
023 * The message reads <c>message at position N:</c>, followed by the source line containing the position and a caret line
024 * (tabs copied, other characters as spaces) under the position.
025 */
026@SuppressWarnings({
027   "java:S110" // Exception hierarchy mirrors the resolver exception layering
028})
029public class ExprException extends VarResolverException {
030   private static final long serialVersionUID = 1L;
031
032   private final String baseMessage;
033   private final String source;
034   private final int pos;
035
036   /**
037    * Constructor.
038    *
039    * @param message The plain (not printf-style) description.
040    * @param source The expression source text.
041    * @param pos The zero-based character offset of the problem.
042    */
043   public ExprException(String message, String source, int pos) {
044      this(message, source, pos, null);
045   }
046
047   /**
048    * Constructor.
049    *
050    * @param message The plain (not printf-style) description.
051    * @param source The expression source text.
052    * @param pos The zero-based character offset of the problem.
053    * @param cause The underlying failure.  Can be <jk>null</jk>.
054    */
055   public ExprException(String message, String source, int pos, Throwable cause) {
056      super(cause, "%s", render(message, source, pos));
057      this.baseMessage = message;
058      this.source = source;
059      this.pos = pos;
060   }
061
062   private static String render(String message, String source, int pos) {
063      int p = Math.max(0, Math.min(pos, source.length()));
064      int ls = source.lastIndexOf('\n', p - 1) + 1;
065      int le = source.indexOf('\n', p);
066      if (le < 0) le = source.length();
067      var line = source.substring(ls, le);
068      var pad = new StringBuilder();
069      for (int i = ls; i < p; i++)
070         pad.append(source.charAt(i) == '\t' ? '\t' : ' ');
071      return message + " at position " + pos + ":\n" + line + "\n" + pad + "^";
072   }
073
074   /** @return The description without position or source. */
075   public String getBaseMessage() { return baseMessage; }
076
077   /** @return The expression source text. */
078   public String getSource() { return source; }
079
080   /** @return The zero-based character offset of the problem. */
081   public int getPos() { return pos; }
082}