// $Id: DatasetSource.java,v 1.13 2004/12/14 22:46:52 edavis Exp $

package thredds.cataloggen.config;

import thredds.catalog.*;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.log4j.Logger;

// @todo Add state machine to control what can happen when.
// State machine: 1) constructed and modified, 2) expanded, 3) named,
// 4) metadata-ified, and 5) sorted.

/**
 * Represents a source from which a collection of datasets can be found and
 * expanded into an InvCatalog. Besides expanding the DatasetSource, the datasets
 * in the resulting catalog can be named, sorted, and have metadata added to them.
 *
 * <p>
 * Currently an abstract class, the abstract methods may be extracted into an
 * interface and DatasetSource will use instances of that interface depending on
 * they setup of the DatasetSource (rather than the current subclassing). These
 * methods are getTopLevelDataset():InvDataset, isCollection():boolean, and
 * expandThisLevel(InvDataset):List -- they replace expandThisType().
 * </p>
 *
 * @author Ethan Davis
 * @version $Id: DatasetSource.java,v 1.13 2004/12/14 22:46:52 edavis Exp $
 */
abstract public class DatasetSource
{
  private static Logger logger = Logger.getLogger( DatasetSource.class.getName());

  /** The catalog that results from expanding this DatasetSource.  */
  protected InvCatalog resultingCatalog = null;

  /** List of resulting catalogs in catalogRef tree, null if this is not the
   *  top-level DatasetSource. */
  protected java.util.HashMap resultingCatalogList = null;

  // The dataset that contains the metadata element that
  // contains this DatasetSource.
  protected InvDatasetImpl parentDataset = null;

  /** Top-level dataset of the catalog generated by this DatasetSource. */
  protected InvDataset accessPointDataset = null;

  // When a DatasetSource generates a catalog that references other catalogs,
  // a tree of DatasetSources are generated similar to the tree of catalogs.
  // The top-level DatasetSource has a list of resulting catalog files into
  // which all the DatasetSources add their catalogs.
  /** The parent DatasetSource of this DatasetSource (part of catalogRef tree). */
  protected DatasetSource parentDatasetSource = null;

  // attributes of the datasetSource element
  private String name = null;
  protected DatasetSourceType type = null;
  private DatasetSourceStructure structure = null;
  private boolean flatten = false;
  private String accessPoint = null;

  // Required child ResultService
  private ResultService resultService = null;

  // list of optional children of the datasetNamer element
  protected List datasetNamerList = new ArrayList();
  protected List datasetFilterList = new ArrayList();
  protected List catalogRefExpanderList = new ArrayList();

  // for validation
  protected boolean isValid = true;
  protected StringBuffer log = new StringBuffer();

  /** Default Constructor */
  //public DatasetSource(){}

  // @todo Change DatasetSource to use Strategies instead of subclasses.
  /**
   * Factory method for this abstract class.
   * @param parentDs
   * @param parentDsSource
   * @param name
   * @param type
   * @param structure
   * @param accessPoint
   */
  public static final DatasetSource newDatasetSource( InvDatasetImpl parentDs,
                                             DatasetSource parentDsSource,
                                             String name, DatasetSourceType type,
                                             DatasetSourceStructure structure,
                                             String accessPoint,
                                             ResultService resultService )
  {
    DatasetSource tmpDsSource = null;

    if ( type == DatasetSourceType.getType( "Local"))
    {
      tmpDsSource = new LocalDatasetSource();
    }
    else if ( type == DatasetSourceType.getType( "DodsDir"))
    {
      tmpDsSource = new DodsDirDatasetSource();
    }
    else if ( type == DatasetSourceType.getType( "DodsFileServer"))
    {
      tmpDsSource = new DodsFileServerDatasetSource();
    }
    else if ( type == DatasetSourceType.getType( "GrADSDataServer"))
    {
      tmpDsSource = new GrADSDataServerDatasetSource();
    }
    else
    {
      // Handle instance of unsupported DatasetSource type.
      tmpDsSource = new BadDatasetSource();
    }
    tmpDsSource.setParentDataset( parentDs);
    tmpDsSource.setParentDatasetSource( parentDsSource);
    tmpDsSource.setName( name);
    tmpDsSource.setStructure( structure);
    tmpDsSource.setAccessPoint( accessPoint);
    tmpDsSource.setResultService( resultService);
    // Test validity and append messages to log.

    logger.debug( "DatasetSource(): constructor done.");
    StringBuffer log = new StringBuffer();
    if ( tmpDsSource.validate( log))
    {
      logger.debug( "DatasetSource(): new DatasetSource is valid: " + log.toString());
    }
    else
    {
      logger.debug( "DatasetSource(): new DatasetSource is invalid: " + log.toString());
    }

    return( tmpDsSource);
  }

