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
             <index>false</index>
             <manifest>
               <addClasspath>true</addClasspath>
+              <mainClass>org.apache.sis.util.About</mainClass>
               
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
             </manifest>
             <manifestEntries>

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<String> warnings() {
-        final List<String> 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<String> warnings() {
+        final List<String> 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, <i>etc</i>.
+     *
+     * @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:
+ *
+ * <ul>
+ *   <li>Version numbers (SIS, Java, Operation system).</li>
+ *   <li>Default locale, timezone and character encoding.</li>
+ *   <li>Current directory, user home and Java home.</li>
+ *   <li>Libraries on the classpath and extension directories.</li>
+ * </ul>
+ *
+ * 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:
+     *
+     * <ul>
+     *   <li>Apache SIS version</li>
+     *   <li>Java runtime version and vendor</li>
+     *   <li>Operation system name and version</li>
+     * </ul>
+     */
+    VERSIONS(Vocabulary.Keys.Versions),
+
+    /**
+     * Information about default locale, timezone and character encoding.
+     * This section includes:
+     *
+     * <ul>
+     *   <li>Default locale, completed by ISO 3-letter codes</li>
+     *   <li>Default timezone, completed by timezone offset</li>
+     *   <li>Current date and time in the default timezone</li>
+     *   <li>Default character encoding</li>
+     * </ul>
+     */
+    LOCALIZATION(Vocabulary.Keys.Localization),
+
+    /**
+     * Information about user home directory, java installation directory or 
other kind of data.
+     * This section includes:
+     *
+     * <ul>
+     *   <li>User directory</li>
+     *   <li>Default directory</li>
+     *   <li>Java home directory</li>
+     * </ul>
+     */
+    PATHS(Vocabulary.Keys.Paths),
+
+    /**
+     * Information about the libraries.
+     * This section includes:
+     *
+     * <ul>
+     *   <li>JAR files in the extension directories</li>
+     *   <li>JAR files and directories in the application classpath</li>
+     * </ul>
+     */
+    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:
+     *
+     * <ul>
+     *   <li>{@code --version}: prints only Apache SIS version number.</li>
+     *   <li>{@code --verbose}: prints all information including the 
libraries.</li>
+     * </ul>
+     *
+     * @param args Command-line options.
+     */
+    public static void main(final String[] args) {
+        final EnumSet<About> 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<About> 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<String> 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<children.length; j+=2) {
+                    final String c = children[j+1];
+                    if (c != null) {
+                        final TreeTable.Node child = node.newChild();
+                        child.setValue(NAME, children[j]);
+                        child.setValue(VALUE_AS_TEXT, c);
+                    }
+                }
+            }
+            if (!(value instanceof Map<?,?>)) {
+                node.setValue(VALUE_AS_TEXT, value.toString());
+                continue;
+            }
+            /*
+             * Special case for values of kind Map<File,String>.
+             * They are extension paths or application class paths.
+             */
+            @SuppressWarnings("unchecked")
+            final Map<File,String> paths = (Map<File,String>) 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<Map.Entry<File,String>> 
it=paths.entrySet().iterator(); it.hasNext();) {
+                    final Map.Entry<File,String> 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<File,CharSequence> classpath(final String paths, final 
boolean asDirectories) {
+        if (paths == null) {
+            return null;
+        }
+        final Map<File,CharSequence> 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<File,CharSequence> 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.
+     *
+     * <p>See the "<cite>Reduce the depth of a tree</cite>" example in {@link 
TreeTables}
+     * for more information.</p>
+     *
+     * @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<TreeTable.Node> children = node.getChildren();
+        final int size = children.size();
+        for (int i=0; i<size; i++) {
+            children.set(i, concatenateSingletons(children.get(i), false));
+        }
+        if (!skip && size == 1) {
+            if (node.getValue(VALUE_AS_TEXT) == null) {
+                final TreeTable.Node child = children.remove(0);
+                final StringBuilder name = new 
StringBuilder(node.getValue(NAME));
+                if (!File.separator.contentEquals(name)) {
+                    name.append(File.separatorChar);
+                }
+                child.setValue(NAME, name.append(child.getValue(NAME)));
+                return child;
+            }
+        }
+        return node;
+    }
+
+    /**
+     * Concatenates the given strings in the format "main (complement)".
+     * Any of the given strings can be null.
+     *
+     * @param main        The main string to show first.
+     * @param complement  The string to show after the main one.
+     * @param parenthesis {@code true} for writing the complement between 
parenthesis.
+     */
+    private static CharSequence concatenate(final CharSequence main, final 
CharSequence complement, final boolean parenthesis) {
+        if (main != null && main.length() != 0) {
+            if (complement != null && complement.length() != 0) {
+                final StringBuilder buffer = (main instanceof StringBuilder)
+                        ? (StringBuilder) main : new StringBuilder(main);
+                buffer.append(' ');
+                if (parenthesis) buffer.append('(');
+                buffer.append(complement);
+                if (parenthesis) buffer.append(')');
+                return buffer;
+            }
+            return main;
+        }
+        return complement;
+    }
+
+    /**
+     * Returns the given text between parenthesis.
+     */
+    private static CharSequence parenthesis(final String text) {
+        return new StringBuilder(text.length() + 
2).append('(').append(text).append(')');
+    }
+
+    /**
+     * Returns the ISO language or country code for the given locale.
+     */
+    private static String getCode(final Locale locale, final boolean country) {
+        try {
+            return country ? locale.getISO3Country() : 
locale.getISO3Language();
+        } catch (MissingResourceException e) {
+            Logging.recoverableException(About.class, "configuration", e);
+            return null;
+        }
+    }
+
+    /**
+     * Formats the given value preceded by a plus or minus sign.
+     * This method is used for formatting timezone offset.
+     *
+     * @param df     The {@link DateFormat} to use for formatting the offset.
+     * @param offset The offset to format, as a positive or negative value.
+     * @param buffer The buffer where to format the offset.
+     * @return       The given buffer, returned for convenience.
+     */
+    private static StringBuffer format(final Format df, final int offset, 
final StringBuffer buffer) {
+        return df.format(Math.abs(offset), buffer.append(offset < 0 ? '-' : 
'+').append(' '), new FieldPosition(0));
+    }
+
+    /**
+     * Filters the JAR files in an extension directory.
+     */
+    private static final class JARFilter implements FileFilter {
+        @Override public boolean accept(final File pathname) {
+            return pathname.getName().endsWith(".jar");
+        }
+    }
+
+    /**
+     * Returns the given file relative to the given root, or {@code null} if 
the root is not
+     * a parent of that file.
+     *
+     * @param  root The root directory (typically Java home or user home 
directory).
+     * @param  file The file to make relative to the root.
+     * @return The file relative to the given root, or {@code null} if none.
+     */
+    private static File relativize(final File root, final File file) {
+        File parent = file.getParentFile();
+        if (parent == null) {
+            return null;
+        }
+        if (root.equals(parent)) {
+            parent = null;
+        } else {
+            parent = relativize(root, parent);
+            if (parent == null) {
+                return null;
+            }
+        }
+        return new File(parent, file.getName());
+    }
+}

