| /* |
| * 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.ignite.tools.ant.beautifier; |
| |
| import java.io.BufferedOutputStream; |
| import java.io.Closeable; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.io.OutputStream; |
| import java.io.Reader; |
| import java.io.StringWriter; |
| import java.nio.charset.Charset; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import jodd.jerry.Jerry; |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.DirectoryScanner; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.taskdefs.MatchingTask; |
| |
| /** |
| * Ant task fixing known HTML issues for Javadoc. |
| */ |
| public class GridJavadocAntTask extends MatchingTask { |
| /** */ |
| private static final String SH_URL = "http://agorbatchev.typepad.com/pub/sh/3_0_83"; |
| |
| /** Directory. */ |
| private File dir; |
| |
| /** CSS file name. */ |
| private String css; |
| |
| /** Whether to verify JavaDoc HTML. */ |
| private boolean verify = true; |
| |
| /** |
| * Sets directory. |
| * |
| * @param dir Directory to set. |
| */ |
| public void setDir(File dir) { |
| assert dir != null; |
| |
| this.dir = dir; |
| } |
| |
| /** |
| * Sets CSS file name. |
| * |
| * @param css CSS file name to set. |
| */ |
| public void setCss(String css) { |
| assert css != null; |
| |
| this.css = css; |
| } |
| |
| /** |
| * Sets whether to verify JavaDoc HTML. |
| * |
| * @param verify Verify flag. |
| */ |
| public void setVerify(Boolean verify) { |
| assert verify != null; |
| |
| this.verify = verify; |
| } |
| |
| /** |
| * Closes resource. |
| * |
| * @param closeable Resource to close. |
| */ |
| private void close(Closeable closeable) { |
| if (closeable != null) { |
| try { |
| closeable.close(); |
| } |
| catch (IOException e) { |
| log("Failed closing [resource=" + closeable + ", message=" + e.getLocalizedMessage() + ']', |
| Project.MSG_WARN); |
| } |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @Override public void execute() { |
| if (dir == null) |
| throw new BuildException("'dir' attribute must be specified."); |
| |
| if (css == null) |
| throw new BuildException("'css' attribute must be specified."); |
| |
| log("dir=" + dir, Project.MSG_DEBUG); |
| log("css=" + css, Project.MSG_DEBUG); |
| |
| DirectoryScanner scanner = getDirectoryScanner(dir); |
| |
| boolean fail = false; |
| |
| for (String fileName : scanner.getIncludedFiles()) { |
| String file = dir.getAbsolutePath() + '/' + fileName; |
| |
| try { |
| processFile(file); |
| } |
| catch (IOException e) { |
| throw new BuildException("IO error while processing: " + file, e); |
| } |
| catch (IllegalArgumentException e) { |
| System.err.println("JavaDoc error: " + e.getMessage()); |
| |
| fail = true; |
| } |
| } |
| |
| if (fail) |
| throw new BuildException("Execution failed due to previous errors."); |
| } |
| |
| /** |
| * Processes file (validating and cleaning up Javadoc's HTML). |
| * |
| * @param file File to cleanup. |
| * @throws IOException Thrown in case of any I/O error. |
| * @throws IllegalArgumentException In JavaDoc HTML validation failed. |
| */ |
| private void processFile(String file) throws IOException { |
| assert file != null; |
| |
| String fileContent = readFileToString(file, Charset.forName("UTF-8")); |
| |
| if (verify) { |
| // Parse HTML. |
| Jerry doc = Jerry.jerry(fileContent); |
| |
| if (file.endsWith("overview-summary.html")) { |
| // Try to find Other Packages section. |
| Jerry otherPackages = |
| doc.find("div.contentContainer table.overviewSummary caption span:contains('Other Packages')"); |
| |
| if (otherPackages.size() > 0) |
| throw new IllegalArgumentException("'Other Packages' section should not be present, " + |
| "all packages should have corresponding documentation groups: " + file); |
| } |
| else if (!isViewHtml(file)) { |
| // Try to find a class description block. |
| Jerry descBlock = doc.find("div.contentContainer div.description ul.blockList li.blockList div.block"); |
| |
| if (descBlock.size() == 0) |
| throw new IllegalArgumentException("Class doesn't have description in file: " + file); |
| } |
| } |
| |
| GridJavadocCharArrayLexReader lexer = new GridJavadocCharArrayLexReader(fileContent.toCharArray()); |
| |
| Collection<GridJavadocToken> toks = new ArrayList<>(); |
| |
| StringBuilder tokBuf = new StringBuilder(); |
| |
| int ch; |
| |
| while ((ch = lexer.read()) != GridJavadocCharArrayLexReader.EOF) { |
| // Instruction, tag or comment. |
| if (ch =='<') { |
| if (tokBuf.length() > 0) { |
| toks.add(new GridJavadocToken(GridJavadocTokenType.TOKEN_TEXT, tokBuf.toString())); |
| |
| tokBuf.setLength(0); |
| } |
| |
| tokBuf.append('<'); |
| |
| ch = lexer.read(); |
| |
| if (ch == GridJavadocCharArrayLexReader.EOF) |
| throw new IOException("Unexpected EOF: " + file); |
| |
| // Instruction or comment. |
| if (ch == '!') { |
| for (; ch != GridJavadocCharArrayLexReader.EOF && ch != '>'; ch = lexer.read()) |
| tokBuf.append((char)ch); |
| |
| if (ch == GridJavadocCharArrayLexReader.EOF) |
| throw new IOException("Unexpected EOF: " + file); |
| |
| assert ch == '>'; |
| |
| tokBuf.append('>'); |
| |
| String val = tokBuf.toString(); |
| |
| toks.add(new GridJavadocToken(val.startsWith("<!--") ? GridJavadocTokenType.TOKEN_COMM : |
| GridJavadocTokenType.TOKEN_INSTR, val)); |
| |
| tokBuf.setLength(0); |
| } |
| // Tag. |
| else { |
| for (; ch != GridJavadocCharArrayLexReader.EOF && ch != '>'; ch = lexer.read()) |
| tokBuf.append((char)ch); |
| |
| if (ch == GridJavadocCharArrayLexReader.EOF) |
| throw new IOException("Unexpected EOF: " + file); |
| |
| assert ch == '>'; |
| |
| tokBuf.append('>'); |
| |
| if (tokBuf.length() <= 2) |
| throw new IOException("Invalid HTML in [file=" + file + ", html=" + tokBuf + ']'); |
| |
| String val = tokBuf.toString(); |
| |
| toks.add(new GridJavadocToken(val.startsWith("</") ? |
| GridJavadocTokenType.TOKEN_CLOSE_TAG : GridJavadocTokenType.TOKEN_OPEN_TAG, val)); |
| |
| tokBuf.setLength(0); |
| } |
| } |
| else |
| tokBuf.append((char)ch); |
| } |
| |
| if (tokBuf.length() > 0) |
| toks.add(new GridJavadocToken(GridJavadocTokenType.TOKEN_TEXT, tokBuf.toString())); |
| |
| for (GridJavadocToken tok : toks) { |
| String val = tok.value(); |
| |
| switch (tok.type()) { |
| case TOKEN_COMM: { |
| break; |
| } |
| |
| case TOKEN_OPEN_TAG: { |
| tok.update(fixColors(tok.value())); |
| |
| break; |
| } |
| |
| case TOKEN_CLOSE_TAG: { |
| if ("</head>".equalsIgnoreCase(val)) |
| tok.update( |
| "<link rel='shortcut icon' href='https://ignite.apache.org/favicon.ico'/>\n" + |
| "<link type='text/css' rel='stylesheet' href='" + SH_URL + "/styles/shCore.css'/>\n" + |
| "<link type='text/css' rel='stylesheet' href='" + SH_URL + |
| "/styles/shThemeDefault.css'/>\n" + |
| "<script type='text/javascript' src='" + SH_URL + "/scripts/shCore.js'></script>\n" + |
| "<script type='text/javascript' src='" + SH_URL + "/scripts/shLegacy.js'></script>\n" + |
| "<script type='text/javascript' src='" + SH_URL + "/scripts/shBrushJava.js'></script>\n" + |
| "<script type='text/javascript' src='" + SH_URL + "/scripts/shBrushPlain.js'></script>\n" + |
| "<script type='text/javascript' src='" + SH_URL + |
| "/scripts/shBrushJScript.js'></script>\n" + |
| "<script type='text/javascript' src='" + SH_URL + "/scripts/shBrushBash.js'></script>\n" + |
| "<script type='text/javascript' src='" + SH_URL + "/scripts/shBrushXml.js'></script>\n" + |
| "<script type='text/javascript' src='" + SH_URL + "/scripts/shBrushScala.js'></script>\n" + |
| "<script type='text/javascript' src='" + SH_URL + "/scripts/shBrushGroovy.js'></script>\n" + |
| "</head>\n"); |
| else if ("</body>".equalsIgnoreCase(val)) |
| tok.update( |
| "<!--FOOTER-->" + |
| "<script type='text/javascript'>" + |
| "SyntaxHighlighter.all();" + |
| "dp.SyntaxHighlighter.HighlightAll('code');" + |
| "!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');" + |
| "</script>\n" + |
| "</body>\n"); |
| |
| break; |
| } |
| |
| case TOKEN_INSTR: { |
| // No-op. |
| |
| break; |
| } |
| |
| case TOKEN_TEXT: { |
| tok.update(fixColors(val)); |
| |
| break; |
| } |
| |
| default: |
| assert false; |
| } |
| } |
| |
| StringBuilder buf = new StringBuilder(); |
| StringBuilder tmp = new StringBuilder(); |
| |
| boolean inPre = false; |
| |
| // Second pass for unstructured replacements. |
| for (GridJavadocToken tok : toks) { |
| String val = tok.value(); |
| |
| switch (tok.type()) { |
| case TOKEN_INSTR: |
| case TOKEN_TEXT: |
| case TOKEN_COMM: { |
| tmp.append(val); |
| |
| break; |
| } |
| |
| case TOKEN_OPEN_TAG: { |
| if (val.toLowerCase().startsWith("<pre name=")) { |
| inPre = true; |
| |
| buf.append(fixBrackets(tmp.toString())); |
| |
| tmp.setLength(0); |
| } |
| |
| tmp.append(val); |
| |
| break; |
| } |
| |
| case TOKEN_CLOSE_TAG: { |
| if (val.toLowerCase().startsWith("</pre") && inPre) { |
| inPre = false; |
| |
| buf.append(tmp.toString()); |
| |
| tmp.setLength(0); |
| } |
| |
| tmp.append(val); |
| |
| break; |
| } |
| |
| default: |
| assert false; |
| } |
| } |
| |
| String s = buf.append(fixBrackets(tmp.toString())).toString(); |
| |
| s = fixExternalLinks(s); |
| s = fixDeprecated(s); |
| s = fixNullable(s); |
| s = fixTodo(s); |
| |
| replaceFile(file, s); |
| } |
| |
| /** |
| * Checks whether a file is a view-related HTML file rather than a single |
| * class documentation. |
| * |
| * @param fileName HTML file name. |
| * @return {@code True} if it's a view-related HTML. |
| */ |
| private boolean isViewHtml(String fileName) { |
| String baseName = new File(fileName).getName(); |
| |
| return "index.html".equals(baseName) || baseName.contains("-"); |
| } |
| |
| /** |
| * |
| * @param s String token. |
| * @return Token with replaced colors. |
| */ |
| private String fixColors(String s) { |
| return s.replace("0000c0", "000000"). |
| replace("000000", "333333"). |
| replace("c00000", "333333"). |
| replace("008000", "999999"). |
| replace("990000", "336699"). |
| replace("font color=\"#808080\"", "font size=-2 color=\"#aaaaaa\""); |
| } |
| |
| /** |
| * |
| * @param s String token. |
| * @return Fixed token value. |
| */ |
| private String fixBrackets(String s) { |
| return s.replace("<", "<span class='angle_bracket'><</span>"). |
| replace(">", "<span class='angle_bracket'>></span>"); |
| } |
| |
| /** |
| * |
| * @param s String token. |
| * @return Fixed token value. |
| */ |
| private String fixTodo(String s) { |
| return s.replace("TODO", "<span class='todo'>TODO</span>"); |
| } |
| |
| /** |
| * |
| * @param s String token. |
| * @return Fixed token value. |
| */ |
| private String fixNullable(String s) { |
| return s.replace("<FONT SIZE=\"-1\">@Nullable", "<FONT SIZE=\"-1\" class='nullable'>@Nullable"); |
| } |
| |
| /** |
| * |
| * @param s String token. |
| * @return Fixed token value. |
| */ |
| private String fixDeprecated(String s) { |
| return s.replace("<B>Deprecated.</B>", "<span class='deprecated'>Deprecated.</span>"); |
| } |
| |
| /** |
| * |
| * @param s String token. |
| * @return Fixed token value. |
| */ |
| private String fixExternalLinks(String s) { |
| return s.replace("A HREF=\"http://java.sun.com/j2se/1.6.0", |
| "A target='jse5javadoc' HREF=\"http://java.sun.com/j2se/1.6.0"); |
| } |
| |
| /** |
| * Replaces file with given body. |
| * |
| * @param file File to replace. |
| * @param body New body for the file. |
| * @throws IOException Thrown in case of any errors. |
| */ |
| private void replaceFile(String file, String body) throws IOException { |
| try (OutputStream out = new BufferedOutputStream(new FileOutputStream(file))) { |
| out.write(body.getBytes()); |
| } |
| } |
| |
| /** |
| * Reads file to string using specified charset. |
| * |
| * @param fileName File name. |
| * @param charset File charset. |
| * @return File content. |
| * @throws IOException If error occurred. |
| */ |
| public static String readFileToString(String fileName, Charset charset) throws IOException { |
| Reader input = new InputStreamReader(new FileInputStream(fileName), charset); |
| |
| StringWriter output = new StringWriter(); |
| |
| char[] buf = new char[4096]; |
| |
| int n; |
| |
| while ((n = input.read(buf)) != -1) |
| output.write(buf, 0, n); |
| |
| return output.toString(); |
| } |
| } |