All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.codehaus.groovy.tools.DocGenerator.groovy Maven / Gradle / Ivy

There is a newer version: 3.0.22
Show newest version
/*
 * Copyright 2003-2014 the original author or authors.
 *
 * 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.codehaus.groovy.tools

import com.thoughtworks.qdox.model.JavaClass
import com.thoughtworks.qdox.model.JavaMethod
import org.codehaus.groovy.runtime.DefaultGroovyMethods
import com.thoughtworks.qdox.JavaDocBuilder
import org.codehaus.groovy.tools.shell.util.Logger

/**
 * Generate documentation about the methods provided by the Groovy Development Kit
 * that enhance the standard JDK classes.
 *
 * @author Guillaume Laforge, John Wilson, Bernhard Huber, Paul King
 */
class DocGenerator {
    private static final String PRIMITIVE_TYPE_PSEUDO_PACKAGE = 'primitive-types'
    private static final Logger log = Logger.create(DocGenerator)
    private static final String TITLE = "Groovy JDK"
    def sourceFiles = []
    File outputFolder
    JavaDocBuilder builder
    // categorize all groovy methods per core JDK class to which it applies
    Map> enhancedMethodsForClass = [:]
    Map enhancedClasses = [:]
    Map packages = [:]
    Set sortedPackages

    DocGenerator(sourceFiles, File outputFolder) {
        this.sourceFiles = sourceFiles
        this.outputFolder = outputFolder
        parse()
    }

    /**
     * Parse the DefaultGroovyMethods class to build a graph representing the structure of the class,
     * with its methods, javadoc comments and tags.
     */
    private void parse() {
        builder = new JavaDocBuilder()
        sourceFiles.each {
            if (it.exists()) {
                builder.addSource(it.newReader())
                log.debug "adding reader for $it"
            } else log.debug "not found, skipping: $it.path"
        }

        def sources = builder.getSources()

        def methods = []
        sources.each {source ->
            def classes = source.getClasses()
            classes.each {aClass ->
                methods.addAll(aClass.methods.findAll { !it.annotations.any { it.type.fullyQualifiedName == 'java.lang.Deprecated' } })
            }
        }

        for (method in methods) {
            if (method.isPublic() && method.isStatic()) {
                def parameters = method.parameters
                if (parameters.size() == 0) continue
                String jdkClass = parameters[0].type.toString()
                if (jdkClass.equals('T') || jdkClass.equals('U') || jdkClass.equals('E') || jdkClass.equals('K') || jdkClass.equals('V') || jdkClass.equals('G')) {
                    jdkClass = 'java.lang.Object'
                } else if (jdkClass.equals('T[]') || jdkClass.equals('E[]')) {
                    jdkClass = 'java.lang.Object[]'
                }
                if (jdkClass.startsWith('groovy')) {
                    log.debug "skipping $jdkClass"
                    continue
                }
                String key = jdkClass //.endsWith('[]') ? jdkClass[0..-3] : jdkClass
                if (enhancedMethodsForClass.containsKey(key)) {
                    List l = enhancedMethodsForClass[key];
                    l.add(method)
                }
                else {
                    enhancedMethodsForClass[key] = [method]
                    enhancedClasses[key] = parameters[0].type.javaClass
                }
            }
        }

        enhancedMethodsForClass.keySet().each {className ->
            def thePackage = className.contains(".") ? className.replaceFirst(/\.[^\.]*$/, "") : ""
            if (!packages.containsKey(thePackage)) {
                packages[thePackage] = []
            }
            packages[thePackage] << className
        }
        sortedPackages = new TreeSet(packages.keySet())
    }