  /** Return the parent dataset of this DatasetSource */
  public InvDatasetImpl getParentDataset()
  { return( this.parentDataset); }
  /** Set the parent dataset of this DatasetSource */
  public void setParentDataset( InvDatasetImpl parentDataset)
  { this.parentDataset = parentDataset; }

  public DatasetSource getParentDatasetSource()
  {
    return( this.parentDatasetSource);
  }
  public void setParentDatasetSource( DatasetSource parent)
  {
    this.parentDatasetSource = parent;
  }

  public InvCatalog getResultingCatalog() { return( this.resultingCatalog); }
  public java.util.HashMap getResultingCatalogList()
  {
    if ( this.resultingCatalogList != null )
      return( this.resultingCatalogList);
    if ( this.parentDatasetSource != null)
      return( this.parentDatasetSource.getResultingCatalogList());
    return( null); // @todo This shouldn't happen. do what instead???
    // @todo Make subclasses into Strategies and make getTopLevelDataset() add new catalog to list if appropriate.
  }

  public String getName() { return( this.name); }
  public void setName( String name) { this.name = name; }

  public DatasetSourceType getType() { return( this.type); }
  //public void setType( DatasetSourceType type) { this.type = type; }
  //public void setType( String typeName)
  //{
  //    this.type = DatasetSourceType.getType( typeName);
  //    // Check that given typeName is a valid type.
  //    if ( this.getType() == null)
  //    {
  //        this.isValid = false;
  //        log.append( " ** DatasetSource (3): invalid type =(" + typeName+
  //                ").");
  //    }
  //}

  public DatasetSourceStructure getStructure() { return( this.structure); }
  public void setStructure( DatasetSourceStructure structure)
  {
    this.structure = structure;
    if ( this.structure == DatasetSourceStructure.FLAT)
      flatten = true;
    else
      flatten = false;
  }
  public void setStructure( String structureName)
  {
    DatasetSourceStructure tmp = DatasetSourceStructure.getStructure( structureName);
    if ( tmp == null)
    {
      this.isValid = false;
      log.append( " ** DatasetSource (4): invalid structure =(" +
                  structureName + ").");
    }
    this.setStructure( tmp);
  }

  public boolean isFlatten() { return( this.flatten); }

  public String getAccessPoint() { return( this.accessPoint); }
  public void setAccessPoint( String accessPoint)
  { this.accessPoint = accessPoint; }

  public ResultService getResultService() { return( this.resultService); }
  public void setResultService( ResultService resultService)
  { this.resultService = resultService; }

  public java.util.List getDatasetNamerList() { return( this.datasetNamerList); }
  public void addDatasetNamer( DatasetNamer datasetNamer)
  { this.datasetNamerList.add( datasetNamer); }

  public java.util.List getDatasetFilterList() { return( this.datasetFilterList); }
  public void addDatasetFilter( DatasetFilter datasetFilter)
  { this.datasetFilterList.add( datasetFilter); }

  public java.util.List getCatalogRefExpanderList() { return( this.catalogRefExpanderList); }
  public void addCatalogRefExpander( CatalogRefExpander catRefExpander)
  { this.catalogRefExpanderList.add( catRefExpander); }

