View Javadoc

1   /*
2    * Copyright 2005 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * 
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.apache.struts.flow.core.location;
17  
18  import java.lang.ref.WeakReference;
19  import java.util.ArrayList;
20  import java.util.List;
21  
22  import javax.xml.transform.SourceLocator;
23  import javax.xml.transform.TransformerException;
24  
25  import org.xml.sax.Locator;
26  import org.xml.sax.SAXParseException;
27  
28  /***
29   * Location-related utility methods.
30   *
31   * @version $Id: LocationUtils.java 280946 2005-09-14 21:43:05Z sylvain $
32   * @since 2.1.8
33   */
34  public class LocationUtils {
35      
36      /***
37       * The string representation of an unknown location: "<code>[unknown location]</code>".
38       */
39      public static final String UNKNOWN_STRING = "[unknown location]";
40      
41      private static List finders = new ArrayList();
42      
43      /***
44       * An finder or object locations
45       *
46       * @since 2.1.8
47       */
48      public interface LocationFinder {
49          /***
50           * Get the location of an object
51           * @param obj the object for which to find a location
52           * @param description and optional description to be added to the object's location
53           * @return the object's location or <code>null</code> if object's class isn't handled
54           *         by this finder.
55           */
56          Location getLocation(Object obj, String description);
57      }
58  
59      private LocationUtils() {
60          // Forbid instanciation
61      }
62      
63      /***
64       * Builds a string representation of a location, in the
65       * "<code><em>descripton</em> - <em>uri</em>:<em>line</em>:<em>column</em></code>"
66       * format (e.g. "<code>foo - file://path/to/file.xml:3:40</code>"). For {@link Location#UNKNOWN an unknown location}, returns
67       * {@link #UNKNOWN_STRING}.
68       * 
69       * @return the string representation
70       */
71      public static String toString(Location location) {
72          StringBuffer result = new StringBuffer();
73  
74          String description = location.getDescription();
75          if (description != null) {
76              result.append(description).append(" - ");
77          }
78  
79          String uri = location.getURI();
80          if (uri != null) {
81              result.append(uri).append(':').append(location.getLineNumber()).append(':').append(location.getColumnNumber());
82          } else {
83              result.append(UNKNOWN_STRING);
84          }
85          
86          return result.toString();
87      }
88  
89      /***
90       * Parse a location string of the form "<code><em>uri</em>:<em>line</em>:<em>column</em></code>" (e.g.
91       * "<code>path/to/file.xml:3:40</code>") to a Location object. Additionally, a description may
92       * also optionally be present, separated with an hyphen (e.g. "<code>foo - path/to/file.xml:3.40</code>").
93       * 
94       * @param text the text to parse
95       * @return the location (possibly <code>null</code> if text was null or in an incorrect format)
96       */
97      public static LocationImpl parse(String text) throws IllegalArgumentException {
98          if (text == null || text.length() == 0) {
99              return null;
100         }
101 
102         // Do we have a description?
103         String description;
104         int uriStart = text.lastIndexOf(" - "); // lastIndexOf to allow the separator to be in the description
105         if (uriStart > -1) {
106             description = text.substring(0, uriStart);
107             uriStart += 3; // strip " - "
108         } else {
109             description = null;
110             uriStart = 0;
111         }
112         
113         try {
114             int colSep = text.lastIndexOf(':');
115             if (colSep > -1) {
116                 int column = Integer.parseInt(text.substring(colSep + 1));
117                 
118                 int lineSep = text.lastIndexOf(':', colSep - 1);
119                 if (lineSep > -1) {
120                     int line = Integer.parseInt(text.substring(lineSep + 1, colSep));
121                     return new LocationImpl(description, text.substring(uriStart, lineSep), line, column);
122                 }
123             } else {
124                 // unkonwn?
125                 if (text.endsWith(UNKNOWN_STRING)) {
126                     return LocationImpl.UNKNOWN;
127                 }
128             }
129         } catch(Exception e) {
130             // Ignore: handled below
131         }
132         
133         return LocationImpl.UNKNOWN;
134     }
135 
136     /***
137      * Checks if a location is known, i.e. it is not null nor equal to {@link Location#UNKNOWN}.
138      * 
139      * @param location the location to check
140      * @return <code>true</code> if the location is known
141      */
142     public static boolean isKnown(Location location) {
143         return location != null && !Location.UNKNOWN.equals(location);
144     }
145 
146     /***
147      * Checks if a location is unknown, i.e. it is either null or equal to {@link Location#UNKNOWN}.
148      * 
149      * @param location the location to check
150      * @return <code>true</code> if the location is unknown
151      */
152     public static boolean isUnknown(Location location) {
153         return location == null || Location.UNKNOWN.equals(location);
154     }
155 
156     /***
157      * Add a {@link LocationFinder} to the list of finders that will be queried for an object's
158      * location by {@link #getLocation(Object, String)}.
159      * <p>
160      * <b>Important:</b> LocationUtils internally stores a weak reference to the finder. This
161      * avoids creating strong links between the classloader holding this class and the finder's
162      * classloader, which can cause some weird memory leaks if the finder's classloader is to
163      * be reloaded. Therefore, you <em>have</em> to keep a strong reference to the finder in the
164      * calling code, e.g.:
165      * <pre>
166      *   private static LocationUtils.LocationFinder myFinder =
167      *       new LocationUtils.LocationFinder() {
168      *           public Location getLocation(Object obj, String desc) {
169      *               ...
170      *           }
171      *       };
172      *
173      *   static {
174      *       LocationUtils.addFinder(myFinder);
175      *   }
176      * </pre>
177      * 
178      * @param finder the location finder to add
179      */
180     public static void addFinder(LocationFinder finder) {
181         if (finder == null) {
182             return;
183         }
184 
185         synchronized(LocationFinder.class) {
186             // Update a clone of the current finder list to avoid breaking
187             // any iteration occuring in another thread.
188             List newFinders = new ArrayList(finders);
189             newFinders.add(new WeakReference(finder));
190             finders = newFinders;
191         }
192     }
193     
194     /***
195      * Get the location of an object. Some well-known located classes built in the JDK are handled
196      * by this method. Handling of other located classes can be handled by adding new location finders.
197      * 
198      * @param obj the object of which to get the location
199      * @return the object's location, or {@link Location#UNKNOWN} if no location could be found
200      */
201     public static Location getLocation(Object obj) {
202         return getLocation(obj, null);
203     }
204     
205     /***
206      * Get the location of an object. Some well-known located classes built in the JDK are handled
207      * by this method. Handling of other located classes can be handled by adding new location finders.
208      * 
209      * @param obj the object of which to get the location
210      * @param description an optional description of the object's location, used if a Location object
211      *        has to be created.
212      * @return the object's location, or {@link Location#UNKNOWN} if no location could be found
213      */
214     public static Location getLocation(Object obj, String description) {
215         if (obj instanceof Locatable) {
216             return ((Locatable)obj).getLocation();
217         }
218         
219         // Check some well-known locatable exceptions
220         if (obj instanceof SAXParseException) {
221             SAXParseException spe = (SAXParseException)obj;
222             if (spe.getSystemId() != null) {
223                 return new LocationImpl(description, spe.getSystemId(), spe.getLineNumber(), spe.getColumnNumber());
224             } else {
225                 return Location.UNKNOWN;
226             }
227         }
228         
229         if (obj instanceof TransformerException) {
230             TransformerException ex = (TransformerException)obj;
231             SourceLocator locator = ex.getLocator();
232             if (locator != null && locator.getSystemId() != null) {
233                 return new LocationImpl(description, locator.getSystemId(), locator.getLineNumber(), locator.getColumnNumber());
234             } else {
235                 return Location.UNKNOWN;
236             }
237         }
238         
239         if (obj instanceof Locator) {
240             Locator locator = (Locator)obj;
241             if (locator.getSystemId() != null) {
242                 return new LocationImpl(description, locator.getSystemId(), locator.getLineNumber(), locator.getColumnNumber());
243             } else {
244                 return Location.UNKNOWN;
245             }
246         }
247 
248         List currentFinders = finders; // Keep the current list
249         int size = currentFinders.size();
250         for (int i = 0; i < size; i++) {
251             WeakReference ref = (WeakReference)currentFinders.get(i);
252             LocationFinder finder = (LocationFinder)ref.get();
253             if (finder == null) {
254                 // This finder was garbage collected: update finders
255                 synchronized(LocationFinder.class) {
256                     // Update a clone of the current list to avoid breaking current iterations
257                     List newFinders = new ArrayList(finders);
258                     newFinders.remove(ref);
259                     finders = newFinders;
260                 }
261             }
262             
263             Location result = finder.getLocation(obj, description);
264             if (result != null) {
265                 return result;
266             }
267         }
268 
269         return Location.UNKNOWN;
270     }
271 }