Java tutorial
/* * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * WSO2 Inc. 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.ballerinalang.docgen.docs; import org.apache.commons.io.FileUtils; import org.ballerinalang.compiler.CompilerOptionName; import org.ballerinalang.compiler.CompilerPhase; import org.ballerinalang.config.ConfigRegistry; import org.ballerinalang.docgen.Generator; import org.ballerinalang.docgen.Writer; import org.ballerinalang.docgen.docs.utils.BallerinaDocUtils; import org.ballerinalang.docgen.model.Caption; import org.ballerinalang.docgen.model.Link; import org.ballerinalang.docgen.model.PackageDoc; import org.ballerinalang.docgen.model.PackageName; import org.ballerinalang.docgen.model.Page; import org.ballerinalang.model.elements.PackageID; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.wso2.ballerinalang.compiler.Compiler; import org.wso2.ballerinalang.compiler.FileSystemProjectDirectory; import org.wso2.ballerinalang.compiler.PackageLoader; import org.wso2.ballerinalang.compiler.SourceDirectory; import org.wso2.ballerinalang.compiler.semantics.analyzer.CodeAnalyzer; import org.wso2.ballerinalang.compiler.semantics.analyzer.SemanticAnalyzer; import org.wso2.ballerinalang.compiler.semantics.model.SymbolTable; import org.wso2.ballerinalang.compiler.tree.BLangPackage; import org.wso2.ballerinalang.compiler.util.CompilerContext; import org.wso2.ballerinalang.compiler.util.CompilerOptions; import org.wso2.ballerinalang.compiler.util.Names; import org.wso2.ballerinalang.compiler.util.ProjectDirConstants; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; /** * Main class to generate a ballerina documentation. */ public class BallerinaDocGenerator { private static final Logger log = LoggerFactory.getLogger(BallerinaDocGenerator.class); private static final PrintStream out = System.out; private static final String BSOURCE_FILE_EXT = ".bal"; private static final String MODULE_CONTENT_FILE = "Module.md"; private static final Path BAL_BUILTIN = Paths.get("ballerina", "builtin"); private static final String HTML = ".html"; /** * API to generate Ballerina API documentation. * @param sourceRoot project root * @param output path to the output directory where the API documentation will be written to. * @param packageFilter comma separated list of package names to be filtered from the documentation. * @param isNative whether the given packages are native or not. * @param offline is offline generation * @param sources either the path to the directories where Ballerina source files reside or a */ public static void generateApiDocs(String sourceRoot, String output, String packageFilter, boolean isNative, boolean offline, String... sources) { out.println("docerina: API documentation generation for sources - " + Arrays.toString(sources)); List<Link> primitives = primitives(); // generate package docs Map<String, PackageDoc> docsMap = generatePackageDocsMap(sourceRoot, packageFilter, isNative, sources, offline); if (docsMap.size() == 0) { out.println("docerina: no module definitions found!"); return; } if (BallerinaDocUtils.isDebugEnabled()) { out.println("docerina: generating HTML API documentation..."); } // validate output path String userDir = System.getProperty("user.dir"); // If output directory is empty if (output == null) { output = System.getProperty(BallerinaDocConstants.HTML_OUTPUT_PATH_KEY, userDir + File.separator + ProjectDirConstants.TARGET_DIR_NAME + File.separator + "api-docs"); } if (BallerinaDocUtils.isDebugEnabled()) { out.println("docerina: creating output directory: " + output); } try { // Create output directory Files.createDirectories(Paths.get(output)); } catch (IOException e) { out.println(String.format("docerina: API documentation generation failed. Couldn't create the [output " + "directory] %s. Cause: %s", output, e.getMessage())); log.error( String.format("API documentation generation failed. Couldn't create the [output directory] %s. " + "" + "" + "Cause: %s", output, e.getMessage()), e); return; } if (BallerinaDocUtils.isDebugEnabled()) { out.println("docerina: successfully created the output directory: " + output); } // Sort packages by package path List<PackageDoc> packageList = new ArrayList<>(docsMap.values()); packageList.sort(Comparator.comparing(pkg -> pkg.bLangPackage.packageID.toString())); // Sort the package names List<String> packageNames = new ArrayList<>(docsMap.keySet()); Collections.sort(packageNames); List<Link> packageNameList = PackageName.convertList(packageNames); String packageTemplateName = System.getProperty(BallerinaDocConstants.MODULE_TEMPLATE_NAME_KEY, "page"); String packageToCTemplateName = System.getProperty(BallerinaDocConstants.MODULE_TOC_TEMPLATE_NAME_KEY, "toc"); List<Path> resources = new ArrayList<>(); //Iterate over the packages to generate the pages for (PackageDoc packageDoc : packageList) { try { BLangPackage bLangPackage = packageDoc.bLangPackage; String pkgDescription = packageDoc.description; // Sort functions, connectors, structs, type mappers and annotationDefs sortPackageConstructs(bLangPackage); String packagePath = refinePackagePath(bLangPackage); if (BallerinaDocUtils.isDebugEnabled()) { out.println("docerina: starting to generate docs for module: " + packagePath); } // other normal packages Page page = Generator.generatePage(bLangPackage, packageNameList, pkgDescription, primitives); String filePath = output + File.separator + packagePath + HTML; Writer.writeHtmlDocument(page, packageTemplateName, filePath); if (ConfigRegistry.getInstance().getAsBoolean(BallerinaDocConstants.GENERATE_TOC)) { // generates ToC into a separate HTML - requirement of Central out.println( "docerina: generating toc: " + output + File.separator + packagePath + "-toc" + HTML); String tocFilePath = output + File.separator + packagePath + "-toc" + HTML; Writer.writeHtmlDocument(page, packageToCTemplateName, tocFilePath); } if (Names.BUILTIN_PACKAGE.getValue().equals(packagePath)) { // primitives are in builtin package Page primitivesPage = Generator.generatePageForPrimitives(bLangPackage, packageNameList, primitives); String primitivesFilePath = output + File.separator + "primitive-types" + HTML; Writer.writeHtmlDocument(primitivesPage, packageTemplateName, primitivesFilePath); } // collect package resources resources.addAll(packageDoc.resources); if (BallerinaDocUtils.isDebugEnabled()) { out.println("docerina: generated docs for module: " + packagePath); } } catch (IOException e) { out.println(String.format("docerina: API documentation generation failed for module %s: %s", packageDoc.bLangPackage.packageID.toString(), e.getMessage())); log.error(String.format("API documentation generation failed for %s", packageDoc.bLangPackage.packageID.toString()), e); } } if (BallerinaDocUtils.isDebugEnabled()) { out.println("docerina: copying HTML theme into " + output); } try { BallerinaDocUtils.copyResources("docerina-theme", output); } catch (IOException e) { out.println(String.format("docerina: failed to copy the docerina-theme resource. Cause: %s", e.getMessage())); log.error("Failed to copy the docerina-theme resource.", e); } if (BallerinaDocUtils.isDebugEnabled()) { out.println("docerina: successfully copied HTML theme into " + output); } if (!resources.isEmpty()) { String resourcesDir = output + File.separator + "resources"; File resourcesDirFile = new File(resourcesDir); if (BallerinaDocUtils.isDebugEnabled()) { out.println("docerina: copying project resources into " + resourcesDir); } resources.parallelStream().forEach(path -> { try { FileUtils.copyFileToDirectory(path.toFile(), resourcesDirFile); } catch (IOException e) { out.println(String.format( "docerina: failed to copy [resource] %s into [resources directory] " + "%s. Cause: %s", path.toString(), resourcesDir, e.getMessage())); log.error(String.format( "docerina: failed to copy [resource] %s into [resources directory] " + "%s. Cause: %s", path.toString(), resourcesDir, e.getMessage()), e); } }); if (BallerinaDocUtils.isDebugEnabled()) { out.println("docerina: successfully copied project resources into " + resourcesDir); } } if (BallerinaDocUtils.isDebugEnabled()) { out.println("docerina: generating the index HTML file."); } try { //Generate the index file with the list of all modules String indexTemplateName = System.getProperty(BallerinaDocConstants.MODULE_TEMPLATE_NAME_KEY, "index"); String indexFilePath = output + File.separator + "index" + HTML; Writer.writeHtmlDocument(packageNameList, indexTemplateName, indexFilePath); } catch (IOException e) { out.println(String.format("docerina: failed to create the index.html. Cause: %s", e.getMessage())); log.error("Failed to create the index.html file.", e); } if (BallerinaDocUtils.isDebugEnabled()) { out.println("docerina: successfully generated the index HTML file."); out.println("docerina: generating the module-list HTML file."); } try { // Generate module-list.html file which prints the list of processed packages String pkgListTemplateName = System.getProperty(BallerinaDocConstants.MODULE_LIST_TEMPLATE_NAME_KEY, "module-list"); String pkgListFilePath = output + File.separator + "module-list" + HTML; Writer.writeHtmlDocument(packageNameList, pkgListTemplateName, pkgListFilePath); } catch (IOException e) { out.println( String.format("docerina: failed to create the module-list.html. Cause: %s", e.getMessage())); log.error("Failed to create the module-list.html file.", e); } if (BallerinaDocUtils.isDebugEnabled()) { out.println("docerina: successfully generated the module-list HTML file."); } try { String zipPath = System.getProperty(BallerinaDocConstants.OUTPUT_ZIP_PATH); if (zipPath != null) { if (BallerinaDocUtils.isDebugEnabled()) { out.println("docerina: generating the documentation zip file."); } BallerinaDocUtils.packageToZipFile(output, zipPath); if (BallerinaDocUtils.isDebugEnabled()) { out.println("docerina: successfully generated the documentation zip file."); } } } catch (IOException e) { out.println(String.format("docerina: API documentation zip packaging failed for %s: %s", output, e.getMessage())); log.error(String.format("API documentation zip packaging failed for %s", output), e); } if (BallerinaDocUtils.isDebugEnabled()) { out.println("docerina: documentation generation is done."); } } private static void sortPackageConstructs(BLangPackage bLangPackage) { bLangPackage.getFunctions().sort(Comparator.comparing( f -> (f.getReceiver() == null ? "" : f.getReceiver().getName()) + f.getName().getValue())); bLangPackage.getAnnotations().sort(Comparator.comparing(a -> a.getName().getValue())); bLangPackage.getTypeDefinitions().sort(Comparator.comparing(a -> a.getName().getValue())); bLangPackage.getGlobalVariables().sort(Comparator.comparing(a -> a.getName().getValue())); } private static Map<String, PackageDoc> generatePackageDocsMap(String sourceRoot, String packageFilter, boolean isNative, String[] sources, boolean offline) { for (String source : sources) { source = source.trim(); try { generatePackageDocsFromBallerina(sourceRoot, source, packageFilter, isNative, offline); } catch (IOException e) { out.println(String.format("docerina: API documentation generation failed for %s: %s", source, e.getMessage())); log.error(String.format("API documentation generation failed for %s", source), e); // we continue, as there may be other valid packages. continue; } } return BallerinaDocDataHolder.getInstance().getPackageMap(); } /** * Generates {@link BLangPackage} objects for each Ballerina package from the given ballerina files. * * @param sourceRoot points to the folder relative to which package path is given * @param packagePath should point either to a ballerina file or a folder with ballerina files. * @return a map of {@link BLangPackage} objects. Key - Ballerina package name Value - {@link BLangPackage} * @throws IOException on error. */ protected static Map<String, PackageDoc> generatePackageDocsFromBallerina(String sourceRoot, String packagePath) throws IOException { return generatePackageDocsFromBallerina(sourceRoot, packagePath, null, true); } /** * Generates {@link BLangPackage} objects for each Ballerina package from the given ballerina files. * * @param sourceRoot points to the folder relative to which package path is given * @param packagePath should point either to a ballerina file or a folder with ballerina files. * @param packageFilter comma separated list of package names/patterns to be filtered from the documentation. * @param offline is offline generation * @return a map of {@link BLangPackage} objects. Key - Ballerina package name Value - {@link BLangPackage} * @throws IOException on error. */ protected static Map<String, PackageDoc> generatePackageDocsFromBallerina(String sourceRoot, String packagePath, String packageFilter, boolean offline) throws IOException { return generatePackageDocsFromBallerina(sourceRoot, packagePath, packageFilter, false, offline); } /** * Generates {@link BLangPackage} objects for each Ballerina package from the given ballerina files. * * @param sourceRoot points to the folder relative to which package path is given * @param packagePath should point either to a ballerina file or a folder with ballerina files. * @param packageFilter comma separated list of package names/patterns to be filtered from the documentation. * @param isNative whether this is a native package or not. * @param offline is offline generation * @return a map of {@link BLangPackage} objects. Key - Ballerina package name Value - {@link BLangPackage} * @throws IOException on error. */ protected static Map<String, PackageDoc> generatePackageDocsFromBallerina(String sourceRoot, String packagePath, String packageFilter, boolean isNative, boolean offline) throws IOException { return generatePackageDocsFromBallerina(sourceRoot, Paths.get(packagePath), packageFilter, isNative, offline); } /** * Generates {@link BLangPackage} objects for each Ballerina package from the given ballerina files. * * @param sourceRoot points to the folder relative to which package path is given * @param packagePath a {@link Path} object pointing either to a ballerina file or a folder with ballerina files. * @param packageFilter comma separated list of package names/patterns to be filtered from the documentation. * @param isNative whether the given packages are native or not. * @param offline is offline generation * @return a map of {@link BLangPackage} objects. Key - Ballerina package name Value - {@link BLangPackage} * @throws IOException on error. */ protected static Map<String, PackageDoc> generatePackageDocsFromBallerina(String sourceRoot, Path packagePath, String packageFilter, boolean isNative, boolean offline) throws IOException { // find the Module.md file Path packageMd; Path absolutePkgPath = Paths.get(sourceRoot).resolve(packagePath); Optional<Path> o = Files.find(absolutePkgPath, 1, (path, attr) -> { Path fileName = path.getFileName(); if (fileName != null) { return fileName.toString().equals(MODULE_CONTENT_FILE); } return false; }).findFirst(); packageMd = o.isPresent() ? o.get() : null; // find the resources of the package Path resourcesDirPath = absolutePkgPath.resolve("resources"); List<Path> resources = new ArrayList<>(); if (resourcesDirPath.toFile().exists()) { resources = Files.walk(resourcesDirPath).filter(path -> !path.equals(resourcesDirPath)) .collect(Collectors.toList()); } BallerinaDocDataHolder dataHolder = BallerinaDocDataHolder.getInstance(); if (!isNative) { // This is necessary to be true in order to Ballerina to work properly System.setProperty("skipNatives", "true"); } BLangPackage bLangPackage; CompilerContext context = new CompilerContext(); CompilerOptions options = CompilerOptions.getInstance(context); options.put(CompilerOptionName.PROJECT_DIR, sourceRoot); options.put(CompilerOptionName.COMPILER_PHASE, CompilerPhase.TYPE_CHECK.toString()); options.put(CompilerOptionName.PRESERVE_WHITESPACE, "false"); options.put(CompilerOptionName.OFFLINE, Boolean.valueOf(offline).toString()); options.put(CompilerOptionName.EXPERIMENTAL_FEATURES_ENABLED, Boolean.TRUE.toString()); context.put(SourceDirectory.class, new FileSystemProjectDirectory(Paths.get(sourceRoot))); Compiler compiler = Compiler.getInstance(context); // TODO: Remove this and the related constants once these are properly handled in the core if (absolutePkgPath.endsWith(BAL_BUILTIN.toString())) { bLangPackage = loadBuiltInPackage(context); } else { // compile the given package Path fileOrPackageName = packagePath.getFileName(); bLangPackage = compiler .compile(fileOrPackageName == null ? packagePath.toString() : fileOrPackageName.toString()); } if (bLangPackage == null) { out.println(String.format("docerina: invalid Ballerina module: %s", packagePath)); } else { String packageName = packageNameToString(bLangPackage.packageID); if (isFilteredPackage(packageName, packageFilter)) { if (BallerinaDocUtils.isDebugEnabled()) { out.println("docerina: module " + packageName + " excluded"); } } else { dataHolder.getPackageMap().put(packageName, new PackageDoc( packageMd == null ? null : packageMd.toAbsolutePath(), resources, bLangPackage)); } } return dataHolder.getPackageMap(); } private static String packageNameToString(PackageID pkgId) { String pkgName = pkgId.getName().getValue(); return ".".equals(pkgName) ? pkgId.sourceFileName.getValue() : pkgName; } private static boolean isFilteredPackage(String packageName, String packageFilter) { if ((packageFilter != null) && (packageFilter.trim().length() > 0)) { return Arrays.asList(packageFilter.split(",")).stream() .filter(e -> packageName.startsWith(e.replace(".*", ""))).findAny().isPresent(); } return false; } private static BLangPackage loadBuiltInPackage(CompilerContext context) { SymbolTable symbolTable = SymbolTable.getInstance(context); // Load built-in packages. BLangPackage builtInPkg = getBuiltInPackage(context); symbolTable.builtInPackageSymbol = builtInPkg.symbol; return builtInPkg; } private static BLangPackage getBuiltInPackage(CompilerContext context) { PackageLoader pkgLoader = PackageLoader.getInstance(context); SemanticAnalyzer semAnalyzer = SemanticAnalyzer.getInstance(context); CodeAnalyzer codeAnalyzer = CodeAnalyzer.getInstance(context); return codeAnalyzer.analyze(semAnalyzer.analyze(pkgLoader.loadAndDefinePackage(Names.BUILTIN_ORG.getValue(), Names.BUILTIN_PACKAGE.getValue(), Names.EMPTY.getValue()))); } private static List<Link> primitives() { List<String> primitives = BallerinaDocUtils.loadPrimitivesDescriptions(true); List<Link> primitiveLinks = new ArrayList<>(); for (String type : primitives) { primitiveLinks.add(new Link(new Caption(type), BallerinaDocConstants.PRIMITIVE_TYPES_PAGE_HREF.concat("" + ".html#" + type), true)); } return primitiveLinks; } private static String refinePackagePath(BLangPackage bLangPackage) { if (bLangPackage == null) { return ""; } if (bLangPackage.getPosition().getSource().getPackageName().equals(".")) { return bLangPackage.getPosition().getSource().pkgID.sourceFileName.getValue(); } return bLangPackage.packageID.getName().getValue(); } }