001: /*
002: * Copyright 2005 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package com.opensymphony.xwork.util.location;
017:
018: import java.lang.ref.WeakReference;
019: import java.util.ArrayList;
020: import java.util.List;
021: import java.net.URL;
022:
023: import javax.xml.transform.SourceLocator;
024: import javax.xml.transform.TransformerException;
025:
026: import org.xml.sax.Locator;
027: import org.xml.sax.SAXParseException;
028:
029: import org.w3c.dom.Element;
030: import com.opensymphony.util.ClassLoaderUtil;
031:
032: /**
033: * Location-related utility methods.
034: */
035: public class LocationUtils {
036:
037: /**
038: * The string representation of an unknown location: "<code>[unknown location]</code>".
039: */
040: public static final String UNKNOWN_STRING = "[unknown location]";
041:
042: private static List finders = new ArrayList();
043:
044: /**
045: * An finder or object locations
046: */
047: public interface LocationFinder {
048: /**
049: * Get the location of an object
050: * @param obj the object for which to find a location
051: * @param description and optional description to be added to the object's location
052: * @return the object's location or <code>null</code> if object's class isn't handled
053: * by this finder.
054: */
055: Location getLocation(Object obj, String description);
056: }
057:
058: private LocationUtils() {
059: // Forbid instanciation
060: }
061:
062: /**
063: * Builds a string representation of a location, in the
064: * "<code><em>descripton</em> - <em>uri</em>:<em>line</em>:<em>column</em></code>"
065: * format (e.g. "<code>foo - file://path/to/file.xml:3:40</code>"). For {@link Location#UNKNOWN an unknown location}, returns
066: * {@link #UNKNOWN_STRING}.
067: *
068: * @return the string representation
069: */
070: public static String toString(Location location) {
071: StringBuffer result = new StringBuffer();
072:
073: String description = location.getDescription();
074: if (description != null) {
075: result.append(description).append(" - ");
076: }
077:
078: String uri = location.getURI();
079: if (uri != null) {
080: result.append(uri).append(':').append(
081: location.getLineNumber()).append(':').append(
082: location.getColumnNumber());
083: } else {
084: result.append(UNKNOWN_STRING);
085: }
086:
087: return result.toString();
088: }
089:
090: /**
091: * Parse a location string of the form "<code><em>uri</em>:<em>line</em>:<em>column</em></code>" (e.g.
092: * "<code>path/to/file.xml:3:40</code>") to a Location object. Additionally, a description may
093: * also optionally be present, separated with an hyphen (e.g. "<code>foo - path/to/file.xml:3.40</code>").
094: *
095: * @param text the text to parse
096: * @return the location (possibly <code>null</code> if text was null or in an incorrect format)
097: */
098: public static LocationImpl parse(String text)
099: throws IllegalArgumentException {
100: if (text == null || text.length() == 0) {
101: return null;
102: }
103:
104: // Do we have a description?
105: String description;
106: int uriStart = text.lastIndexOf(" - "); // lastIndexOf to allow the separator to be in the description
107: if (uriStart > -1) {
108: description = text.substring(0, uriStart);
109: uriStart += 3; // strip " - "
110: } else {
111: description = null;
112: uriStart = 0;
113: }
114:
115: try {
116: int colSep = text.lastIndexOf(':');
117: if (colSep > -1) {
118: int column = Integer.parseInt(text
119: .substring(colSep + 1));
120:
121: int lineSep = text.lastIndexOf(':', colSep - 1);
122: if (lineSep > -1) {
123: int line = Integer.parseInt(text.substring(
124: lineSep + 1, colSep));
125: return new LocationImpl(description, text
126: .substring(uriStart, lineSep), line, column);
127: }
128: } else {
129: // unkonwn?
130: if (text.endsWith(UNKNOWN_STRING)) {
131: return LocationImpl.UNKNOWN;
132: }
133: }
134: } catch (Exception e) {
135: // Ignore: handled below
136: }
137:
138: return LocationImpl.UNKNOWN;
139: }
140:
141: /**
142: * Checks if a location is known, i.e. it is not null nor equal to {@link Location#UNKNOWN}.
143: *
144: * @param location the location to check
145: * @return <code>true</code> if the location is known
146: */
147: public static boolean isKnown(Location location) {
148: return location != null && !Location.UNKNOWN.equals(location);
149: }
150:
151: /**
152: * Checks if a location is unknown, i.e. it is either null or equal to {@link Location#UNKNOWN}.
153: *
154: * @param location the location to check
155: * @return <code>true</code> if the location is unknown
156: */
157: public static boolean isUnknown(Location location) {
158: return location == null || Location.UNKNOWN.equals(location);
159: }
160:
161: /**
162: * Add a {@link LocationFinder} to the list of finders that will be queried for an object's
163: * location by {@link #getLocation(Object, String)}.
164: * <p>
165: * <b>Important:</b> LocationUtils internally stores a weak reference to the finder. This
166: * avoids creating strong links between the classloader holding this class and the finder's
167: * classloader, which can cause some weird memory leaks if the finder's classloader is to
168: * be reloaded. Therefore, you <em>have</em> to keep a strong reference to the finder in the
169: * calling code, e.g.:
170: * <pre>
171: * private static LocationUtils.LocationFinder myFinder =
172: * new LocationUtils.LocationFinder() {
173: * public Location getLocation(Object obj, String desc) {
174: * ...
175: * }
176: * };
177: *
178: * static {
179: * LocationUtils.addFinder(myFinder);
180: * }
181: * </pre>
182: *
183: * @param finder the location finder to add
184: */
185: public static void addFinder(LocationFinder finder) {
186: if (finder == null) {
187: return;
188: }
189:
190: synchronized (LocationFinder.class) {
191: // Update a clone of the current finder list to avoid breaking
192: // any iteration occuring in another thread.
193: List newFinders = new ArrayList(finders);
194: newFinders.add(new WeakReference(finder));
195: finders = newFinders;
196: }
197: }
198:
199: /**
200: * Get the location of an object. Some well-known located classes built in the JDK are handled
201: * by this method. Handling of other located classes can be handled by adding new location finders.
202: *
203: * @param obj the object of which to get the location
204: * @return the object's location, or {@link Location#UNKNOWN} if no location could be found
205: */
206: public static Location getLocation(Object obj) {
207: return getLocation(obj, null);
208: }
209:
210: /**
211: * Get the location of an object. Some well-known located classes built in the JDK are handled
212: * by this method. Handling of other located classes can be handled by adding new location finders.
213: *
214: * @param obj the object of which to get the location
215: * @param description an optional description of the object's location, used if a Location object
216: * has to be created.
217: * @return the object's location, or {@link Location#UNKNOWN} if no location could be found
218: */
219: public static Location getLocation(Object obj, String description) {
220: if (obj instanceof Location) {
221: return (Location) obj;
222: }
223:
224: if (obj instanceof Locatable) {
225: return ((Locatable) obj).getLocation();
226: }
227:
228: // Check some well-known locatable exceptions
229: if (obj instanceof SAXParseException) {
230: SAXParseException spe = (SAXParseException) obj;
231: if (spe.getSystemId() != null) {
232: return new LocationImpl(description, spe.getSystemId(),
233: spe.getLineNumber(), spe.getColumnNumber());
234: } else {
235: return Location.UNKNOWN;
236: }
237: }
238:
239: if (obj instanceof TransformerException) {
240: TransformerException ex = (TransformerException) obj;
241: SourceLocator locator = ex.getLocator();
242: if (locator != null && locator.getSystemId() != null) {
243: return new LocationImpl(description, locator
244: .getSystemId(), locator.getLineNumber(),
245: locator.getColumnNumber());
246: } else {
247: return Location.UNKNOWN;
248: }
249: }
250:
251: if (obj instanceof Locator) {
252: Locator locator = (Locator) obj;
253: if (locator.getSystemId() != null) {
254: return new LocationImpl(description, locator
255: .getSystemId(), locator.getLineNumber(),
256: locator.getColumnNumber());
257: } else {
258: return Location.UNKNOWN;
259: }
260: }
261:
262: if (obj instanceof Element) {
263: return LocationAttributes.getLocation((Element) obj);
264: }
265:
266: List currentFinders = finders; // Keep the current list
267: int size = currentFinders.size();
268: for (int i = 0; i < size; i++) {
269: WeakReference ref = (WeakReference) currentFinders.get(i);
270: LocationFinder finder = (LocationFinder) ref.get();
271: if (finder == null) {
272: // This finder was garbage collected: update finders
273: synchronized (LocationFinder.class) {
274: // Update a clone of the current list to avoid breaking current iterations
275: List newFinders = new ArrayList(finders);
276: newFinders.remove(ref);
277: finders = newFinders;
278: }
279: }
280:
281: Location result = finder.getLocation(obj, description);
282: if (result != null) {
283: return result;
284: }
285: }
286:
287: if (obj instanceof Throwable) {
288: Throwable t = (Throwable) obj;
289: StackTraceElement[] stack = t.getStackTrace();
290: if (stack != null && stack.length > 0) {
291: StackTraceElement trace = stack[0];
292: if (trace.getLineNumber() >= 0) {
293: String uri = trace.getClassName();
294: if (trace.getFileName() != null) {
295: uri = uri.replace('.', '/');
296: uri = uri
297: .substring(0, uri.lastIndexOf('/') + 1);
298: uri = uri + trace.getFileName();
299: URL url = ClassLoaderUtil.getResource(uri,
300: LocationUtils.class);
301: if (url != null) {
302: uri = url.toString();
303: }
304: }
305: if (description == null) {
306: StringBuffer sb = new StringBuffer();
307: sb.append("Class: ").append(
308: trace.getClassName()).append("\n");
309: sb.append("File: ").append(trace.getFileName())
310: .append("\n");
311: sb.append("Method: ").append(
312: trace.getMethodName()).append("\n");
313: sb.append("Line: ").append(
314: trace.getLineNumber());
315: description = sb.toString();
316: }
317: return new LocationImpl(description, uri, trace
318: .getLineNumber(), -1);
319: }
320: }
321: }
322:
323: return Location.UNKNOWN;
324: }
325: }
|