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.opendj.maven.doc.Utils.*;
029
030import com.thoughtworks.qdox.JavaProjectBuilder;
031import com.thoughtworks.qdox.model.JavaClass;
032import com.thoughtworks.qdox.model.JavaField;
033import com.thoughtworks.qdox.model.JavaType;
034import org.apache.maven.plugin.AbstractMojo;
035import org.apache.maven.plugin.MojoExecutionException;
036import org.apache.maven.plugin.MojoFailureException;
037import org.apache.maven.plugins.annotations.LifecyclePhase;
038import org.apache.maven.plugins.annotations.Mojo;
039import org.apache.maven.plugins.annotations.Parameter;
040import org.forgerock.opendj.ldap.ResultCode;
041
042import java.io.File;
043import java.io.IOException;
044import java.text.SimpleDateFormat;
045import java.util.Date;
046import java.util.HashMap;
047import java.util.LinkedList;
048import java.util.List;
049import java.util.Map;
050
051/**
052 * Generates documentation source for LDAP result codes based on
053 * {@code org.forgerock.opendj.ldap.ResultCode}.
054 * <br>
055 * This implementation parses the source to match Javadoc comments with result codes.
056 * It is assumed that the class's ResultCode fields are named with result code enum values,
057 * and that those fields have Javadoc comments describing each result code.
058 */
059@Mojo(name = "generate-result-code-doc", defaultPhase = LifecyclePhase.COMPILE)
060public class GenerateResultCodeDocMojo extends AbstractMojo {
061    /**
062     * The Java file containing the source of the ResultCode class,
063     * {@code org.forgerock.opendj.ldap.ResultCode}.
064     * <br>
065     * For example, {@code opendj-core/src/main/java/org/forgerock/opendj/ldap/ResultCode.java}.
066     */
067    @Parameter(required = true)
068    private File resultCodeSource;
069
070    /** The XML file to generate. */
071    @Parameter(required = true)
072    private File xmlFile;
073
074    /**
075     * Generates documentation source for LDAP result codes.
076     *
077     * @throws MojoExecutionException   Generation failed
078     * @throws MojoFailureException     Not used
079     */
080    @Override
081    public void execute() throws MojoExecutionException, MojoFailureException {
082        final Map<String, Object> map = new HashMap<>();
083        map.put("year", new SimpleDateFormat("yyyy").format(new Date()));
084
085        // The overall explanation in the generated doc is the class comment.
086        final JavaClass resultCodeClass;
087        try {
088            resultCodeClass = getJavaClass();
089        } catch (IOException e) {
090            throw new MojoExecutionException("Could not read " + resultCodeSource.getPath(), e);
091        }
092        map.put("classComment", cleanComment(resultCodeClass.getComment()));
093
094        // Documentation for each result code comes from the Javadoc for the code,
095        // and from the value and friendly name of the code.
096        final Map<String, Object> comments = new HashMap<>();
097        for (final JavaField field : resultCodeClass.getFields()) {
098            final JavaType type = field.getType();
099            if (type.getValue().equals("ResultCode")) {
100                comments.put(field.getName(), cleanComment(field.getComment()));
101            }
102        }
103        map.put("resultCodes", getResultCodesDoc(comments));
104
105        final String template = "appendix-ldap-result-codes.ftl";
106        try {
107            writeStringToFile(applyTemplate(template, map), xmlFile);
108        } catch (IOException e) {
109            throw new MojoExecutionException("Could not write to " + xmlFile.getPath(), e);
110        }
111        getLog().info("Wrote " + xmlFile.getPath());
112    }
113
114    /**
115     * Returns an object to access to the result code Java source.
116     * @return An object to access to the result code Java source.
117     * @throws IOException  Could not read the source
118     */
119    private JavaClass getJavaClass() throws IOException {
120        final JavaProjectBuilder builder = new JavaProjectBuilder();
121        builder.addSource(resultCodeSource);
122        return builder.getClassByName("org.forgerock.opendj.ldap.ResultCode");
123    }
124
125    /**
126     * Returns a clean string for use in generated documentation.
127     * @param comment   The comment to clean.
128     * @return A clean string for use in generated documentation.
129     */
130    private String cleanComment(String comment) {
131        return stripCodeValueSentences(stripTags(convertLineSeparators(comment))).trim();
132    }
133
134    /**
135     * Returns a string with line separators converted to spaces.
136     * @param string    The string to convert.
137     * @return A string with line separators converted to spaces.
138     */
139    private String convertLineSeparators(String string) {
140        return string.replaceAll(System.lineSeparator(), " ");
141    }
142
143    /**
144     * Returns a string with the HTML tags removed.
145     * @param string    The string to strip.
146     * @return A string with the HTML tags removed.
147     */
148    private String stripTags(String string) {
149        return string.replaceAll("<[^>]*>", "");
150    }
151
152    /**
153     * Returns a string with lines sentences of the following form removed:
154     * This result code corresponds to the LDAP result code value of &#x7b;&#x40;code 0&#x7d;.
155     * @param string    The string to strip.
156     * @return A string with lines sentences of the matching form removed.
157     */
158    private String stripCodeValueSentences(String string) {
159        return string
160                .replaceAll("This result code corresponds to the LDAP result code value of \\{@code \\d+\\}.", "");
161    }
162
163    /**
164     * Returns a list of documentation objects for all result codes.
165     * @param comments  A map of field names to the clean comments.
166     * @return A list of documentation objects for all result codes.
167     */
168    private List<Map<String, Object>> getResultCodesDoc(Map<String, Object> comments) {
169        final List<Map<String, Object>> list = new LinkedList<>();
170        if (comments == null || comments.isEmpty()) {
171            return list;
172        }
173
174        for (ResultCode resultCode : ResultCode.values()) {
175            final Map<String, Object> doc = new HashMap<>();
176            doc.put("intValue", resultCode.intValue());
177            doc.put("name", resultCode.getName());
178            final Object comment = comments.get(resultCode.asEnum().toString());
179            doc.put("comment", comment);
180            list.add(doc);
181        }
182        return list;
183    }
184}