Java tutorial
package org.codehaus.mojo.l10n; /* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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. */ import org.apache.maven.doxia.sink.Sink; import org.apache.maven.doxia.siterenderer.Renderer; import org.apache.maven.model.Resource; import org.apache.maven.project.MavenProject; import org.apache.maven.reporting.AbstractMavenReport; import org.apache.maven.reporting.AbstractMavenReportRenderer; import org.apache.maven.reporting.MavenReportException; import org.codehaus.plexus.util.DirectoryScanner; import org.codehaus.plexus.util.IOUtil; import org.codehaus.plexus.util.StringUtils; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.ResourceBundle; import java.util.Set; import java.util.TreeSet; import java.util.regex.Pattern; /** * A simple report for keeping track of l10n status. It lists all bundle properties * files and the number of properties in them. For a configurable list of locales it also * tracks the progress of localization. * * @author <a href="mkleint@codehaus.org">Milos Kleint</a> * @goal report */ public class L10NStatusReport extends AbstractMavenReport { /** * Report output directory. * * @parameter default-value="${project.build.directory}/generated-site/xdoc" */ private File outputDirectory; /** * Doxia Site Renderer. * * @component */ private Renderer siteRenderer; /** * A list of locale strings that are to be watched for l10n status. * * @parameter */ private List locales; /** * The Maven Project. * * @parameter expression="${project}" * @required * @readonly */ private MavenProject project; /** * The list of resources that are scanned for properties bundles. * * @parameter default-value="${project.resources}" * @readonly */ private List resources; /** * A list of exclude patterns to use. By default no files are excluded. * * @parameter */ private List excludes; /** * A list of include patterns to use. By default all <code>*.properties</code> files are included. * * @parameter */ private List includes; /** * The projects in the reactor for aggregation report. * * @parameter expression="${reactorProjects}" * @readonly */ protected List reactorProjects; /** * Whether to build an aggregated report at the root, or build individual reports. * * @parameter expression="${maven.l10n.aggregate}" default-value="false" */ protected boolean aggregate; private static final String[] DEFAULT_INCLUDES = { "**/*.properties" }; private static final String[] EMPTY_STRING_ARRAY = {}; /** * @see org.apache.maven.reporting.AbstractMavenReport#getSiteRenderer() */ protected Renderer getSiteRenderer() { return siteRenderer; } /** * @see org.apache.maven.reporting.AbstractMavenReport#getOutputDirectory() */ protected String getOutputDirectory() { return outputDirectory.getAbsolutePath(); } /** * @see org.apache.maven.reporting.AbstractMavenReport#getProject() */ protected MavenProject getProject() { return project; } public boolean canGenerateReport() { return canGenerateReport(constructResourceDirs()); } /** * @param sourceDirs * @return true if the report can be generated */ protected boolean canGenerateReport(Map sourceDirs) { boolean canGenerate = !sourceDirs.isEmpty(); if (aggregate && !project.isExecutionRoot()) { canGenerate = false; } return canGenerate; } /** * Collects resource definitions from all projects in reactor. * * @return */ protected Map constructResourceDirs() { Map sourceDirs = new HashMap(); if (aggregate) { for (Iterator i = reactorProjects.iterator(); i.hasNext();) { MavenProject prj = (MavenProject) i.next(); if (prj.getResources() != null && !prj.getResources().isEmpty()) { sourceDirs.put(prj, new ArrayList(prj.getResources())); } } } else { if (resources != null && !resources.isEmpty()) { sourceDirs.put(project, new ArrayList(resources)); } } return sourceDirs; } /** * @see org.apache.maven.reporting.AbstractMavenReport#executeReport(java.util.Locale) */ protected void executeReport(Locale locale) throws MavenReportException { Set included = new TreeSet(new WrapperComparator()); Map res = constructResourceDirs(); for (Iterator it = res.keySet().iterator(); it.hasNext();) { MavenProject prj = (MavenProject) it.next(); List lst = (List) res.get(prj); for (Iterator i = lst.iterator(); i.hasNext();) { Resource resource = (Resource) i.next(); File resourceDirectory = new File(resource.getDirectory()); if (!resourceDirectory.exists()) { getLog().info("Resource directory does not exist: " + resourceDirectory); continue; } DirectoryScanner scanner = new DirectoryScanner(); scanner.setBasedir(resource.getDirectory()); List allIncludes = new ArrayList(); if (resource.getIncludes() != null && !resource.getIncludes().isEmpty()) { allIncludes.addAll(resource.getIncludes()); } if (includes != null && !includes.isEmpty()) { allIncludes.addAll(includes); } if (allIncludes.isEmpty()) { scanner.setIncludes(DEFAULT_INCLUDES); } else { scanner.setIncludes((String[]) allIncludes.toArray(EMPTY_STRING_ARRAY)); } List allExcludes = new ArrayList(); if (resource.getExcludes() != null && !resource.getExcludes().isEmpty()) { allExcludes.addAll(resource.getExcludes()); } else if (excludes != null && !excludes.isEmpty()) { allExcludes.addAll(excludes); } scanner.setExcludes((String[]) allExcludes.toArray(EMPTY_STRING_ARRAY)); scanner.addDefaultExcludes(); scanner.scan(); List includedFiles = Arrays.asList(scanner.getIncludedFiles()); for (Iterator j = includedFiles.iterator(); j.hasNext();) { String name = (String) j.next(); File source = new File(resource.getDirectory(), name); included.add(new Wrapper(name, source, prj)); } } } // Write the overview L10NStatusRenderer r = new L10NStatusRenderer(getSink(), getBundle(locale), included, locale); r.render(); } /** * @see org.apache.maven.reporting.MavenReport#getDescription(java.util.Locale) */ public String getDescription(Locale locale) { return getBundle(locale).getString("report.l10n.description"); } /** * @see org.apache.maven.reporting.MavenReport#getName(java.util.Locale) */ public String getName(Locale locale) { return getBundle(locale).getString("report.l10n.name"); } /** * @see org.apache.maven.reporting.MavenReport#getOutputName() */ public String getOutputName() { return "l10n-status"; } private static ResourceBundle getBundle(Locale locale) { return ResourceBundle.getBundle("l10n-status-report", locale, L10NStatusReport.class.getClassLoader()); } /** * Generates an overview page with a list of properties bundles * and a link to each locale's status. */ class L10NStatusRenderer extends AbstractMavenReportRenderer { private final ResourceBundle bundle; /** * The locale in which the report will be rendered. */ private final Locale rendererLocale; private Set files; private Pattern localedPattern = Pattern.compile(".*_[a-zA-Z]{2}[_]?[a-zA-Z]{0,2}?\\.properties"); public L10NStatusRenderer(Sink sink, ResourceBundle bundle, Set files, Locale rendererLocale) { super(sink); this.bundle = bundle; this.files = files; this.rendererLocale = rendererLocale; } /** * @see org.apache.maven.reporting.MavenReportRenderer#getTitle() */ public String getTitle() { return bundle.getString("report.l10n.title"); } /** * @see org.apache.maven.reporting.AbstractMavenReportRenderer#renderBody() */ public void renderBody() { startSection(getTitle()); paragraph(bundle.getString("report.l10n.intro")); startSection(bundle.getString("report.l10n.summary")); startTable(); tableCaption(bundle.getString("report.l10n.summary.caption")); String defaultLocaleColumnName = bundle.getString("report.l10n.column.default"); String pathColumnName = bundle.getString("report.l10n.column.path"); String missingFileLabel = bundle.getString("report.l10n.missingFile"); String missingKeysLabel = bundle.getString("report.l10n.missingKey"); String okLabel = bundle.getString("report.l10n.ok"); String totalLabel = bundle.getString("report.l10n.total"); String additionalKeysLabel = bundle.getString("report.l10n.additional"); String nontranslatedKeysLabel = bundle.getString("report.l10n.nontranslated"); String[] headers = new String[locales != null ? locales.size() + 2 : 2]; Map localeDisplayNames = new HashMap(); headers[0] = pathColumnName; headers[1] = defaultLocaleColumnName; if (locales != null) { Iterator it = locales.iterator(); int ind = 2; while (it.hasNext()) { final String localeCode = (String) it.next(); headers[ind] = localeCode; ind = ind + 1; Locale locale = createLocale(localeCode); if (locale == null) { // If the localeCode were in an unknown format use the localeCode itself as a fallback value localeDisplayNames.put(localeCode, localeCode); } else { localeDisplayNames.put(localeCode, locale.getDisplayName(rendererLocale)); } } } tableHeader(headers); int[] count = new int[locales != null ? locales.size() + 1 : 1]; Arrays.fill(count, 0); Iterator it = files.iterator(); MavenProject lastPrj = null; Set usedFiles = new TreeSet(new WrapperComparator()); while (it.hasNext()) { Wrapper wr = (Wrapper) it.next(); if (reactorProjects.size() > 1 && (lastPrj == null || lastPrj != wr.getProject())) { lastPrj = wr.getProject(); sink.tableRow(); String name = wr.getProject().getName(); if (name == null) { name = wr.getProject().getGroupId() + ":" + wr.getProject().getArtifactId(); } tableCell("<b><i>" + name + "</b></i>", true); sink.tableRow_(); } if (wr.getFile().getName().endsWith(".properties") && !localedPattern.matcher(wr.getFile().getName()).matches()) { usedFiles.add(wr); sink.tableRow(); tableCell(wr.getPath()); Properties props = new Properties(); BufferedInputStream in = null; try { in = new BufferedInputStream(new FileInputStream(wr.getFile())); props.load(in); wr.getProperties().put(Wrapper.DEFAULT_LOCALE, props); tableCell("" + props.size(), true); count[0] = count[0] + props.size(); if (locales != null) { Iterator it2 = locales.iterator(); int i = 1; while (it2.hasNext()) { String loc = (String) it2.next(); String nm = wr.getFile().getName(); String fn = nm.substring(0, nm.length() - ".properties".length()); File locFile = new File(wr.getFile().getParentFile(), fn + "_" + loc + ".properties"); if (locFile.exists()) { BufferedInputStream in2 = null; Properties props2 = new Properties(); try { in2 = new BufferedInputStream(new FileInputStream(locFile)); props2.load(in2); wr.getProperties().put(loc, props2); Set missing = new HashSet(props.keySet()); missing.removeAll(props2.keySet()); Set additional = new HashSet(props2.keySet()); additional.removeAll(props.keySet()); Set nonTranslated = new HashSet(); Iterator itx = props.keySet().iterator(); while (itx.hasNext()) { String k = (String) itx.next(); String val1 = props.getProperty(k); String val2 = props2.getProperty(k); if (val2 != null && val1.equals(val2)) { nonTranslated.add(k); } } count[i] = count[i] + (props.size() - missing.size() - nonTranslated.size()); StringBuffer statusRows = new StringBuffer(); if (missing.size() != 0) { statusRows.append("<tr><td>" + missingKeysLabel + "</td><td><b>" + missing.size() + "</b></td></tr>"); } else { statusRows.append("<tr><td> </td><td> </td></tr>"); } if (additional.size() != 0) { statusRows.append("<tr><td>" + additionalKeysLabel + "</td><td><b>" + additional.size() + "</b></td></tr>"); } else { statusRows.append("<tr><td> </td><td> </td></tr>"); } if (nonTranslated.size() != 0) { statusRows.append("<tr><td>" + nontranslatedKeysLabel + "</td><td><b>" + nonTranslated.size() + "</b></td></tr>"); } tableCell(wrapInTable(okLabel, statusRows.toString()), true); } finally { IOUtil.close(in2); } } else { tableCell(missingFileLabel); count[i] = count[i] + 0; } i = i + 1; } } } catch (IOException ex) { getLog().error(ex); } finally { IOUtil.close(in); } sink.tableRow_(); } } sink.tableRow(); tableCell(totalLabel); for (int i = 0; i < count.length; i++) { if (i != 0 && count[0] != 0) { tableCell("<b>" + count[i] + "</b><br />(" + (count[i] * 100 / count[0]) + " %)", true); } else if (i == 0) { tableCell("<b>" + count[i] + "</b>", true); } } sink.tableRow_(); endTable(); sink.paragraph(); text(bundle.getString("report.l10n.legend")); sink.paragraph_(); sink.list(); sink.listItem(); text(bundle.getString("report.l10n.list1")); sink.listItem_(); sink.listItem(); text(bundle.getString("report.l10n.list2")); sink.listItem_(); sink.listItem(); text(bundle.getString("report.l10n.list3")); sink.listItem_(); sink.list_(); sink.paragraph(); text(bundle.getString("report.l10n.note")); sink.paragraph_(); endSection(); if (locales != null) { Iterator itx = locales.iterator(); sink.list(); while (itx.hasNext()) { String x = (String) itx.next(); sink.listItem(); link("#" + x, x + " - " + localeDisplayNames.get(x)); sink.listItem_(); } sink.list_(); itx = locales.iterator(); while (itx.hasNext()) { String x = (String) itx.next(); startSection(x + " - " + localeDisplayNames.get(x)); sink.anchor(x); sink.anchor_(); startTable(); tableCaption(bundle.getString("report.l10n.locale") + " " + localeDisplayNames.get(x)); tableHeader(new String[] { bundle.getString("report.l10n.tableheader1"), bundle.getString("report.l10n.tableheader2"), bundle.getString("report.l10n.tableheader3"), bundle.getString("report.l10n.tableheader4") }); Iterator usedIter = usedFiles.iterator(); while (usedIter.hasNext()) { sink.tableRow(); Wrapper wr = (Wrapper) usedIter.next(); tableCell(wr.getPath()); Properties defs = (Properties) wr.getProperties().get(Wrapper.DEFAULT_LOCALE); Properties locals = (Properties) wr.getProperties().get(x); if (locals == null) { locals = new Properties(); } Set missing = new TreeSet(defs.keySet()); missing.removeAll(locals.keySet()); String cell = ""; Iterator ms = missing.iterator(); while (ms.hasNext()) { cell = cell + "<tr><td>" + ms.next() + "</td></tr>"; } tableCell(wrapInTable(okLabel, cell), true); Set additional = new TreeSet(locals.keySet()); additional.removeAll(defs.keySet()); Iterator ex = additional.iterator(); cell = ""; while (ex.hasNext()) { cell = cell + "<tr><td>" + ex.next() + "</td></tr>"; } tableCell(wrapInTable(okLabel, cell), true); Set nonTranslated = new TreeSet(); Iterator itnt = defs.keySet().iterator(); while (itnt.hasNext()) { String k = (String) itnt.next(); String val1 = defs.getProperty(k); String val2 = locals.getProperty(k); if (val2 != null && val1.equals(val2)) { nonTranslated.add(k); } } Iterator nt = nonTranslated.iterator(); cell = ""; while (nt.hasNext()) { String n = (String) nt.next(); cell = cell + "<tr><td>" + n + "</td><td>\"" + defs.getProperty(n) + "\"</td></tr>"; } tableCell(wrapInTable(okLabel, cell), true); sink.tableRow_(); } endTable(); endSection(); } } endSection(); } /** * Take the supplied locale code, split into its different parts and create a Locale object from it. * * @param localeCode The code for a locale in the format language[_country[_variant]] * @return A suitable Locale object, ot <code>null</code> if the code was in an unknown format */ private Locale createLocale(String localeCode) { // Split the localeCode into language/country/variant String[] localeComponents = StringUtils.split(localeCode, "_"); Locale locale = null; if (localeComponents.length == 1) { locale = new Locale(localeComponents[0]); } else if (localeComponents.length == 2) { locale = new Locale(localeComponents[0], localeComponents[1]); } else if (localeComponents.length == 3) { locale = new Locale(localeComponents[0], localeComponents[1], localeComponents[2]); } return locale; } private String wrapInTable(String okLabel, String cell) { if (cell.length() == 0) { cell = okLabel; } else { cell = "<table><tbody>" + cell + "</tbody></table>"; } return cell; } } private static class Wrapper { private String path; private File file; private MavenProject proj; private Map properties; static final String DEFAULT_LOCALE = "Default"; public Wrapper(String p, File f, MavenProject prj) { path = p; file = f; proj = prj; properties = new HashMap(); } public File getFile() { return file; } public String getPath() { return path; } public MavenProject getProject() { return proj; } public Map getProperties() { return properties; } } private static class WrapperComparator implements Comparator { public int compare(Object o1, Object o2) { Wrapper wr1 = (Wrapper) o1; Wrapper wr2 = (Wrapper) o2; int comp1 = wr1.getProject().getBasedir().compareTo(wr2.getProject().getBasedir()); if (comp1 != 0) { return comp1; } return wr1.getFile().compareTo(wr2.getFile()); } } }