Propchange: 
sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/About.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: 
sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/About.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Modified: 
sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java?rev=1427049&r1=1427048&r2=1427049&view=diff
==============================================================================
--- 
sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
 (original)
+++ 
sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.java
 Mon Dec 31 07:38:18 2012
@@ -61,7 +61,82 @@ public final class Vocabulary extends In
         /**
          * Degrees
          */
-        public static final int Degrees = 10;
+        public static final int AngularDegrees = 10;
+
+        /**
+         * Minutes
+         */
+        public static final int AngularMinutes = 11;
+
+        /**
+         * Seconds
+         */
+        public static final int AngularSeconds = 12;
+
+        /**
+         * Character encoding
+         */
+        public static final int CharacterEncoding = 17;
+
+        /**
+         * Classpath
+         */
+        public static final int Classpath = 29;
+
+        /**
+         * {0} code
+         */
+        public static final int Code_1 = 21;
+
+        /**
+         * Current date and time
+         */
+        public static final int CurrentDateTime = 25;
+
+        /**
+         * Current directory
+         */
+        public static final int CurrentDirectory = 33;
+
+        /**
+         * Daylight time
+         */
+        public static final int DaylightTime = 24;
+
+        /**
+         * Directory
+         */
+        public static final int Directory = 36;
+
+        /**
+         * Java extensions
+         */
+        public static final int JavaExtensions = 26;
+
+        /**
+         * Java home directory
+         */
+        public static final int JavaHome = 30;
+
+        /**
+         * Libraries
+         */
+        public static final int Libraries = 35;
+
+        /**
+         * Local configuration
+         */
+        public static final int LocalConfiguration = 14;
+
+        /**
+         * Locale
+         */
+        public static final int Locale = 18;
+
+        /**
+         * Localization
+         */
+        public static final int Localization = 19;
 
         /**
          * Maximum value
@@ -79,11 +154,6 @@ public final class Vocabulary extends In
         public static final int MinimumValue = 4;
 
         /**
-         * Minutes
-         */
-        public static final int Minutes = 11;
-
-        /**
          * Name
          */
         public static final int Name = 0;
