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: */
017: package org.apache.cocoon.util.location;
018:
019: import java.lang.ref.WeakReference;
020: import java.util.ArrayList;
021: import java.util.List;
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: /**
030: * Location-related utility methods.
031: *
032: * @version $Id: LocationUtils.java 446956 2006-09-16 21:32:15Z vgritsenko $
033: * @since 2.1.8
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: * @since 2.1.8
048: */
049: public interface LocationFinder {
050: /**
051: * Get the location of an object
052: * @param obj the object for which to find a location
053: * @param description and optional description to be added to the object's location
054: * @return the object's location or <code>null</code> if object's class isn't handled
055: * by this finder.
056: */
057: Location getLocation(Object obj, String description);
058: }
059:
060: private LocationUtils() {
061: // Forbid instanciation
062: }
063:
064: /**
065: * Builds a string representation of a location, in the
066: * "<code><em>descripton</em> - <em>uri</em>:<em>line</em>:<em>column</em></code>"
067: * format (e.g. "<code>foo - file://path/to/file.xml:3:40</code>"). For {@link Location#UNKNOWN an unknown location}, returns
068: * {@link #UNKNOWN_STRING}.
069: *
070: * @return the string representation
071: */
072: public static String toString(Location location) {
073: StringBuffer result = new StringBuffer();
074:
075: String description = location.getDescription();
076: if (description != null) {
077: result.append(description).append(" - ");
078: }
079:
080: String uri = location.getURI();
081: if (uri != null) {
082: result.append(uri);
083: if (location.getLineNumber() != -1) {
084: result.append(':').append(location.getLineNumber());
085: if (location.getColumnNumber() != -1) {
086: result.append(':').append(
087: location.getColumnNumber());
088: }
089: }
090: } else {
091: result.append(UNKNOWN_STRING);
092: }
093:
094: return result.toString();
095: }
096:
097: /**
098: * Parse a location string of the form "<code><em>uri</em>:<em>line</em>:<em>column</em></code>" (e.g.
099: * "<code>path/to/file.xml:3:40</code>") to a Location object. Additionally, a description may
100: * also optionally be present, separated with an hyphen (e.g. "<code>foo - path/to/file.xml:3.40</code>").
101: *
102: * @param text the text to parse
103: * @return the location (possibly <code>null</code> if text was null or in an incorrect format)
104: */
105: public static LocationImpl parse(String text)
106: throws IllegalArgumentException {
107: if (text == null || text.length() == 0) {
108: return null;
109: }
110:
111: // Do we have a description?
112: String description;
113: int uriStart = text.lastIndexOf(" - "); // lastIndexOf to allow the separator to be in the description
114: if (uriStart > -1) {
115: description = text.substring(0, uriStart);
116: uriStart += 3; // strip " - "
117: } else {
118: description = null;
119: uriStart = 0;
120: }
121:
122: try {
123: int colSep = text.lastIndexOf(':');
124: if (colSep > -1) {
125: int column = Integer.parseInt(text
126: .substring(colSep + 1));
127:
128: int lineSep = text.lastIndexOf(':', colSep - 1);
129: if (lineSep > -1) {
130: int line = Integer.parseInt(text.substring(
131: lineSep + 1, colSep));
132: return new LocationImpl(description, text
133: .substring(uriStart, lineSep), line, column);
134: }
135: } else {
136: // unkonwn?
137: if (text.endsWith(UNKNOWN_STRING)) {
138: return LocationImpl.UNKNOWN;
139: }
140: }
141: } catch (Exception e) {
142: // Ignore: handled below
143: }
144:
145: return LocationImpl.UNKNOWN;
146: }
147:
148: /**
149: * Checks if a location is known, i.e. it is not null nor equal to {@link Location#UNKNOWN}.
150: *
151: * @param location the location to check
152: * @return <code>true</code> if the location is known
153: */
154: public static boolean isKnown(Location location) {
155: return location != null && !Location.UNKNOWN.equals(location);
156: }
157:
158: /**
159: * Checks if a location is unknown, i.e. it is either null or equal to {@link Location#UNKNOWN}.
160: *
161: * @param location the location to check
162: * @return <code>true</code> if the location is unknown
163: */
164: public static boolean isUnknown(Location location) {
165: return location == null || Location.UNKNOWN.equals(location);
166: }
167:
168: /**
169: * Add a {@link LocationFinder} to the list of finders that will be queried for an object's
170: * location by {@link #getLocation(Object, String)}.
171: * <p>
172: * <b>Important:</b> LocationUtils internally stores a weak reference to the finder. This
173: * avoids creating strong links between the classloader holding this class and the finder's
174: * classloader, which can cause some weird memory leaks if the finder's classloader is to
175: * be reloaded. Therefore, you <em>have</em> to keep a strong reference to the finder in the
176: * calling code, e.g.:
177: * <pre>
178: * private static LocationUtils.LocationFinder myFinder =
179: * new LocationUtils.LocationFinder() {
180: * public Location getLocation(Object obj, String desc) {
181: * ...
182: * }
183: * };
184: *
185: * static {
186: * LocationUtils.addFinder(myFinder);
187: * }
188: * </pre>
189: *
190: * @param finder the location finder to add
191: */
192: public static void addFinder(LocationFinder finder) {
193: if (finder == null) {
194: return;
195: }
196:
197: synchronized (LocationFinder.class) {
198: // Update a clone of the current finder list to avoid breaking
199: // any iteration occuring in another thread.
200: List newFinders = new ArrayList(finders);
201: newFinders.add(new WeakReference(finder));
202: finders = newFinders;
203: }
204: }
205:
206: /**
207: * Get the location of an object. Some well-known located classes built in the JDK are handled
208: * by this method. Handling of other located classes can be handled by adding new location finders.
209: *
210: * @param obj the object of which to get the location
211: * @return the object's location, or {@link Location#UNKNOWN} if no location could be found
212: */
213: public static Location getLocation(Object obj) {
214: return getLocation(obj, null);
215: }
216:
217: /**
218: * Get the location of an object. Some well-known located classes built in the JDK are handled
219: * by this method. Handling of other located classes can be handled by adding new location finders.
220: *
221: * @param obj the object of which to get the location
222: * @param description an optional description of the object's location, used if a Location object
223: * has to be created.
224: * @return the object's location, or {@link Location#UNKNOWN} if no location could be found
225: */
226: public static Location getLocation(Object obj, String description) {
227: if (obj instanceof Locatable) {
228: return ((Locatable) obj).getLocation();
229: }
230:
231: // Check some well-known locatable exceptions
232: if (obj instanceof SAXParseException) {
233: SAXParseException spe = (SAXParseException) obj;
234: if (spe.getSystemId() != null) {
235: return new LocationImpl(description, spe.getSystemId(),
236: spe.getLineNumber(), spe.getColumnNumber());
237: } else {
238: return Location.UNKNOWN;
239: }
240: }
241:
242: if (obj instanceof TransformerException) {
243: TransformerException ex = (TransformerException) obj;
244: SourceLocator locator = ex.getLocator();
245: if (locator != null && locator.getSystemId() != null) {
246: return new LocationImpl(description, locator
247: .getSystemId(), locator.getLineNumber(),
248: locator.getColumnNumber());
249: } else {
250: return Location.UNKNOWN;
251: }
252: }
253:
254: if (obj instanceof Locator) {
255: Locator locator = (Locator) obj;
256: if (locator.getSystemId() != null) {
257: return new LocationImpl(description, locator
258: .getSystemId(), locator.getLineNumber(),
259: locator.getColumnNumber());
260: } else {
261: return Location.UNKNOWN;
262: }
263: }
264:
265: List currentFinders = finders; // Keep the current list
266: int size = currentFinders.size();
267: for (int i = 0; i < size; i++) {
268: WeakReference ref = (WeakReference) currentFinders.get(i);
269: LocationFinder finder = (LocationFinder) ref.get();
270: if (finder == null) {
271: // This finder was garbage collected: update finders
272: synchronized (LocationFinder.class) {
273: // Update a clone of the current list to avoid breaking current iterations
274: List newFinders = new ArrayList(finders);
275: newFinders.remove(ref);
276: finders = newFinders;
277: }
278: continue;
279: }
280:
281: Location result = finder.getLocation(obj, description);
282: if (result != null) {
283: return result;
284: }
285: }
286:
287: return Location.UNKNOWN;
288: }
289: }
|