| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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.components.source.impl; |
| |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.net.MalformedURLException; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.avalon.framework.configuration.Configurable; |
| import org.apache.avalon.framework.configuration.Configuration; |
| import org.apache.avalon.framework.configuration.ConfigurationException; |
| import org.apache.avalon.framework.logger.AbstractLogEnabled; |
| import org.apache.avalon.framework.service.ServiceException; |
| import org.apache.avalon.framework.service.ServiceManager; |
| import org.apache.avalon.framework.service.Serviceable; |
| import org.apache.avalon.framework.thread.ThreadSafe; |
| import org.apache.excalibur.source.Source; |
| import org.apache.excalibur.source.SourceException; |
| import org.apache.excalibur.source.SourceFactory; |
| import org.apache.excalibur.source.SourceResolver; |
| import org.apache.regexp.RE; |
| import org.apache.regexp.RESyntaxException; |
| |
| |
| /** |
| * This {@link org.apache.excalibur.source.SourceFactory SourceFactory} creates {@link |
| * org.apache.excalibur.source.Source Source}s for the <code>javadoc:</code> URI scheme. |
| * |
| * <p>The goal for this <code>SourceFactory</code> is to provide a <code>Source</code> |
| * for a Java sourcefile containing as much information as possible to mimic the |
| * standard Javadoc output.</p> |
| * |
| * <p>The Source provides the following content: |
| * <ul> |
| * <li>Classname</li> |
| * <li>Superclass</li> |
| * <li>Imports, including <code>java.lang</code> and the class' package</li> |
| * <li>Implemented interfaces</li> |
| * <li>Inner classes/interfaces, including superclass, implemented interfaces and |
| * Javadoc (inner classes can be requested separately)</li> |
| * <li>Fields, including type, name and Javadoc</li> |
| * <li>Constructors, including parameters with their types and names, signature, |
| * Javadoc and thrown exceptions</li> |
| * <li>Methods, including returntype, parameters, signature, Javadoc and thrown |
| * exceptions</li> |
| * <li>Inheritance tree for each Class member, if needed</li> |
| * <li>Private members, if needed</li> |
| * </ul> |
| * </p> |
| * |
| * <p>With this <code>SourceFactory</code>, you create Doclets with XSLT stylesheets |
| * instead of Java code.</p> |
| * |
| * <p>The <code>QDoxSourceFactory</code> uses <a href="http://qdox.sf.net/">QDox</a> |
| * to parse the Java sourcefiles. |
| * </p> |
| * |
| * @author <a href="mailto:b.guijt1@chello.nl">Bart Guijt</a> |
| * @version CVS $Id$ |
| */ |
| public final class QDoxSourceFactory |
| extends AbstractLogEnabled |
| implements SourceFactory, Serviceable, Configurable, ThreadSafe { |
| |
| protected final static String INCLUDE_INHERITANCE_ELEMENT = "include-inheritance"; |
| protected final static String VALUE_ATTRIBUTE = "value"; |
| protected final static String SOURCE_GROUP_ELEMENT = "source-roots"; |
| protected final static String GROUP_ATTRIBUTE = "group"; |
| protected final static String SOURCE_ROOT_ELEMENT = "source-root"; |
| protected final static String URI_ATTRIBUTE = "uri"; |
| |
| protected ServiceManager manager; |
| protected List sourceRootUris; |
| |
| /** |
| * RegExp matcher for Java classnames: distinguishes package and classname. |
| */ |
| protected RE rePackageClass; |
| |
| /** |
| * RegExp matcher for Java classnames: distinguishes package, classname and innerclassname. |
| */ |
| protected RE rePackageClassInnerclass; |
| |
| |
| /** |
| * Represents an URI and which packages it contains. |
| * |
| * <p>Using this class, the QDoxSourceFactory can quickly find the right SourceRoot URI given a specified |
| * package.</p> |
| */ |
| protected static final class SourceRoot { |
| private List packages; |
| private String sourceRootUri; |
| |
| protected SourceRoot(String uri) { |
| if (!uri.endsWith(File.separator)) { |
| uri += '/'; |
| } |
| sourceRootUri = uri; |
| packages = new ArrayList(); |
| } |
| |
| protected void addPackage(String packageName) { |
| packages.add(packageName); |
| } |
| |
| protected boolean hasPackage(String packageName) { |
| return packages.contains(packageName); |
| } |
| |
| protected String getUri() { |
| return sourceRootUri; |
| } |
| } |
| |
| |
| /** |
| * @see org.apache.excalibur.source.SourceFactory#getSource(java.lang.String, java.util.Map) |
| */ |
| public Source getSource(String location, Map parameters) throws MalformedURLException, IOException, SourceException { |
| String className = location.substring(location.indexOf(':') + 1); |
| Source javaSource = null; |
| if (className.length() > 0) { |
| try { |
| if(getLogger().isDebugEnabled()) { |
| getLogger().debug("getSource called with className=" + className); |
| } |
| javaSource = getSource(className); |
| } catch (ServiceException se) { |
| throw new SourceException("SourceResolver not found", se); |
| } |
| } else { |
| throw new MalformedURLException(); |
| } |
| |
| QDoxSource result = null; |
| if (javaSource != null) { |
| return new QDoxSource(location, javaSource, getLogger(), manager); |
| } |
| |
| if(getLogger().isDebugEnabled()) { |
| getLogger().debug("returning source=" + result + " for className=" + className); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager) |
| */ |
| public void service(ServiceManager manager) throws ServiceException { |
| if (getLogger().isDebugEnabled()) { |
| getLogger().debug("Composing the QDoxSourceFactory..."); |
| } |
| this.manager = manager; |
| |
| try { |
| rePackageClass = new RE("([$\\w.]+)\\.([$\\w]+)"); |
| rePackageClassInnerclass = new RE("([$\\w.]+)\\.([$\\w]+)\\.([$\\w]+)"); |
| } catch (RESyntaxException e) { |
| getLogger().error("RegExp syntax error!", e); |
| } |
| } |
| |
| /** |
| * @see org.apache.avalon.framework.configuration.Configurable#configure(org.apache.avalon.framework.configuration.Configuration) |
| */ |
| public void configure(Configuration config) throws ConfigurationException { |
| Configuration[] sourceRootGroups = config.getChildren(SOURCE_GROUP_ELEMENT); |
| sourceRootUris = new ArrayList(); |
| |
| for (int i=0; i<sourceRootGroups.length; i++) { |
| Configuration[] sourceRootConfigs = sourceRootGroups[i].getChildren(SOURCE_ROOT_ELEMENT); |
| |
| for (int j=0; j<sourceRootConfigs.length; j++) { |
| String uri = sourceRootConfigs[j].getAttribute(URI_ATTRIBUTE); |
| sourceRootUris.add(new SourceRoot(uri)); |
| } |
| } |
| |
| if (sourceRootUris.size() == 0 && getLogger().isErrorEnabled()) { |
| getLogger().error("No source roots configured!"); |
| } |
| } |
| |
| /** |
| * Releases the specified Source. |
| * |
| * @see org.apache.excalibur.source.SourceFactory#release(org.apache.excalibur.source.Source) |
| */ |
| public void release(Source source) { |
| // ??? What to do here? |
| } |
| |
| /** |
| * Method getSource. |
| * |
| * @param className |
| * @return File |
| */ |
| private Source getSource(String className) throws ServiceException { |
| String classFileName = className; |
| String packageName; |
| |
| if (rePackageClass.match(className)) { |
| packageName = rePackageClass.getParen(1); |
| } else { |
| packageName = ""; |
| } |
| |
| classFileName = classFileName.replace('.', '/') + ".java"; |
| SourceResolver resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); |
| |
| Source source = getSource(classFileName, packageName, resolver); |
| if (source == null && rePackageClassInnerclass.match(className)) { |
| // Inner class? |
| |
| packageName = rePackageClassInnerclass.getParen(1); |
| classFileName = className.substring(0, className.lastIndexOf('.')).replace('.', '/') + ".java"; |
| source = getSource(classFileName, packageName, resolver); |
| } |
| manager.release(resolver); |
| |
| if (source == null && getLogger().isWarnEnabled()) { |
| getLogger().warn("No source found for class '" + className + "'!"); |
| } |
| |
| return source; |
| } |
| |
| private Source getSource(String classFileName, String packageName, SourceResolver resolver) { |
| // First, test whether there are configured packages to speed things up: |
| for (Iterator i = sourceRootUris.iterator(); i.hasNext();) { |
| SourceRoot sourceRoot = (SourceRoot) i.next(); |
| |
| if (sourceRoot.hasPackage(packageName)) { |
| String uri = sourceRoot.getUri() + classFileName; |
| Source source = getSource(uri, resolver); |
| if (source != null) { |
| return source; |
| } |
| } |
| } |
| |
| // No suitable package found, iterate all source roots: |
| for (Iterator i = sourceRootUris.iterator(); i.hasNext();) { |
| SourceRoot sourceRoot = (SourceRoot) i.next(); |
| String uri = sourceRoot.getUri() + classFileName; |
| |
| Source source = getSource(uri, resolver); |
| if (source != null) { |
| sourceRoot.addPackage(packageName); |
| return source; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Method getSource. |
| * |
| * @param uri |
| * @param resolver |
| * @return Source |
| */ |
| private Source getSource(String uri, SourceResolver resolver) { |
| if (getLogger().isDebugEnabled()) { |
| getLogger().debug("Testing uri <" + uri + ">..."); |
| } |
| try { |
| Source source = resolver.resolveURI(uri); |
| |
| if (source != null && source.getInputStream() != null) { |
| return source; |
| } else { |
| if (getLogger().isDebugEnabled()) { |
| getLogger().debug("uri <" + uri + "> is invalid."); |
| } |
| } |
| } catch (Exception e) { |
| if (getLogger().isDebugEnabled()) { |
| getLogger().debug("uri <" + uri + "> is invalid: " + e.getClass().getName() + " says " + e.getMessage()); |
| } |
| } |
| return null; |
| } |
| } |