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.components.source.impl;
018:
019: import java.io.File;
020: import java.io.IOException;
021: import java.net.MalformedURLException;
022: import java.util.ArrayList;
023: import java.util.Iterator;
024: import java.util.List;
025: import java.util.Map;
026:
027: import org.apache.avalon.framework.configuration.Configurable;
028: import org.apache.avalon.framework.configuration.Configuration;
029: import org.apache.avalon.framework.configuration.ConfigurationException;
030: import org.apache.avalon.framework.logger.AbstractLogEnabled;
031: import org.apache.avalon.framework.service.ServiceException;
032: import org.apache.avalon.framework.service.ServiceManager;
033: import org.apache.avalon.framework.service.Serviceable;
034: import org.apache.avalon.framework.thread.ThreadSafe;
035: import org.apache.excalibur.source.Source;
036: import org.apache.excalibur.source.SourceException;
037: import org.apache.excalibur.source.SourceFactory;
038: import org.apache.excalibur.source.SourceResolver;
039: import org.apache.regexp.RE;
040: import org.apache.regexp.RESyntaxException;
041:
042: /**
043: * This {@link org.apache.excalibur.source.SourceFactory SourceFactory} creates {@link
044: * org.apache.excalibur.source.Source Source}s for the <code>javadoc:</code> URI scheme.
045: *
046: * <p>The goal for this <code>SourceFactory</code> is to provide a <code>Source</code>
047: * for a Java sourcefile containing as much information as possible to mimic the
048: * standard Javadoc output.</p>
049: *
050: * <p>The Source provides the following content:
051: * <ul>
052: * <li>Classname</li>
053: * <li>Superclass</li>
054: * <li>Imports, including <code>java.lang</code> and the class' package</li>
055: * <li>Implemented interfaces</li>
056: * <li>Inner classes/interfaces, including superclass, implemented interfaces and
057: * Javadoc (inner classes can be requested separately)</li>
058: * <li>Fields, including type, name and Javadoc</li>
059: * <li>Constructors, including parameters with their types and names, signature,
060: * Javadoc and thrown exceptions</li>
061: * <li>Methods, including returntype, parameters, signature, Javadoc and thrown
062: * exceptions</li>
063: * <li>Inheritance tree for each Class member, if needed</li>
064: * <li>Private members, if needed</li>
065: * </ul>
066: * </p>
067: *
068: * <p>With this <code>SourceFactory</code>, you create Doclets with XSLT stylesheets
069: * instead of Java code.</p>
070: *
071: * <p>The <code>QDoxSourceFactory</code> uses <a href="http://qdox.sf.net/">QDox</a>
072: * to parse the Java sourcefiles.
073: * </p>
074: *
075: * @author <a href="mailto:b.guijt1@chello.nl">Bart Guijt</a>
076: * @version CVS $Id: QDoxSourceFactory.java 433543 2006-08-22 06:22:54Z crossley $
077: */
078: public final class QDoxSourceFactory extends AbstractLogEnabled
079: implements SourceFactory, Serviceable, Configurable, ThreadSafe {
080:
081: protected final static String INCLUDE_INHERITANCE_ELEMENT = "include-inheritance";
082: protected final static String VALUE_ATTRIBUTE = "value";
083: protected final static String SOURCE_GROUP_ELEMENT = "source-roots";
084: protected final static String GROUP_ATTRIBUTE = "group";
085: protected final static String SOURCE_ROOT_ELEMENT = "source-root";
086: protected final static String URI_ATTRIBUTE = "uri";
087:
088: protected ServiceManager manager;
089: protected List sourceRootUris;
090:
091: /**
092: * RegExp matcher for Java classnames: distinguishes package and classname.
093: */
094: protected RE rePackageClass;
095:
096: /**
097: * RegExp matcher for Java classnames: distinguishes package, classname and innerclassname.
098: */
099: protected RE rePackageClassInnerclass;
100:
101: /**
102: * Represents an URI and which packages it contains.
103: *
104: * <p>Using this class, the QDoxSourceFactory can quickly find the right SourceRoot URI given a specified
105: * package.</p>
106: */
107: protected static final class SourceRoot {
108: private List packages;
109: private String sourceRootUri;
110:
111: protected SourceRoot(String uri) {
112: if (!uri.endsWith(File.separator)) {
113: uri += '/';
114: }
115: sourceRootUri = uri;
116: packages = new ArrayList();
117: }
118:
119: protected void addPackage(String packageName) {
120: packages.add(packageName);
121: }
122:
123: protected boolean hasPackage(String packageName) {
124: return packages.contains(packageName);
125: }
126:
127: protected String getUri() {
128: return sourceRootUri;
129: }
130: }
131:
132: /**
133: * @see org.apache.excalibur.source.SourceFactory#getSource(java.lang.String, java.util.Map)
134: */
135: public Source getSource(String location, Map parameters)
136: throws MalformedURLException, IOException, SourceException {
137: String className = location
138: .substring(location.indexOf(':') + 1);
139: Source javaSource = null;
140: if (className.length() > 0) {
141: try {
142: if (getLogger().isDebugEnabled()) {
143: getLogger().debug(
144: "getSource called with className="
145: + className);
146: }
147: javaSource = getSource(className);
148: } catch (ServiceException se) {
149: throw new SourceException("SourceResolver not found",
150: se);
151: }
152: } else {
153: throw new MalformedURLException();
154: }
155:
156: QDoxSource result = null;
157: if (javaSource != null) {
158: return new QDoxSource(location, javaSource, getLogger(),
159: manager);
160: }
161:
162: if (getLogger().isDebugEnabled()) {
163: getLogger().debug(
164: "returning source=" + result + " for className="
165: + className);
166: }
167:
168: return result;
169: }
170:
171: /**
172: * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
173: */
174: public void service(ServiceManager manager) throws ServiceException {
175: if (getLogger().isDebugEnabled()) {
176: getLogger().debug("Composing the QDoxSourceFactory...");
177: }
178: this .manager = manager;
179:
180: try {
181: rePackageClass = new RE("([$\\w.]+)\\.([$\\w]+)");
182: rePackageClassInnerclass = new RE(
183: "([$\\w.]+)\\.([$\\w]+)\\.([$\\w]+)");
184: } catch (RESyntaxException e) {
185: getLogger().error("RegExp syntax error!", e);
186: }
187: }
188:
189: /**
190: * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration)
191: */
192: public void configure(Configuration config)
193: throws ConfigurationException {
194: Configuration[] sourceRootGroups = config
195: .getChildren(SOURCE_GROUP_ELEMENT);
196: sourceRootUris = new ArrayList();
197:
198: for (int i = 0; i < sourceRootGroups.length; i++) {
199: Configuration[] sourceRootConfigs = sourceRootGroups[i]
200: .getChildren(SOURCE_ROOT_ELEMENT);
201:
202: for (int j = 0; j < sourceRootConfigs.length; j++) {
203: String uri = sourceRootConfigs[j]
204: .getAttribute(URI_ATTRIBUTE);
205: sourceRootUris.add(new SourceRoot(uri));
206: }
207: }
208:
209: if (sourceRootUris.size() == 0 && getLogger().isErrorEnabled()) {
210: getLogger().error("No source roots configured!");
211: }
212: }
213:
214: /**
215: * Releases the specified Source.
216: *
217: * @see org.apache.excalibur.source.SourceFactory#release(org.apache.excalibur.source.Source)
218: */
219: public void release(Source source) {
220: // ??? What to do here?
221: }
222:
223: /**
224: * Method getSource.
225: *
226: * @param className
227: * @return File
228: */
229: private Source getSource(String className) throws ServiceException {
230: String classFileName = className;
231: String packageName;
232:
233: if (rePackageClass.match(className)) {
234: packageName = rePackageClass.getParen(1);
235: } else {
236: packageName = "";
237: }
238:
239: classFileName = classFileName.replace('.', '/') + ".java";
240: SourceResolver resolver = (SourceResolver) manager
241: .lookup(SourceResolver.ROLE);
242:
243: Source source = getSource(classFileName, packageName, resolver);
244: if (source == null && rePackageClassInnerclass.match(className)) {
245: // Inner class?
246:
247: packageName = rePackageClassInnerclass.getParen(1);
248: classFileName = className.substring(0,
249: className.lastIndexOf('.')).replace('.', '/')
250: + ".java";
251: source = getSource(classFileName, packageName, resolver);
252: }
253: manager.release(resolver);
254:
255: if (source == null && getLogger().isWarnEnabled()) {
256: getLogger().warn(
257: "No source found for class '" + className + "'!");
258: }
259:
260: return source;
261: }
262:
263: private Source getSource(String classFileName, String packageName,
264: SourceResolver resolver) {
265: // First, test whether there are configured packages to speed things up:
266: for (Iterator i = sourceRootUris.iterator(); i.hasNext();) {
267: SourceRoot sourceRoot = (SourceRoot) i.next();
268:
269: if (sourceRoot.hasPackage(packageName)) {
270: String uri = sourceRoot.getUri() + classFileName;
271: Source source = getSource(uri, resolver);
272: if (source != null) {
273: return source;
274: }
275: }
276: }
277:
278: // No suitable package found, iterate all source roots:
279: for (Iterator i = sourceRootUris.iterator(); i.hasNext();) {
280: SourceRoot sourceRoot = (SourceRoot) i.next();
281: String uri = sourceRoot.getUri() + classFileName;
282:
283: Source source = getSource(uri, resolver);
284: if (source != null) {
285: sourceRoot.addPackage(packageName);
286: return source;
287: }
288: }
289: return null;
290: }
291:
292: /**
293: * Method getSource.
294: *
295: * @param uri
296: * @param resolver
297: * @return Source
298: */
299: private Source getSource(String uri, SourceResolver resolver) {
300: if (getLogger().isDebugEnabled()) {
301: getLogger().debug("Testing uri <" + uri + ">...");
302: }
303: try {
304: Source source = resolver.resolveURI(uri);
305:
306: if (source != null && source.getInputStream() != null) {
307: return source;
308: } else {
309: if (getLogger().isDebugEnabled()) {
310: getLogger().debug("uri <" + uri + "> is invalid.");
311: }
312: }
313: } catch (Exception e) {
314: if (getLogger().isDebugEnabled()) {
315: getLogger().debug(
316: "uri <" + uri + "> is invalid: "
317: + e.getClass().getName() + " says "
318: + e.getMessage());
319: }
320: }
321: return null;
322: }
323: }
|