  public boolean validate( StringBuffer out)
  {
    this.isValid = true;

    // If log from construction has content, append to validation output msg.
    if (this.log.length() > 0)
    {
      out.append( this.log);
    }

    // Check that name is not null (it can be an empty string).
    if ( this.getName() == null)
    {
      this.isValid = false;
      out.append(" ** DatasetSource (5): null value for name is not valid.");
    }
    // Check that type is not null.
    if ( this.getType() == null)
    {
      this.isValid = false;
      out.append(" ** DatasetSource (6): null value for type is not valid (set with bad string?).");
    }
    // Check that structure is not null.
    if ( this.getStructure() == null)
    {
      this.isValid = false;
      out.append(" ** DatasetSource (7): null value for structure is not valid (set with bad string?).");
    }

    // Validate ResultService child element.
    if ( this.getResultService() != null )
    {
      this.isValid &= this.getResultService().validate( out);
    }

    // Validate DatasetNamer child elements.
    java.util.Iterator dsnIter = this.getDatasetNamerList().iterator();
    while (dsnIter.hasNext())
    {
      this.isValid &= ((DatasetNamer) dsnIter.next()).validate( out);
    }

    // Validate DatasetFilters child elements.
    java.util.Iterator dsfIter = this.getDatasetFilterList().iterator();
    while (dsfIter.hasNext())
    {
      this.isValid &= ((DatasetFilter) dsfIter.next()).validate( out);
    }

    return( this.isValid);
  }

  /** string representation */
  public String toString()
  {
    StringBuffer tmp = new StringBuffer();
    tmp.append( "DatasetSource[name:<" + this.getName() +
                "> type:<" + this.getType() +
                "> structure:<" + this.getStructure() +
                "> accessPoint:<" + this.getAccessPoint() +
                "> and children - " +
                "ResultService(" + this.getResultService().getName() + ") - " +
                "DatasetNamer(" + this.getDatasetNamerList().size() + ") - " +
                "DatasetFilter(" + this.getDatasetFilterList().size() + ")]");
    return( tmp.toString());
  }

  /**
   * Crawl this DatasetSource and generate a new InvCatalog, return the
   * top-level InvDataset.
   *
   * Each object found on the DatasetSource becomes an InvDataset if it is
   * accepted by at least one DatasetFilter. The catalog reflects the
   * heirarchical structure of the DatasetSource. All datasets are
   * named with their location on the datasetSource.
   *
   * @return the top-level InvDataset in the generated InvCatalog.
   */
  public InvDataset expand()
  {
    this.accessPointDataset = this.getTopLevelDataset();
    if ( ! this.isCollection( this.accessPointDataset) )
    {
      logger.warn( "expand(): " + this.accessPointDataset.getName());
      return( this.accessPointDataset);
    }

    expandRecursive( this.accessPointDataset);

    ((InvCatalogImpl) this.accessPointDataset.getParentCatalog()).finish();

    return( this.accessPointDataset);
  }

  private void expandRecursive( InvDataset dataset)
  {
    // Get all datasets at this level.
    List listAllDatasets = this.expandThisLevel( dataset);
    InvDataset curDs = null;

    for( Iterator i = listAllDatasets.iterator(); i.hasNext(); )
    {
      curDs = (InvDataset) i.next();

      // Filter out unwanted datasets.
      if ( ! this.acceptDataset( curDs ))
        continue;

      // Expand each collection dataset.
      if ( this.isCollection( curDs))
      {
        // Decide whether to create a CatalogRef and corresponding child DatasetSource.
//        CatalogRefExpander curCatRefExpander = null;
//        for ( Iterator it = this.getCatalogRefExpanderList().iterator(); it.hasNext(); )
//        {
//          curCatRefExpander = (CatalogRefExpander) it.next();
//          if ( curCatRefExpander.isExpandAsCatalogRef( curDs))
//          {
//
//          }
//        }

        // If not flattening the directory structure, add current collection dataset to parent dataset.
        if ( ! this.isFlatten())
        {
          ((InvDatasetImpl) dataset).addDataset( (InvDatasetImpl) curDs);
        }
        this.expandRecursive( curDs);
      }
      // Otherwise, add dataset to collection.
      else
      {
        // If not flattening the directory structure, add current dataset to parent dataset.
        if ( ! this.isFlatten())
        {
          ((InvDatasetImpl) dataset).addDataset( (InvDatasetImpl) curDs);
        }
        // Otherwise, add current dataset to top-level dataset.
        else
        {
          ((InvDatasetImpl) this.accessPointDataset).addDataset( (InvDatasetImpl) curDs);
        }
      }
    }
  }

