001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2015 ForgeRock AS.
025 */
026package org.forgerock.opendj.maven.doc;
027
028import static org.forgerock.util.Utils.*;
029
030import freemarker.template.Configuration;
031import freemarker.template.Template;
032import freemarker.template.TemplateExceptionHandler;
033import org.apache.maven.artifact.DependencyResolutionRequiredException;
034import org.apache.maven.plugin.logging.Log;
035import org.apache.maven.project.MavenProject;
036
037import java.io.ByteArrayOutputStream;
038import java.io.File;
039import java.io.FileInputStream;
040import java.io.FileOutputStream;
041import java.io.IOException;
042import java.io.InputStream;
043import java.io.OutputStream;
044import java.io.OutputStreamWriter;
045import java.io.PrintWriter;
046import java.io.Writer;
047import java.net.MalformedURLException;
048import java.net.URISyntaxException;
049import java.net.URL;
050import java.net.URLClassLoader;
051import java.util.Collections;
052import java.util.LinkedHashSet;
053import java.util.List;
054import java.util.Map;
055import java.util.Set;
056
057/**
058 * Provides utility methods for generating documentation.
059 */
060public final class Utils {
061
062    /** Line separator. */
063    static final String EOL = System.getProperty("line.separator");
064
065    /**
066     * Creates a directory unless it already exists.
067     * @param directory     The directory to create.
068     * @throws IOException  Failed to create directory.
069     */
070    static void createDirectory(final String directory) throws IOException {
071        File dir = new File(directory);
072        if (!dir.exists()) {
073            if (!dir.mkdirs()) {
074                throw new IOException("Failed to create directory: " + directory);
075            }
076        }
077    }
078
079    /**
080     * Returns the path to the current Java executable.
081     * @return The path to the current Java executable.
082     */
083    static String getJavaCommand() {
084        return System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
085    }
086
087    /**
088     * Copies the content of the original file to the copy.
089     * @param original      The original file.
090     * @param copy          The copy.
091     * @throws IOException  Failed to make the copy.
092     */
093    static void copyFile(File original, File copy) throws IOException {
094        copyInputStreamToFile(new FileInputStream(original), copy);
095    }
096
097    /**
098     * Copies the content of the original input stream to the copy.
099     * @param original      The original input stream.
100     * @param copy          The copy.
101     * @throws IOException  Failed to make the copy.
102     */
103    static void copyInputStreamToFile(InputStream original, File copy) throws IOException {
104        if (original == null) {
105            throw new IOException("Could not read input to copy.");
106        }
107        createFile(copy);
108        try (OutputStream outputStream = new FileOutputStream(copy)) {
109            int bytesRead;
110            byte[] buffer = new byte[4096];
111            while ((bytesRead = original.read(buffer)) > 0) {
112                outputStream.write(buffer, 0, bytesRead);
113            }
114        } finally {
115            closeSilently(original);
116        }
117    }
118
119    /**
120     * Writes a string to a file.
121     * @param string    The string to write
122     * @param file      The file to write to
123     * @throws IOException  The file did not exist, or could not be created for writing.
124     */
125    static void writeStringToFile(final String string, final File file) throws IOException {
126        createFile(file);
127        PrintWriter printWriter = new PrintWriter(file);
128        printWriter.print(string);
129        printWriter.close();
130    }
131
132    /**
133     * Creates a file including parent directories if it does not yet exist.
134     * @param file          The file to create
135     * @throws IOException  Failed to create the file
136     */
137    private static void createFile(File file) throws IOException {
138        if (!file.exists()) {
139            createDirectory(file.getParent());
140            if (!file.createNewFile()) {
141                throw new IOException("Failed to create " + file.getPath());
142            }
143        }
144    }
145
146    /**
147     * Returns the classpath for the class loader and its parent.
148     * @param classLoader   Contains the URLs of the class path to return.
149     * @return The classpath for the class loader and its parent.
150     */
151    static String getClassPath(URLClassLoader classLoader) throws URISyntaxException {
152        Set<URL> urls = new LinkedHashSet<>();
153        Collections.addAll(urls, classLoader.getURLs());
154        Collections.addAll(urls, ((URLClassLoader) classLoader.getParent()).getURLs());
155        Set<String> paths = new LinkedHashSet<>();
156        for (URL url: urls) {
157            paths.add(new File(url.toURI()).getPath());
158        }
159        return joinAsString(File.pathSeparator, paths);
160    }
161
162    /**
163     * Returns a ClassLoader including the project's runtime classpath elements.
164     * This is useful when running a Java command from inside a Maven plugin.
165     *
166     * @param project   The Maven project holding runtime classpath elements.
167     * @param log       A plugin log to use for debugging.
168     * @return A ClassLoader including the project's runtime classpath elements.
169     * @throws DependencyResolutionRequiredException    Failed to access the runtime classpath
170     * @throws MalformedURLException                    Failed to add an element to the classpath
171     */
172    static URLClassLoader getRuntimeClassLoader(MavenProject project, Log log)
173            throws DependencyResolutionRequiredException, MalformedURLException {
174        List<String> runtimeClasspathElements = project.getRuntimeClasspathElements();
175        Set<URL> runtimeUrls = new LinkedHashSet<>();
176        for (String element : runtimeClasspathElements) {
177            runtimeUrls.add(new File(element).toURI().toURL());
178        }
179
180        final URLClassLoader urlClassLoader = new URLClassLoader(
181                runtimeUrls.toArray(new URL[runtimeClasspathElements.size()]),
182                Thread.currentThread().getContextClassLoader());
183        debugClassPathElements(urlClassLoader, log);
184        return urlClassLoader;
185    }
186
187    /**
188     * Logs what is on the classpath for debugging.
189     * @param classLoader   The ClassLoader with the classpath.
190     * @param log           The Maven plugin log in which to write debug messages.
191     */
192    static void debugClassPathElements(ClassLoader classLoader, Log log) {
193        if (null == classLoader) {
194            return;
195        }
196        log.debug("--------------------");
197        log.debug(classLoader.toString());
198        if (classLoader instanceof URLClassLoader) {
199            final URLClassLoader ucl = (URLClassLoader) classLoader;
200            int i = 0;
201            for (URL url : ucl.getURLs()) {
202                log.debug("url[" + (i++) + "]=" + url);
203            }
204        }
205        debugClassPathElements(classLoader.getParent(), log);
206    }
207
208    /** FreeMarker template configuration. */
209    static Configuration configuration;
210
211    /**
212     * Returns a FreeMarker configuration for applying templates.
213     * @return A FreeMarker configuration for applying templates.
214     */
215    static Configuration getConfiguration() {
216        if (configuration == null) {
217            configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
218            configuration.setClassForTemplateLoading(Utils.class, "/templates");
219            configuration.setDefaultEncoding("UTF-8");
220            configuration.setTemplateExceptionHandler(TemplateExceptionHandler.DEBUG_HANDLER);
221        }
222        return configuration;
223    }
224
225    /**
226     * Returns the String result from applying a FreeMarker template.
227     * @param template The name of a template file found in {@code resources/templates/}.
228     * @param map      The map holding the data to use in the template.
229     * @return The String result from applying a FreeMarker template.
230     */
231    static String applyTemplate(final String template, final Map<String, Object> map) {
232        // FreeMarker requires a configuration to find the template.
233        configuration = getConfiguration();
234
235        // FreeMarker takes the data and a Writer to process the template.
236        try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
237                Writer writer = new OutputStreamWriter(outputStream)) {
238            Template configurationTemplate = configuration.getTemplate(template);
239            configurationTemplate.process(map, writer);
240            return outputStream.toString();
241        } catch (Exception e) {
242            throw new RuntimeException(e.getMessage(), e);
243        }
244    }
245
246    private Utils() {
247        // Not used.
248    }
249}