Return-Path: X-Original-To: apmail-sis-commits-archive@www.apache.org Delivered-To: apmail-sis-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id E1B7DE4E6 for ; Mon, 31 Dec 2012 07:38:47 +0000 (UTC) Received: (qmail 46373 invoked by uid 500); 31 Dec 2012 07:38:47 -0000 Delivered-To: apmail-sis-commits-archive@sis.apache.org Received: (qmail 46348 invoked by uid 500); 31 Dec 2012 07:38:47 -0000 Mailing-List: contact commits-help@sis.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: sis-dev@sis.apache.org Delivered-To: mailing list commits@sis.apache.org Received: (qmail 46338 invoked by uid 99); 31 Dec 2012 07:38:47 -0000 Received: from nike.apache.org (HELO nike.apache.org) (192.87.106.230) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 31 Dec 2012 07:38:47 +0000 X-ASF-Spam-Status: No, hits=-2000.0 required=5.0 tests=ALL_TRUSTED X-Spam-Check-By: apache.org Received: from [140.211.11.4] (HELO eris.apache.org) (140.211.11.4) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 31 Dec 2012 07:38:40 +0000 Received: from eris.apache.org (localhost [127.0.0.1]) by eris.apache.org (Postfix) with ESMTP id 311A82388993; Mon, 31 Dec 2012 07:38:19 +0000 (UTC) Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit Subject: svn commit: r1427049 - in /sis/branches/JDK7: ./ sis-utility/src/main/java/org/apache/sis/internal/util/ sis-utility/src/main/java/org/apache/sis/measure/ sis-utility/src/main/java/org/apache/sis/util/ sis-utility/src/main/java/org/apache/sis/util/reso... Date: Mon, 31 Dec 2012 07:38:18 -0000 To: commits@sis.apache.org From: desruisseaux@apache.org X-Mailer: svnmailer-1.0.8-patched Message-Id: <20121231073819.311A82388993@eris.apache.org> X-Virus-Checked: Checked by ClamAV on apache.org Author: desruisseaux Date: Mon Dec 31 07:38:18 2012 New Revision: 1427049 URL: http://svn.apache.org/viewvc?rev=1427049&view=rev Log: Added an About class for providing information on the Apache SIS runtime environment. The information provided in this class will be expanded in future version with things like SIS data directory and URL to the EPSG database directory. Added: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/About.java (with props) Modified: sis/branches/JDK7/pom.xml sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/Supervisor.java sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/SupervisorMBean.java sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/SexagesimalConverter.java sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties sis/branches/JDK7/sis-utility/src/main/resources/org/apache/sis/internal/util/Descriptions.properties sis/branches/JDK7/sis-utility/src/main/resources/org/apache/sis/internal/util/Descriptions_fr.properties sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java Modified: sis/branches/JDK7/pom.xml URL: http://svn.apache.org/viewvc/sis/branches/JDK7/pom.xml?rev=1427049&r1=1427048&r2=1427049&view=diff ============================================================================== --- sis/branches/JDK7/pom.xml (original) +++ sis/branches/JDK7/pom.xml Mon Dec 31 07:38:18 2012 @@ -330,6 +330,7 @@ Apache SIS is a toolkit for describing l false true + org.apache.sis.util.About true Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/Supervisor.java URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/Supervisor.java?rev=1427049&r1=1427048&r2=1427049&view=diff ============================================================================== --- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/Supervisor.java (original) +++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/Supervisor.java Mon Dec 31 07:38:18 2012 @@ -32,9 +32,11 @@ import javax.management.JMException; import javax.management.NotCompliantMBeanException; import java.lang.management.ManagementFactory; +import org.apache.sis.util.About; import org.apache.sis.util.Localized; import org.apache.sis.util.logging.Logging; import org.apache.sis.util.resources.Errors; +import org.apache.sis.util.collection.TreeTable; /** @@ -126,22 +128,6 @@ public final class Supervisor extends St } /** - * If there is something wrong with the current Apache Supervisor status, - * returns descriptions of the problems. Otherwise returns {@code null}. - */ - @Override - public List warnings() { - final List warnings = Threads.listDeadThreads(); - if (warnings != null) { - final Errors resources = Errors.getResources(locale); - for (int i=warnings.size(); --i>=0;) { - warnings.set(i, resources.getString(Errors.Keys.DeadThread_1, warnings.get(i))); - } - } - return warnings; - } - - /** * Returns the operations impact, which is {@code INFO}. */ @Override @@ -196,4 +182,31 @@ public final class Supervisor extends St return ResourceBundle.getBundle("org.apache.sis.internal.util.Descriptions", locale, Supervisor.class.getClassLoader()).getString(resourceKey); } + + // ----------------------------------------------------------------------- + // Implementation of SupervisorMBean interface + // ----------------------------------------------------------------------- + + /** + * {@inheritDoc} + */ + @Override + public TreeTable configuration() { + return About.configuration(locale); + } + + /** + * {@inheritDoc} + */ + @Override + public List warnings() { + final List warnings = Threads.listDeadThreads(); + if (warnings != null) { + final Errors resources = Errors.getResources(locale); + for (int i=warnings.size(); --i>=0;) { + warnings.set(i, resources.getString(Errors.Keys.DeadThread_1, warnings.get(i))); + } + } + return warnings; + } } Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/SupervisorMBean.java URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/SupervisorMBean.java?rev=1427049&r1=1427048&r2=1427049&view=diff ============================================================================== --- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/SupervisorMBean.java (original) +++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/internal/util/SupervisorMBean.java Mon Dec 31 07:38:18 2012 @@ -17,6 +17,7 @@ package org.apache.sis.internal.util; import java.util.List; +import org.apache.sis.util.collection.TreeTable; /** @@ -29,6 +30,17 @@ import java.util.List; */ public interface SupervisorMBean { /** + * Returns information about the current configuration. + * This method tries to focus on the information that are the most relevant to SIS. + * Those information are grouped in sections: a "Versions" section containing the + * Apache SIS version, Java version and operation system version; a "Classpath" + * section containing bootstrap, extension and user classpath, etc. + * + * @return Configuration information, as a tree for grouping some configuration by sections. + */ + TreeTable configuration(); + + /** * If there is something wrong with the current Apache SIS status, * returns descriptions of the problems. Otherwise returns {@code null}. * Modified: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/SexagesimalConverter.java URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/SexagesimalConverter.java?rev=1427049&r1=1427048&r2=1427049&view=diff ============================================================================== --- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/SexagesimalConverter.java (original) +++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/measure/SexagesimalConverter.java Mon Dec 31 07:38:18 2012 @@ -197,7 +197,7 @@ class SexagesimalConverter extends UnitC if (min >= 0) deg++; else deg--; min = 0; } else { - throw illegalField(angle, min, Vocabulary.Keys.Minutes); + throw illegalField(angle, min, Vocabulary.Keys.AngularMinutes); } } if (sec <= -60 || sec >= 60) { // Do not enter for NaN @@ -205,7 +205,7 @@ class SexagesimalConverter extends UnitC if (sec >= 0) min++; else min--; sec = 0; } else { - throw illegalField(angle, sec, Vocabulary.Keys.Seconds); + throw illegalField(angle, sec, Vocabulary.Keys.AngularSeconds); } } return (sec/60 + min)/60 + deg; Added: sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/About.java URL: http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/About.java?rev=1427049&view=auto ============================================================================== --- sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/About.java (added) +++ sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/About.java Mon Dec 31 07:38:18 2012 @@ -0,0 +1,674 @@ +/* + * 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. + */ +package org.apache.sis.util; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.EnumSet; +import java.util.Map; +import java.util.LinkedHashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Date; +import java.util.TimeZone; +import java.util.MissingResourceException; +import java.util.jar.Attributes; +import java.util.jar.JarFile; +import java.util.jar.Manifest; +import java.io.Console; +import java.io.File; +import java.io.FileFilter; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.IOException; +import java.text.Format; +import java.text.DateFormat; +import java.text.FieldPosition; +import java.nio.charset.Charset; +import org.apache.sis.util.logging.Logging; +import org.apache.sis.util.resources.Errors; +import org.apache.sis.util.resources.Vocabulary; +import org.apache.sis.util.collection.TreeTable; +import org.apache.sis.util.collection.TreeTables; +import org.apache.sis.util.collection.DefaultTreeTable; + +import static java.lang.System.getProperty; +import static org.apache.sis.util.collection.TableColumn.NAME; +import static org.apache.sis.util.collection.TableColumn.VALUE_AS_TEXT; + + +/** + * Provides information about the Apache SIS running environment. + * This class collects information from various places like {@link Version#SIS}, + * {@link System#getProperties()}, {@link Locale#getDefault()} or {@link TimeZone#getDefault()}. + * This class does not collect every possible information. Instead, it tries to focus on the most + * important information for SIS, as determined by experience in troubleshooting. + * Some of those information are: + * + *
    + *
  • Version numbers (SIS, Java, Operation system).
  • + *
  • Default locale, timezone and character encoding.
  • + *
  • Current directory, user home and Java home.
  • + *
  • Libraries on the classpath and extension directories.
  • + *