  private boolean acceptDataset( InvDataset curDs )
  {
    // If no DatasetFilters, accept all datasets.
    if ( this.getDatasetFilterList().isEmpty())
    {
      return( true);
    }

    // Loop through DatasetFilter list to check if current dataset should be accepted.
    DatasetFilter curFilter = null;
    for ( Iterator it = this.getDatasetFilterList().iterator(); it.hasNext(); )
    {
      curFilter = (DatasetFilter) it.next();

      if ( curFilter.accept( (InvDatasetImpl) curDs))
      {
        return( true); // Dataset accepted by current DatasetFilter.
      }
    }
    return( false);    // Dataset not accepted by any DatasetFilter.
  }

  /**
   * Crawl this DatasetSource and generate a new InvCatalog with all datasets
   * named, sorted, and organized as defined by this DatasetSource.
   *
   * Return the generated InvCatalog if the parent dataset of this
   * DatasetSource is null. Otherwise, append the datasets contained
   * in the generated catalog to the parent dataset and return the
   * catalog that contains the parent dataset.
   *
   * @return the generated InvCatalog if the parent dataset is null, otherwise
   * return the parent catalog with the new InvCatalog appended at the parent dataset.
   */
  public InvCatalog fullExpand()
  {
    logger.info( "fullExpand(): expanding DatasetSource named \"" + this.getName() + "\"");

    InvDataset topDs = this.expand();
    InvCatalog generatedCat = topDs.getParentCatalog();

    // Name all datasets.
    logger.debug( "fullExpand(): naming the datasets.");
    this.nameDatasets( (InvDatasetImpl) topDs );

    // Sort all datasets
    logger.debug( "expand(): sorting the datasets.");
    // Create default dataset Comparator.
    java.util.Comparator dsComparator = new java.util.Comparator()
    {
      public int compare( Object obj1, Object obj2)
      {
        InvDataset ds1 = (InvDataset) obj1;
        InvDataset ds2 = (InvDataset) obj2;

        return( -ds1.getName().compareTo( ds2.getName()));
      }
    };
    this.sortDatasets( topDs, dsComparator);

    // Return the generated catalog if the parent dataset is null.
    if ( this.parentDataset == null)
    {
      ((InvCatalogImpl) generatedCat).finish();
      return( generatedCat);
    }

    // If parent dataset is not null, append the datasets contained in
    // the generated catalog to the parent dataset and return the catalog
    // that contains the parent dataset.

    // Add all services in the generated catalog to the parent catalog.
    for( Iterator it = generatedCat.getServices().iterator(); it.hasNext(); )
    {
      ( (InvCatalogImpl) this.parentDataset.getParentCatalog() ).addService( (InvService) it.next() );
    }

    // Add generated datasets to the parent datasets.
    if ( ! this.isFlatten())
    {
      this.parentDataset.addDataset( (InvDatasetImpl) topDs);
    }
    else
    {
      // Check if any of the DatasetNamers added a container level.
      boolean addLevel = false;
      DatasetNamer curDsNamer = null;
      for ( Iterator it = this.getDatasetNamerList().iterator(); it.hasNext(); )
      {
        curDsNamer = (DatasetNamer) it.next();
        addLevel |= curDsNamer.getAddLevel();
      }

      // Add contained datasets to parent dataset
      InvDatasetImpl curDataset = null;
      for ( Iterator it = topDs.getDatasets().iterator(); it.hasNext(); )
      {
        curDataset = (InvDatasetImpl) it.next();
        if ( addLevel)
        {
          // Add service to the current top-level generated dataset and make it inherited.
          ThreddsMetadata tm = new ThreddsMetadata( false);
          tm.setServiceName( topDs.getServiceDefault().getName());
          InvMetadata md = new InvMetadata( topDs, null, JaxpFactory.CATALOG_NAMESPACE_10, "", true, true, null, tm);
          curDataset.getLocalMetadata().addMetadata( md);
        }
        else
        {
          // Add service to each generated dataset.
          curDataset.getLocalMetadata().setServiceName(  topDs.getServiceDefault().getName());
        }
        this.parentDataset.addDataset( curDataset);
      }
    }

    this.parentDataset.finish();

    // Return the parent catalog.
    return( this.parentDataset.getParentCatalog());
  }

