001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2012-2015 ForgeRock AS.
015 */
016
017package org.forgerock.doc.maven.utils;
018
019import java.io.File;
020import java.io.FileFilter;
021import java.io.FilenameFilter;
022import java.io.IOException;
023import java.util.Set;
024import java.util.TreeSet;
025import java.util.regex.Pattern;
026
027import org.apache.commons.io.FileUtils;
028import org.apache.commons.io.FilenameUtils;
029import org.codehaus.plexus.util.StringUtils;
030
031/**
032 * Utility methods to work with documents.
033 */
034public final class NameUtils {
035    /**
036     * Pattern to validate the document names.
037     *
038     * <br>
039     *
040     * project-doc-version names are not expected in published docs,
041     * @see #renameDoc(String, String, String, String)
042     *
043     * <br>
044     *
045     * When published, documentation file names
046     * should include the version before the document name:
047     * Project-Version-Doc-Name.ext
048     *
049     * @deprecated since 3.0.0
050     *
051     * <p>Valid names:</p>
052     * <ul>
053     *     <li>guide</li>
054     *     <li>admin-quide</li>
055     *     <li>OpenTEST-guide</li>
056     *     <li>OpenTEST-admin-guide</li>
057     *     <li>OpenTEST-admin-guide-1.1.1.0</li>
058     *     <li>OpenTEST-admin-guide-1.1.1.0-SNAPSHOT</li>
059     *     <li>OpenTEST-admin-guide-1.1.1.0-express</li>
060     *     <li>OpenTEST-admin-guide-1.1.1.0-Xpress</li>
061     *     <li>OpenTEST-admin-guide-1.1.1.0-Xpress1</li>
062     *     <li>OpenTEST-10.1.0-admin-guide</li>
063     *     <li>OpenTEST-10.1.0-SNAPSHOT-admin-guide</li>
064     *     <li>OpenTEST-10.1.0-Xpress2-admin-guide</li>
065     *     <li>db2-connector-1.1.0.0-SNAPSHOT</li>
066     * </ul>
067     *
068     * <p>Invalid names:</p>
069     * <ul>
070     *     <li>guide.</li>
071     *     <li>guide-1</li>
072     *     <li>guide-.</li>
073     * </ul>
074     */
075    @Deprecated
076    public static final Pattern DOCUMENT_FILE_PATTERN = Pattern
077            .compile("^([a-zA-Z0-9]+)(-?[0-9].[0-9\\.]*[0-9])?(-SNAPSHOT|(-Ex|-ex|-X)press[0-9])"
078                    + "?([a-zA-Z-]*)((-?[0-9].[0-9\\.]*[0-9])?-?(SNAPSHOT|(Ex|ex|X)press[0-9]?)?)$");
079
080    /**
081     * Rename document to reflect project and document name. For example,
082     * index.pdf could be renamed OpenAM-Admin-Guide.pdf.
083     *
084     * @param projectName
085     *            Short name of the project, such as OpenAM, OpenDJ, OpenIDM
086     * @param docName
087     *            Short name for the document, such as admin-guide,
088     *            release-notes, reference
089     * @param extension
090     *            File name extension not including dot, e.g. pdf
091     * @return New name for document. Can be "" if rename failed.
092     */
093    public static String renameDoc(final String projectName,
094                                   final String docName,
095                                   final String extension) {
096        return renameDoc(projectName, docName, "", extension);
097    }
098
099    /**
100     * Rename document to reflect project and document name. For example,
101     * index.pdf could be renamed OpenAM-10.0.0-Admin-Guide.pdf.
102     *
103     * @param projectName
104     *            Short name of the project, such as OpenAM, OpenDJ, OpenIDM
105     * @param docName
106     *            Short name for the document, such as admin-guide,
107     *            release-notes, reference
108     * @param version
109     *            Document version such as 10.0.0, 2.5.0, 2.0.2
110     * @param extension
111     *            File name extension not including dot, e.g. pdf
112     * @return New name for document. Can be "" if rename failed.
113     */
114    public static String renameDoc(final String projectName,
115                                   final String docName,
116                                   final String version,
117                                   final String extension) {
118
119        // Doc name must be non-empty.
120        if (!StringUtils.isNotBlank(docName)) {
121            return "";
122        }
123
124        StringBuilder sb = new StringBuilder();
125
126        // If project name exists, capitalize it, replace spaces with hyphens,
127        // and follow the result with a hyphen.
128        if (StringUtils.isNotBlank(projectName)) {
129            sb.append(spacesToHyphens(capitalize(projectName))).append('-');
130
131            // Version precedes the document name.
132            // It only makes sense to use a version if a project name is defined.
133            // If version exists, follow it with a hyphen.
134            if (StringUtils.isNotBlank(version)) {
135                sb.append(version).append('-');
136            }
137        }
138
139        // Capitalize the doc name.
140        sb.append(capitalize(docName));
141
142        // If extension exists, precede it with a .
143        if (StringUtils.isNotBlank(extension)) {
144            sb.append('.').append(extension);
145        }
146
147        return sb.toString();
148    }
149
150    /**
151     * Capitalize initial letters in a document name.
152     *
153     * @param docName
154     *            Name of the document such as reference or admin-guide
155     * @return Capitalized name such as Reference or Admin-Guide
156     */
157    protected static String capitalize(final String docName) {
158        char[] chars = docName.toCharArray();
159
160        boolean isInitial = true;
161        for (int i = 0; i < chars.length; i++) {
162            if (isInitial && Character.isLetter(chars[i])) {
163                chars[i] = Character.toUpperCase(chars[i]);
164                isInitial = false;
165            } else {
166                isInitial = !Character.isLetter(chars[i]);
167            }
168        }
169
170        return String.valueOf(chars);
171    }
172
173    /**
174     * Replace spaces with hyphens.
175     *
176     * @param   string  String in which to replace spaces with hyphens
177     * @return  String with spaces replaced by hyphens.
178     */
179    protected static String spacesToHyphens(final String string) {
180        return string.replaceAll(" ", "-");
181    }
182
183    /**
184     * Returns names of directories that mirror the document names and contain
185     * DocBook XML documents to build.
186     *
187     * @param srcDir
188     *            Directory containing DocBook XML sources. Document directories
189     *            like admin-guide or reference are one level below this
190     *            directory.
191     * @param docFile
192     *            Name of a file common to all documents to build, such as
193     *            index.xml.
194     * @return Document names, as in admin-guide or reference
195     */
196    public static Set<String> getDocumentNames(final File srcDir,
197                                               final String docFile) {
198        Set<String> documentDirectories = new TreeSet<String>();
199
200        // Match directories containing DocBook document entry point files,
201        // and ignore everything else.
202        FileFilter filter = new FileFilter() {
203            @Override
204            public boolean accept(final File file) {
205                return file.isDirectory();
206            }
207        };
208
209        File[] directories = srcDir.listFiles(filter);
210        if (directories != null && directories.length > 0) {
211
212            FilenameFilter nameFilter = new FilenameFilter() {
213                @Override
214                public boolean accept(final File file, final String name) {
215                    return name.equalsIgnoreCase(docFile);
216                }
217            };
218
219            for (File dir : directories) {
220                String[] found = dir.list(nameFilter);
221                if (found.length > 0) {
222                    documentDirectories.add(dir.getName());
223                }
224            }
225        }
226
227        return documentDirectories;
228    }
229
230    /**
231     * Rename a single built document.
232     * For example, rename {@code index.pdf} to {@code OpenAM-Admin-Guide.pdf}.
233     *
234     * @param builtDocument File to rename, such as {@code index.pdf}.
235     * @param docName       Simple document name such as {@code admin-guide}.
236     * @param projectName   Project name, such as {@code OpenAM}.
237     * @throws IOException  Something went wrong renaming the file.
238     */
239    public static void renameDocument(final File builtDocument,
240                                      final String docName,
241                                      final String projectName)
242            throws IOException {
243        String ext = FilenameUtils.getExtension(builtDocument.getName());
244        File newFile = new File(builtDocument.getParent(), renameDoc(projectName, docName, ext));
245        if (!newFile.exists()) {
246            FileUtils.moveFile(builtDocument, newFile);
247        }
248    }
249
250    /**
251     * Not used.
252     */
253    private NameUtils() {
254    }
255}