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 *      Copyright 2015 ForgeRock AS.
024 */
025package org.forgerock.opendj.maven.doc;
026
027import static com.forgerock.opendj.ldap.CoreMessages.*;
028import static org.forgerock.opendj.maven.doc.Utils.*;
029
030import org.apache.maven.plugin.AbstractMojo;
031import org.apache.maven.plugin.MojoExecutionException;
032import org.apache.maven.plugin.MojoFailureException;
033import org.apache.maven.plugins.annotations.Mojo;
034import org.apache.maven.plugins.annotations.Parameter;
035import org.forgerock.opendj.ldap.schema.CoreSchemaSupportedLocales;
036
037import java.io.File;
038import java.io.IOException;
039import java.text.SimpleDateFormat;
040import java.util.Date;
041import java.util.HashMap;
042import java.util.LinkedList;
043import java.util.List;
044import java.util.Locale;
045import java.util.Map;
046import java.util.Set;
047import java.util.TreeMap;
048
049/**
050 * Generate schema-related reference documentation sources.
051 */
052@Mojo(name = "generate-schema-ref")
053public class GenerateSchemaDocMojo extends AbstractMojo {
054    /** The locale for which to generate the documentation. */
055    @Parameter(defaultValue = "en")
056    private String locale;
057
058    /** Output directory for source files. */
059    @Parameter(defaultValue = "${project.build.directory}/docbkx-sources/shared")
060    private File outputDirectory;
061
062    /**
063     * Writes schema reference documentation source files.
064     * @throws MojoExecutionException   Not used.
065     * @throws MojoFailureException     Failed to write a file.
066     */
067    @Override
068    public void execute() throws MojoExecutionException, MojoFailureException {
069        final Locale currentLocale = getLocaleFromTag(locale);
070        final String localeReference = getLocalesAndSubTypesDocumentation(currentLocale);
071        final File localeReferenceFile = new File(outputDirectory, "sec-locales-subtypes.xml");
072        try {
073            writeStringToFile(localeReference, localeReferenceFile);
074        } catch (IOException e) {
075            throw new MojoExecutionException("Failed to write " + localeReferenceFile.getPath());
076        }
077    }
078
079    /**
080     * Returns a DocBook XML Section element documenting supported locales and language subtypes.
081     * @param currentLocale The locale for which to generate the documentation.
082     * @return A DocBook XML Section element documenting supported locales and language subtypes.
083     */
084    private String getLocalesAndSubTypesDocumentation(final Locale currentLocale) {
085        final Map<String, Object> map = new HashMap<>();
086        map.put("year", new SimpleDateFormat("yyyy").format(new Date()));
087        map.put("lang", getTagFromLocale(currentLocale));
088        map.put("title", DOC_LOCALE_SECTION_TITLE.get());
089        map.put("info", DOC_LOCALE_SECTION_INFO.get());
090        map.put("locales", getLocalesDocMap(currentLocale));
091        map.put("subtypes", getSubTypesDocMap(currentLocale));
092        return applyTemplate("sec-locales-subtypes.ftl", map);
093    }
094
095    private final Map<String, String> localeTagsToOids =
096            CoreSchemaSupportedLocales.getJvmSupportedLocaleNamesToOids();
097
098    /** Container for documentation regarding a locale. */
099    private class LocaleDoc {
100        String tag;
101        String language;
102        String oid;
103    }
104
105    /**
106     * Returns a map of languages to Locale documentation containers.
107     * @param currentLocale     The Locale of the resulting documentation.
108     * @return A map of languages to Locale documentation containers.
109     */
110    private Map<String, LocaleDoc> getLanguagesToLocalesMap(final Locale currentLocale) {
111        Map<String, LocaleDoc> locales = new TreeMap<>();
112        for (String tag : localeTagsToOids.keySet()) {
113            final Locale locale = getLocaleFromTag(tag);
114            if (locale == null) {
115                continue;
116            }
117            final LocaleDoc localeDoc = new LocaleDoc();
118            localeDoc.tag = tag;
119            localeDoc.language = locale.getDisplayName(currentLocale);
120            localeDoc.oid = localeTagsToOids.get(tag);
121            if (!localeDoc.language.equals(localeDoc.tag)) {
122                // No display language so must not be supported in current JVM
123                locales.put(localeDoc.language, localeDoc);
124            } else if (localeDoc.tag.equals("sh")) {
125                localeDoc.language = DOC_LANGUAGE_SH.get().toString(currentLocale);
126                locales.put(localeDoc.language, localeDoc);
127            }
128        }
129        return locales;
130    }
131
132    /**
133     * Returns a map of information for documenting supported locales.
134     * @param currentLocale The locale for which to generate the information.
135     * @return A map of information for documenting supported locales.
136     */
137    private Map<String, Object> getLocalesDocMap(final Locale currentLocale) {
138        final Map<String, Object> result = new HashMap<>();
139        result.put("title", DOC_SUPPORTED_LOCALES_TITLE.get());
140        result.put("indexTerm", DOC_SUPPORTED_LOCALES_INDEXTERM.get());
141        final Map<String, LocaleDoc> localesMap = getLanguagesToLocalesMap(currentLocale);
142        final Set<String> sortedLanguages = localesMap.keySet();
143        final List<Map<String, Object>> locales = new LinkedList<>();
144        for (final String language : sortedLanguages) {
145            final LocaleDoc locale = localesMap.get(language);
146            final Map<String, Object> map = new HashMap<>();
147            map.put("language", locale.language);
148            map.put("tag", DOC_LOCALE_TAG.get(locale.tag));
149            map.put("oid", DOC_LOCALE_OID.get(locale.oid));
150            locales.add(map);
151        }
152        result.put("locales", locales);
153        return result;
154    }
155
156    /**
157     * Returns a map of information for documenting supported language subtypes.
158     * @param currentLocale The locale for which to generate the information.
159     * @return A map of information for documenting supported language subtypes.
160     */
161    private Map<String, Object> getSubTypesDocMap(final Locale currentLocale) {
162        final Map<String, Object> result = new HashMap<>();
163        result.put("title", DOC_SUPPORTED_SUBTYPES_TITLE.get());
164        result.put("indexTerm", DOC_SUPPORTED_SUBTYPES_INDEXTERM.get());
165        final List<Map<String, Object>> locales = new LinkedList<>();
166        for (final String tag : localeTagsToOids.keySet()) {
167            final Map<String, Object> map = new HashMap<>();
168            int idx = tag.indexOf('-');
169            if (idx == -1) {
170                final Locale locale = getLocaleFromTag(tag);
171                if (locale == null) {
172                    continue;
173                }
174                final String language = locale.getDisplayName(currentLocale);
175                if (!language.equals(tag)) {
176                    map.put("language", language);
177                    map.put("tag", tag);
178                } else if (tag.equals("sh")) {
179                    map.put("language", DOC_LANGUAGE_SH.get().toString(currentLocale));
180                    map.put("tag", tag);
181                }
182                if (!map.isEmpty()) {
183                    locales.add(map);
184                }
185            }
186        }
187        result.put("locales", locales);
188        return result;
189    }
190
191    /**
192     * Returns the Locale based on the tag, or null if the tag is null.
193     * <br>
194     * Java 6 is missing {@code Locale.forLanguageTag()}.
195     * @param tag   The tag for the locale, such as {@code en_US}.
196     * @return The Locale based on the tag, or null if the tag is null.
197     */
198    private Locale getLocaleFromTag(final String tag) {
199        if (tag == null) {
200            return null;
201        }
202
203        // Apparently Locale tags can include not only languages and countries,
204        // but also variants, e.g. es_ES_Traditional_WIN.
205        // Pull these apart to be able to construct the locale.
206        //
207        // OpenDJ does not seem to have any locales with variants, but just in case...
208        // The separator in OpenDJ seems to be '-'.
209        // @see CoreSchemaSupportedLocales#LOCALE_NAMES_TO_OIDS
210        final char sep = '-';
211        int langIdx = tag.indexOf(sep);
212        final String lang;
213        if (langIdx == -1) {
214            // No country or variant
215            return new Locale(tag);
216        } else {
217            lang = tag.substring(0, langIdx);
218        }
219
220        int countryIdx = tag.indexOf(sep, langIdx + 1);
221        final String country;
222        if (countryIdx == -1) {
223            // No variant
224            country = tag.substring(langIdx + 1);
225            return new Locale(lang, country);
226        } else {
227            country = tag.substring(langIdx + 1, countryIdx);
228            final String variant = tag.substring(countryIdx + 1);
229            return new Locale(lang, country, variant);
230        }
231    }
232
233    /**
234     * Returns the tag based on the Locale, or null if the Locale is null.
235     * <br>
236     * Java 6 is missing {@code Locale.toLanguageTag()}.
237     * @param locale        The Locale for which to return the tag.
238     * @return The tag based on the Locale, or null if the Locale is null.
239     */
240    private String getTagFromLocale(final Locale locale) {
241        if (locale == null) {
242            return null;
243        }
244
245        final String  lang    = locale.getLanguage();
246        final String  country = locale.getCountry();
247        final String  variant = locale.getVariant();
248        final char    sep     = '-';
249        StringBuilder tag     = new StringBuilder();
250        if (lang != null) {
251            tag.append(lang);
252        }
253        if (country != null && !country.isEmpty()) {
254            tag.append(sep).append(country);
255        }
256        if (variant != null && !variant.isEmpty()) {
257            tag.append(sep).append(variant);
258        }
259        return tag.toString();
260    }
261}