  /**
   * This is the abstract method that must be implemented to create a real DatasetSource.
   * Expand this dataset source as appropriate for the concrete type of the DatasetSource.
   * Place all datasets in the given parent dataset.
   *
   * @param parent
   * @param givenLocation
   * @deprecated use fullExpand() instead
   *
   */
  abstract protected void expandThisType(InvDatasetImpl parent, String givenLocation);

  /**
   * Build an unnamed InvCatalog for this DatasetSource and return the
   * top-level InvDataset. The ResultService for this DatasetSource is
   * used to create the InvService for the new InvCatalog. Each InvDataset
   * in the catalog is named with the location of the object they represent
   * on the dataset source.
   *
   * @return the top-level dataset of the newly constructed InvCatalog.
   */
  abstract protected InvDataset getTopLevelDataset();

  /**
   * Return true if the given dataset is a collection dataset, false otherwise.
   *
   * @param dataset - the InvDataset to test for being a collection dataset.
   * @return true if the given dataset is a collection dataset, false otherwise.
   * @throws NullPointerException if the given InvDataset is null.
   * @throws ClassCastException if the given InvDataset is not a LocalInvDataset.
   */
  abstract protected boolean isCollection( InvDataset dataset);

  /**
   * Return a list of the InvDatasets contained in the given collection dataset
   * on this DatasetSource.
   *
   * @param collectionDataset - the collection dataset to be expanded.
   * @return A list of the InvDatasets contained in the given collection dataset.
   * @throws IllegalArgumentException when given dataset is not a collection dataset.
   * @throws NullPointerException if given dataset is null.
   * @throws ClassCastException if the given InvDataset is not a LocalInvDataset.
   */
  abstract protected List expandThisLevel( InvDataset collectionDataset );

  /**
   * Use the list of dsNamers to name the given list of datasets.
   *
   * @param datasetContainer - a InvDatasetImpl that contains one or
   *  more datasets to be named.
   */
  private void nameDatasets( InvDatasetImpl datasetContainer)
  {
    if ( this.isFlatten())
    {
      logger.debug( "nameDatasets(): structure is FLAT calling nameDatasetList()");
      this.nameDatasetList( datasetContainer);
    }
    else
    {
      logger.debug( "nameDatasets(): structure is DIRECTORY_TREE calling" +
              " nameDatasetTree() on each dataset in dataset container");
      InvDatasetImpl curDs = null;
      for ( int j = 0; j < datasetContainer.getDatasets().size(); j++)
      {
        curDs = (InvDatasetImpl) datasetContainer.getDatasets().get( j);
        this.nameDatasetTree( curDs);
      }
    }
    return;
  }

