/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2007-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.referencing.factory;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
// The following are just data structure, not dependencies to the whole Swing framework.
import javax.swing.tree.TreeNode;
import javax.swing.tree.MutableTreeNode;
import javax.swing.tree.DefaultMutableTreeNode;
import org.opengis.metadata.Identifier;
import org.opengis.metadata.citation.Citation;
import org.opengis.referencing.Factory;
import org.opengis.referencing.AuthorityFactory;
import org.opengis.referencing.crs.CRSFactory;
import org.opengis.referencing.crs.CRSAuthorityFactory;
import org.opengis.referencing.cs.CSFactory;
import org.opengis.referencing.cs.CSAuthorityFactory;
import org.opengis.referencing.datum.DatumFactory;
import org.opengis.referencing.datum.DatumAuthorityFactory;
import org.opengis.referencing.operation.CoordinateOperationAuthorityFactory;
import org.opengis.referencing.operation.CoordinateOperationFactory;
import org.geotools.factory.BufferedFactory;
import org.geotools.factory.OptionalFactory;
import org.geotools.referencing.ReferencingFactoryFinder;
import org.geotools.resources.OptionalDependencies;
import org.geotools.resources.Classes;
import org.geotools.resources.X364;
/**
* Build a tree of factory dependencies.
*
* @since 2.4
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux
*/
public class FactoryDependencies {
/**
* A list of interfaces that may be implemented by this class. Used for
* the properties printed between parenthesis by {@link #createTree()}.
*/
private static final Class[] TYPES = {
CRSFactory .class,
CRSAuthorityFactory .class,
CSFactory .class,
CSAuthorityFactory .class,
DatumFactory .class,
DatumAuthorityFactory .class,
CoordinateOperationFactory .class,
CoordinateOperationAuthorityFactory .class,
BufferedFactory .class,
OptionalFactory .class,
Factory .class // Processed in a special way.
};
/**
* Labels for {@link #TYPES}.
*/
private static final String[] TYPE_LABELS = {
"crs", "crs", "cs", "cs", "datum", "datum", "operation", "operation",
"buffered", "optional", "registered"
};
/**
* The number of elements in {@link #TYPES} which are referencing factories.
* They are printed in a different color than the last elements.
*/
private static final int FACTORY_COUNT = TYPES.length - 3;
/**
* The factory to format.
*/
private final Factory factory;
/**
* {@code true} for applying colors on a ANSI X3.64 (aka ECMA-48 and ISO/IEC 6429)
* compliant output device.
*/
private boolean colorEnabled;
/**
* {@code true} for printing attributes {@link #TYPE_LABELS} between parenthesis
* after the factory name.
*/
private boolean attributes;
/**
* Creates a new dependency tree for the specified factory.
*/
public FactoryDependencies(final Factory factory) {
this.factory = factory;
}
/**
* Returns {@code true} if syntax coloring is enabled.
* Syntax coloring is disabled by default.
*/
public boolean isColorEnabled() {
return colorEnabled;
}
/**
* Enables or disables syntax coloring on ANSI X3.64 (aka ECMA-48 and ISO/IEC 6429)
* compatible terminal. By default, syntax coloring is disabled.
*/
public void setColorEnabled(final boolean enabled) {
colorEnabled = enabled;
}
/**
* Returns {@code true} if attributes are to be printed.
* By default, attributes are not printed.
*/
public boolean isAttributeEnabled() {
return attributes;
}
/**
* Enables or disables the addition of attributes after factory names. Attributes
* are labels like "{@code crs}", "{@code datum}", <cite>etc.</cite> put between
* parenthesis. They give indications on the services implemented by the factory
* (e.g. {@link CRSAuthorityFactory}, {@link DatumAuthorityFactory}, <cite>etc.</cite>).
*/
public void setAttributeEnabled(final boolean enabled) {
attributes = enabled;
}
/**
* Prints the dependencies as a tree to the specified printer.
*/
public void print(final PrintWriter out) {
out.print(OptionalDependencies.toString(asTree()));
}
/**
* Prints the dependencies as a tree to the specified writer.
*
* @param out The writer where to print the tree.
* @throws IOException if an error occured while writting to the stream.
*/
public void print(final Writer out) throws IOException {
out.write(OptionalDependencies.toString(asTree()));
}
/**
* Returns the dependencies as a tree.
*/
public TreeNode asTree() {
return createTree(factory, new HashSet<Factory>());
}
/**
* Returns the dependencies for the specified factory.
*/
private MutableTreeNode createTree(final Factory factory, final Set<Factory> done) {
final DefaultMutableTreeNode root = createNode(factory);
if (factory instanceof ReferencingFactory) {
final Collection<?> dep = ((ReferencingFactory) factory).dependencies();
if (dep != null) {
for (final Object element : dep) {
final MutableTreeNode child;
if (element instanceof Factory) {
final Factory candidate = (Factory) element;
if (!done.add(candidate)) {
continue;
}
child = createTree(candidate, done);
if (!done.remove(candidate)) {
throw new AssertionError(); // Should never happen.
}
} else {
child = OptionalDependencies.createTreeNode(String.valueOf(element), element, false);
}
root.add(child);
}
}
}
return root;
}
/**
* Creates a single node for the specified factory.
*/
private DefaultMutableTreeNode createNode(final Factory factory) {
final StringBuilder buffer =
new StringBuilder(Classes.getShortClassName(factory)).append('[');
if (factory instanceof AuthorityFactory) {
final Citation authority = ((AuthorityFactory) factory).getAuthority();
if (authority != null) {
final Collection<? extends Identifier> identifiers = authority.getIdentifiers();
if (identifiers != null && !identifiers.isEmpty()) {
boolean next = false;
for (final Identifier id : identifiers) {
if (next) {
buffer.append(", ");
}
appendIdentifier(buffer, id.getCode());
next = true;
}
} else {
appendIdentifier(buffer, authority.getTitle());
}
}
} else {
if (colorEnabled) buffer.append(X364.RED);
buffer.append("direct");
if (colorEnabled) buffer.append(X364.DEFAULT);
}
buffer.append(']');
if (attributes) {
boolean hasFound = false;
for (int i=0; i<TYPES.length; i++) {
final Class type = TYPES[i];
if (!type.isInstance(factory)) {
continue;
}
if (type.equals(Factory.class)) { // Special case.
if (!ReferencingFactoryFinder.isRegistered(factory)) {
continue;
}
}
buffer.append(hasFound ? ", " : " (");
if (colorEnabled) {
buffer.append(i < FACTORY_COUNT ? X364.GREEN : X364.CYAN);
}
buffer.append(TYPE_LABELS[i]);
if (colorEnabled) {
buffer.append(X364.DEFAULT);
}
hasFound = true;
}
if (hasFound) {
buffer.append(')');
}
}
return OptionalDependencies.createTreeNode(buffer.toString(), factory, true);
}
/**
* Appends an identifier to the specified buffer.
*/
private void appendIdentifier(final StringBuilder buffer, final CharSequence identifier) {
if (colorEnabled) buffer.append(X364.MAGENTA);
buffer.append('"').append(identifier).append('"');
if (colorEnabled) buffer.append(X364.DEFAULT);
}
}