+ * + * This class can be invoked from the command line. + * See the {@link #main(String[])} method for more information. + * + * @author Martin Desruisseaux (Geomatys) + * @since 0.3 + * @version 0.3 + * @module + */ +public enum About { + /** + * Information about software version numbers. + * This section includes: + * + *
    + *
  • Apache SIS version
  • + *
  • Java runtime version and vendor
  • + *
  • Operation system name and version
  • + *
+ */ + VERSIONS(Vocabulary.Keys.Versions), + + /** + * Information about default locale, timezone and character encoding. + * This section includes: + * + *
    + *
  • Default locale, completed by ISO 3-letter codes
  • + *
  • Default timezone, completed by timezone offset
  • + *
  • Current date and time in the default timezone
  • + *
  • Default character encoding
  • + *
+ */ + LOCALIZATION(Vocabulary.Keys.Localization), + + /** + * Information about user home directory, java installation directory or other kind of data. + * This section includes: + * + *
    + *
  • User directory
  • + *
  • Default directory
  • + *
  • Java home directory
  • + *
+ */ + PATHS(Vocabulary.Keys.Paths), + + /** + * Information about the libraries. + * This section includes: + * + *
    + *
  • JAR files in the extension directories
  • + *
  • JAR files and directories in the application classpath
  • + *
+ */ + LIBRARIES(Vocabulary.Keys.Libraries); + + /** + * The resource key for this section in the {@link Vocabulary} resources bundle. + */ + private final int resourceKey; + + /** + * Creates a new section to be formatted using the given resource. + */ + private About(final int resourceKey) { + this.resourceKey = resourceKey; + } + + /** + * Prints the information to the standard output stream. + * This method can be invoked from the command-line as below (the "{@code sis-utility}" file + * can be replaced by any SIS module, and its filename needs to be completed with the actual + * version number): + * + * {@preformat java + * java -jar sis-utility.jar + * } + * + * By default this command prints all information except the {@link #LIBRARIES} section, + * which is verbose. Available options are: + * + *
    + *
  • {@code --version}: prints only Apache SIS version number.
  • + *
  • {@code --verbose}: prints all information including the libraries.
  • + *
+ * + * @param args Command-line options. + */ + public static void main(final String[] args) { + final EnumSet sections = EnumSet.allOf(About.class); + String configuration = null; + /* + * Command-line arguments processing. + */ + if (args.length == 0) { + sections.remove(About.LIBRARIES); + } else { + final String arg = args[0]; + if (arg.equals("--version")) { + configuration = "Apache SIS version " + Version.SIS; + } else if (!arg.equals("--verbose")) { + System.err.println(Errors.format(Errors.Keys.IllegalArgumentValue_2, 1, arg)); + return; + } + if (args.length != 1) { // Checked only after we verified the first argument. + System.err.println(Errors.format(Errors.Keys.IllegalArgumentValue_2, 2, args[1])); + return; + } + } + /* + * Format the tree and write result to the console. + */ + if (configuration == null) { + configuration = configuration(sections, Locale.getDefault()).toString(); + } + final Console console = System.console(); + if (console != null) { + final PrintWriter out = console.writer(); + out.write(configuration); + out.write(System.lineSeparator()); + out.flush(); + } else { + final PrintStream out = System.out; + out.println(configuration); + out.flush(); + } + } + + /** + * Returns all known information about the current Apache SIS running environment. + * + * @param locale The locale to use for formatting the texts in the tree. + * @return Configuration information, as a tree for grouping some configuration by sections. + */ + public static TreeTable configuration(final Locale locale) { + return configuration(EnumSet.allOf(About.class), locale); + } + + /** + * Returns a subset of the information about the current Apache SIS running environment. + * + * @param sections The section for which information are desired. + * @param locale The locale to use for formatting the texts in the tree. + * @return Configuration information, as a tree for grouping some configuration by sections. + */ + public static TreeTable configuration(final Set sections, final Locale locale) { + ArgumentChecks.ensureNonNull("sections", sections); + ArgumentChecks.ensureNonNull("locale", locale); + String userHome = null; + String javaHome = null; + final Date now = new Date(); + final Vocabulary resources = Vocabulary.getResources(locale); + final DefaultTreeTable table = new DefaultTreeTable(NAME, VALUE_AS_TEXT); + final TreeTable.Node root = table.getRoot(); + root.setValue(NAME, resources.getString(Vocabulary.Keys.LocalConfiguration)); + table.setRoot(root); + /* + * Begin with the "Versions" section. The 'newSection' variable will be updated in the + * switch statement when new section will begin, and reset to 'null' after the 'section' + * variable has been updated accordingly. + */ + TreeTable.Node section = null; + About newSection = VERSIONS; +fill: for (int i=0; ; i++) { + int nameKey = 0; // The Vocabulary.Key for 'name', used only if name is null. + String name = null; // The value to put in the 'Name' column of the table. + Object value = null; // The value to put in the 'Value' column of the table. + String[] children = null; // Optional children to write below the node. + switch (i) { + case 0: { + if (sections.contains(VERSIONS)) { + name = "Apache SIS"; + value = Version.SIS; + } + break; + } + case 1: { + if (sections.contains(VERSIONS)) { + name = "Java"; + value = concatenate(getProperty("java.version"), getProperty("java.vendor"), true); + } + break; + } + case 2: { + if (sections.contains(VERSIONS)) { + nameKey = Vocabulary.Keys.OperatingSystem; + value = concatenate(concatenate(getProperty("os.name"), + getProperty("os.version"), false), getProperty("os.arch"), true); + } + break; + } + case 3: { + newSection = LOCALIZATION; + if (sections.contains(LOCALIZATION)) { + final Locale current = Locale.getDefault(); + if (current != null) { + nameKey = Vocabulary.Keys.Locale; + value = current.getDisplayName(locale); + final CharSequence code = concatenate(getCode(locale, false), getCode(locale, true), true); + if (code != null) { + children = new String[] {resources.getString(Vocabulary.Keys.Code_1, "ISO"), code.toString()}; + } + } + } + break; + } + case 4: { + if (sections.contains(LOCALIZATION)) { + final TimeZone current = TimeZone.getDefault(); + if (current != null) { + nameKey = Vocabulary.Keys.Timezone; + final boolean inDaylightTime = current.inDaylightTime(now); + value = concatenate(current.getDisplayName(inDaylightTime, TimeZone.LONG, locale), current.getID(), true); + final DateFormat df = DateFormat.getTimeInstance(DateFormat.SHORT, locale); + df.setTimeZone(TimeZone.getTimeZone("UTC")); + int offset = current.getOffset(now.getTime()); + StringBuffer buffer = format(df, offset, new StringBuffer("UTC ")); + offset -= current.getRawOffset(); + if (offset != 0) { + buffer = format(df, offset, buffer.append(" (") + .append(resources.getString(Vocabulary.Keys.DaylightTime)).append(' ')).append(')'); + } + children = new String[] {resources.getString(Vocabulary.Keys.Offset), buffer.toString()}; + } + } + break; + } + case 5: { + if (sections.contains(LOCALIZATION)) { + nameKey = Vocabulary.Keys.CurrentDateTime; + value = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale).format(now); + } + break; + } + case 6: { + if (sections.contains(LOCALIZATION)) { + final Charset current = Charset.defaultCharset(); + if (current != null) { + nameKey = Vocabulary.Keys.CharacterEncoding; + value = current.displayName(locale); + final Set aliases = current.aliases(); + if (aliases != null && !aliases.isEmpty()) { + final StringBuilder buffer = new StringBuilder((String) value); + String separator = " ("; + for (final String alias : aliases) { + buffer.append(separator).append(alias); + separator = ", "; + } + value = buffer.append(')'); + } + } + } + break; + } + case 7: { + newSection = PATHS; + if (sections.contains(PATHS)) { + nameKey = Vocabulary.Keys.UserHome; + value = userHome = getProperty("user.home"); + } + break; + } + case 8: { + if (sections.contains(PATHS)) { + nameKey = Vocabulary.Keys.CurrentDirectory; + value = getProperty("user.dir"); + } + break; + } + case 9: { + if (sections.contains(PATHS)) { + nameKey = Vocabulary.Keys.TemporaryFiles; + value = getProperty("java.io.tmpdir"); + } + break; + } + case 10: { + if (sections.contains(PATHS)) { + nameKey = Vocabulary.Keys.JavaHome; + value = javaHome = getProperty("java.home"); + } + break; + } + case 11: { + newSection = LIBRARIES; + if (sections.contains(LIBRARIES)) { + nameKey = Vocabulary.Keys.JavaExtensions; + value = classpath(getProperty("java.ext.dirs"), true); + } + break; + } + case 12: { + if (sections.contains(LIBRARIES)) { + nameKey = Vocabulary.Keys.Classpath; + value = classpath(getProperty("java.class.path"), false); + } + break; + } + default: break fill; + } + /* + * At this point, we have the information about one node to create. + * If the 'newSection' variable is non-null, then this new node shall + * appear in a new section. + */ + if (value == null) { + continue; + } + if (newSection != null) { + section = root.newChild(); + section.setValue(NAME, resources.getString(newSection.resourceKey)); + newSection = null; + } + if (name == null) { + name = resources.getString(nameKey); + } + final TreeTable.Node node = section.newChild(); + node.setValue(NAME, name); + if (children != null) { + for (int j=0; j)) { + node.setValue(VALUE_AS_TEXT, value.toString()); + continue; + } + /* + * Special case for values of kind Map. + * They are extension paths or application class paths. + */ + @SuppressWarnings("unchecked") + final Map paths = (Map) value; +pathTree: for (int j=0; ; j++) { + TreeTable.Node directory = null; + final String home; + final int homeKey; + switch (j) { + case 0: home = javaHome; homeKey = Vocabulary.Keys.JavaHome; break; + case 1: home = userHome; homeKey = Vocabulary.Keys.UserHome; break; + case 2: home = ""; homeKey = 0; directory = node; break; + default: break pathTree; + } + if (home == null) { + // Should never happen since "user.home" and "java.home" are + // standard properties of the Java platform, but let be safe. + continue; + } + final File homeDirectory = home.isEmpty() ? null : new File(home); + for (final Iterator> it=paths.entrySet().iterator(); it.hasNext();) { + final Map.Entry entry = it.next(); + File file = entry.getKey(); + if (homeDirectory != null) { + file = relativize(homeDirectory, file); + if (file == null) continue; + } + if (directory == null) { + directory = node.newChild(); + directory.setValue(NAME, parenthesis(resources.getString(homeKey))); + } + CharSequence description = entry.getValue(); + if (description == null) { + description = parenthesis(resources.getString(entry.getKey().isDirectory() ? + Vocabulary.Keys.Directory : Vocabulary.Keys.Untitled).toLowerCase(locale)); + } + TreeTables.nodeForPath(directory, NAME, file).setValue(VALUE_AS_TEXT, description); + it.remove(); + } + if (directory != null) { + concatenateSingletons(directory, true); + omitMavenRedundancy(directory); + } + } + } + TreeTables.valuesAsStrings(table, locale); + return table; + } + + /** + * Returns a map of all JAR files or class directories found in the given paths, + * associated to a description obtained from their {@code META-INF/MANIFEST.MF}. + * + * @param paths The paths, separated by {@link File#pathSeparatorChar}. + * @param asDirectories {@code true} if the paths shall contain directories, + * or {@code false} if it shall contain JAR files. + */ + private static Map classpath(final String paths, final boolean asDirectories) { + if (paths == null) { + return null; + } + final Map files = new LinkedHashMap<>(); + for (final CharSequence path : CharSequences.split(paths, File.pathSeparatorChar)) { + final File file = new File(path.toString()); + if (file.exists()) { + if (!asDirectories) { + files.put(file, null); + } else { + // If we are scanning extensions, then the path are directories + // rather than files. So we need to scan the directory content. + final JARFilter filter = new JARFilter(); + final File[] list = file.listFiles(filter); + if (list != null) { + Arrays.sort(list); + for (final File ext : list) { + files.put(ext, null); + } + } + } + } + } + /* + * At this point, we have collected all JAR files. + * Now set the description from the MANIFEST.MF file. + */ + IOException error = null; + for (final Map.Entry entry : files.entrySet()) { + final File file = entry.getKey(); + if (file.isFile()) { + CharSequence name = null; + try (final JarFile jar = new JarFile(file)) { + final Manifest manifest = jar.getManifest(); + if (manifest != null) { + final Attributes attributes = manifest.getMainAttributes(); + if (attributes != null) { + name = concatenate(attributes.getValue(Attributes.Name.IMPLEMENTATION_TITLE), + attributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION), false); + if (name == null) { + name = concatenate(attributes.getValue(Attributes.Name.SPECIFICATION_TITLE), + attributes.getValue(Attributes.Name.SPECIFICATION_VERSION), false); + } + } + } + } catch (IOException e) { + if (error == null) { + error = e; + } else { + error.addSuppressed(e); + } + continue; + } + entry.setValue(name); + } + } + if (error != null) { + Logging.unexpectedException(About.class, "configuration", error); + } + return files; + } + + /** + * If a file path in the given node or any children follow the Maven pattern, remove the + * artefact name and version numbers redundancies in order to make the name more compact. + * For example this method replaces {@code "org/opengis/geoapi/3.0.0/geoapi-3.0.0.jar"} + * by {@code "org/opengis/(…)/geoapi-3.0.0.jar"}. + */ + private static void omitMavenRedundancy(final TreeTable.Node node) { + for (final TreeTable.Node child : node.getChildren()) { + omitMavenRedundancy(child); + } + final CharSequence name = node.getValue(NAME); + final int length = name.length(); + final int s2 = CharSequences.lastIndexOf(name, File.separatorChar, 0, length); + if (s2 >= 0) { + final int s1 = CharSequences.lastIndexOf(name, File.separatorChar, 0, s2); + if (s1 >= 0) { + final int s0 = CharSequences.lastIndexOf(name, File.separatorChar, 0, s1) + 1; + final StringBuilder buffer = new StringBuilder(s2 - s0).append(name, s0, s2); + buffer.setCharAt(s1-s0, '-'); + if (CharSequences.regionMatches(name, s2+1, buffer)) { + buffer.setLength(0); + node.setValue(NAME, buffer.append(name, 0, s0).append("(…)").append(name, s2, length)); + } + } + } + } + + /** + * For every branch containing only one child and no value, merges in-place that branch and the + * node together. This method is used for simplifying depth trees into something less verbose. + * However for any column other than {@code NAME}, this method preserves the values of the child + * node but lost all value of the parent node. For this reason, we perform the merge only if the + * parent has no value. + * + *

See the "Reduce the depth of a tree" example in {@link TreeTables} + * for more information.

+ * + * @param node The root of the node to simplify. + * @param skip {@code true} for disabling concatenation of root node. + * @return The root of the simplified tree. May be the given {@code node} or a child. + */ + private static TreeTable.Node concatenateSingletons(final TreeTable.Node node, final boolean skip) { + final List children = node.getChildren(); + final int size = children.size(); + for (int i=0; i