PageRenderTime 49ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

/atlassian-docco/src/main/java/com/atlassian/docco/Docco.java

https://bitbucket.org/Arnauld/atlassian-docco
Java | 492 lines | 327 code | 60 blank | 105 comment | 45 complexity | 66819d6de5461e42462f5a2bc181645a MD5 | raw file
  1. /*!
  2. This is the main class for parsing docco comments and generating [DoccoParts](${basePath}/src/main/java/com/atlassian/docco/DoccoParts.java.html) that can be used to generate docco html.
  3. Although this class has methods for actually generating a single html file, it's more common to use [DoccoBatch](${basePath}/src/main/java/com/atlassian/docco/DoccoBatch.java.html) to do the html generation over a batch of files.
  4. To construct a Docco object you could call the constructor in this class, but it is easier and preferred to use the [DoccoBuilder](${basePath}/src/main/java/com/atlassian/docco/builder/DoccoBuilder.java.html).
  5. **Note:** You'll only need to create a Docco object if you're not using DoccoBatch.
  6. */
  7. package com.atlassian.docco;
  8. import java.io.*;
  9. import java.net.URISyntaxException;
  10. import java.net.URL;
  11. import java.util.List;
  12. import java.util.Map;
  13. import java.util.regex.Matcher;
  14. import com.atlassian.docco.builder.DoccoBuilder;
  15. import com.atlassian.docco.mapping.DoccoCommentPatterns;
  16. import com.atlassian.docco.mapping.DoccoFileMappingManager;
  17. import com.google.template.soy.SoyFileSet;
  18. import com.google.template.soy.data.SanitizedContent;
  19. import com.google.template.soy.data.SoyListData;
  20. import com.google.template.soy.data.SoyMapData;
  21. import com.google.template.soy.tofu.SoyTofu;
  22. import org.apache.commons.io.FileUtils;
  23. import org.apache.commons.lang.StringUtils;
  24. import org.pegdown.PegDownProcessor;
  25. import static com.google.common.base.Preconditions.checkNotNull;
  26. import static com.google.common.base.Preconditions.checkState;
  27. public final class Docco
  28. {
  29. private final boolean stripJavadoc;
  30. private final boolean includeDefaultResources;
  31. private final DoccoFileMappingManager fileMappings;
  32. private final PegDownProcessor pegDown;
  33. private final List<File> customResources;
  34. private final SoyTofu horizontalTofu;
  35. private final SoyTofu verticalTofu;
  36. /*!####Construct a Docco object.
  37. This is usually called by the [DoccoBuilder](${basePath}/src/main/java/com/atlassian/docco/builder/DoccoBuilder.java.html) instead of being invoked directly.
  38. */
  39. public Docco(URL horizonatlSoyTemplate, URL verticalSoyTemplate, DoccoFileMappingManager fileMappings, boolean stripJavadoc, boolean includeDefaultResources, List<File> customResources, PegDownProcessor pegDown)
  40. {
  41. this.fileMappings = fileMappings;
  42. this.stripJavadoc = stripJavadoc;
  43. this.pegDown = pegDown;
  44. this.includeDefaultResources = includeDefaultResources;
  45. this.customResources = customResources;
  46. /*!####ToFu Anyone?
  47. Here we create the soyTofu compilers for both horizontal and vertical displays so we can reuse them when making multiple calls to render the html.
  48. */
  49. SoyFileSet soyFileSetH = new SoyFileSet.Builder().add(horizonatlSoyTemplate).build();
  50. this.horizontalTofu = soyFileSetH.compileToJavaObj().forNamespace("atlassian.docco");
  51. SoyFileSet soyFileSetV = new SoyFileSet.Builder().add(verticalSoyTemplate).build();
  52. this.verticalTofu = soyFileSetV.compileToJavaObj().forNamespace("atlassian.docco");
  53. }
  54. /*!####Get the DoccoParts
  55. Given a source file, this will return a [DoccoParts](${basePath}/src/main/java/com/atlassian/docco/DoccoParts.java.html) object representing the file.
  56. */
  57. public DoccoParts getDoccoParts(File sourceFile) throws IOException
  58. {
  59. checkNotNull(sourceFile);
  60. checkState(sourceFile.exists(),"Source file doesn't exist!");
  61. DoccoParts doccoParts = new DoccoParts(pegDown);
  62. DoccoCommentPatterns patterns = fileMappings.getPatternsForFile(sourceFile);
  63. boolean forceJavadoc = fileMappings.getForceKeepJavadocForFile(sourceFile);
  64. try
  65. {
  66. BufferedReader buff = new BufferedReader(new FileReader(sourceFile));
  67. String line;
  68. Section section = new Section(pegDown);
  69. String doc = "";
  70. String indent = "";
  71. String groups = "";
  72. boolean insideJavadoc = false;
  73. boolean insideDocco = false;
  74. boolean foundMatch = false;
  75. boolean pastHeader = false;
  76. /*!####Read The File
  77. After setting up the DoccoParts container and the comment mappings, we're reay to read the file line by line.
  78. */
  79. while((line = buff.readLine()) != null)
  80. {
  81. foundMatch = false;
  82. /*!
  83. First we test for a special comment that can add the file to multiple "groups" to be used as custom index navigation headers.
  84. A group comment looks like:
  85. \/*!! some groupname, some other groupname *\/
  86. *notice the double __!!__ *
  87. The group comment must be the first thing in the file
  88. */
  89. Matcher groupLine = patterns.getGroupsPattern().matcher(line);
  90. if(groupLine.matches() && !insideJavadoc && !pastHeader)
  91. {
  92. foundMatch = true;
  93. groups = groupLine.group(1);
  94. if(!pastHeader)
  95. {
  96. String[] groupNames = StringUtils.split(groups,",");
  97. if(null != groupNames && groupNames.length > 0)
  98. {
  99. for(String groupName : groupNames)
  100. {
  101. doccoParts.addGroup(StringUtils.trim(groupName));
  102. }
  103. }
  104. }
  105. insideDocco = false;
  106. continue;
  107. }
  108. /*!
  109. Next we test for a single line comment. If we find one and it appears before any other code in the file, we treat it as a header, if it appears after some other code, we start a new section for it.
  110. We also do a check to see if the comment starts with a **-** character, and if it does, we mark it as "hidden" so the template can choose to collapse it.
  111. A single line comment looks like:
  112. \/*! some comment in here *\/
  113. *notice the single __!__ *
  114. */
  115. Matcher singleLine = patterns.getSingleLinePattern().matcher(line);
  116. if(singleLine.matches() && !insideJavadoc)
  117. {
  118. foundMatch = true;
  119. doc = StringUtils.replace(StringUtils.replace(singleLine.group(1),"\\/*","/*"),"*\\/","*/");
  120. if(!pastHeader)
  121. {
  122. doccoParts.getHeader().addDoc(doc);
  123. pastHeader = true;
  124. }
  125. else
  126. {
  127. section = flushSection(section,doccoParts);
  128. if(doc.startsWith("-"))
  129. {
  130. section.setHidden(true);
  131. section.setTitle(doc.substring(1));
  132. }
  133. else
  134. {
  135. section.addDoc(doc);
  136. }
  137. }
  138. insideDocco = false;
  139. continue;
  140. }
  141. /*!
  142. Next we test for the start of a multi-line comment.
  143. This follows the same header/section/hidden rules as the single line comment
  144. A multi-line start comment looks like:
  145. \/*! some comment
  146. *notice the single __!__ *
  147. */
  148. Matcher multiStart = patterns.getMultilineBeginPattern().matcher(line);
  149. if(multiStart.matches())
  150. {
  151. foundMatch = true;
  152. indent = multiStart.group(1);
  153. doc = StringUtils.replace(StringUtils.replace(multiStart.group(2),"\\/*","/*"),"*\\/","*/");
  154. if(!pastHeader)
  155. {
  156. doccoParts.getHeader().addDoc(doc);
  157. }
  158. else
  159. {
  160. section = flushSection(section,doccoParts);
  161. if(doc.startsWith("-"))
  162. {
  163. section.setHidden(true);
  164. section.setTitle(doc.substring(1));
  165. }
  166. else
  167. {
  168. section.addDoc(doc);
  169. }
  170. }
  171. insideDocco = true;
  172. continue;
  173. }
  174. /*!
  175. Check for a "normal" javadoc style comment. If we find one and we're not supposed to strip it, we add it to the code
  176. */
  177. Matcher javadoc = patterns.getJavadocBeginPattern().matcher(line);
  178. if(javadoc.matches())
  179. {
  180. foundMatch = true;
  181. insideJavadoc = true;
  182. pastHeader = true;
  183. if(!stripJavadoc || forceJavadoc)
  184. {
  185. section.addCode(line);
  186. }
  187. continue;
  188. }
  189. /*!
  190. When we find the end of a multi-line comment, we need to see if we're processing a docco comment or a java doc comment.
  191. If it's a docco comment, we also check to see if we're a header or not, then either add the doc to the header or the current section.
  192. If it's a javadoc and we're not stripping it, we add it to the code.
  193. */
  194. Matcher multiEnd = patterns.getMultilineEndPattern().matcher(line);
  195. if(multiEnd.matches())
  196. {
  197. foundMatch = true;
  198. if(insideDocco)
  199. {
  200. doc = StringUtils.replace(StringUtils.replace(multiEnd.group(1),"\\/*","/*"),"*\\/","*/");
  201. if(!pastHeader)
  202. {
  203. doccoParts.getHeader().addDoc(doc);
  204. pastHeader = true;
  205. }
  206. else
  207. {
  208. section.addDoc(doc);
  209. }
  210. }
  211. if(insideJavadoc && (!stripJavadoc || forceJavadoc))
  212. {
  213. section.addCode(line);
  214. }
  215. insideDocco = false;
  216. insideJavadoc = false;
  217. continue;
  218. }
  219. /*!
  220. Finally, if none of the above match, we determine if we're inside a header or docco comment and add the appropriate docs, or we add the line to the section's code.
  221. */
  222. if(!foundMatch)
  223. {
  224. if(insideDocco)
  225. {
  226. doc = StringUtils.replace(StringUtils.replace(line,"\\/*","/*"),"*\\/","*/");
  227. if(!pastHeader)
  228. {
  229. doccoParts.getHeader().addDoc(doc);
  230. }
  231. else
  232. {
  233. section.addDoc(doc.replaceAll("^" + indent,""));
  234. }
  235. }
  236. else if(!insideJavadoc || (!stripJavadoc || forceJavadoc))
  237. {
  238. if(pastHeader)
  239. {
  240. section.addCode(line);
  241. }
  242. else if (StringUtils.isNotBlank(line))
  243. {
  244. pastHeader = true;
  245. section.addCode(line);
  246. }
  247. }
  248. }
  249. }
  250. flushSection(section,doccoParts);
  251. }
  252. catch (FileNotFoundException e)
  253. {
  254. //this should never happen since we have a precondition
  255. e.printStackTrace();
  256. }
  257. /*!####Add the Default Group
  258. If a group comment wasn't found, we add the file's directory as the group
  259. */
  260. if(doccoParts.getGroups().isEmpty())
  261. {
  262. doccoParts.addGroup("file://" + sourceFile.getParentFile().getCanonicalPath());
  263. }
  264. return doccoParts;
  265. }
  266. /*!####Flushing Sections
  267. This simply creates a new section if the current section has both docs and code or returns the current section
  268. */
  269. private Section flushSection(Section section, DoccoParts doccoParts)
  270. {
  271. if(section.isCommitted())
  272. {
  273. if(!section.isEmpty())
  274. {
  275. doccoParts.addSection(section);
  276. }
  277. return new Section(pegDown);
  278. }
  279. return section;
  280. }
  281. /*!####Render Time
  282. Writes the docco html for a given source file to the given output path.
  283. This will create two directories under the output path named "horizontal" and "vertical" to hold the respective docco layouts of the source file html.
  284. This will use the google closure templates to render the html and copy any required css/js resources
  285. */
  286. public void writeHtml(File sourceFile, File outputFile) throws IOException
  287. {
  288. checkNotNull(sourceFile);
  289. checkState(sourceFile.exists(),"Source file doesn't exist!");
  290. checkNotNull(outputFile);
  291. File hBase = new File(outputFile.getParentFile(),"horizontal");
  292. FileUtils.forceMkdir(hBase);
  293. File hFile = new File(hBase,outputFile.getName());
  294. File vBase = new File(outputFile.getParentFile(),"vertical");
  295. FileUtils.forceMkdir(vBase);
  296. File vFile = new File(vBase,outputFile.getName());
  297. String syntax = fileMappings.getSyntaxForFile(sourceFile);
  298. DoccoParts parts = getDoccoParts(sourceFile);
  299. SanitizedContent header = new SanitizedContent(parts.getHeader().getDoc(), SanitizedContent.ContentKind.HTML);
  300. String fileNoExt = StringUtils.substringBeforeLast(sourceFile.getName(),".");
  301. SoyMapData soyData = new SoyMapData("basePath", ".", "title",sourceFile.getName(),"nameWithoutExtension",fileNoExt,"syntax", syntax, "header",header,"sections",Docco.getSoySectionData(parts.getSections()));
  302. String renderedH = horizontalTofu.newRenderer(".singlePage").setData(soyData).render();
  303. FileUtils.writeStringToFile(hFile,renderedH);
  304. String renderedV = verticalTofu.newRenderer(".singlePage").setData(soyData).render();
  305. FileUtils.writeStringToFile(vFile,renderedV);
  306. copyResources(hBase,vBase);
  307. }
  308. /*!
  309. A simple wrapper that uses the source file's absolute path as the output folder
  310. */
  311. public void writeHtml(File sourceFile) throws IOException
  312. {
  313. writeHtml(sourceFile, new File(sourceFile.getAbsolutePath() + ".html"));
  314. }
  315. /*!
  316. Copies all the resources needed for the html display.
  317. This will copy the default packaged resources if includeDefaultResources = true (the default) and then it will copy any custom resources provided
  318. */
  319. public void copyResources(File hBase, File vBase) throws IOException
  320. {
  321. if(includeDefaultResources)
  322. {
  323. copyDefaultResources(hBase, vBase);
  324. }
  325. for(File file : customResources)
  326. {
  327. if(file.isDirectory())
  328. {
  329. FileUtils.copyDirectory(file,hBase);
  330. FileUtils.copyDirectory(file,vBase);
  331. }
  332. else if(file.isFile())
  333. {
  334. FileUtils.copyFile(file, new File(hBase, file.getName()));
  335. FileUtils.copyFile(file, new File(vBase, file.getName()));
  336. }
  337. }
  338. }
  339. /*!- copy default resources */
  340. public void copyDefaultResources(File hBase, File vBase) throws IOException
  341. {
  342. //horizontal
  343. FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/css/bootstrap.min.css"),new File(hBase,"css/bootstrap.min.css"));
  344. FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/css/bootstrap-responsive.min.css"),new File(hBase,"css/bootstrap-responsive.min.css"));
  345. FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/css/atlassian-docco-h.css"),new File(hBase,"css/atlassian-docco.css"));
  346. FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/js/atlassian-docco-h.js"),new File(hBase,"js/atlassian-docco.js"));
  347. FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/js/bootstrap.min.js"),new File(hBase,"js/bootstrap.min.js"));
  348. FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/js/jquery.cookie.js"),new File(hBase,"js/jquery.cookie.js"));
  349. //vertical
  350. FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/css/bootstrap.min.css"),new File(vBase,"css/bootstrap.min.css"));
  351. FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/css/bootstrap-responsive.min.css"),new File(vBase,"css/bootstrap-responsive.min.css"));
  352. FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/css/atlassian-docco-v.css"),new File(vBase,"css/atlassian-docco.css"));
  353. FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/js/atlassian-docco-v.js"),new File(vBase,"js/atlassian-docco.js"));
  354. FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/js/bootstrap.min.js"),new File(vBase,"js/bootstrap.min.js"));
  355. FileUtils.copyInputStreamToFile(getClass().getResourceAsStream("/js/jquery.cookie.js"),new File(vBase,"js/jquery.cookie.js"));
  356. }
  357. /*!- getters for default templates */
  358. public static File getDefaultTemplate() throws URISyntaxException
  359. {
  360. return new File(Docco.class.getResource("/template/atlassian-docco-h.soy").toURI());
  361. }
  362. public static URL getDefaultHorizontalTemplate() throws URISyntaxException
  363. {
  364. URL url = Docco.class.getResource("/template/atlassian-docco-h.soy");
  365. //return new File(url.toURI());
  366. return url;
  367. }
  368. public static URL getDefaultVerticalTemplate() throws URISyntaxException
  369. {
  370. URL url = Docco.class.getResource("/template/atlassian-docco-v.soy");
  371. //return new File(url.toURI());
  372. return url;
  373. }
  374. /*!
  375. Converts a list of docco [Section](${basePath}/src/main/java/com/atlassian/docco/Section.java.html) objects to a SoyListData object that gets passed to the soy templates.
  376. */
  377. public static SoyListData getSoySectionData(List<Section> sections) throws IOException
  378. {
  379. SoyListData soyList = new SoyListData();
  380. for(Section section : sections)
  381. {
  382. SanitizedContent doc = new SanitizedContent(section.getDoc(), SanitizedContent.ContentKind.HTML);
  383. SanitizedContent code = new SanitizedContent(section.getCode(), SanitizedContent.ContentKind.HTML);
  384. SoyMapData soyMap = new SoyMapData("doc",doc,"code",section.getCode(),"hidden", section.isHidden(),"title",section.getTitle());
  385. soyList.add(soyMap);
  386. }
  387. return soyList;
  388. }
  389. /*!
  390. Does the same as *getSoySectionData* but accepts a map of name/value pairs to be used as token replacements in docco comments.
  391. This is typically used to make linking easier by supplying the **basePath** to the root folder as a token replacement.
  392. */
  393. public static SoyListData getSoySectionDataWithTokenReplacement(List<Section> sections,Map<String,String> replaceMap) throws IOException
  394. {
  395. SoyListData soyList = new SoyListData();
  396. for(Section section : sections)
  397. {
  398. SanitizedContent doc = new SanitizedContent(section.getDocWithTokenReplacement(replaceMap), SanitizedContent.ContentKind.HTML);
  399. SanitizedContent code = new SanitizedContent(section.getCode(), SanitizedContent.ContentKind.HTML);
  400. SoyMapData soyMap = new SoyMapData("doc",doc,"code",section.getCode(),"hidden", section.isHidden(),"title",section.getTitle());
  401. soyList.add(soyMap);
  402. }
  403. return soyList;
  404. }
  405. /*!- convenience method to get builder */
  406. public static DoccoBuilder builder()
  407. {
  408. return new DoccoBuilder();
  409. }
  410. }