@@ -99,14 +169,39 @@ public final class Vocabulary extends In
         public static final int NumberOfValues = 2;
 
         /**
+         * Offset
+         */
+        public static final int Offset = 22;
+
+        /**
+         * Operating system
+         */
+        public static final int OperatingSystem = 16;
+
+        /**
+         * Others
+         */
+        public static final int Others = 34;
+
+        /**
+         * Paths
+         */
+        public static final int Paths = 27;
+
+        /**
+         * Root
+         */
+        public static final int Root = 28;
+
+        /**
          * Root Mean Square
          */
         public static final int RootMeanSquare = 7;
 
         /**
-         * Seconds
+         * Scale
          */
-        public static final int Seconds = 12;
+        public static final int Scale = 23;
 
         /**
          * Standard deviation
@@ -114,9 +209,39 @@ public final class Vocabulary extends In
         public static final int StandardDeviation = 8;
 
         /**
+         * Temporary files
+         */
+        public static final int TemporaryFiles = 31;
+
+        /**
+         * Timezone
+         */
+        public static final int Timezone = 20;
+
+        /**
          * Type
          */
         public static final int Type = 1;
+
+        /**
+         * Untitled
+         */
+        public static final int Untitled = 37;
+
+        /**
+         * User home directory
+         */
+        public static final int UserHome = 32;
+
+        /**
+         * Value
+         */
+        public static final int Value = 13;
+
+        /**
+         * Versions
+         */
+        public static final int Versions = 15;
     }
 
     /**

Modified: 
sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties?rev=1427049&r1=1427048&r2=1427049&view=diff
==============================================================================
--- 
sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
 (original)
+++ 
sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary.properties
 Mon Dec 31 07:38:18 2012
@@ -14,16 +14,41 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-Angle             = Angle
-Degrees           = Degrees
-MaximumValue      = Maximum value
-MeanValue         = Mean value
-MinimumValue      = Minimum value
-Minutes           = Minutes
-Name              = Name
-NumberOfValues    = Number of values
-NumberOfNaN       = Number of \u2018NaN\u2019
-RootMeanSquare    = Root Mean Square
-Seconds           = Seconds
-StandardDeviation = Standard deviation
-Type              = Type
+Angle              = Angle
+AngularDegrees     = Degrees
+AngularMinutes     = Minutes
+AngularSeconds     = Seconds
+CharacterEncoding  = Character encoding
+Classpath          = Classpath
+Code_1             = {0} code
+CurrentDateTime    = Current date and time
+CurrentDirectory   = Current directory
+DaylightTime       = Daylight time
+Directory          = Directory
+JavaExtensions     = Java extensions
+JavaHome           = Java home directory
+Libraries          = Libraries
+LocalConfiguration = Local configuration
+Locale             = Locale
+Localization       = Localization
+MaximumValue       = Maximum value
+MeanValue          = Mean value
+MinimumValue       = Minimum value
+Name               = Name
+NumberOfValues     = Number of values
+NumberOfNaN        = Number of \u2018NaN\u2019
+Offset             = Offset
+OperatingSystem    = Operating system
+Others             = Others
+Paths              = Paths
+Root               = Root
+RootMeanSquare     = Root Mean Square
+Scale              = Scale
+StandardDeviation  = Standard deviation
+TemporaryFiles     = Temporary files
+Timezone           = Timezone
+Type               = Type
+Untitled           = Untitled
+UserHome           = User home directory
+Value              = Value
+Versions           = Versions

Modified: 
sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties?rev=1427049&r1=1427048&r2=1427049&view=diff
==============================================================================
--- 
sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
 (original)
+++ 
sis/branches/JDK7/sis-utility/src/main/java/org/apache/sis/util/resources/Vocabulary_fr.properties
 Mon Dec 31 07:38:18 2012
@@ -14,16 +14,41 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-Angle             = Angle
-Degrees           = Degr\u00e9s
-MaximumValue      = Valeur maximale
-MeanValue         = Valeur moyenne
-MinimumValue      = Valeur minimale
-Minutes           = Minutes
-Name              = Nom
-NumberOfValues    = Nombre de valeurs
-NumberOfNaN       = Nombre de \u2018NaN\u2019
-RootMeanSquare    = Moyenne quadratique
-Seconds           = Secondes
-StandardDeviation = \u00c9cart type
-Type              = Type
+Angle              = Angle
+AngularDegrees     = Degr\u00e9s
+AngularMinutes     = Minutes
+AngularSeconds     = Secondes
+CharacterEncoding  = Encodage des caract\u00e8res
+Classpath          = Chemin de classes
+Code_1             = Code {0}
+CurrentDateTime    = Date et heure courantes
+CurrentDirectory   = R\u00e9pertoire courant
+DaylightTime       = Heure normale
+Directory          = R\u00e9pertoire
+JavaExtensions     = Extensions du Java
+JavaHome           = R\u00e9pertoire du Java
+Libraries          = Biblioth\u00e8ques
+LocalConfiguration = Configuration locale
+Locale             = Locale
+Localization       = R\u00e9gionalisation
+MaximumValue       = Valeur maximale
+MeanValue          = Valeur moyenne
+MinimumValue       = Valeur minimale
+Name               = Nom
+NumberOfValues     = Nombre de valeurs
+NumberOfNaN        = Nombre de \u2018NaN\u2019
+Offset             = D\u00e9calage
+OperatingSystem    = Syst\u00e8me d'exploitation
+Others             = Autres
+Paths              = Chemins
+Root               = Racine
+RootMeanSquare     = Moyenne quadratique
+Scale              = \u00c9chelle
+StandardDeviation  = \u00c9cart type
+TemporaryFiles     = Fichiers temporaires
+Timezone           = Fuseau horaire
+Type               = Type
+Untitled           = Sans titre
+UserHome           = R\u00e9pertoire de l'utilisateur
+Value              = Valeur
+Versions           = Versions

Modified: 
sis/branches/JDK7/sis-utility/src/main/resources/org/apache/sis/internal/util/Descriptions.properties
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/resources/org/apache/sis/internal/util/Descriptions.properties?rev=1427049&r1=1427048&r2=1427049&view=diff
==============================================================================
--- 
sis/branches/JDK7/sis-utility/src/main/resources/org/apache/sis/internal/util/Descriptions.properties
 (original)
+++ 
sis/branches/JDK7/sis-utility/src/main/resources/org/apache/sis/internal/util/Descriptions.properties
 Mon Dec 31 07:38:18 2012
@@ -1,5 +1,6 @@
 # Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.
-supervisor = Provides information about the state of a running Apache SIS 
library.
-org.apache.sis.internal.util.Supervisor = Creates a new supervisor.
-locale = The language of supervisor messages.
-warnings = List of problems detected in the running Apache SIS library. If 
this list contains any item, then an application reboot is strongly recommended.
+supervisor=Provides information about the state of a running Apache SIS 
library.
+org.apache.sis.internal.util.Supervisor=Creates a new supervisor.
+locale=The language of supervisor messages.
+configuration=Tree of information about the Apache SIS running environment.
+warnings=List of problems detected in the running Apache SIS library. If this 
list contains any item, then an application reboot is strongly recommended.

Modified: 
sis/branches/JDK7/sis-utility/src/main/resources/org/apache/sis/internal/util/Descriptions_fr.properties
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/main/resources/org/apache/sis/internal/util/Descriptions_fr.properties?rev=1427049&r1=1427048&r2=1427049&view=diff
==============================================================================
--- 
sis/branches/JDK7/sis-utility/src/main/resources/org/apache/sis/internal/util/Descriptions_fr.properties
 (original)
+++ 
sis/branches/JDK7/sis-utility/src/main/resources/org/apache/sis/internal/util/Descriptions_fr.properties
 Mon Dec 31 07:38:18 2012
@@ -1,5 +1,6 @@
 # Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.
-supervisor = Fournit des informations \u00e0 propos de l\u2019\u00e9tat 
d\u2019une biblioth\u00e8que Apache SIS en cours d\u2019ex\u00e9cution.
-org.apache.sis.internal.util.Supervisor = Construit un nouveau superviseur.
-locale = La langue des messages produits par le superviseur.
-warnings = Liste des probl\u00e8mes d\u00e9tect\u00e9s dans la 
biblioth\u00e8que Apache SIS en cours d\u2019ex\u00e9cution. Si cette liste 
contient des items, alors un red\u00e9marrage de l\u2019application est 
fortement recommand\u00e9.
+supervisor=Fournit des informations \u00e0 propos de l\u2019\u00e9tat 
d\u2019une biblioth\u00e8que Apache SIS en cours d\u2019ex\u00e9cution.
+org.apache.sis.internal.util.Supervisor=Construit un nouveau superviseur.
+locale=La langue des messages produits par le superviseur.
+configuration=Arborescence d'information concernant l\u2019environnement 
d\u2019ex\u00e9cution de Apache SIS.
+warnings=Liste des probl\u00e8mes d\u00e9tect\u00e9s dans la biblioth\u00e8que 
Apache SIS en cours d\u2019ex\u00e9cution. Si cette liste contient des items, 
alors un red\u00e9marrage de l\u2019application est fortement recommand\u00e9.

Modified: 
sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java
URL: 
http://svn.apache.org/viewvc/sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java?rev=1427049&r1=1427048&r2=1427049&view=diff
==============================================================================
--- 
sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java
 (original)
+++ 
sis/branches/JDK7/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java
 Mon Dec 31 07:38:18 2012
@@ -57,6 +57,7 @@ import org.junit.runners.Suite;
     org.apache.sis.util.collection.DerivedMapTest.class,
     org.apache.sis.util.collection.TableColumnTest.class,
     org.apache.sis.util.collection.DefaultTreeTableTest.class,
+    org.apache.sis.util.collection.TreeTablesTest.class,
 
     // GeoAPI most basic types.
     org.apache.sis.util.iso.TypesTest.class,


Reply via email to