blob: a98e96b2d6a9117b1f467a0245bda1df8d0b25a7 [file] [log] [blame]
/*
* Copyright 2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cocoon.util.location;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import javax.xml.transform.SourceLocator;
import javax.xml.transform.TransformerException;
import org.xml.sax.Locator;
import org.xml.sax.SAXParseException;
/**
* Location-related utility methods.
*
* @version $Id$
* @since 2.1.8
*/
public class LocationUtils {
/**
* The string representation of an unknown location: "<code>[unknown location]</code>".
*/
public static final String UNKNOWN_STRING = "[unknown location]";
private static List finders = new ArrayList();
/**
* An finder or object locations
*
* @since 2.1.8
*/
public interface LocationFinder {
/**
* Get the location of an object
* @param obj the object for which to find a location
* @param description and optional description to be added to the object's location
* @return the object's location or <code>null</code> if object's class isn't handled
* by this finder.
*/
Location getLocation(Object obj, String description);
}
private LocationUtils() {
// Forbid instanciation
}
/**
* Builds a string representation of a location, in the
* "<code><em>descripton</em> - <em>uri</em>:<em>line</em>:<em>column</em></code>"
* format (e.g. "<code>foo - file://path/to/file.xml:3:40</code>"). For {@link Location#UNKNOWN an unknown location}, returns
* {@link #UNKNOWN_STRING}.
*
* @return the string representation
*/
public static String toString(Location location) {
StringBuffer result = new StringBuffer();
String description = location.getDescription();
if (description != null) {
result.append(description).append(" - ");
}
String uri = location.getURI();
if (uri != null) {
result.append(uri).append(':').append(location.getLineNumber()).append(':').append(location.getColumnNumber());
} else {
result.append(UNKNOWN_STRING);
}
return result.toString();
}
/**
* Parse a location string of the form "<code><em>uri</em>:<em>line</em>:<em>column</em></code>" (e.g.
* "<code>path/to/file.xml:3:40</code>") to a Location object. Additionally, a description may
* also optionally be present, separated with an hyphen (e.g. "<code>foo - path/to/file.xml:3.40</code>").
*
* @param text the text to parse
* @return the location (possibly <code>null</code> if text was null or in an incorrect format)
*/
public static LocationImpl parse(String text) throws IllegalArgumentException {
if (text == null || text.length() == 0) {
return null;
}
// Do we have a description?
String description;
int uriStart = text.lastIndexOf(" - "); // lastIndexOf to allow the separator to be in the description
if (uriStart > -1) {
description = text.substring(0, uriStart);
uriStart += 3; // strip " - "
} else {
description = null;
uriStart = 0;
}
try {
int colSep = text.lastIndexOf(':');
if (colSep > -1) {
int column = Integer.parseInt(text.substring(colSep + 1));
int lineSep = text.lastIndexOf(':', colSep - 1);
if (lineSep > -1) {
int line = Integer.parseInt(text.substring(lineSep + 1, colSep));
return new LocationImpl(description, text.substring(uriStart, lineSep), line, column);
}
} else {
// unkonwn?
if (text.endsWith(UNKNOWN_STRING)) {
return LocationImpl.UNKNOWN;
}
}
} catch(Exception e) {
// Ignore: handled below
}
return LocationImpl.UNKNOWN;
}
/**
* Checks if a location is known, i.e. it is not null nor equal to {@link Location#UNKNOWN}.
*
* @param location the location to check
* @return <code>true</code> if the location is known
*/
public static boolean isKnown(Location location) {
return location != null && !Location.UNKNOWN.equals(location);
}
/**
* Checks if a location is unknown, i.e. it is either null or equal to {@link Location#UNKNOWN}.
*
* @param location the location to check
* @return <code>true</code> if the location is unknown
*/
public static boolean isUnknown(Location location) {
return location == null || Location.UNKNOWN.equals(location);
}
/**
* Add a {@link LocationFinder} to the list of finders that will be queried for an object's
* location by {@link #getLocation(Object, String)}.
* <p>
* <b>Important:</b> LocationUtils internally stores a weak reference to the finder. This
* avoids creating strong links between the classloader holding this class and the finder's
* classloader, which can cause some weird memory leaks if the finder's classloader is to
* be reloaded. Therefore, you <em>have</em> to keep a strong reference to the finder in the
* calling code, e.g.:
* <pre>
* private static LocationUtils.LocationFinder myFinder =
* new LocationUtils.LocationFinder() {
* public Location getLocation(Object obj, String desc) {
* ...
* }
* };
*
* static {
* LocationUtils.addFinder(myFinder);
* }
* </pre>
*
* @param finder the location finder to add
*/
public static void addFinder(LocationFinder finder) {
if (finder == null) {
return;
}
synchronized(LocationFinder.class) {
// Update a clone of the current finder list to avoid breaking
// any iteration occuring in another thread.
List newFinders = new ArrayList(finders);
newFinders.add(new WeakReference(finder));
finders = newFinders;
}
}
/**
* Get the location of an object. Some well-known located classes built in the JDK are handled
* by this method. Handling of other located classes can be handled by adding new location finders.
*
* @param obj the object of which to get the location
* @return the object's location, or {@link Location#UNKNOWN} if no location could be found
*/
public static Location getLocation(Object obj) {
return getLocation(obj, null);
}
/**
* Get the location of an object. Some well-known located classes built in the JDK are handled
* by this method. Handling of other located classes can be handled by adding new location finders.
*
* @param obj the object of which to get the location
* @param description an optional description of the object's location, used if a Location object
* has to be created.
* @return the object's location, or {@link Location#UNKNOWN} if no location could be found
*/
public static Location getLocation(Object obj, String description) {
if (obj instanceof Locatable) {
return ((Locatable)obj).getLocation();
}
// Check some well-known locatable exceptions
if (obj instanceof SAXParseException) {
SAXParseException spe = (SAXParseException)obj;
if (spe.getSystemId() != null) {
return new LocationImpl(description, spe.getSystemId(), spe.getLineNumber(), spe.getColumnNumber());
} else {
return Location.UNKNOWN;
}
}
if (obj instanceof TransformerException) {
TransformerException ex = (TransformerException)obj;
SourceLocator locator = ex.getLocator();
if (locator != null && locator.getSystemId() != null) {
return new LocationImpl(description, locator.getSystemId(), locator.getLineNumber(), locator.getColumnNumber());
} else {
return Location.UNKNOWN;
}
}
if (obj instanceof Locator) {
Locator locator = (Locator)obj;
if (locator.getSystemId() != null) {
return new LocationImpl(description, locator.getSystemId(), locator.getLineNumber(), locator.getColumnNumber());
} else {
return Location.UNKNOWN;
}
}
List currentFinders = finders; // Keep the current list
int size = currentFinders.size();
for (int i = 0; i < size; i++) {
WeakReference ref = (WeakReference)currentFinders.get(i);
LocationFinder finder = (LocationFinder)ref.get();
if (finder == null) {
// This finder was garbage collected: update finders
synchronized(LocationFinder.class) {
// Update a clone of the current list to avoid breaking current iterations
List newFinders = new ArrayList(finders);
newFinders.remove(ref);
finders = newFinders;
}
}
Location result = finder.getLocation(obj, description);
if (result != null) {
return result;
}
}
return Location.UNKNOWN;
}
}