  /** Name the datasets contained in the given dataset.
   *  The given dataset contains a flat list of datasets. */
  private void nameDatasetList( InvDatasetImpl dataset)
  {
    // Create temporary dataset in which to hold named datasets.
    InvDatasetImpl namedDs = new InvDatasetImpl( dataset,
          "nameDatastList() temp dataset", null, null, null);
    // InvDatasetImpl(parentDs, name, dataType, serviceName, urlPath)
    dataset.addDataset( namedDs);

    // Loop through the DatasetNamers
    DatasetNamer curNamer = null;
    for ( int i = 0; i < this.datasetNamerList.size(); i++)
    {
      curNamer = (DatasetNamer) this.datasetNamerList.get( i);
      logger.debug( "nameDatasetList(): trying namer (" + curNamer.getName() + ")");

      // If the current DatasetNamer adds a new level, create a new dataset.
      InvDatasetImpl addLevelDs = null;
      if ( curNamer.getAddLevel())
      {
        addLevelDs = new InvDatasetImpl( null, curNamer.getName(),
                                         null, null, null );
      }

      // Iterate over remaining unnamed datasets.
      InvDatasetImpl curDs = null;
      java.util.Iterator dsIter = dataset.getDatasets().iterator();
      while ( dsIter.hasNext())
      {
        curDs = (InvDatasetImpl) dsIter.next();
        logger.debug( "nameDatasetList(): try namer on this ds (" + curDs.getName() +
                " - " + curDs.getUrlPath() + ")");

        // Try to name the current dataset.
        if ( curNamer.nameDataset( curDs))
        {
          logger.debug( "nameDatasetList(): ds named (" + curDs.getName() + ")");
          // If adding a level, add named datasets to the added level dataset.
          if ( curNamer.getAddLevel())
          {
            addLevelDs.addDataset( curDs);
          }
          // Otherwise, add the named datasets to namedDs.
          else
          {
            namedDs.addDataset( curDs);
          }

          // Remove the now-named dataset from list of unnamed datasets.
          dsIter.remove();
        }
      } // END - InvDatasetImpl loop

      // If the namer added a level and a dataset was named by this namer, add the
      // new level to the list of named datasets.
      if ( curNamer.getAddLevel())
      {
        if ( addLevelDs.hasNestedDatasets())
        {
          namedDs.addDataset( addLevelDs);
        }
      }

    } // END - DatasetNamer loop
    namedDs.finish();

    // Once all datasets are named (or unnamable with these DatasetNamers),
    // add all the datasets in namedDs back into the given containerDataset.
    logger.debug( "nameDatasetList(): number of unnamed datasets is " + dataset.getDatasets().size() + ".");
    logger.debug( "nameDatasetList(): add named datasets back to container.");
    for ( int i = 0; i < namedDs.getDatasets().size(); i++)
    {
      dataset.addDataset( (InvDatasetImpl) namedDs.getDatasets().get( i));
    }
    dataset.removeDataset( namedDs);

    return;
  }

  /** Name the datasets in the given dataset hierarchy using this
   *  DatasetSource's list of datasetNamers. */
  private void nameDatasetTree( InvDatasetImpl dataset)
  {
    // If dataset does not have a name, try naming it with dsNamers.
    // @todo Hacked in naming of directories, need to rethink.
    if ( dataset.getName().equals("") || ! dataset.hasAccess())
    {
      logger.debug( "nameDatasetTree(): naming dataset (" + dataset.getUrlPath() + ") ...");
      DatasetNamer dsN = null;
      for ( int i = 0; i < this.datasetNamerList.size(); i++)
      {
        dsN = (DatasetNamer) this.datasetNamerList.get( i);
        if ( dsN.nameDataset( dataset))
        {
          logger.debug( "nameDatasetTree(): ... used namer (" + dsN.getName() + ")");
          break;
        }
      }
    }

    // Try to name any child datasets.
    InvDatasetImpl curDs = null;
    for ( int j = 0; j < dataset.getDatasets().size(); j++)
    {
      curDs = (InvDatasetImpl) dataset.getDatasets().get( j);
      logger.debug( "nameDatasetTree(): recurse to name child dataset (" + curDs.getUrlPath() + ")");
      this.nameDatasetTree( curDs);
    }

    return;
  }