    /**
     * Builds an HTML page from the structure of DefaultGroovyMethods.
     */
    def generateNew() {
        def engine = new groovy.text.SimpleTemplateEngine()

        // the index
        def templateIndex = createTemplate(engine, 'index.html')
        def out = new File(outputFolder, 'index.html')
        def binding = [packages: sortedPackages]
        out.withWriter {
            it << templateIndex.make(binding)
        }
        // the overview
        def templateOverview = createTemplate(engine, 'overview-summary.html')
        out = new File(outputFolder, 'overview-summary.html')
        binding = [packages: sortedPackages]
        out.withWriter {
            it << templateOverview.make(binding)
        }

        def templateOverviewFrame = createTemplate(engine, 'template.overview-frame.html')
        out = new File(outputFolder, 'overview-frame.html')
        binding = [packages: sortedPackages, title: TITLE]
        out.withWriter {
            it << templateOverviewFrame.make(binding)
        }
        
        // the package list
        out = new File(outputFolder, 'package-list')
        out.withWriter { writer ->
            packages.keySet().findAll{ it }.each{ writer.println it }
        }
        
        // the allclasses-frame.html
        def templateAllClasses = createTemplate(engine, 'template.allclasses-frame.html')
        out = new File(outputFolder, 'allclasses-frame.html')
        def fixPrimitivePackage = {className -> className.contains('.') ? className : "${PRIMITIVE_TYPE_PSEUDO_PACKAGE}.$className"}
        binding = [classes: enhancedMethodsForClass.keySet().collect(fixPrimitivePackage).sort {it.replaceAll('.*\\.', '')}]
        out.withWriter {
            it << templateAllClasses.make(binding)
        }

        // the package-frame.html for each package
        def templatePackageFrame = createTemplate(engine, 'template.package-frame.html')
        packages.each {curPackage, packageClasses ->
            def packageName = curPackage ?: PRIMITIVE_TYPE_PSEUDO_PACKAGE
            generatePackageFrame(templatePackageFrame, packageName, packageClasses)
        }

        // the class.html for each class
        def templateClass = createTemplate(engine, 'template.class.html')
        packages.each {curPackage, packageClasses ->
            def packageName = curPackage ?: PRIMITIVE_TYPE_PSEUDO_PACKAGE
            packageClasses.each {
                generateClassDetails(templateClass, packageName, it)
            }
        }

        // the index-all.html
        def templateIndexAll = createTemplate(engine, 'template.index-all.html')
        out = new File(outputFolder, 'index-all.html')
        binding = ['indexMap': generateIndex(packages), title: TITLE]
        out.withWriter {
            it << templateIndexAll.make(binding)
        }

        // copy resources
        ['groovy.ico', 'stylesheet.css'].each { String resource ->
            new File(outputFolder, resource).withOutputStream { outs ->
                getClass().getResource(resource).withInputStream { ins ->
                    outs << ins
                }
            }
        }
    }

