<commit_before>
// -*- mode: c++; c-basic-offset:4 -*-

// This file is part of libdap, A C++ implementation of the OPeNDAP Data
// Access Protocol.

// Copyright (c) 2002,2003 OPeNDAP, Inc.
// Author: <NAME> <<EMAIL>>
//
// 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; either
// version 2.1 of the License, or (at your option) any later version.
//
// 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.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.

// (c) COPYRIGHT URI/MIT 1994-1999
// Please read the full copyright statement in the file COPYRIGHT_URI.
//
// Authors:
//      jhrg,jimg       <NAME> <<EMAIL>>

// Implementation for Array.
//
// jhrg 9/13/94

#include "config.h"

//#define DODS_DEBUG

#include <algorithm>
#include <functional>
#include <sstream>

#include "Array.h"
#include "Grid.h"

#include "D4Attributes.h"
#include "DMR.h"
#include "D4Dimensions.h"
#include "D4Maps.h"
#include "D4Group.h"
#include "D4EnumDefs.h"
#include "D4Enum.h"
#include "XMLWriter.h"

#include "util.h"
#include "debug.h"
#include "InternalErr.h"
#include "escaping.h"
#include "DapIndent.h"

using namespace std;

namespace libdap {

Array::dimension::dimension(D4Dimension *d) : dim(d), use_sdim_for_slice(true)
{
    size = d->size();
    name = d->name();

    start = 0;
    stop = size - 1;
    stride = 1;
    c_size = size;
}

void
Array::_duplicate(const Array &a)
{
    _shape = a._shape;

    // Deep copy the Maps if they are being used.
    if (a.d_maps) {
    	d_maps = new D4Maps(*(a.d_maps));
    }
    else {
    	d_maps = 0;
    }
    // d_maps = a.d_maps ? new D4Maps(*(a.d_maps)) : 0;
}

// The first method of calculating length works when only one dimension is
// constrained and you want the others to appear in total. This is important
// when selecting from grids since users may not select from all dimensions
// in which case that means they want the whole thing. Array projection
// should probably work this way too, but it doesn't. 9/21/2001 jhrg

/** @deprecated Calling this method should never be necessary. It is used
    internally called whenever the size of the Array is changed, e.g., by a
    constraint.

    Changes the length property of the array.
*/
void
Array::update_length(int)
{
    int length = 1;
    for (Dim_citer i = _shape.begin(); i != _shape.end(); i++) {
#if 0
        // If the size of any dimension is zero, then the array is not
        // capable of storing any values. jhrg 1/28/16
        length *= (*i).c_size > 0 ? (*i).c_size : 1;
#endif
        length *= (*i).c_size;
    }

    set_length(length);
}

// Construct an instance of Array. The (BaseType *) is assumed to be
// allocated using new - The dtor for Vector will delete this object.

/** Build an array with a name and an element type. The name may be omitted,
    which will create a nameless variable. The template (element type) pointer
    may also be omitted, but if it is omitted when the Array is created, it
    \e must be added (with \c add_var()) before \c read() or \c deserialize()
    is called.

    @todo Force the Array::add_var() method to be used to add \e v.
    This version of add_var() calls Vector::add_var().

    @param n A string containing the name of the variable to be
    created.
    @param v A pointer to a variable of the type to be included
    in the Array. May be null and set later using add_var() or add_var_nocopy()
    @brief Array constructor
*/
Array::Array(const string &n, BaseType *v, bool is_dap4 /* default:false */)
	: Vector(n, 0, dods_array_c, is_dap4), d_maps(0)
{
    add_var(v); // Vector::add_var() stores null if v is null
}

/** Build an array on the server-side with a name, a dataset name from which
    this Array is being created, and an element type.

    @todo Force the Array::add_var() method to be used to add \e v.
    This version of add_var() calls Vector::add_var().

    @param n A string containing the name of the variable to be created.
    @param d A string containing the name of the dataset from which this
    variable is being created.
    @param v A pointer to a variable of the type to be included
    in the Array.
    @brief Array constructor
*/
Array::Array(const string &n, const string &d, BaseType *v, bool is_dap4 /* default:false */)
    : Vector(n, d, 0, dods_array_c, is_dap4), d_maps(0)
{
    add_var(v); // Vector::add_var() stores null if v is null
}

/** @brief The Array copy constructor. */
Array::Array(const Array &rhs) : Vector(rhs)
{
    _duplicate(rhs);
}

/** @brief The Array destructor. */
Array::~Array()
{
	delete d_maps;
}

BaseType *
Array::ptr_duplicate()
{
    return new Array(*this);
}

Array &
Array::operator=(const Array &rhs)
{
    if (this == &rhs)
        return *this;

    dynamic_cast<Vector &>(*this) = rhs;

    _duplicate(rhs);

    return *this;
}

void
Array::transform_to_dap4(D4Group *root, Constructor *container)
{
    DBG(cerr << __func__ << "() - BEGIN (array:" << name() << ")" << endl;);
	Array *dest = static_cast<Array*>(ptr_duplicate());

	// If it's already a DAP4 object then we can just return it!
	if(is_dap4()){
        DBG(cerr << __func__ << "() - Already DAP4 type: Just making a copy and adding to container. " << endl;);
	    container->add_var_nocopy(dest);
	    DBG(cerr << __func__ << "() - END (Already DAP4 type)" << endl;);
	}
	// Process the Array's dimensions, making D4 shared dimensions for
	// D2 dimensions that are named. If there is just a size, don't make
	// a D4Dimension (In DAP4 you cannot share a dimension unless it has
	// a name). jhrg 3/18/14

	D4Dimensions *root_dims = root->dims();
	for (Array::Dim_iter dap2_dim = dest->dim_begin(), e = dest->dim_end(); dap2_dim != e; ++dap2_dim) {
		if (!(*dap2_dim).name.empty()) {
	        DBG(cerr << __func__ << "() - Processing the array dimension '" << (*dap2_dim).name << "'" << endl;);

			// If a D4Dimension with the name already exists, use it.
			D4Dimension *d4_dim = root_dims->find_dim((*dap2_dim).name);
			if (!d4_dim) {
				d4_dim = new D4Dimension((*dap2_dim).name, (*dap2_dim).size);
				root_dims->add_dim_nocopy(d4_dim);
		        DBG(cerr << __func__ << "() -" <<
		            " Added NEW D4Dimension '"<< d4_dim->name() << "' (" <<
		            (void *)d4_dim << ") to root->dims()"<< endl;);
			}
	        else {
	            DBG(cerr << __func__ << "() -" <<
	                " Using Existing D4Dimension '"<< d4_dim->name() << "' (" <<
	                (void *)d4_dim << ")"<< endl;);

	            if (d4_dim->size() != (unsigned long) (*dap2_dim).size) {
                // TODO Revisit this decision. jhrg 3/18/14
                // ...in case the name/size are different, make a unique D4Dimension
                // but don't fiddle with the name. Not sure I like this idea, so I'm
                // making the case explicit (could be rolled in to the block above).
                // jhrg 3/18/14
                //
                // This is causing problems in the FITS handler because there are cases
                // where two arrays have dimensions with the same name but different
                // sizes. The deserializing code is using the first size listed, which is
                // wrong in some cases. I'm going to try making this new D4Dimension using
                // the dim name along with the variable name. jhrg 8/15/14
                    d4_dim = new D4Dimension((*dap2_dim).name + "_" + name(), (*dap2_dim).size);
                    DBG(cerr << __func__ << "() -" <<
                         " Utilizing Name/Size Conflict Naming Artifice. name'"<< d4_dim->name() << "' (" <<
                         (void *)d4_dim << ")"<< endl;);
                    root_dims->add_dim_nocopy(d4_dim);
	            }
			}
			// At this point d4_dim's name and size == those of (*d) so just set
			// the D4Dimension pointer so it matches the one in the D4Group.
			(*dap2_dim).dim = d4_dim;
		}

	}

	// Copy the D2 attributes to D4 Attributes
	dest->attributes()->transform_to_dap4(get_attr_table());
	dest->set_is_dap4(true);
	container->add_var_nocopy(dest);
    DBG(cerr << __func__ << "() - END (array:" << name() << ")" << endl;);
}

bool Array::is_dap2_grid(){
    bool is_grid = false;
    if(this->is_dap4()){
        DBG( cerr << __func__ << "() - Array '"<< name() << "' is DAP4 object!"  << endl;)
        D4Maps *d4_maps = this->maps();
        is_grid = d4_maps->size(); // It can't be a grid if there are no maps...
        if(is_grid){
            DBG( cerr << __func__ << "() - Array '"<< name() << "' has D4Maps." << endl;)
            // hmmm this might be a DAP2 Grid...
            D4Maps::D4MapsIter i = d4_maps->map_begin();
            D4Maps::D4MapsIter e = d4_maps->map_end();
            while(i!=e){
                DBG( cerr << __func__ << "() - Map '"<< (*i)->array()->name() << " has " << (*i)->array()->_shape.size()  << " dimension(s)."  << endl;)
                if((*i)->array()->_shape.size() > 1){
                    is_grid = false;
                    i = e;
                }
                else {
                    i++;
                }
            }
        }
        else {
            DBG( cerr << __func__ << "() - Array '"<< name() << "' has no D4Maps." << endl;)
        }
    }
    DBG( cerr << __func__ << "() - is_grid: "<< (is_grid?"true":"false") << endl;)
    return is_grid;
}

/**
 * @brief Transforms this instance of a D4Array into the corresponding DAP2 object.
 *
 * This transformation may return an Array or a Grid object. The DAP2 Grid construct
 * is semantically contained in the DAP4 concept of arrays with Map arrays. If all
 * of the Maps are one dimensional then the D4Array can be represented as a
 * Grid object.
 *
 * @param  The AttrTable pointer parent_attr_table is used by Groups, which disappear
 * from the DAP2 representation. Their children are returned in the the BAseType vector
 * their attributes are added to parent_attr_table.
 * @return A pointer to a vector of BaseType pointers (right?). In this D4Array case
 * returned vector may contain a DAP2 Array or a Grid. Or, if the Array' prototype is
 * a type that cannot be represented in DAP2 the return will be NULL.
 */
std::vector<BaseType *> *
Array::transform_to_dap2(AttrTable *){
    DBG(cerr << __func__ << "() - BEGIN Array '"<< name() << "'" << endl;);

    BaseType *dest;
    if(is_dap4()){ // Don't convert a DAP2 thing

        // Can Precious be represented as a DAP2 Grid
        if(is_dap2_grid()){
            // Oh yay! Grids are special.
            DBG(cerr << __func__ << "() - Array '"<< name() << "' is dap2 Grid!"  << endl;);
            Grid *g = new Grid(name());
            dest = g;
            Array *grid_array = (Array *) this->ptr_duplicate();
            g->set_array(grid_array);

#if 0 // The enclosed operations are redundant. FIXME jhrg
	    // Including this block 'fixes' DDSTest::get_das_test_2() and completely
	    // 'fixes' the failures in DmrRoundTripTest. jhrg 6/17/19
            // Get the metadata into the Grid Array
            AttrTable *grid_attrs = attributes()->get_AttrTable(name());
            grid_array->set_attr_table(*grid_attrs); // Copy it into the Grid object.
            // grid_array->set_attr_table(*grid_attrs); // Copy it into the data Array.
            delete grid_attrs;

            // Clear the Grid attributes.
            AttrTable at;
            at.set_name(name());
            g->set_attr_table(at);
#endif

            // Process the Map Arrays.
            D4Maps *d4_maps = this->maps();
            vector<BaseType *> dropped_maps;
            D4Maps::D4MapsIter miter = d4_maps->map_begin();
            D4Maps::D4MapsIter end = d4_maps->map_end();
            for( ; miter!=end; miter++){
                D4Map *d4_map =  (*miter);
                Array *d4_map_array = const_cast<Array*>(d4_map->array());
                vector<BaseType *> *d2_result = d4_map_array->transform_to_dap2(&(g->get_attr_table()));
                if(d2_result){
                    if(d2_result->size()>1)
                        throw Error(internal_error,string(__func__)+"() - ERROR: D4Map Array conversion resulted in multiple DAP2 objects.");

                    // TODO - This is probably slow and needs a better pattern. const_cast? static_cast?
                    Array *d2_map_array = dynamic_cast<Array *>((*d2_result)[0]);
                    if(d2_map_array){
                        if(d2_map_array->dimensions()!=1)
                            throw Error(internal_error,string(__func__)+"() - ERROR: DAP2 array from D4Map Array conversion has more than 1 dimension.");

                        g->add_map(d2_map_array,false);
                        AttrTable at = d2_map_array->get_attr_table();
                        DBG( cerr << __func__ << "() - " <<
                            "DAS For Grid Map '" << d2_map_array->name() << "':" << endl;
                             at.print(cerr); );
                    }
                    else {
                        throw Error(internal_error,string(__func__)+"() - Unable to interpret returned DAP2 content.");
                    }
                    delete d2_result;
                }
                else {
                    dropped_maps.push_back(d4_map_array);
                }
            }

            // Did we have a transform failure?
            if(!dropped_maps.empty()){
                // Yup... tell the story in the attributes.
                AttrTable *dv_table = Constructor::make_dropped_vars_attr_table(&dropped_maps);
                dest->get_attr_table().append_container(dv_table,dv_table->get_name());
            }
        }
        else {
            // It's not a Grid so we can make a simple copy of our Precious.
            DBG( cerr << __func__ << "() - Array '"<< name() << "' is not a Grid!"  << endl);
            BaseType *proto = this->prototype();
            switch(proto->type()){
            case dods_int64_c:
            case dods_uint64_c:
            case dods_enum_c:
            case dods_opaque_c:
            {
                // For now we punt on these type as they have no easy representation in
                // the DAP2 data model. By setting this to NULL we cause the Array to be
                // dropped and this will be reflected in the metadata (DAS).
                dest = NULL;
                break;
            }
            default:
            {
                // ptr_duplicate() does the Attributes too.
                dest = this->ptr_duplicate();<commit_msg>Another hack to fix DDSTests added.<commit_after>
// -*- mode: c++; c-basic-offset:4 -*-

// This file is part of libdap, A C++ implementation of the OPeNDAP Data
// Access Protocol.

// Copyright (c) 2002,2003 OPeNDAP, Inc.
// Author: <NAME> <<EMAIL>>
//
// 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; either
// version 2.1 of the License, or (at your option) any later version.
//
// 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.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//
// You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.

// (c) COPYRIGHT URI/MIT 1994-1999
// Please read the full copyright statement in the file COPYRIGHT_URI.
//
// Authors:
//      jhrg,jimg       <NAME> <<EMAIL>>

// Implementation for Array.
//
// jhrg 9/13/94

#include "config.h"

//#define DODS_DEBUG

#include <algorithm>
#include <functional>
#include <sstream>

#include "Array.h"
#include "Grid.h"

#include "D4Attributes.h"
#include "DMR.h"
#include "D4Dimensions.h"
#include "D4Maps.h"
#include "D4Group.h"
#include "D4EnumDefs.h"
#include "D4Enum.h"
#include "XMLWriter.h"

#include "util.h"
#include "debug.h"
#include "InternalErr.h"
#include "escaping.h"
#include "DapIndent.h"

using namespace std;

namespace libdap {

Array::dimension::dimension(D4Dimension *d) : dim(d), use_sdim_for_slice(true)
{
    size = d->size();
    name = d->name();

    start = 0;
    stop = size - 1;
    stride = 1;
    c_size = size;
}

void
Array::_duplicate(const Array &a)
{
    _shape = a._shape;

    // Deep copy the Maps if they are being used.
    if (a.d_maps) {
    	d_maps = new D4Maps(*(a.d_maps));
    }
    else {
    	d_maps = 0;
    }
    // d_maps = a.d_maps ? new D4Maps(*(a.d_maps)) : 0;
}

// The first method of calculating length works when only one dimension is
// constrained and you want the others to appear in total. This is important
// when selecting from grids since users may not select from all dimensions
// in which case that means they want the whole thing. Array projection
// should probably work this way too, but it doesn't. 9/21/2001 jhrg

/** @deprecated Calling this method should never be necessary. It is used
    internally called whenever the size of the Array is changed, e.g., by a
    constraint.

    Changes the length property of the array.
*/
void
Array::update_length(int)
{
    int length = 1;
    for (Dim_citer i = _shape.begin(); i != _shape.end(); i++) {
#if 0
        // If the size of any dimension is zero, then the array is not
        // capable of storing any values. jhrg 1/28/16
        length *= (*i).c_size > 0 ? (*i).c_size : 1;
#endif
        length *= (*i).c_size;
    }

    set_length(length);
}

// Construct an instance of Array. The (BaseType *) is assumed to be
// allocated using new - The dtor for Vector will delete this object.

/** Build an array with a name and an element type. The name may be omitted,
    which will create a nameless variable. The template (element type) pointer
    may also be omitted, but if it is omitted when the Array is created, it
    \e must be added (with \c add_var()) before \c read() or \c deserialize()
    is called.

    @todo Force the Array::add_var() method to be used to add \e v.
    This version of add_var() calls Vector::add_var().

    @param n A string containing the name of the variable to be
    created.
    @param v A pointer to a variable of the type to be included
    in the Array. May be null and set later using add_var() or add_var_nocopy()
    @brief Array constructor
*/
Array::Array(const string &n, BaseType *v, bool is_dap4 /* default:false */)
	: Vector(n, 0, dods_array_c, is_dap4), d_maps(0)
{
    add_var(v); // Vector::add_var() stores null if v is null
}

/** Build an array on the server-side with a name, a dataset name from which
    this Array is being created, and an element type.

    @todo Force the Array::add_var() method to be used to add \e v.
    This version of add_var() calls Vector::add_var().

    @param n A string containing the name of the variable to be created.
    @param d A string containing the name of the dataset from which this
    variable is being created.
    @param v A pointer to a variable of the type to be included
    in the Array.
    @brief Array constructor
*/
Array::Array(const string &n, const string &d, BaseType *v, bool is_dap4 /* default:false */)
    : Vector(n, d, 0, dods_array_c, is_dap4), d_maps(0)
{
    add_var(v); // Vector::add_var() stores null if v is null
}

/** @brief The Array copy constructor. */
Array::Array(const Array &rhs) : Vector(rhs)
{
    _duplicate(rhs);
}

/** @brief The Array destructor. */
Array::~Array()
{
	delete d_maps;
}

BaseType *
Array::ptr_duplicate()
{
    return new Array(*this);
}

Array &
Array::operator=(const Array &rhs)
{
    if (this == &rhs)
        return *this;

    dynamic_cast<Vector &>(*this) = rhs;

    _duplicate(rhs);

    return *this;
}

void
Array::transform_to_dap4(D4Group *root, Constructor *container)
{
    DBG(cerr << __func__ << "() - BEGIN (array:" << name() << ")" << endl;);
	Array *dest = static_cast<Array*>(ptr_duplicate());

	// If it's already a DAP4 object then we can just return it!
	if(is_dap4()){
        DBG(cerr << __func__ << "() - Already DAP4 type: Just making a copy and adding to container. " << endl;);
	    container->add_var_nocopy(dest);
	    DBG(cerr << __func__ << "() - END (Already DAP4 type)" << endl;);
	}
	// Process the Array's dimensions, making D4 shared dimensions for
	// D2 dimensions that are named. If there is just a size, don't make
	// a D4Dimension (In DAP4 you cannot share a dimension unless it has
	// a name). jhrg 3/18/14

	D4Dimensions *root_dims = root->dims();
	for (Array::Dim_iter dap2_dim = dest->dim_begin(), e = dest->dim_end(); dap2_dim != e; ++dap2_dim) {
		if (!(*dap2_dim).name.empty()) {
	        DBG(cerr << __func__ << "() - Processing the array dimension '" << (*dap2_dim).name << "'" << endl;);

			// If a D4Dimension with the name already exists, use it.
			D4Dimension *d4_dim = root_dims->find_dim((*dap2_dim).name);
			if (!d4_dim) {
				d4_dim = new D4Dimension((*dap2_dim).name, (*dap2_dim).size);
				root_dims->add_dim_nocopy(d4_dim);
		        DBG(cerr << __func__ << "() -" <<
		            " Added NEW D4Dimension '"<< d4_dim->name() << "' (" <<
		            (void *)d4_dim << ") to root->dims()"<< endl;);
			}
	        else {
	            DBG(cerr << __func__ << "() -" <<
	                " Using Existing D4Dimension '"<< d4_dim->name() << "' (" <<
	                (void *)d4_dim << ")"<< endl;);

	            if (d4_dim->size() != (unsigned long) (*dap2_dim).size) {
                // TODO Revisit this decision. jhrg 3/18/14
                // ...in case the name/size are different, make a unique D4Dimension
                // but don't fiddle with the name. Not sure I like this idea, so I'm
                // making the case explicit (could be rolled in to the block above).
                // jhrg 3/18/14
                //
                // This is causing problems in the FITS handler because there are cases
                // where two arrays have dimensions with the same name but different
                // sizes. The deserializing code is using the first size listed, which is
                // wrong in some cases. I'm going to try making this new D4Dimension using
                // the dim name along with the variable name. jhrg 8/15/14
                    d4_dim = new D4Dimension((*dap2_dim).name + "_" + name(), (*dap2_dim).size);
                    DBG(cerr << __func__ << "() -" <<
                         " Utilizing Name/Size Conflict Naming Artifice. name'"<< d4_dim->name() << "' (" <<
                         (void *)d4_dim << ")"<< endl;);
                    root_dims->add_dim_nocopy(d4_dim);
	            }
			}
			// At this point d4_dim's name and size == those of (*d) so just set
			// the D4Dimension pointer so it matches the one in the D4Group.
			(*dap2_dim).dim = d4_dim;
		}

	}

	// Copy the D2 attributes to D4 Attributes
	dest->attributes()->transform_to_dap4(get_attr_table());
	dest->set_is_dap4(true);
	container->add_var_nocopy(dest);
    DBG(cerr << __func__ << "() - END (array:" << name() << ")" << endl;);
}

bool Array::is_dap2_grid(){
    bool is_grid = false;
    if(this->is_dap4()){
        DBG( cerr << __func__ << "() - Array '"<< name() << "' is DAP4 object!"  << endl;)
        D4Maps *d4_maps = this->maps();
        is_grid = d4_maps->size(); // It can't be a grid if there are no maps...
        if(is_grid){
            DBG( cerr << __func__ << "() - Array '"<< name() << "' has D4Maps." << endl;)
            // hmmm this might be a DAP2 Grid...
            D4Maps::D4MapsIter i = d4_maps->map_begin();
            D4Maps::D4MapsIter e = d4_maps->map_end();
            while(i!=e){
                DBG( cerr << __func__ << "() - Map '"<< (*i)->array()->name() << " has " << (*i)->array()->_shape.size()  << " dimension(s)."  << endl;)
                if((*i)->array()->_shape.size() > 1){
                    is_grid = false;
                    i = e;
                }
                else {
                    i++;
                }
            }
        }
        else {
            DBG( cerr << __func__ << "() - Array '"<< name() << "' has no D4Maps." << endl;)
        }
    }
    DBG( cerr << __func__ << "() - is_grid: "<< (is_grid?"true":"false") << endl;)
    return is_grid;
}

/**
 * @brief Transforms this instance of a D4Array into the corresponding DAP2 object.
 *
 * This transformation may return an Array or a Grid object. The DAP2 Grid construct
 * is semantically contained in the DAP4 concept of arrays with Map arrays. If all
 * of the Maps are one dimensional then the D4Array can be represented as a
 * Grid object.
 *
 * @param  The AttrTable pointer parent_attr_table is used by Groups, which disappear
 * from the DAP2 representation. Their children are returned in the the BAseType vector
 * their attributes are added to parent_attr_table.
 * @return A pointer to a vector of BaseType pointers (right?). In this D4Array case
 * returned vector may contain a DAP2 Array or a Grid. Or, if the Array' prototype is
 * a type that cannot be represented in DAP2 the return will be NULL.
 */
std::vector<BaseType *> *
Array::transform_to_dap2(AttrTable *){
    DBG(cerr << __func__ << "() - BEGIN Array '"<< name() << "'" << endl;);

    BaseType *dest;
    if(is_dap4()){ // Don't convert a DAP2 thing

        // Can Precious be represented as a DAP2 Grid
        if(is_dap2_grid()){
            // Oh yay! Grids are special.
            DBG(cerr << __func__ << "() - Array '"<< name() << "' is dap2 Grid!"  << endl;);
            Grid *g = new Grid(name());
            dest = g;
            Array *grid_array = (Array *) this->ptr_duplicate();
            g->set_array(grid_array);

#if 0 // The enclosed operations are redundant. FIXME jhrg
	    // Including this block 'fixes' DDSTest::get_das_test_2() and completely
	    // 'fixes' the failures in DmrRoundTripTest. jhrg 6/17/19
            // Get the metadata into the Grid Array
            AttrTable *grid_attrs = attributes()->get_AttrTable(name());
            grid_array->set_attr_table(*grid_attrs); // Copy it into the Grid object.
            // grid_array->set_attr_table(*grid_attrs); // Copy it into the data Array.
            delete grid_attrs;

            // Clear the Grid attributes.
            AttrTable at;
            at.set_name(name());
            g->set_attr_table(at);
#endif

            // Process the Map Arrays.
            D4Maps *d4_maps = this->maps();
            vector<BaseType *> dropped_maps;
            D4Maps::D4MapsIter miter = d4_maps->map_begin();
            D4Maps::D4MapsIter end = d4_maps->map_end();
            for( ; miter!=end; miter++){
                D4Map *d4_map =  (*miter);
                Array *d4_map_array = const_cast<Array*>(d4_map->array());
                vector<BaseType *> *d2_result = d4_map_array->transform_to_dap2(&(g->get_attr_table()));
                if(d2_result){
                    if(d2_result->size()>1)
                        throw Error(internal_error,string(__func__)+"() - ERROR: D4Map Array conversion resulted in multiple DAP2 objects.");

                    // TODO - This is probably slow and needs a better pattern. const_cast? static_cast?
                    Array *d2_map_array = dynamic_cast<Array *>((*d2_result)[0]);
                    if(d2_map_array){
                        if(d2_map_array->dimensions()!=1)
                            throw Error(internal_error,string(__func__)+"() - ERROR: DAP2 array from D4Map Array conversion has more than 1 dimension.");

                        g->add_map(d2_map_array,false);
                        AttrTable at = d2_map_array->get_attr_table();
                        DBG( cerr << __func__ << "() - " <<
                            "DAS For Grid Map '" << d2_map_array->name() << "':" << endl;
                             at.print(cerr); );
                    }
                    else {
                        throw Error(internal_error,string(__func__)+"() - Unable to interpret returned DAP2 content.");
                    }
                    delete d2_result;
                }
                else {
                    dropped_maps.push_back(d4_map_array);
                }
            }

            // Did we have a transform failure?
            if(!dropped_maps.empty()){
                // Yup... tell the story in the attributes.
                AttrTable *dv_table = Constructor::make_dropped_vars_attr_table(&dropped_maps);
                dest->get_attr_table().append_container(dv_table,dv_table->get_name());
            }
        }
        else {
            // It's not a Grid so we can make a simple copy of our Precious.
            DBG( cerr << __func__ << "() - Array '"<< name() << "' is not a Grid!"  << endl);
            BaseType *proto = this->prototype();
            switch(proto->type()){
            case dods_int64_c:
            case dods_uint64_c:
            case dods_enum_c:
            case dods_opaque_c:
            {
                // For now we punt on these type as they have no easy representation in
                // the DAP2 data model. By setting this to NULL we cause the Array to be
                // dropped and this will be reflected in the metadata (DAS).
                dest = NULL;
                break;
            }
            default:
            {
                // ptr_duplicate() does the Attributes too.
                dest = this->ptr_duplicate();
#if 0 // FIXME Adding this back into the code 'fixes' two of the DDSTests
		// get_das_test_6 and get_das_test_5. jhrg 6/17/19 
                // convert the d4 attributes to a dap2 attribute table.
                AttrTable *attrs = this->attributes()->get_AttrTable(name());
                dest->set_attr_table(*attrs);
#endif
{'fraction_non_alphanumeric': 0.12601053906326712, 'fraction_numerical': 0.01571040487022551, 'mean_word_length': 2.2135044173327723, 'pattern_counts': {'":': 2, '<': 403, '<?xml version=': 0, '>': 146, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1097997', 'n_tokens_mistral': 9756, 'n_tokens_neox': 9332, 'n_words': 3159}