  private void sortDatasets( InvDataset dataset,
                             java.util.Comparator theComparator)
  {
    // Sort grand children datasets.
    InvDataset curDs = null;
    java.util.Iterator dsIter = dataset.getDatasets().iterator();
    while ( dsIter.hasNext())
    {
      curDs = (InvDataset) dsIter.next();
      this.sortDatasets( curDs, theComparator);
    }

    // Sort child datasets.
    logger.debug( "sortDatasets(): sort the datasets contained by dataset (" + dataset.getName() + ")");
    java.util.Collections.sort( dataset.getDatasets(), theComparator);

    return;
  }

  private void addNewDatasetToParent( InvDataset container)
  {

  }
}

/*
* $Log: DatasetSource.java,v $
* Revision 1.13  2004/12/14 22:46:52  edavis
* Add simple interface to thredds.cataloggen and continue adding catalogRef capabilities.
*
* Revision 1.12  2004/11/30 22:49:12  edavis
* Start changing DatasetSource into a more usable API.
*
* Revision 1.11  2004/07/08 20:26:12  edavis
* A fix for some cataloggen problems.
*
* Revision 1.10  2004/06/03 20:27:23  edavis
* Update for thredds.catalog changes for InvCatalog 1.0.
*
* Revision 1.9  2004/05/11 20:38:46  edavis
* Update for changes to thredds.catalog object model (still InvCat 0.6).
* Start adding some logging statements.
*
* Revision 1.8  2003/09/05 22:04:27  edavis
* Add more logging. Change some logging from debug() to info(). Fix
* problem with expand() dropping named datasets if building FLAT
* structure.
*
* Revision 1.7  2003/08/29 21:41:46  edavis
* The following changes where made:
*
*  1) Added more extensive logging (changed from thredds.util.Log and
* thredds.util.Debug to using Log4j).
*
* 2) Improved existing error handling and added additional error
* handling where problems could fall through the cracks. Added some
* catching and throwing of exceptions but also, for problems that aren't
* fatal, added the inclusion in the resulting catalog of datasets with
* the error message as its name.
*
* 3) Change how the CatGenTimerTask constructor is given the path to the
* config files and the path to the resulting files so that resulting
* catalogs are placed in the servlet directory space. Also, add ability
* for servlet to serve the resulting catalogs.
*
* 4) Switch from using java.lang.String to using java.io.File for
* handling file location information so that path seperators will be
* correctly handled. Also, switch to java.net.URI rather than
* java.io.File or java.lang.String where necessary to handle proper
* URI/URL character encoding.
*
* 5) Add handling of requests when no path ("") is given, when the root
* path ("/") is given, and when the admin path ("/admin") is given.
*
* 6) Fix the PUTting of catalogGenConfig files.
*
* 7) Start adding GDS DatasetSource capabilities.
*
* Revision 1.6  2003/08/20 18:04:00  edavis
* More changes to make things work with DatasetSource an abstract class.
*
* Revision 1.5  2003/07/03 20:02:27  edavis
* Made DatasetSource an abstract class and each type of DatasetSource
* its own subclass.
*
* Revision 1.4  2003/04/30 18:28:15  edavis
* Hacked in naming of directories, need to rethink.
*
* Revision 1.3  2003/03/18 21:11:55  edavis
* Add code to expand a DODS directory listing.
*
* Revision 1.2  2003/03/04 23:06:45  edavis
* Fixed problem that arose on Windows machines where urlPath attributes
* contained backslash ('\') characters as path seperators instead of slash
* ('/') characters.
*
* Revision 1.1.1.1  2002/12/11 22:27:55  edavis
* CatGen into reorged thredds CVS repository.
*
*/