PageRenderTime 52ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/buildSrc/src/main/groovy/org/gradle/build/docs/dsl/docbook/ClassDoc.groovy

https://github.com/mattcallanan/gradle
Groovy | 335 lines | 269 code | 51 blank | 15 comment | 55 complexity | 6adbd3dac18fa9764b8bebf5e8b6f13f MD5 | raw file
  1. /*
  2. * Copyright 2010 the original author or authors.
  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.gradle.build.docs.dsl.docbook
  17. import org.gradle.build.docs.dsl.model.ClassMetaData
  18. import org.gradle.build.docs.dsl.model.PropertyMetaData
  19. import org.w3c.dom.Document
  20. import org.w3c.dom.Element
  21. import org.w3c.dom.Node
  22. import org.gradle.build.docs.dsl.model.MethodMetaData
  23. import org.w3c.dom.Text
  24. import org.gradle.build.docs.dsl.model.MixinMetaData
  25. import org.gradle.build.docs.dsl.model.ClassExtensionMetaData
  26. import org.gradle.build.docs.dsl.model.ExtensionMetaData
  27. class ClassDoc {
  28. private final String className
  29. private final String id
  30. private final String simpleName
  31. final ClassMetaData classMetaData
  32. private final Element classSection
  33. private final ClassExtensionMetaData extensionMetaData
  34. private final List<PropertyDoc> classProperties = []
  35. private final List<MethodDoc> classMethods = []
  36. private final List<BlockDoc> classBlocks = []
  37. private final List<ClassExtensionDoc> classExtensions = []
  38. private final JavadocConverter javadocConverter
  39. private final DslDocModel model
  40. private final Element propertiesTable
  41. private final Element methodsTable
  42. private final Element propertiesSection
  43. private final Element methodsSection
  44. private List<Element> comment
  45. private final GenerationListener listener = new DefaultGenerationListener()
  46. ClassDoc(String className, Element classContent, Document targetDocument, ClassMetaData classMetaData, ClassExtensionMetaData extensionMetaData, DslDocModel model, JavadocConverter javadocConverter) {
  47. this.className = className
  48. id = className
  49. simpleName = className.tokenize('.').last()
  50. this.classMetaData = classMetaData
  51. this.javadocConverter = javadocConverter
  52. this.model = model
  53. this.extensionMetaData = extensionMetaData
  54. classSection = targetDocument.createElement('chapter')
  55. classContent.childNodes.each { Node n ->
  56. classSection << n
  57. }
  58. propertiesTable = getTable('Properties')
  59. propertiesSection = propertiesTable.parentNode
  60. methodsTable = getTable('Methods')
  61. methodsSection = methodsTable.parentNode
  62. }
  63. def getId() { return id }
  64. def getName() { return className }
  65. def getSimpleName() { return simpleName }
  66. def getComment() { return comment }
  67. def getClassProperties() { return classProperties }
  68. def getClassMethods() { return classMethods }
  69. def getClassBlocks() { return classBlocks }
  70. def getClassExtensions() { return classExtensions }
  71. def getClassSection() { return classSection }
  72. def getPropertiesTable() { return propertiesTable }
  73. def getPropertiesSection() { return propertiesSection }
  74. def getPropertyDetailsSection() { return getSection('Property details') }
  75. def getMethodsTable() { return methodsTable }
  76. def getMethodsSection() { return methodsSection }
  77. def getMethodDetailsSection() { return getSection('Method details') }
  78. def getBlocksTable() { return getTable('Script blocks') }
  79. def getBlockDetailsSection() { return getSection('Script block details') }
  80. ClassDoc mergeContent() {
  81. buildDescription()
  82. buildProperties()
  83. buildMethods()
  84. buildExtensions()
  85. return this
  86. }
  87. ClassDoc buildDescription() {
  88. comment = javadocConverter.parse(classMetaData, listener).docbook
  89. return this
  90. }
  91. ClassDoc buildProperties() {
  92. List<Element> header = propertiesTable.thead.tr[0].td.collect { it }
  93. if (header.size() < 1) {
  94. throw new RuntimeException("Expected at least 1 <td> in <thead>/<tr>, found: $header")
  95. }
  96. Map<String, Element> inheritedValueTitleMapping = [:]
  97. List<Element> valueTitles = []
  98. header.eachWithIndex { element, index ->
  99. if (index == 0) { return }
  100. Element override = element.overrides[0]
  101. if (override) {
  102. element.removeChild(override)
  103. inheritedValueTitleMapping.put(override.textContent, element)
  104. }
  105. if (element.firstChild instanceof Text) {
  106. element.firstChild.textContent = element.firstChild.textContent.replaceFirst(/^\s+/, '')
  107. }
  108. if (element.lastChild instanceof Text) {
  109. element.lastChild.textContent = element.lastChild.textContent.replaceFirst(/\s+$/, '')
  110. }
  111. valueTitles.add(element)
  112. }
  113. ClassDoc superClass = classMetaData.superClassName ? model.getClassDoc(classMetaData.superClassName) : null
  114. Map<String, PropertyDoc> props = new TreeMap<String, PropertyDoc>()
  115. if (superClass) {
  116. superClass.getClassProperties().each { propertyDoc ->
  117. def additionalValues = new LinkedHashMap<String, ExtraAttributeDoc>()
  118. propertyDoc.additionalValues.each { attributeDoc ->
  119. def key = attributeDoc.key
  120. if (inheritedValueTitleMapping[key]) {
  121. ExtraAttributeDoc newAttribute = new ExtraAttributeDoc(inheritedValueTitleMapping[key], attributeDoc.valueCell)
  122. additionalValues.put(newAttribute.key, newAttribute)
  123. } else {
  124. additionalValues.put(key, attributeDoc)
  125. }
  126. }
  127. props[propertyDoc.name] = propertyDoc.forClass(classMetaData, additionalValues.values() as List)
  128. }
  129. }
  130. propertiesTable.tr.each { Element tr ->
  131. def cells = tr.td.collect { it }
  132. if (cells.size() != header.size()) {
  133. throw new RuntimeException("Expected ${header.size()} <td> elements in <tr>, found: $tr")
  134. }
  135. String propName = cells[0].text().trim()
  136. PropertyMetaData property = classMetaData.findProperty(propName)
  137. if (!property) {
  138. throw new RuntimeException("No metadata for property '$className.$propName'. Available properties: ${classMetaData.propertyNames}")
  139. }
  140. def additionalValues = new LinkedHashMap<String, ExtraAttributeDoc>()
  141. if (superClass) {
  142. def overriddenProp = props.get(propName)
  143. if (overriddenProp) {
  144. overriddenProp.additionalValues.each { attributeDoc ->
  145. additionalValues.put(attributeDoc.key, attributeDoc)
  146. }
  147. }
  148. }
  149. header.eachWithIndex { col, i ->
  150. if (i == 0 || !cells[i].firstChild) { return }
  151. def attributeDoc = new ExtraAttributeDoc(valueTitles[i-1], cells[i])
  152. additionalValues.put(attributeDoc.key, attributeDoc)
  153. }
  154. PropertyDoc propertyDoc = new PropertyDoc(property, javadocConverter.parse(property, listener).docbook, additionalValues.values() as List)
  155. if (propertyDoc.description == null) {
  156. throw new RuntimeException("Docbook content for '$className.$propName' does not contain a description paragraph.")
  157. }
  158. props[propName] = propertyDoc
  159. }
  160. classProperties.addAll(props.values())
  161. return this
  162. }
  163. ClassDoc buildMethods() {
  164. Set signatures = [] as Set
  165. methodsTable.tr.each { Element tr ->
  166. def cells = tr.td
  167. if (cells.size() != 1) {
  168. throw new RuntimeException("Expected 1 cell in <tr>, found: $tr")
  169. }
  170. String methodName = cells[0].text().trim()
  171. Collection<MethodMetaData> methods = classMetaData.declaredMethods.findAll { it.name == methodName }
  172. if (!methods) {
  173. throw new RuntimeException("No metadata for method '$className.$methodName()'. Available methods: ${classMetaData.declaredMethods.collect {it.name} as TreeSet}")
  174. }
  175. methods.each { method ->
  176. def methodDoc = new MethodDoc(method, javadocConverter.parse(method, listener).docbook)
  177. if (!methodDoc.description) {
  178. throw new RuntimeException("Docbook content for '$className $method.signature' does not contain a description paragraph.")
  179. }
  180. def property = findProperty(method.name)
  181. def multiValued = false
  182. if (method.parameters.size() == 1 && method.parameters[0].type.signature == Closure.class.name && property) {
  183. def type = property.metaData.type
  184. if (type.name == 'java.util.List' || type.name == 'java.util.Collection' || type.name == 'java.util.Set' || type.name == 'java.util.Iterable') {
  185. type = type.typeArgs[0]
  186. multiValued = true
  187. }
  188. classBlocks << new BlockDoc(methodDoc, property, type, multiValued)
  189. } else {
  190. classMethods << methodDoc
  191. signatures << method.overrideSignature
  192. }
  193. }
  194. }
  195. if (classMetaData.superClassName) {
  196. ClassDoc supertype = model.getClassDoc(classMetaData.superClassName)
  197. supertype.getClassMethods().each { method ->
  198. if (signatures.add(method.metaData.overrideSignature)) {
  199. classMethods << method.forClass(classMetaData)
  200. }
  201. }
  202. }
  203. classMethods.sort { it.metaData.overrideSignature }
  204. classBlocks.sort { it.name }
  205. return this
  206. }
  207. ClassDoc buildExtensions() {
  208. def plugins = [:]
  209. extensionMetaData.mixinClasses.each { MixinMetaData mixin ->
  210. def pluginId = mixin.pluginId
  211. def classExtensionDoc = plugins[pluginId]
  212. if (!classExtensionDoc) {
  213. classExtensionDoc = new ClassExtensionDoc(pluginId, classMetaData)
  214. plugins[pluginId] = classExtensionDoc
  215. }
  216. classExtensionDoc.mixinClasses << model.getClassDoc(mixin.mixinClass)
  217. }
  218. extensionMetaData.extensionClasses.each { ExtensionMetaData extension ->
  219. def pluginId = extension.pluginId
  220. def classExtensionDoc = plugins[pluginId]
  221. if (!classExtensionDoc) {
  222. classExtensionDoc = new ClassExtensionDoc(pluginId, classMetaData)
  223. plugins[pluginId] = classExtensionDoc
  224. }
  225. classExtensionDoc.extensionClasses[extension.extensionId] = model.getClassDoc(extension.extensionClass)
  226. }
  227. classExtensions.addAll(plugins.values())
  228. classExtensions.each { extension -> extension.buildMetaData(model) }
  229. classExtensions.sort { it.pluginId }
  230. return this
  231. }
  232. String getStyle() {
  233. return classMetaData.groovy ? 'groovydoc' : 'javadoc'
  234. }
  235. private Element getTable(String title) {
  236. def table = getSection(title).table[0]
  237. if (!table) {
  238. throw new RuntimeException("Section '$title' does not contain a <table> element.")
  239. }
  240. if (!table.thead[0]) {
  241. throw new RuntimeException("Table '$title' does not contain a <thead> element.")
  242. }
  243. if (!table.thead[0].tr[0]) {
  244. throw new RuntimeException("Table '$title' does not contain a <thead>/<tr> element.")
  245. }
  246. return table
  247. }
  248. private Element getSection(String title) {
  249. def sections = classSection.section.findAll {
  250. it.title[0] && it.title[0].text().trim() == title
  251. }
  252. if (sections.size() < 1) {
  253. throw new RuntimeException("Docbook content for $className does not contain a '$title' section.")
  254. }
  255. return sections[0]
  256. }
  257. Element getHasDescription() {
  258. def paras = classSection.para
  259. return paras.size() > 0 ? paras[0] : null
  260. }
  261. Element getDescription() {
  262. def paras = classSection.para
  263. if (paras.size() < 1) {
  264. throw new RuntimeException("Docbook content for $className does not contain a description paragraph.")
  265. }
  266. return paras[0]
  267. }
  268. PropertyDoc findProperty(String name) {
  269. return classProperties.find { it.name == name }
  270. }
  271. BlockDoc getBlock(String name) {
  272. def block = classBlocks.find { it.name == name }
  273. if (block) {
  274. return block
  275. }
  276. for (extensionDoc in classExtensions) {
  277. block = extensionDoc.extensionBlocks.find { it.name == name }
  278. if (block) {
  279. return block
  280. }
  281. }
  282. throw new RuntimeException("Class $className does not have a script block '$name'.")
  283. }
  284. }