    /**
     * Generate an index.
     * 

* This method creates a index map indexed by the first letter of the * method in upper case, the map value is a list of methods. *

* e.g.: 'A' : [ m1, m2, m3 .. ] * The values m1, m2, m3 are sorted by the method name, and the parameter signature. * The method names of m1, m2, m3 start either with 'a', or 'A'. * * @return index */ private def generateIndex(def packages) { def index = [] packages.each {curPackage, packageClasses -> def packageName = curPackage ? curPackage : 'primitive-types' packageClasses.each {className -> def simpleClassName = className.replaceAll('.*\\.', '') def fullClassName = packageName + '.' + simpleClassName // Class index.add([ 'index': simpleClassName.getAt(0).toUpperCase(), 'packageName': packageName, 'simpleClassName': simpleClassName, 'className': fullClassName, 'classDesc': enhancedClasses[className].interface ? 'interface' : 'class', 'shortComment': "", // empty because cannot get a comment of JDK ]) // Methods def listOfMethods = enhancedMethodsForClass[className] listOfMethods.each {method -> def methodName = method.name index.add([ 'index': methodName.getAt(0).toUpperCase(), 'packageName': packageName, 'simpleClassName': simpleClassName, 'className': fullClassName, 'classDesc': enhancedClasses[className].interface ? 'interface' : 'class', 'method': method, 'parametersSignature': getParametersDecl(method), 'shortComment': linkify(getFirstSentence(getComment(method)), curPackage), ]) } } } def indexMap = new TreeMap() def indexListComparator = [compare: {a, b -> final String aSortKey = (a.method) ? (a.method.name + ' ' + getParametersDecl(a.method) + ' ' + a.className) : a.className final String bSortKey = (b.method) ? (b.method.name + ' ' + getParametersDecl(b.method) + ' ' + b.className) : b.className return aSortKey.compareTo(bSortKey) }] as Comparator for (indexEntry in index) { final String key = indexEntry['index'] if (indexMap.containsKey(key)) { def indexEntryList = indexMap.get(key) indexEntryList.add(indexEntry) } else { final TreeSet indexEntryList = new TreeSet(indexListComparator) indexEntryList.add(indexEntry) indexMap.put(key, indexEntryList) } } return indexMap } private getFirstSentence(text) { def boundary = java.text.BreakIterator.getSentenceInstance(Locale.getDefault()) // todo - allow locale to be passed in boundary.setText(text) int start = boundary.first() int end = boundary.next() if (start > -1 && end > -1) { text = text.substring(start, end) } text } private generateClassDetails(template, curPackage, aClass) { def packagePath = generatePackagePath(curPackage) def dir = new File(outputFolder, packagePath) dir.mkdirs() def out = new File(dir, aClass.replaceAll('.*\\.', '') + '.html') def listOfMethods = enhancedMethodsForClass[aClass].sort {it.name} def methods = [] listOfMethods.each {method -> def parameters = method.getTagsByName("param").collect { [name: it.value.replaceAll(' .*', ''), comment: linkify(it.value.replaceAll('^\\w*', ''), curPackage)] } if (parameters) parameters.remove(0) // method is static, first arg is the "real this" def seeComments = method.getTagsByName("see").collect { [target: getDocUrl(it.value, curPackage)]} def returnType = getReturnType(method) def comment = getComment(method) def methodInfo = [ name: method.name, comment: linkify(comment, curPackage), shortComment: linkify(getFirstSentence(comment), curPackage), returnComment: method.getTagByName("return")?.getValue() ?: '', seeComments: seeComments, returnTypeDocUrl: getDocUrl(returnType, curPackage), parametersSignature: getParametersDecl(method), parametersDocUrl: getParametersDocUrl(method, curPackage), parameters: parameters, isStatic: method.parentClass.name == 'DefaultGroovyStaticMethods', since: method.getTagByName("since")?.getValue() ?: null ] methods << methodInfo } def binding = [ className: aClass.replaceAll(/.*\./, ''), classDesc: enhancedClasses[aClass].interface ? 'Interface' : 'Class', packageName: curPackage, methods: methods, title: TITLE ] out.withWriter { it << template.make(binding) } } private String getParametersDocUrl(method, curPackage) { getParameters(method).collect {"${getDocUrl(it.type.toString(), curPackage)} $it.name"}.join(", ") } private String getDocUrl(type, curPackage) { def inGdk = false if (type.startsWith('#')) { def matchNameArgs = /#([^(]*)\(([^)]+)\)/ def m = type =~ matchNameArgs def name = m[0][1] def args = m[0][2].split(/,\s?/).toList() def first = args.remove(0) type = "$first#$name(${args.join(', ')})".toString() inGdk = true } if (type in ['T', 'U', 'V', 'K', 'V']) { type = "java.lang.Object" } else if (type == 'T[]') { type = "java.lang.Object[]" } if (!type.contains('.')) { return type } def target = type.split('#') def shortClassName = target[0].replaceAll(/.*\./, "") def packageName = (shortClassName.size() == target[0].size()) ? "DefaultPackage" : target[0][0..(-shortClassName.size() - 2)] shortClassName += (target.size() > 1 ? '#' + target[1].split('\\(')[0] : '') def apiBaseUrl, title if (inGdk) { apiBaseUrl = "" curPackage.split('\\.').size().times { apiBaseUrl += '../'} title = "GDK enhancement for ${target[0]}" } else if (type.startsWith("groovy") || type.startsWith("org.codehaus.groovy")) { // TODO don't hardcode apiBaseUrl = "http://groovy.codehaus.org/gapi/" title = "Groovy class in $packageName" } else { // TODO don't hardcode apiBaseUrl = "http://docs.oracle.com/javase/7/docs/api/" title = "JDK class in $packageName" } def url = apiBaseUrl + target[0].replace('.', '/') + '.html' + (target.size() > 1 ? '#' + target[1] : '') return "$shortClassName" } private generatePackagePath(curPackage) { def fileSep = File.separator // need to escape separator on windows for regex's sake if (fileSep == '\\') fileSep *= 2 return curPackage.replaceAll('\\.', fileSep) } private generatePackageFrame(templatePackageFrame, curPackage, packageClasses) { def packagePath = generatePackagePath(curPackage) def dir = new File(outputFolder, packagePath) dir.mkdirs() def out = new File(dir, 'package-frame.html') def binding = [ classes: packageClasses.findAll{ !enhancedClasses[it].interface }.sort().collect {it.replaceAll(/.*\./, '')}, interfaces: packageClasses.findAll{ enhancedClasses[it].interface }.sort().collect {it.replaceAll(/.*\./, '')}, packageName: curPackage] out.withWriter { it << templatePackageFrame.make(binding) } } def createTemplate(templateEngine, resourceFile) { def resourceUrl = getClass().getResource(resourceFile) return templateEngine.createTemplate(resourceUrl.text) } /** * Retrieves a String representing the return type */ private getReturnType(method) { def returnType = method.getReturns() if (returnType != null) { return returnType.toString() } return "" } /** * Retrieve a String representing the declaration of the parameters of the method passed as parameter. * * @param method a method * @return the declaration of the method (long version) */ private getParametersDecl(method) { getParameters(method).collect {"${it.getType()}"}.join(", ") } /** * Retrieve the parameters of the method. * * @param method a method * @return a list of parameters without the first one */ private getParameters(method) { if (method.getParameters().size() > 1) { return method.getParameters().toList()[1..-1] } return [] } /** * Retrieve the JavaDoc comment associated with the method passed as parameter. * * @param method a method * @return the JavaDoc comment associated with this method */ private getComment(method) { def ans = method.getComment() if (ans == null) return "" return ans } private linkify(orig, curPackage) { orig.replaceAll(/\{@link\s+([^}]*)\s*\}/) {all, link -> getDocUrl(link, curPackage) } } /** * Main entry point. */ static void main(args) { // TODO don't hardcode def outFolder = new File("target/html/groovy-jdk") outFolder.mkdirs() def start = System.currentTimeMillis() def srcFiles = args.collect {getSourceFile(it)} def srcFileNames = args.collect {getSourceFile(it).canonicalPath} try { Class[] classes = DefaultGroovyMethods.additionals classes.each { def name = it.name if (name.indexOf('$') > 0) { name = name.tokenize('$')[0] } def newFile = getSourceFile(name) def newFileName = newFile.canonicalPath if (!srcFileNames.contains(newFileName)) { srcFileNames << newFileName srcFiles << newFile } } } catch (MissingPropertyException mpe) { log.error mpe.message, mpe // no call site change available, so ignore it } def docGen = new DocGenerator(srcFiles, outFolder) docGen.generateNew() def end = System.currentTimeMillis() log.debug "Done. Took ${end - start} millis." } private static File getSourceFile(String classname) { // TODO don't hardcode like this if (classname.contains("/")) return new File(classname) new File("src/main/" + classname.replace('.', '/') + ".java") } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy