Coverage Report - org.geotoolkit.referencing.factory.FactoryDependencies
 
Classes in this File Line Coverage Branch Coverage Complexity
FactoryDependencies
33 %
32/96
18 %
11/58
2,556
 
 1  
 /*
 2  
  *    Geotoolkit.org - An Open Source Java GIS Toolkit
 3  
  *    http://www.geotoolkit.org
 4  
  *
 5  
  *    (C) 2007-2012, Open Source Geospatial Foundation (OSGeo)
 6  
  *    (C) 2009-2012, Geomatys
 7  
  *
 8  
  *    This library is free software; you can redistribute it and/or
 9  
  *    modify it under the terms of the GNU Lesser General Public
 10  
  *    License as published by the Free Software Foundation;
 11  
  *    version 2.1 of the License.
 12  
  *
 13  
  *    This library is distributed in the hope that it will be useful,
 14  
  *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 15  
  *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 16  
  *    Lesser General Public License for more details.
 17  
  */
 18  
 package org.geotoolkit.referencing.factory;
 19  
 
 20  
 import java.util.Map;
 21  
 import java.util.Collection;
 22  
 import java.util.IdentityHashMap;
 23  
 import java.io.PrintWriter;
 24  
 import java.io.IOException;
 25  
 import java.lang.annotation.Annotation;
 26  
 
 27  
 import org.opengis.util.Factory;
 28  
 import org.opengis.metadata.Identifier;
 29  
 import org.opengis.metadata.citation.Citation;
 30  
 import org.opengis.referencing.AuthorityFactory;
 31  
 import org.opengis.referencing.crs.CRSFactory;
 32  
 import org.opengis.referencing.crs.CRSAuthorityFactory;
 33  
 import org.opengis.referencing.cs.CSFactory;
 34  
 import org.opengis.referencing.cs.CSAuthorityFactory;
 35  
 import org.opengis.referencing.datum.DatumFactory;
 36  
 import org.opengis.referencing.datum.DatumAuthorityFactory;
 37  
 import org.opengis.referencing.operation.CoordinateOperationFactory;
 38  
 import org.opengis.referencing.operation.CoordinateOperationAuthorityFactory;
 39  
 
 40  
 import org.geotoolkit.io.X364;
 41  
 import org.geotoolkit.lang.Buffered;
 42  
 import org.geotoolkit.factory.FactoryFinder;
 43  
 import org.geotoolkit.util.converter.Classes;
 44  
 import org.geotoolkit.gui.swing.tree.Trees;
 45  
 import org.geotoolkit.gui.swing.tree.TreeNode;
 46  
 import org.geotoolkit.gui.swing.tree.NamedTreeNode;
 47  
 import org.geotoolkit.gui.swing.tree.MutableTreeNode;
 48  
 import org.geotoolkit.gui.swing.tree.DefaultMutableTreeNode;
 49  
 import org.geotoolkit.internal.io.IOUtilities;
 50  
 
 51  
 import static org.geotoolkit.util.collection.XCollections.isNullOrEmpty;
 52  
 
 53  
 
 54  
 /**
 55  
  * Build a tree of factory dependencies, usually for printing to the console. This is a
 56  
  * convenience utility for inspecting the dependencies between Geotk referencing factories.
 57  
  * For example the following code will prints the full set of factories used by the
 58  
  * {@link org.geotoolkit.referencing.CRS#decode(String)} method:
 59  
  *
 60  
  * {@preformat java
 61  
  *   FactoryDependencies report = new FactoryDependencies(CRS.getAuthorityFactory(null));
 62  
  *   report.setColorEnabled(true); // Use only if the output console is ANSI X3.64 compliant.
 63  
  *   report.setAbridged(true);
 64  
  *   report.print();
 65  
  * }
 66  
  *
 67  
  * The output will looks like the tree below (actual output may vary depending the plugins
 68  
  * available on the classpath). The "{@code …⬏}" suffix means that the factory has already
 69  
  * been defined in a previous line and its dependencies are not repeated.
 70  
  *
 71  
  * {@preformat text
 72  
  * DefaultAuthorityFactory["All"]
 73  
  * └───AllAuthoritiesFactory["All"]
 74  
  *     ├───ThreadedEpsgFactory["EPSG"]
 75  
  *     │   └───AnsiDialectEpsgFactory["EPSG"]
 76  
  *     │       ├───ReferencingObjectFactory[objects]
 77  
  *     │       └───DatumAliases[objects]
 78  
  *     ├───AutoCRSFactory["AUTO2", "AUTO"]
 79  
  *     │   ├───ReferencingObjectFactory[objects] …⬏
 80  
  *     │   └───DatumAliases[objects] …⬏
 81  
  *     ├───WebCRSFactory["CRS", "OGC"]
 82  
  *     │   ├───ReferencingObjectFactory[objects] …⬏
 83  
  *     │   └───DatumAliases[objects] …⬏
 84  
  *     ├───URN_AuthorityFactory["urn:ogc:def", "urn:x-ogc:def"]
 85  
  *     │   └───AllAuthoritiesFactory["All"]
 86  
  *     │       ├───ThreadedEpsgFactory["EPSG"] …⬏
 87  
  *     │       ├───AutoCRSFactory["AUTO2"] …⬏
 88  
  *     │       └───WebCRSFactory["CRS"] …⬏
 89  
  *     └───HTTP_AuthorityFactory["http://www.opengis.net"]
 90  
  *         └───AllAuthoritiesFactory["All"] …⬏
 91  
  * }
 92  
  *
 93  
  * For example if an {@value org.geotoolkit.referencing.factory.epsg.PropertyEpsgFactory#FILENAME}
 94  
  * file is provided on the classpath, then the above code snippet is useful for verifying that
 95  
  * {@code PropertyEpsgFactory} appears as expected. It should be visible below
 96  
  * {@code ThreadedEpsgFactory} in a fallback chain.
 97  
  * <p>
 98  
  * An other way to gather information about the factories available at runtime is to set the
 99  
  * logging level of {@code org.geotoolkit} loggers to {@code CONFIG} or a finer level.
 100  
  *
 101  
  * @author Martin Desruisseaux (IRD)
 102  
  * @version 3.00
 103  
  *
 104  
  * @since 2.4
 105  
  * @module
 106  
  */
 107  
 public class FactoryDependencies {
 108  
     /**
 109  
      * A list of interfaces or annotations that may be implemented by this class.
 110  
      * Used for the properties printed between parenthesis by {@link #createTree()}.
 111  
      */
 112  1
     private static final Class<?>[] TYPES = {
 113  
         CRSFactory                          .class,
 114  
         CRSAuthorityFactory                 .class,
 115  
         CSFactory                           .class,
 116  
         CSAuthorityFactory                  .class,
 117  
         DatumFactory                        .class,
 118  
         DatumAuthorityFactory               .class,
 119  
         CoordinateOperationFactory          .class,
 120  
         CoordinateOperationAuthorityFactory .class,
 121  
         Buffered                            .class,
 122  
         Factory                             .class  // Processed in a special way.
 123  
     };
 124  
 
 125  
     /**
 126  
      * Labels for {@link #TYPES}.
 127  
      */
 128  1
     private static final String[] TYPE_LABELS = {
 129  
         "crs", "crs", "cs", "cs", "datum", "datum", "operation", "operation",
 130  
         "buffered", "registered"
 131  
     };
 132  
 
 133  
     /**
 134  
      * The number of elements in {@link #TYPES} which are referencing factories.
 135  
      * They are printed in a different color than the last elements.
 136  
      */
 137  1
     private static final int FACTORY_COUNT = TYPES.length - 2;
 138  
 
 139  
     /**
 140  
      * The factory to format.
 141  
      */
 142  
     private final Factory factory;
 143  
 
 144  
     /**
 145  
      * {@code true} for applying colors on a ANSI X3.64 (aka ECMA-48 and ISO/IEC 6429)
 146  
      * compliant output device.
 147  
      */
 148  
     private boolean colorEnabled;
 149  
 
 150  
     /**
 151  
      * {@code true} for printing attributes {@link #TYPE_LABELS} between parenthesis
 152  
      * after the factory name.
 153  
      */
 154  
     private boolean attributes;
 155  
 
 156  
     /**
 157  
      * If {@code true}, only the first node of duplicated factories will be reported.
 158  
      */
 159  
     private boolean abridged;
 160  
 
 161  
     /**
 162  
      * Creates a new dependency tree for the specified factory.
 163  
      *
 164  
      * @param factory The factory for which to build a dependency tree.
 165  
      */
 166  109
     public FactoryDependencies(final Factory factory) {
 167  109
         this.factory = factory;
 168  109
     }
 169  
 
 170  
     /**
 171  
      * Returns {@code true} if syntax coloring is enabled.
 172  
      * Syntax coloring is disabled by default.
 173  
      *
 174  
      * @return {@code true} if syntax coloring is enabled.
 175  
      */
 176  
     public boolean isColorEnabled() {
 177  0
         return colorEnabled;
 178  
     }
 179  
 
 180  
     /**
 181  
      * Enables or disables syntax coloring on ANSI X3.64 (aka ECMA-48 and ISO/IEC 6429)
 182  
      * compatible terminal. By default, syntax coloring is disabled.
 183  
      *
 184  
      * @param enabled {@code true} for enabling syntax coloring.
 185  
      */
 186  
     public void setColorEnabled(final boolean enabled) {
 187  0
         colorEnabled = enabled;
 188  0
     }
 189  
 
 190  
     /**
 191  
      * Returns {@code true} if attributes are to be printed.
 192  
      * By default, attributes are not printed.
 193  
      *
 194  
      * @return {@code true} if attributes will be printed.
 195  
      */
 196  
     public boolean isAttributeEnabled() {
 197  0
         return attributes;
 198  
     }
 199  
 
 200  
     /**
 201  
      * Enables or disables the addition of attributes after factory names. Attributes
 202  
      * are labels like "{@code crs}", "{@code datum}", <i>etc.</i> put between
 203  
      * parenthesis. They give indications on the services implemented by the factory
 204  
      * (e.g. {@link CRSAuthorityFactory}, {@link DatumAuthorityFactory}, <i>etc.</i>).
 205  
      *
 206  
      * @param enabled {@code true} for printing attributes.
 207  
      */
 208  
     public void setAttributeEnabled(final boolean enabled) {
 209  0
         attributes = enabled;
 210  0
     }
 211  
 
 212  
     /**
 213  
      * Returns {@code true} if only the first node of duplicated factories will be reported.
 214  
      * The default value is {@code false}, which means that the full branch will be expanded
 215  
      * on every occurrence of a factory in the dependency graph.
 216  
      *
 217  
      * @return {@code true} if only the first node of duplicated factories will be reported,
 218  
      *         except for the first occurrence.
 219  
      *
 220  
      * @since 3.00
 221  
      */
 222  
     public boolean isAbridged() {
 223  0
         return abridged;
 224  
     }
 225  
 
 226  
     /**
 227  
      * Sets whetever the tree should be abridged. If {@code true}, only the first node of
 228  
      * duplicated factories will be reported (except the first occurrence which is expanded
 229  
      * like usual).
 230  
      *
 231  
      * @param abridged {@code true} for an abridged tree, or {@code false} for expanding
 232  
      *        every branches unconditionally.
 233  
      *
 234  
      * @since 3.00
 235  
      */
 236  
     public void setAbridged(final boolean abridged) {
 237  0
         this.abridged = abridged;
 238  0
     }
 239  
 
 240  
     /**
 241  
      * Prints the dependencies as a tree to the {@linkplain System#out standard output stream}.
 242  
      *
 243  
      * @since 3.00
 244  
      */
 245  
     public void print() {
 246  0
         final PrintWriter out = IOUtilities.standardPrintWriter();
 247  0
         print(out);
 248  0
         out.flush();
 249  0
     }
 250  
 
 251  
     /**
 252  
      * Prints the dependencies as a tree to the specified printer.
 253  
      *
 254  
      * @param out Where to print the dependencies tree.
 255  
      */
 256  
     public void print(final PrintWriter out) {
 257  0
         out.print(Trees.toString(asTree()));
 258  0
     }
 259  
 
 260  
     /**
 261  
      * Prints the dependencies as a tree to the specified writer.
 262  
      *
 263  
      * @param  out Where to write the dependencies tree.
 264  
      * @throws IOException if an error occurred while writing to the stream.
 265  
      */
 266  
     public void print(final Appendable out) throws IOException {
 267  109
         out.append(Trees.toString(asTree()));
 268  109
     }
 269  
 
 270  
     /**
 271  
      * Returns the dependencies as a tree.
 272  
      *
 273  
      * @return The dependencies as a tree.
 274  
      */
 275  
     public TreeNode asTree() {
 276  109
         return createTree(factory, new IdentityHashMap<Factory,Integer>());
 277  
     }
 278  
 
 279  
     /**
 280  
      * Returns the dependencies for the specified factory.
 281  
      *
 282  
      * @param  factory The factory for which to create a tree.
 283  
      * @param  flags Take trace of factories that are already printed.
 284  
      * @return The created tree.
 285  
      */
 286  
     private MutableTreeNode createTree(final Factory factory, final Map<Factory,Integer> flags) {
 287  
         final int PROGRESS=1, DONE=2; // Bit flags for this method only.
 288  109
         final int bits = intValue(flags.get(factory));
 289  109
         final boolean isFirst = (bits & DONE) == 0;
 290  109
         final DefaultMutableTreeNode root = createNode(factory, isFirst);
 291  109
         flags.put(factory, bits | DONE);
 292  109
         if (factory instanceof ReferencingFactory && (isFirst || !abridged)) {
 293  109
             final Collection<?> dep = ((ReferencingFactory) factory).dependencies();
 294  109
             if (dep != null) {
 295  109
                 for (final Object element : dep) {
 296  
                     final MutableTreeNode child;
 297  0
                     if (element instanceof Factory) {
 298  0
                         final Factory candidate = (Factory) element;
 299  0
                         int bc = intValue(flags.get(candidate));
 300  0
                         if ((bc & PROGRESS) != 0) {
 301  0
                             continue;
 302  
                         }
 303  0
                         flags.put(candidate, bc | PROGRESS);
 304  0
                         child = createTree(candidate, flags);
 305  0
                         bc = intValue(flags.get(candidate));
 306  0
                         flags.put(candidate, bc & ~PROGRESS);
 307  0
                     } else {
 308  0
                         child = new DefaultMutableTreeNode(element, false);
 309  
                     }
 310  0
                     root.add(child);
 311  0
                 }
 312  
             }
 313  
         }
 314  109
         return root;
 315  
     }
 316  
 
 317  
     /**
 318  
      * Creates a single node for the specified factory.
 319  
      *
 320  
      * @param  factory The factory for which to create a node.
 321  
      * @param  isFirst {@code true} if this is the first occurrence of this factory.
 322  
      * @return The created node.
 323  
      */
 324  
     private DefaultMutableTreeNode createNode(final Factory factory, final boolean isFirst) {
 325  109
         final StringBuilder buffer = new StringBuilder(Classes.getShortClassName(factory)).append('[');
 326  109
         if (factory instanceof AuthorityFactory) {
 327  0
             final Citation authority = ((AuthorityFactory) factory).getAuthority();
 328  0
             if (authority != null) {
 329  0
                 final Collection<? extends Identifier> identifiers = authority.getIdentifiers();
 330  0
                 if (!isNullOrEmpty(identifiers)) {
 331  0
                     boolean next = false;
 332  0
                     for (final Identifier id : identifiers) {
 333  0
                         if (next) buffer.append(", ");
 334  0
                         appendIdentifier(buffer, id.getCode(), isFirst);
 335  0
                         if (!isFirst) break;
 336  0
                         next = true;
 337  
                     }
 338  0
                 } else {
 339  0
                     appendIdentifier(buffer, authority.getTitle(), isFirst);
 340  
                 }
 341  
             }
 342  0
         } else {
 343  109
             appendColor(buffer, isFirst ? X364.FOREGROUND_RED : X364.FOREGROUND_YELLOW, "objects");
 344  
         }
 345  109
         buffer.append(']');
 346  109
         if (!isFirst) {
 347  0
             appendColor(buffer, X364.FOREGROUND_YELLOW, " \u2026\u2B0F");
 348  109
         } else if (attributes) {
 349  0
             boolean hasFound = false;
 350  0
             for (int i=0; i<TYPES.length; i++) {
 351  0
                 final Class<?> type = TYPES[i];
 352  0
                 if (Annotation.class.isAssignableFrom(type)) {
 353  0
                     if (!factory.getClass().isAnnotationPresent(type.asSubclass(Annotation.class))) {
 354  0
                         continue;
 355  
                     }
 356  
                 } else {
 357  0
                     if (!type.isInstance(factory)) {
 358  0
                         continue;
 359  
                     }
 360  0
                     if (type.equals(Factory.class)) { // Special case.
 361  0
                         if (!FactoryFinder.isRegistered(factory)) {
 362  0
                             continue;
 363  
                         }
 364  
                     }
 365  
                 }
 366  0
                 buffer.append(hasFound ? ", " : " (");
 367  0
                 appendColor(buffer, i < FACTORY_COUNT ? X364.FOREGROUND_GREEN : X364.FOREGROUND_CYAN, TYPE_LABELS[i]);
 368  0
                 hasFound = true;
 369  
             }
 370  0
             if (hasFound) {
 371  0
                 buffer.append(')');
 372  
             }
 373  
         }
 374  109
         return new NamedTreeNode(buffer.toString(), factory);
 375  
     }
 376  
 
 377  
     /**
 378  
      * Appends an identifier to the specified buffer.
 379  
      */
 380  
     private void appendIdentifier(final StringBuilder buffer, final CharSequence identifier, final boolean isFirst) {
 381  0
         appendColor(buffer, isFirst ? X364.FOREGROUND_MAGENTA : X364.FOREGROUND_YELLOW);
 382  0
         buffer.append('"').append(identifier).append('"');
 383  0
         appendColor(buffer, X364.FOREGROUND_DEFAULT);
 384  0
     }
 385  
 
 386  
     /**
 387  
      * Appends the given color to the given buffer if colors are enabled,
 388  
      * followed by the given text, followed by the default foreground.
 389  
      */
 390  
     private void appendColor(final StringBuilder buffer, final X364 color, final String text) {
 391  109
         appendColor(buffer, color);
 392  109
         buffer.append(text);
 393  109
         appendColor(buffer, X364.FOREGROUND_DEFAULT);
 394  109
     }
 395  
 
 396  
     /**
 397  
      * Appends the given color to the given buffer if colors are enabled.
 398  
      */
 399  
     private void appendColor(final StringBuilder buffer, final X364 color) {
 400  218
         if (colorEnabled) {
 401  0
             buffer.append(color.sequence());
 402  
         }
 403  218
     }
 404  
 
 405  
     /**
 406  
      * Returns the primitive value of the given integer, treating null value as zero.
 407  
      */
 408  
     private static int intValue(final Integer value) {
 409  109
         return (value != null) ? value.intValue() : 0;
 410  
     }
 411  
 
 412  
     /**
 413  
      * Returns the string representation of the dependencies tree.
 414  
      *
 415  
      * @since 3.10
 416  
      */
 417  
     @Override
 418  
     public String toString() {
 419  0
         return Trees.toString(asTree());
 420  
     }
 421  
 }