1 /* Jackson JSON-processor. 2 * 3 * Copyright (c) 2007- Tatu Saloranta, tatu.saloranta@iki.fi 4 */ 5 6 package com.fasterxml.jackson.core; 7 8 import java.nio.charset.Charset; 9 10 /** 11 * Object that encapsulates Location information used for reporting 12 * parsing (or potentially generation) errors, as well as current location 13 * within input streams. 14 */ 15 public class JsonLocation 16 implements java.io.Serializable 17 { 18 private static final long serialVersionUID = 1L; 19 20 /** 21 * Include at most first 500 characters/bytes from contents; should be enough 22 * to give context, but not cause unfortunate side effects in things like 23 * logs. 24 * 25 * @since 2.9 26 */ 27 public static final int MAX_CONTENT_SNIPPET = 500; 28 29 /** 30 * Shared immutable "N/A location" that can be returned to indicate 31 * that no location information is available. 32 *<p> 33 * NOTE: before 2.9, Location was given as String "N/A"; with 2.9 it was 34 * removed so that source should be indicated as "UNKNOWN". 35 */ 36 public final static JsonLocation NA = new JsonLocation(null, -1L, -1L, -1, -1); 37 38 protected final long _totalBytes; 39 protected final long _totalChars; 40 41 protected final int _lineNr; 42 protected final int _columnNr; 43 44 /** 45 * Displayable description for input source: file path, URL. 46 *<p> 47 * NOTE: <code>transient</code> since 2.2 so that Location itself is Serializable. 48 */ 49 final transient Object _sourceRef; 50 JsonLocation(Object srcRef, long totalChars, int lineNr, int colNr)51 public JsonLocation(Object srcRef, long totalChars, int lineNr, int colNr) 52 { 53 /* Unfortunately, none of legal encodings are straight single-byte 54 * encodings. Could determine offset for UTF-16/UTF-32, but the 55 * most important one is UTF-8... 56 * so for now, we'll just not report any real byte count 57 */ 58 this(srcRef, -1L, totalChars, lineNr, colNr); 59 } 60 JsonLocation(Object sourceRef, long totalBytes, long totalChars, int lineNr, int columnNr)61 public JsonLocation(Object sourceRef, long totalBytes, long totalChars, 62 int lineNr, int columnNr) 63 { 64 _sourceRef = sourceRef; 65 _totalBytes = totalBytes; 66 _totalChars = totalChars; 67 _lineNr = lineNr; 68 _columnNr = columnNr; 69 } 70 71 /** 72 * Reference to the original resource being read, if one available. 73 * For example, when a parser has been constructed by passing 74 * a {@link java.io.File} instance, this method would return 75 * that File. Will return null if no such reference is available, 76 * for example when {@link java.io.InputStream} was used to 77 * construct the parser instance. 78 */ getSourceRef()79 public Object getSourceRef() { return _sourceRef; } 80 81 /** 82 * @return Line number of the location (1-based) 83 */ getLineNr()84 public int getLineNr() { return _lineNr; } 85 86 /** 87 * @return Column number of the location (1-based) 88 */ getColumnNr()89 public int getColumnNr() { return _columnNr; } 90 91 /** 92 * @return Character offset within underlying stream, reader or writer, 93 * if available; -1 if not. 94 */ getCharOffset()95 public long getCharOffset() { return _totalChars; } 96 97 /** 98 * @return Byte offset within underlying stream, reader or writer, 99 * if available; -1 if not. 100 */ getByteOffset()101 public long getByteOffset() 102 { 103 return _totalBytes; 104 } 105 106 /** 107 * Accessor for getting a textual description of source reference 108 * (Object returned by {@link #getSourceRef()}), as included in 109 * description returned by {@link #toString()}. 110 *<p> 111 * NOTE: not added as a "getter" to prevent it from getting serialized. 112 * 113 * @since 2.9 114 */ sourceDescription()115 public String sourceDescription() { 116 return _appendSourceDesc(new StringBuilder(100)).toString(); 117 } 118 119 /* 120 /********************************************************** 121 /* Std method overrides 122 /********************************************************** 123 */ 124 125 @Override hashCode()126 public int hashCode() 127 { 128 int hash = (_sourceRef == null) ? 1 : _sourceRef.hashCode(); 129 hash ^= _lineNr; 130 hash += _columnNr; 131 hash ^= (int) _totalChars; 132 hash += (int) _totalBytes; 133 return hash; 134 } 135 136 @Override equals(Object other)137 public boolean equals(Object other) 138 { 139 if (other == this) return true; 140 if (other == null) return false; 141 if (!(other instanceof JsonLocation)) return false; 142 JsonLocation otherLoc = (JsonLocation) other; 143 144 if (_sourceRef == null) { 145 if (otherLoc._sourceRef != null) return false; 146 } else if (!_sourceRef.equals(otherLoc._sourceRef)) return false; 147 148 return (_lineNr == otherLoc._lineNr) 149 && (_columnNr == otherLoc._columnNr) 150 && (_totalChars == otherLoc._totalChars) 151 && (getByteOffset() == otherLoc.getByteOffset()) 152 ; 153 } 154 155 @Override toString()156 public String toString() 157 { 158 StringBuilder sb = new StringBuilder(80); 159 sb.append("[Source: "); 160 _appendSourceDesc(sb); 161 sb.append("; line: "); 162 sb.append(_lineNr); 163 sb.append(", column: "); 164 sb.append(_columnNr); 165 sb.append(']'); 166 return sb.toString(); 167 } 168 _appendSourceDesc(StringBuilder sb)169 protected StringBuilder _appendSourceDesc(StringBuilder sb) 170 { 171 final Object srcRef = _sourceRef; 172 173 if (srcRef == null) { 174 sb.append("UNKNOWN"); 175 return sb; 176 } 177 // First, figure out what name to use as source type 178 Class<?> srcType = (srcRef instanceof Class<?>) ? 179 ((Class<?>) srcRef) : srcRef.getClass(); 180 String tn = srcType.getName(); 181 // standard JDK types without package 182 if (tn.startsWith("java.")) { 183 tn = srcType.getSimpleName(); 184 } else if (srcRef instanceof byte[]) { // then some other special cases 185 tn = "byte[]"; 186 } else if (srcRef instanceof char[]) { 187 tn = "char[]"; 188 } 189 sb.append('(').append(tn).append(')'); 190 // and then, include (part of) contents for selected types: 191 int len; 192 String charStr = " chars"; 193 194 if (srcRef instanceof CharSequence) { 195 CharSequence cs = (CharSequence) srcRef; 196 len = cs.length(); 197 len -= _append(sb, cs.subSequence(0, Math.min(len, MAX_CONTENT_SNIPPET)).toString()); 198 } else if (srcRef instanceof char[]) { 199 char[] ch = (char[]) srcRef; 200 len = ch.length; 201 len -= _append(sb, new String(ch, 0, Math.min(len, MAX_CONTENT_SNIPPET))); 202 } else if (srcRef instanceof byte[]) { 203 byte[] b = (byte[]) srcRef; 204 int maxLen = Math.min(b.length, MAX_CONTENT_SNIPPET); 205 _append(sb, new String(b, 0, maxLen, Charset.forName("UTF-8"))); 206 len = b.length - maxLen; 207 charStr = " bytes"; 208 } else { 209 len = 0; 210 } 211 if (len > 0) { 212 sb.append("[truncated ").append(len).append(charStr).append(']'); 213 } 214 return sb; 215 } 216 _append(StringBuilder sb, String content)217 private int _append(StringBuilder sb, String content) { 218 sb.append('"').append(content).append('"'); 219 return content.length(); 220 } 221 } 222