| /* |
| * Copyright 1999-2002,2004 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.components.source.impl; |
| |
| import java.io.BufferedReader; |
| import java.io.ByteArrayInputStream; |
| import java.io.ByteArrayOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.net.MalformedURLException; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| import com.thoughtworks.qdox.JavaDocBuilder; |
| import com.thoughtworks.qdox.model.AbstractJavaEntity; |
| import com.thoughtworks.qdox.model.DocletTag; |
| import com.thoughtworks.qdox.model.JavaClass; |
| import com.thoughtworks.qdox.model.JavaField; |
| import com.thoughtworks.qdox.model.JavaMethod; |
| import com.thoughtworks.qdox.model.JavaParameter; |
| import com.thoughtworks.qdox.model.JavaSource; |
| import com.thoughtworks.qdox.model.Type; |
| |
| import org.apache.avalon.excalibur.pool.Recyclable; |
| import org.apache.avalon.framework.logger.Logger; |
| import org.apache.avalon.framework.service.ServiceException; |
| import org.apache.avalon.framework.service.ServiceManager; |
| import org.apache.cocoon.serialization.XMLSerializer; |
| import org.apache.excalibur.source.Source; |
| import org.apache.excalibur.source.SourceException; |
| import org.apache.excalibur.source.SourceResolver; |
| import org.apache.excalibur.source.SourceValidity; |
| import org.apache.excalibur.source.impl.AbstractSource; |
| import org.apache.excalibur.xml.sax.XMLizable; |
| import org.apache.regexp.RE; |
| import org.apache.regexp.RESyntaxException; |
| import org.xml.sax.Attributes; |
| import org.xml.sax.ContentHandler; |
| import org.xml.sax.SAXException; |
| import org.xml.sax.helpers.AttributesImpl; |
| |
| /** |
| * Source implementation for XML Javadoc. |
| * |
| * @author <a href="mailto:b.guijt1@chello.nl">Bart Guijt</a> |
| * @version CVS $Id: QDoxSource.java,v 1.7 2004/03/05 13:02:21 bdelacretaz Exp $ $Date: 2004/03/05 13:02:21 $ |
| */ |
| public final class QDoxSource |
| extends AbstractSource |
| implements XMLizable, Recyclable { |
| |
| protected final static String ROOT_CLASSNAME = "java.lang.Object"; |
| |
| protected final static String EMPTY = ""; |
| protected final static String PROTOCOL = "qdox:"; |
| protected final static String NS_URI = "http://apache.org/cocoon/javadoc/1.0"; |
| protected final static String NS_PREFIX = "jd"; |
| protected final static String ATTR_TYPE = "NMTOKEN"; |
| protected final static Attributes EMPTY_ATTRS = new AttributesImpl(); |
| |
| protected final static String CLASS_ELEMENT = "class"; |
| protected final static String CLASSNAME_ATTRIBUTE = "name"; |
| protected final static String PACKAGE_ATTRIBUTE = "package"; |
| protected final static String QNAME_ATTRIBUTE = "qname"; |
| protected final static String INHERIT_ELEMENT = "inherit"; |
| protected final static String INNERCLASSES_ELEMENT = "innerclasses"; |
| protected final static String NESTED_IN_ELEMENT = "nested-in"; |
| protected final static String IMPORTS_ELEMENT = "imports"; |
| protected final static String IMPORT_ELEMENT = "import"; |
| protected final static String IMPORT_ATTRIBUTE = "type"; |
| protected final static String IMPLEMENTS_ELEMENT = "implements"; |
| protected final static String INTERFACE_ELEMENT = "interface"; |
| protected final static String MODIFIERS_ELEMENT = "modifiers"; |
| protected final static String COMMENT_ELEMENT = "comment"; |
| protected final static String LINK_ELEMENT = "link"; |
| protected final static String LINK_CLASS_ATTRIBUTE = "class"; |
| protected final static String LINK_MEMBER_ATTRIBUTE = "member"; |
| protected final static String HREF_ATTRIBUTE = "uri"; |
| protected final static String TAGS_ELEMENT = "tags"; |
| protected final static String FIELDS_ELEMENT = "fields"; |
| protected final static String FIELD_ELEMENT = "field"; |
| protected final static String CONSTRUCTORS_ELEMENT = "constructors"; |
| protected final static String CONSTRUCTOR_ELEMENT = "constructor"; |
| protected final static String METHODS_ELEMENT = "methods"; |
| protected final static String METHOD_ELEMENT = "method"; |
| protected final static String NAME_ATTRIBUTE = "name"; |
| protected final static String TYPE_ATTRIBUTE = "type"; |
| protected final static String DIMENSIONS_ATTRIBUTE = "dimensions"; |
| protected final static String SIGNATURE_ATTRIBUTE = "signature"; |
| protected final static String PARAMETERS_ELEMENT = "parameters"; |
| protected final static String PARAMETER_ELEMENT = "parameter"; |
| protected final static String THROWS_ELEMENT = "throws"; |
| protected final static String EXCEPTION_ELEMENT = "exception"; |
| |
| protected final static int CONSTRUCTOR_MODE = 1; |
| protected final static int METHOD_MODE = 2; |
| |
| protected final static int CLASS_INHERITANCE = 1; |
| protected final static int INTERFACE_INHERITANCE = 2; |
| protected final static int INNERCLASS_INHERITANCE = 3; |
| protected final static int FIELD_INHERITANCE = 4; |
| protected final static int CONSTRUCTOR_INHERITANCE = 5; |
| protected final static int METHOD_INHERITANCE = 6; |
| |
| protected ServiceManager manager; |
| protected Logger logger; |
| |
| protected Source javaSource; |
| protected String javadocUri; |
| protected String javadocClassName; |
| protected JavaClass javadocClass; |
| protected JavaClass containingJavadocClass; // in case javadocClass is an inner class |
| protected Map classMap; |
| |
| /** |
| * This RegExp matches the <code>{</code><code>@link …}</code> occurrances in |
| * Javadoc comments. |
| */ |
| protected RE reLink; |
| |
| /** |
| * Contains a regular expression to match the <code>{</code><code>@link …}</code> occurrances. |
| * |
| * <p>The following <code>{</code><code>@link …}</code> literals are recognized:</p> |
| * |
| * <ul> |
| * <li><code>{</code><code>@link HashMap}</code> - returns 'Hashmap' with <code>reLink.getParen(6)</code>;</li> |
| * <li><code>{</code><code>@link #equals(java.lang.Object) equals(…)}</code> - returns '#equals(java.lang.Object)' with <code>reLink.getParen(2)</code> |
| * and 'equals(…)' with <code>reLink.getParen(5)</code>;</li> |
| * <li><code>{</code><code>@link #indexOf(char, int) indexOf(…)}</code> - returns '#indexOf(char, int)' with <code>reLink.getParen(2)</code> |
| * and 'indexOf(…)' with <code>reLink.getParen(5)</code>.</li> |
| * </ul> |
| * <p>The regexp is as follows:</p> |
| * <code>\{@link\s+((([\w.#,$&;\s]+)|([\w.#,$&;(\s]+[\w.#,$&;)\s]+))\s+([\w()#.,$&;\s]+)|([\w.#,$&;\s()]+))\s*\}</code> |
| * |
| * @see #reLink |
| */ |
| protected final static String RE_LINK = "\\{@link\\s+((([\\w.#,$&;\\s]+)|([\\w.#,$&;(\\s]+[\\w.#,$&;)\\s]+))\\s+([\\w()#.,$&;\\s]+)|([\\w.#,$&;\\s()]+))\\s*\\}"; |
| |
| /** |
| * Constructor for QDoxSource. |
| * |
| * @param location |
| * @param javaSource |
| * @param logger |
| * @param manager |
| */ |
| public QDoxSource(String location, Source javaSource, Logger logger, ServiceManager manager) { |
| this.javadocUri = location; |
| this.javaSource = javaSource; |
| this.logger = logger; |
| this.manager = manager; |
| |
| this.javadocClassName = javadocUri.substring(javadocUri.indexOf(':') + 1); |
| |
| try { |
| createJavadocXml(); |
| } catch (SourceException se) { |
| logger.error("Error reading source!", se); |
| } catch (IOException ioe) { |
| logger.error("Error reading source!", ioe); |
| } |
| |
| // Initialize regular expression: |
| try { |
| reLink = new RE(RE_LINK); |
| } catch (RESyntaxException rse) { |
| logger.error("Regular Expression syntax error!", rse); |
| } |
| } |
| |
| /** |
| * Returns the parsed Java class. |
| * |
| * @return JavaClass |
| */ |
| public JavaClass getJavadocClass() { |
| return javadocClass; |
| } |
| |
| /** |
| * @see XMLizable#toSAX(org.xml.sax.ContentHandler) |
| * @throws SAXException if any error occurs during SAX outputting. |
| */ |
| public void toSAX(ContentHandler handler) throws SAXException { |
| if (javadocClass == null) { |
| logger.error("No classfile loaded! Cannot output SAX events."); |
| return; |
| } |
| |
| if (logger.isDebugEnabled()) { |
| logger.debug("Outputting SAX events for class " + javadocClass.getFullyQualifiedName()); |
| logger.debug(" #fields: " + javadocClass.getFields().length); |
| logger.debug(" #methods and constructors: " + javadocClass.getMethods().length); |
| } |
| |
| // Output SAX 'header': |
| handler.startDocument(); |
| handler.startPrefixMapping(NS_PREFIX, NS_URI); |
| |
| // Output class-level element: |
| outputClassStartElement(handler, javadocClass); |
| |
| // Modifiers: |
| outputModifiers(handler, javadocClass); |
| |
| // Imports: |
| JavaSource parent = javadocClass.getParentSource(); |
| // Add two implicit imports: |
| parent.addImport("java.lang.*"); |
| if (parent.getPackage().length() > 0) { |
| parent.addImport(parent.getPackage() + ".*"); |
| } else { |
| parent.addImport("*"); |
| } |
| String[] imports = parent.getImports(); |
| |
| saxStartElement(handler, IMPORTS_ELEMENT); |
| for (int i=0; i<imports.length; i++) { |
| if (imports[i].endsWith("*")) { |
| // package import: |
| saxStartElement(handler, IMPORT_ELEMENT, new String[][] {{IMPORT_ATTRIBUTE, "package"}}); |
| String imp = imports[i]; |
| while (imp.endsWith("*") || imp.endsWith(".")) { |
| imp = imp.substring(0, imp.length() - 1); |
| } |
| saxCharacters(handler, imp); |
| } else { |
| saxStartElement(handler, IMPORT_ELEMENT, new String[][] {{IMPORT_ATTRIBUTE, "class"}}); |
| saxCharacters(handler, imports[i]); |
| } |
| saxEndElement(handler, IMPORT_ELEMENT); |
| } |
| saxEndElement(handler, IMPORTS_ELEMENT); |
| |
| // Superclass: |
| if (!javadocClass.isInterface()) { |
| outputSuperClassInheritance(handler, javadocClass, CLASS_INHERITANCE); |
| } |
| |
| // Implements: |
| outputImplements(handler, javadocClass, true); |
| |
| // Containing class in case this is an inner class: |
| if (containingJavadocClass != null) { |
| saxStartElement(handler, NESTED_IN_ELEMENT); |
| outputClassStartElement(handler, containingJavadocClass); |
| outputModifiers(handler, containingJavadocClass); |
| outputComment(handler, containingJavadocClass.getComment()); |
| outputTags(handler, containingJavadocClass); |
| outputClassEndElement(handler, containingJavadocClass); |
| saxEndElement(handler, NESTED_IN_ELEMENT); |
| } |
| |
| // Comment: |
| outputComment(handler, javadocClass.getComment()); |
| |
| // Tags: |
| outputTags(handler, javadocClass); |
| |
| // Inner classes: |
| outputInnerClasses(handler, javadocClass, true); |
| |
| // Fields: |
| outputFields(handler, javadocClass, true); |
| |
| // Constructors: |
| outputMethods(handler, javadocClass, CONSTRUCTOR_MODE); |
| |
| // Methods: |
| outputMethods(handler, javadocClass, METHOD_MODE); |
| |
| // Close class-level element: |
| outputClassEndElement(handler, javadocClass); |
| |
| // Output SAX 'footer': |
| handler.endPrefixMapping(NS_PREFIX); |
| handler.endDocument(); |
| } |
| |
| /** |
| * @see Recyclable#recycle() |
| */ |
| public void recycle() { |
| if (logger != null && logger.isDebugEnabled()) { |
| logger.debug("Recycling QDoxSource '" + javadocClassName + "'..."); |
| } |
| |
| manager = null; |
| javaSource = null; |
| javadocUri = null; |
| javadocClassName = null; |
| javadocClass = null; |
| containingJavadocClass = null; |
| classMap = null; |
| logger = null; |
| } |
| |
| /** |
| * Get the content length of the source or -1 if it |
| * is not possible to determine the length. |
| */ |
| public long getContentLength() { |
| return -1L; |
| } |
| |
| /** |
| * @see org.apache.excalibur.source.Source#getLastModified() |
| */ |
| public long getLastModified() { |
| return javaSource.getLastModified(); |
| } |
| |
| /** |
| * @see org.apache.excalibur.source.Source#getMimeType() |
| */ |
| public String getMimeType() { |
| return "text/xml"; |
| } |
| |
| /** |
| * Return the unique identifer for this source |
| */ |
| public String getURI() { |
| return javadocUri; |
| } |
| |
| /** |
| * @see org.apache.excalibur.source.Source#getValidity() |
| */ |
| public SourceValidity getValidity() { |
| return javaSource.getValidity(); |
| } |
| |
| /** |
| * @see org.apache.excalibur.source.Source#getInputStream() |
| */ |
| public InputStream getInputStream() throws IOException, SourceException { |
| if (logger.isDebugEnabled()) { |
| logger.debug("Getting InputStream for class " + javadocClass.getFullyQualifiedName()); |
| } |
| |
| // Serialize the SAX events to the XMLSerializer: |
| |
| XMLSerializer serializer = new XMLSerializer(); |
| //ComponentSelector serializerSelector = null; |
| ByteArrayInputStream inputStream = null; |
| |
| try { |
| //serializerSelector = (ComponentSelector) manager.lookup(Serializer.ROLE + "Selector"); |
| //logger.debug("serializer selector: " + serializerSelector.toString()); |
| //serializer = (XMLSerializer) serializerSelector.select(XMLSerializer.class); |
| ByteArrayOutputStream outputStream = new ByteArrayOutputStream(2048); |
| serializer.setOutputStream(outputStream); |
| toSAX(serializer); |
| inputStream = new ByteArrayInputStream(outputStream.toByteArray()); |
| //} catch (ComponentException ce) { |
| // logger.error("Component not found: " + XMLSerializer.ROLE, ce); |
| // throw new SourceException("Component lookup of XMLSerializer failed!", ce); |
| } catch (SAXException se) { |
| logger.error("SAX exception!", se); |
| throw new SourceException("Serializing SAX to a ByteArray failed!", se); |
| //} finally { |
| //serializerSelector.release(serializer); |
| //manager.release(serializerSelector); |
| } |
| |
| return inputStream; |
| } |
| |
| protected void createJavadocXml() throws SourceException, IOException { |
| if (logger.isDebugEnabled()) { |
| logger.debug("Reading Java source " + javaSource.getURI()); |
| } |
| |
| JavaDocBuilder builder = new JavaDocBuilder(); |
| builder.addSource(new BufferedReader(new InputStreamReader(javaSource.getInputStream()))); |
| |
| javadocClass = builder.getClassByName(javadocClassName); |
| if (javadocClass == null) { |
| // An inner class is specified - let's find it: |
| int index = javadocUri.lastIndexOf('.'); |
| String containingClassName = javadocUri.substring(javadocUri.indexOf(':') + 1, index); |
| String innerClassName = javadocUri.substring(index + 1); |
| containingJavadocClass = builder.getClassByName(containingClassName); |
| javadocClass = getJavadocInnerClass(containingJavadocClass, innerClassName); |
| } |
| } |
| |
| /** |
| * Method resolveMemberNameFromLink. |
| * |
| * @param ref |
| * @return String |
| */ |
| private String resolveMemberNameFromLink(String ref) { |
| int hashIndex = ref.indexOf('#'); |
| if (hashIndex < 0) { |
| return ""; |
| } else { |
| return ref.substring(hashIndex + 1); |
| } |
| } |
| |
| /** |
| * Method resolveClassNameFromLink. |
| * |
| * @param ref |
| * @return String |
| */ |
| private String resolveClassNameFromLink(String ref) { |
| String classPart = null; |
| int hashIndex = ref.indexOf('#'); |
| if (hashIndex < 0) { |
| classPart = ref; |
| } else { |
| classPart = ref.substring(0, hashIndex); |
| } |
| return getQualifiedClassName(classPart); |
| } |
| |
| private String getQualifiedClassName(String classPart) { |
| if (classPart.length() == 0) { |
| // No classname specified: |
| classPart = javadocClass.getFullyQualifiedName(); |
| } else if (classPart.equals("Object")) { |
| // Fastest way to identify the root object - otherwise the next, *expensive* 'if' block is executed! |
| classPart = ROOT_CLASSNAME; |
| } else if (classPart.indexOf('.') < 0) { |
| // No qualified name specified: |
| String[] imports = javadocClass.getParentSource().getImports(); |
| List classImports = new ArrayList(); |
| List packageImports = new ArrayList(); |
| packageImports.add(javadocClass.getPackage()); // Most likely to find sources here, I guess... |
| packageImports.add("java.lang"); // 2nd most likely place to find sources? |
| for (int i=0; i<imports.length; i++) { |
| if (imports[i].endsWith(".*")) { |
| packageImports.add(imports[i].substring(0, imports[i].length() - 2)); |
| } else if (imports[i].endsWith("*")) { |
| packageImports.add(imports[i].substring(0, imports[i].length() - 1)); |
| } else { |
| classImports.add(imports[i]); |
| } |
| } |
| |
| boolean found = false; |
| for (int i=0; !found && i<classImports.size(); i++) { |
| String name = (String) classImports.get(i); |
| if (name.endsWith(classPart)) { |
| classPart = name; |
| found = true; |
| } |
| } |
| |
| if (!found) { |
| SourceResolver resolver = null; |
| try { |
| resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); |
| for (int i=0; !found && i<packageImports.size(); i++) { |
| String name = (String) packageImports.get(i); |
| if (name.length() == 0) { |
| name = classPart; |
| } else { |
| name += '.' + classPart; |
| } |
| |
| // Test whether the classname 'name' is valid: |
| Source source = resolver.resolveURI(PROTOCOL + name); |
| found = source != null && source instanceof QDoxSource; |
| if (found) { |
| classPart = name; |
| } |
| |
| resolver.release(source); |
| } |
| } catch (ServiceException se) { |
| logger.error("Could not find a SourceResolver!", se); |
| } catch (MalformedURLException e) { |
| // ignore - invalid URI (is subject of test) |
| } catch (SourceException e) { |
| // ignore |
| } catch (IOException e) { |
| // ignore |
| } finally { |
| if (resolver != null) { |
| manager.release(resolver); |
| } |
| } |
| } |
| } |
| |
| return classPart; |
| } |
| |
| /** |
| * Method outputClassInheritance. |
| * |
| * @param handler |
| * @param jClass |
| */ |
| private void outputSuperClassInheritance(ContentHandler handler, JavaClass jClass, int mode) throws SAXException { |
| JavaClass superClass = getJavadocSuperClass(jClass); |
| if (superClass != null && hasInheritance(jClass, mode)) { |
| outputClassInheritance(handler, superClass, mode); |
| } |
| } |
| |
| private void outputClassInheritance(ContentHandler handler, JavaClass jClass, int mode) throws SAXException { |
| outputInheritStartElement(handler, jClass); |
| |
| switch (mode) { |
| case CLASS_INHERITANCE : |
| // Already there! |
| outputSuperClassInheritance(handler, jClass, mode); |
| break; |
| |
| case INTERFACE_INHERITANCE : |
| // Output interface inheritance summary: |
| outputImplements(handler, jClass, false); |
| break; |
| |
| case INNERCLASS_INHERITANCE : |
| // Output nested inheritance summary: |
| outputInnerClasses(handler, jClass, false); |
| break; |
| |
| case FIELD_INHERITANCE : |
| // Output field inheritance summary: |
| outputFields(handler, jClass, false); |
| break; |
| |
| case METHOD_INHERITANCE : |
| // Output method inheritance summary from implemented interfaces: |
| Type[] interfaces = jClass.getImplements(); |
| for (int i=0; i<interfaces.length; i++) { |
| logger.debug("inherit from " + interfaces[i].getValue()); |
| outputClassInheritance(handler, getJavaClass(interfaces[i].getValue()), mode); |
| } |
| |
| case CONSTRUCTOR_INHERITANCE : |
| // Output method/constructor inheritance summary from superclass: |
| if (!(mode == METHOD_INHERITANCE && jClass.isInterface())) { |
| outputSuperClassInheritance(handler, jClass, mode); |
| } |
| JavaMethod[] methods = jClass.getMethods(); |
| for (int i=0; i<methods.length; i++) { |
| if ((mode == METHOD_INHERITANCE && methods[i].getReturns() != null) || |
| (mode == CONSTRUCTOR_INHERITANCE && methods[i].getReturns() == null)) { |
| outputMethodStartElement(handler, methods[i]); |
| outputMethodEndElement(handler, methods[i]); |
| } |
| } |
| break; |
| |
| default : |
| break; |
| } |
| |
| saxEndElement(handler, INHERIT_ELEMENT); |
| } |
| |
| private boolean hasInheritance(JavaClass jClass, int mode) { |
| JavaClass superClass = getJavadocSuperClass(jClass); |
| boolean result = false; |
| |
| if (superClass != null) { |
| switch (mode) { |
| case CLASS_INHERITANCE : |
| // Already there! |
| result = true; |
| break; |
| |
| case INTERFACE_INHERITANCE : |
| result = superClass.getImplements().length > 0; |
| break; |
| |
| case INNERCLASS_INHERITANCE : |
| result = superClass.getClasses().length > 0; |
| break; |
| |
| case FIELD_INHERITANCE : |
| result = superClass.getFields().length > 0; |
| break; |
| |
| case METHOD_INHERITANCE : |
| Type[] interfaces = jClass.getImplements(); |
| for (int i=0; i<interfaces.length && !result; i++) { |
| JavaClass iface = getJavaClass(interfaces[i].getValue()); |
| result = iface != null && iface.getMethods().length > 0; |
| } |
| |
| case CONSTRUCTOR_INHERITANCE : |
| JavaMethod[] methods = superClass.getMethods(); |
| for (int i=0; i<methods.length && !result; i++) { |
| result = ((mode == METHOD_INHERITANCE && methods[i].getReturns() != null) || |
| (mode == CONSTRUCTOR_INHERITANCE && methods[i].getReturns() == null)); |
| } |
| break; |
| |
| default : |
| break; |
| } |
| |
| if (!result) { |
| result = hasInheritance(superClass, mode); |
| } |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Method getJavadocSuperClass. |
| * |
| * @param jClass |
| * @return JavaClass |
| */ |
| private JavaClass getJavadocSuperClass(JavaClass jClass) { |
| if (jClass == null) { |
| // May not happen, of course ;-) |
| throw new IllegalArgumentException("Argument 'jClass' must not be <null>!"); |
| } |
| |
| if (jClass.getFullyQualifiedName().equals(ROOT_CLASSNAME)) { |
| // jClass is root class: |
| return null; |
| } |
| |
| JavaClass superClass = null; |
| |
| if (!jClass.isInterface()) { |
| try { |
| // Use QDocx operation to retrieve class: |
| superClass = jClass.getSuperJavaClass(); |
| } catch (UnsupportedOperationException uoe) { |
| // No Cache built (yet)... ignore! |
| } |
| } |
| |
| if (superClass == null) { |
| String superJavadocClassName = null; |
| |
| if (jClass.isInterface()) { |
| Type[] interfaces = jClass.getImplements(); |
| if (interfaces.length == 1) { |
| superJavadocClassName = interfaces[0].getValue(); |
| } |
| } else { |
| superJavadocClassName = jClass.getSuperClass().getValue(); |
| |
| // Is the superClass itself an inner class? |
| if (superJavadocClassName.indexOf('.') == -1 && getJavadocInnerClass(containingJavadocClass, superJavadocClassName) != null) { |
| superJavadocClassName = containingJavadocClass.getFullyQualifiedName() + '.' + superJavadocClassName; |
| } |
| } |
| |
| if (superJavadocClassName != null) { |
| superClass = getJavaClass(superJavadocClassName); |
| } |
| } |
| |
| return superClass; |
| } |
| |
| /** |
| * Method getInnerClass. |
| * |
| * @param jClass |
| * @param className |
| * @return JavaClass |
| */ |
| private JavaClass getJavadocInnerClass(JavaClass jClass, String className) { |
| if (jClass != null) { |
| JavaClass[] classes = jClass.getClasses(); |
| |
| for (int i=0; i<classes.length; i++) { |
| if (classes[i].getName().equals(className)) { |
| return classes[i]; |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Get the meta class for the specified classname. The result is cached internally. |
| * |
| * @param className |
| * @return JavaClass |
| */ |
| private JavaClass getJavaClass(String className) { |
| if (classMap != null && classMap.containsKey(className)) { |
| return (JavaClass) classMap.get(className); |
| } |
| |
| JavaClass jClass = null; |
| SourceResolver resolver = null; |
| |
| try { |
| resolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); |
| Source source = resolver.resolveURI(PROTOCOL + className); |
| if (source instanceof QDoxSource) { |
| QDoxSource javadocSource = (QDoxSource) source; |
| jClass = javadocSource.getJavadocClass(); |
| if (classMap == null) { |
| classMap = new HashMap(); |
| } |
| classMap.put(className, jClass); |
| } |
| resolver.release(source); |
| } catch (ServiceException se) { |
| logger.error("Couldn't return JavaClass!", se); |
| } catch (MalformedURLException mue) { |
| logger.error("Couldn't return JavaClass!", mue); |
| } catch (SourceException se) { |
| logger.error("Couldn't return JavaClass!", se); |
| } catch (IOException ioe) { |
| logger.error("Couldn't return JavaClass!", ioe); |
| } finally { |
| if (resolver != null) { |
| manager.release(resolver); |
| } |
| } |
| |
| return jClass; |
| } |
| |
| /** |
| * Method outputModifiers. |
| * |
| * @param handler |
| * @param entity |
| */ |
| private void outputModifiers(ContentHandler handler, AbstractJavaEntity entity) throws SAXException { |
| String[] modifiers = entity.getModifiers(); |
| if (modifiers.length > 0) { |
| saxStartElement(handler, MODIFIERS_ELEMENT); |
| for (int i=0; i<modifiers.length; i++) { |
| saxStartElement(handler, modifiers[i]); |
| saxEndElement(handler, modifiers[i]); |
| } |
| saxEndElement(handler, MODIFIERS_ELEMENT); |
| } |
| } |
| |
| /** |
| * Method outputCommentAndTags. |
| * |
| * @param handler |
| * @param entity |
| */ |
| private void outputTags(ContentHandler handler, AbstractJavaEntity entity) throws SAXException { |
| DocletTag[] tags = entity.getTags(); |
| |
| boolean tagElementPassed = false; |
| for (int i=0; i<tags.length; i++) { |
| String tagName = tags[i].getName(); |
| String value = tags[i].getValue(); |
| if (!tagElementPassed && !tagName.equals("throws") && !tagName.equals("param")) { |
| saxStartElement(handler, TAGS_ELEMENT); |
| tagElementPassed = true; |
| } |
| |
| if (tagName.equals("see")) { |
| saxStartElement(handler, tagName); |
| outputLink(handler, value, null); |
| saxEndElement(handler, tagName); |
| } else if (!tagName.equals("throws") && !tagName.equals("param")) { |
| // the 'throws' and 'param' tags are handled at method exception and method parameter level: |
| saxStartElement(handler, tagName); |
| outputComment(handler, value); |
| saxEndElement(handler, tagName); |
| } |
| } |
| |
| if (tagElementPassed) { |
| saxEndElement(handler, TAGS_ELEMENT); |
| } |
| } |
| |
| /** |
| * Outputs a Javadoc comment. |
| * |
| * @param handler SAX ContentHandler |
| * @param comment The Javadoc comment |
| * @throws SAXException if something goes wrong |
| */ |
| private void outputComment(ContentHandler handler, String comment) throws SAXException { |
| if (comment != null && comment.length() > 0) { |
| // When newlines are recognized in QDox, uncomment these lines: |
| //if (comment.indexOf("<pre>") >= 0) { |
| //saxStartElement(handler, COMMENT_ELEMENT, new String[][] {{"http://www.w3.org/XML/1998/namespace", |
| // "space", "xml:space", "NMTOKEN", "preserve"}}); |
| //saxStartElement(handler, COMMENT_ELEMENT, new String[][] {{"xml:space", "preserve"}}); |
| //} else { |
| saxStartElement(handler, COMMENT_ELEMENT); |
| //} |
| |
| while (reLink.match(comment)) { |
| String ref = null; |
| String display = null; |
| if (reLink.getParen(6) == null) { |
| // {@link xxx yyy} |
| ref = reLink.getParen(2); |
| display = reLink.getParen(5); |
| } else { |
| // {@link xxx} |
| ref = reLink.getParen(6); |
| display = EMPTY; |
| } |
| |
| // Output SAX: |
| saxCharacters(handler, comment.substring(0, reLink.getParenStart(0))); |
| |
| outputLink(handler, ref, display); |
| |
| // Cut from doc: |
| comment = comment.substring(reLink.getParenEnd(0)); |
| } |
| |
| saxCharacters(handler, comment); |
| |
| saxEndElement(handler, COMMENT_ELEMENT); |
| } |
| } |
| |
| /** |
| * Method outputLink. |
| * |
| * @param handler |
| * @param ref |
| * @param display |
| */ |
| private void outputLink(ContentHandler handler, String ref, String display) throws SAXException { |
| String classPart = resolveClassNameFromLink(ref); |
| String memberPart = resolveMemberNameFromLink(ref); |
| String displayPart = display; |
| |
| List attrs = new ArrayList(); |
| |
| if (classPart != null && classPart.length() > 0) { |
| attrs.add(new String[] {LINK_CLASS_ATTRIBUTE, classPart}); |
| } |
| |
| if (memberPart != null && memberPart.length() > 0) { |
| attrs.add(new String[] {LINK_MEMBER_ATTRIBUTE, memberPart}); |
| } |
| |
| if (display == null || display.length() == 0 && !ref.equals(classPart + "#" + memberPart)) { |
| displayPart = ref.replace('#', '.'); |
| } |
| |
| saxStartElement(handler, LINK_ELEMENT, (String[][]) attrs.toArray(new String[][]{{}})); |
| saxCharacters(handler, displayPart); |
| saxEndElement(handler, LINK_ELEMENT); |
| } |
| |
| /** |
| * Method outputInnerClasses. |
| * |
| * @param handler |
| * @param jClass |
| * @param detailed |
| */ |
| private void outputInnerClasses(ContentHandler handler, JavaClass jClass, boolean detailed) throws SAXException { |
| JavaClass[] innerClasses = jClass.getClasses(); |
| if (innerClasses.length > 0 || hasInheritance(jClass, INNERCLASS_INHERITANCE)) { |
| if (detailed) { |
| saxStartElement(handler, INNERCLASSES_ELEMENT); |
| } |
| |
| // Output inheritance: |
| outputSuperClassInheritance(handler, jClass, INNERCLASS_INHERITANCE); |
| |
| for (int i=0; i<innerClasses.length; i++) { |
| outputClassStartElement(handler, innerClasses[i]); |
| if (detailed) { |
| outputModifiers(handler, innerClasses[i]); |
| outputComment(handler, innerClasses[i].getComment()); |
| outputTags(handler, innerClasses[i]); |
| } |
| outputClassEndElement(handler, innerClasses[i]); |
| } |
| |
| if (detailed) { |
| saxEndElement(handler, INNERCLASSES_ELEMENT); |
| } |
| } |
| } |
| |
| /** |
| * Method outputImplements. |
| * |
| * @param handler |
| * @param jClass |
| */ |
| private void outputImplements(ContentHandler handler, JavaClass jClass, boolean detailed) throws SAXException { |
| Type[] interfaces = jClass.getImplements(); |
| if (interfaces.length > 0 || hasInheritance(jClass, INTERFACE_INHERITANCE)) { |
| if (detailed) { |
| saxStartElement(handler, IMPLEMENTS_ELEMENT); |
| } |
| |
| // Output inheritance: |
| outputSuperClassInheritance(handler, jClass, INTERFACE_INHERITANCE); |
| |
| for (int i=0; i<interfaces.length; i++) { |
| String name = interfaces[i].getValue().toString(); |
| String pckg = name.substring(0, name.lastIndexOf('.')); |
| name = name.substring(pckg.length() + 1); |
| |
| saxStartElement(handler, INTERFACE_ELEMENT, |
| new String[][] {{CLASSNAME_ATTRIBUTE, name}, |
| {PACKAGE_ATTRIBUTE, pckg}, |
| {QNAME_ATTRIBUTE, pckg + '.' + name}}); |
| saxEndElement(handler, INTERFACE_ELEMENT); |
| } |
| |
| if (detailed) { |
| saxEndElement(handler, IMPLEMENTS_ELEMENT); |
| } |
| } |
| } |
| |
| /** |
| * Method outputFields. |
| * |
| * @param handler |
| * @param jClass |
| * @param detailed |
| */ |
| private void outputFields(ContentHandler handler, JavaClass jClass, boolean detailed) throws SAXException { |
| JavaField[] fields = jClass.getFields(); |
| |
| if (fields.length > 0 || hasInheritance(jClass, FIELD_INHERITANCE)) { |
| if (detailed) { |
| saxStartElement(handler, FIELDS_ELEMENT); |
| } |
| |
| // Output inheritance: |
| outputSuperClassInheritance(handler, jClass, FIELD_INHERITANCE); |
| |
| for (int i=0; i<fields.length; i++) { |
| saxStartElement(handler, FIELD_ELEMENT, |
| new String[][] {{NAME_ATTRIBUTE, fields[i].getName()}, |
| {TYPE_ATTRIBUTE, fields[i].getType().getValue()}, |
| {DIMENSIONS_ATTRIBUTE, Integer.toString(fields[i].getType().getDimensions())}}); |
| if (detailed) { |
| outputModifiers(handler, fields[i]); |
| outputComment(handler, fields[i].getComment()); |
| outputTags(handler, fields[i]); |
| } |
| saxEndElement(handler, FIELD_ELEMENT); |
| } |
| |
| if (detailed) { |
| saxEndElement(handler, FIELDS_ELEMENT); |
| } |
| } |
| } |
| |
| /** |
| * Method outputClassStartElement. |
| * |
| * @param handler |
| * @param jClass |
| */ |
| private void outputInheritStartElement(ContentHandler handler, JavaClass jClass) throws SAXException { |
| saxStartElement(handler, INHERIT_ELEMENT, |
| new String[][] {{TYPE_ATTRIBUTE, jClass.isInterface() ? INTERFACE_ELEMENT : CLASS_ELEMENT}, |
| {CLASSNAME_ATTRIBUTE, jClass.getName()}, |
| {PACKAGE_ATTRIBUTE, jClass.getPackage()}, |
| {QNAME_ATTRIBUTE, jClass.getFullyQualifiedName()}}); |
| } |
| |
| /** |
| * Method outputClassStartElement. |
| * |
| * @param handler |
| * @param jClass |
| */ |
| private void outputClassStartElement(ContentHandler handler, JavaClass jClass) throws SAXException { |
| saxStartElement(handler, jClass.isInterface() ? INTERFACE_ELEMENT : CLASS_ELEMENT, |
| new String[][] {{CLASSNAME_ATTRIBUTE, jClass.getName()}, |
| {PACKAGE_ATTRIBUTE, jClass.getPackage()}, |
| {QNAME_ATTRIBUTE, jClass.getFullyQualifiedName()}}); |
| } |
| |
| /** |
| * Method outputClassEndElement. |
| * |
| * @param handler |
| * @param jClass |
| */ |
| private void outputClassEndElement(ContentHandler handler, JavaClass jClass) throws SAXException { |
| saxEndElement(handler, jClass.isInterface() ? INTERFACE_ELEMENT : CLASS_ELEMENT); |
| } |
| |
| /** |
| * Method outputMethods. |
| * |
| * @param handler |
| * @param jClass |
| * @param mode |
| */ |
| private void outputMethods(ContentHandler handler, JavaClass jClass, int mode) throws SAXException { |
| // Are there any methods in <mode>? |
| int size = 0; |
| String elementGroup, element; |
| JavaMethod[] methods = jClass.getMethods(); |
| |
| if (mode == CONSTRUCTOR_MODE) { |
| elementGroup = CONSTRUCTORS_ELEMENT; |
| element = CONSTRUCTOR_ELEMENT; |
| for (int i=0; i<methods.length; i++) { |
| if (methods[i].getReturns() == null) { |
| size++; |
| } |
| } |
| } else { |
| elementGroup = METHODS_ELEMENT; |
| element = METHOD_ELEMENT; |
| for (int i=0; i<methods.length; i++) { |
| if (methods[i].getReturns() != null) { |
| size++; |
| } |
| } |
| } |
| |
| if (size > 0 || (mode == METHOD_MODE && hasInheritance(jClass, METHOD_INHERITANCE)) || |
| (mode == CONSTRUCTOR_MODE && hasInheritance(jClass, CONSTRUCTOR_INHERITANCE))) { |
| saxStartElement(handler, elementGroup); |
| |
| // Output inheritance: |
| if (mode == METHOD_MODE) { |
| outputSuperClassInheritance(handler, jClass, METHOD_INHERITANCE); |
| } else { |
| outputSuperClassInheritance(handler, jClass, CONSTRUCTOR_INHERITANCE); |
| } |
| |
| for (int i=0; i<methods.length; i++) { |
| if (mode == METHOD_MODE && methods[i].getReturns() != null) { |
| outputMethodStartElement(handler, methods[i]); |
| } else if (mode == CONSTRUCTOR_MODE && methods[i].getReturns() == null) { |
| saxStartElement(handler, CONSTRUCTOR_ELEMENT, |
| new String[][] {{NAME_ATTRIBUTE, methods[i].getName()}, |
| {SIGNATURE_ATTRIBUTE, getSignature(methods[i])}}); |
| } else { |
| // Do not process this method or constructor: |
| continue; |
| } |
| |
| JavaParameter[] params = methods[i].getParameters(); |
| DocletTag[] paramTags = methods[i].getTagsByName("param"); |
| DocletTag[] throwsTags = methods[i].getTagsByName("throws"); |
| |
| // Modifiers, comment, tags: |
| outputModifiers(handler, methods[i]); |
| outputComment(handler, methods[i].getComment()); |
| outputTags(handler, methods[i]); |
| |
| // Parameters: |
| if (params.length > 0) { |
| saxStartElement(handler, PARAMETERS_ELEMENT); |
| for (int j=0; j<params.length; j++) { |
| String paramName = params[j].getName(); |
| saxStartElement(handler, PARAMETER_ELEMENT, |
| new String[][] {{NAME_ATTRIBUTE, paramName}, |
| {TYPE_ATTRIBUTE, params[j].getType().getValue()}, |
| {DIMENSIONS_ATTRIBUTE, Integer.toString(params[j].getType().getDimensions())}}); |
| |
| // Is there any doc for this parameter? |
| for (int k=0; k<paramTags.length; k++) { |
| String paramValue = paramTags[k].getValue(); |
| int splitIndex = paramValue.indexOf(' '); |
| String paramValueName = splitIndex > 0 ? paramValue.substring(0, splitIndex) : paramValue; |
| if (paramName.equals(paramValueName)) { |
| outputComment(handler, splitIndex > 0 ? paramValue.substring(splitIndex + 1) : ""); |
| } |
| } |
| |
| saxEndElement(handler, PARAMETER_ELEMENT); |
| } |
| saxEndElement(handler, PARAMETERS_ELEMENT); |
| } |
| |
| // Exceptions: |
| Type[] exceptions = methods[i].getExceptions(); |
| if (exceptions.length + throwsTags.length > 0) { |
| saxStartElement(handler, THROWS_ELEMENT); |
| for (int j=0; j<exceptions.length; j++) { |
| // Iterate each exception which is declared in the throws clause: |
| String exceptionName = exceptions[j].getValue(); |
| saxStartElement(handler, EXCEPTION_ELEMENT, new String[][] {{NAME_ATTRIBUTE, exceptionName}}); |
| |
| // Is there any doc for this exception? |
| if (throwsTags.length > 0) { |
| String exceptionClassName = exceptionName.substring(exceptionName.lastIndexOf('.')); |
| for (int k=0; k<throwsTags.length; k++) { |
| String excValue = throwsTags[k].getValue(); |
| int splitIndex = excValue.indexOf(' '); |
| String excValueName = splitIndex > 0 ? excValue.substring(0, splitIndex) : excValue; |
| if (exceptionClassName.equals(excValueName)) { |
| outputComment(handler, splitIndex > 0 ? excValue.substring(splitIndex + 1) : ""); |
| } |
| } |
| } |
| |
| saxEndElement(handler, EXCEPTION_ELEMENT); |
| } |
| |
| for (int j=0; j<throwsTags.length; j++) { |
| // Iterate each exception which is not declared in the throws clause but documented in javadoc: |
| String content = throwsTags[j].getValue(); |
| int splitIndex = content.indexOf(' '); |
| String exceptionName = content.substring(0, splitIndex); |
| String qualifiedExceptionName = getQualifiedClassName(exceptionName); |
| |
| // Does the exception *not* exist in the throws clause? |
| boolean found = false; |
| for (int k=0; !found && k<exceptions.length; k++) { |
| found = qualifiedExceptionName.equals(exceptions[k].getValue()); |
| } |
| |
| if (!found) { |
| saxStartElement(handler, EXCEPTION_ELEMENT, new String[][] {{NAME_ATTRIBUTE, qualifiedExceptionName}}); |
| outputComment(handler, splitIndex > 0 ? content.substring(splitIndex + 1) : ""); |
| saxEndElement(handler, EXCEPTION_ELEMENT); |
| } |
| } |
| |
| saxEndElement(handler, THROWS_ELEMENT); |
| } |
| |
| saxEndElement(handler, element); |
| } |
| |
| saxEndElement(handler, elementGroup); |
| } |
| } |
| |
| /** |
| * Method getSignature. |
| * |
| * @param javaMethod |
| * @return String |
| */ |
| private String getSignature(JavaMethod javaMethod) { |
| StringBuffer sb = new StringBuffer(javaMethod.getName()); |
| sb.append('('); |
| JavaParameter[] params = javaMethod.getParameters(); |
| for (int j=0; j<params.length; j++) { |
| if (j > 0) { |
| sb.append(", "); |
| } |
| sb.append(params[j].getType().getValue()); |
| int dims = params[j].getType().getDimensions(); |
| for (int k=0; k<dims; k++) { |
| sb.append("[]"); |
| } |
| } |
| sb.append(')'); |
| |
| return sb.toString(); |
| } |
| |
| /** |
| * Method outputMethodStartElement. |
| * |
| * @param handler |
| * @param javaMethod |
| */ |
| private void outputMethodStartElement(ContentHandler handler, JavaMethod javaMethod) throws SAXException { |
| if (javaMethod.getReturns() != null) { |
| saxStartElement(handler, METHOD_ELEMENT, |
| new String[][] {{NAME_ATTRIBUTE, javaMethod.getName()}, |
| {TYPE_ATTRIBUTE, javaMethod.getReturns().getValue()}, |
| {DIMENSIONS_ATTRIBUTE, Integer.toString(javaMethod.getReturns().getDimensions())}, |
| {SIGNATURE_ATTRIBUTE, getSignature(javaMethod)}}); |
| } else { |
| saxStartElement(handler, CONSTRUCTOR_ELEMENT, |
| new String[][] {{NAME_ATTRIBUTE, javaMethod.getName()}, |
| {SIGNATURE_ATTRIBUTE, getSignature(javaMethod)}}); |
| } |
| } |
| |
| /** |
| * Method outputMethodEndElement. |
| * |
| * @param handler |
| */ |
| private void outputMethodEndElement(ContentHandler handler, JavaMethod javaMethod) throws SAXException { |
| if (javaMethod.getReturns() != null) { |
| saxEndElement(handler, METHOD_ELEMENT); |
| } else { |
| saxEndElement(handler, CONSTRUCTOR_ELEMENT); |
| } |
| } |
| |
| /** |
| * Method saxStartElement. |
| * |
| * @param handler |
| * @param localName |
| */ |
| private void saxStartElement(ContentHandler handler, String localName) throws SAXException { |
| handler.startElement(NS_URI, localName, NS_PREFIX + ':' + localName, EMPTY_ATTRS); |
| } |
| |
| /** |
| * Method saxStartElement. |
| * |
| * @param handler |
| * @param localName |
| * @param attrs |
| */ |
| private void saxStartElement(ContentHandler handler, String localName, String[][] attrs) throws SAXException { |
| AttributesImpl saxAttrs = new AttributesImpl(); |
| for (int i=0; i<attrs.length; i++) { |
| if (attrs[i].length == 2) { |
| saxAttrs.addAttribute(EMPTY, attrs[i][0], attrs[i][0], ATTR_TYPE, attrs[i][1]); |
| } else if (attrs[i].length == 5) { |
| saxAttrs.addAttribute(attrs[i][0], attrs[i][1], attrs[i][2], attrs[i][3], attrs[i][4]); |
| } |
| } |
| |
| handler.startElement(NS_URI, localName, NS_PREFIX + ':' + localName, saxAttrs); |
| } |
| |
| /** |
| * Method saxEndElement. |
| * |
| * @param handler |
| * @param localName |
| */ |
| private void saxEndElement(ContentHandler handler, String localName) throws SAXException { |
| handler.endElement(NS_URI, localName, NS_PREFIX + ':' + localName); |
| } |
| |
| /** |
| * Method saxCharacters. |
| * |
| * @param handler |
| * @param text |
| */ |
| private void saxCharacters(ContentHandler handler, String text) throws SAXException { |
| if (text != null && text.length() > 0) { |
| handler.characters(text.toCharArray(), 0, text.length()); |
| } |
| } |
| |
| /** |
| * @see org.apache.excalibur.source.Source#exists() |
| */ |
| public boolean exists() { |
| return true; |
| } |
| } |