1 // -*- mode: c++; c-basic-offset:4 -*-
2 
3 // This file is part of libdap, A C++ implementation of the OPeNDAP Data
4 // Access Protocol.
5 
6 // Copyright (c) 2013 OPeNDAP, Inc.
7 // Author: James Gallagher <jgallagher@opendap.org>
8 //
9 // This library is free software; you can redistribute it and/or
10 // modify it under the terms of the GNU Lesser General Public
11 // License as published by the Free Software Foundation; either
12 // version 2.1 of the License, or (at your option) any later version.
13 //
14 // This library is distributed in the hope that it will be useful,
15 // but WITHOUT ANY WARRANTY; without even the implied warranty of
16 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 // Lesser General Public License for more details.
18 //
19 // You should have received a copy of the GNU Lesser General Public
20 // License along with this library; if not, write to the Free Software
21 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22 //
23 // You can contact OPeNDAP, Inc. at PO Box 112, Saunderstown, RI. 02874-0112.
24 
25 #include "config.h"
26 
27 //#define DODS_DEBUG
28 
29 #include <cassert>
30 
31 #include <iostream>
32 #include <sstream>
33 #include <iomanip>
34 
35 #include <stdint.h>
36 
37 #include "crc.h"
38 
39 #include "BaseType.h"
40 #include "Array.h"
41 
42 #include "XMLWriter.h"
43 #include "D4Attributes.h"
44 #include "D4Dimensions.h"
45 #include "D4Group.h"
46 #include "D4Enum.h"
47 
48 #include "D4StreamMarshaller.h"
49 #include "D4StreamUnMarshaller.h"
50 
51 #include "debug.h"
52 
53 /**
54  * Define this symbol iff we decide to include information about the
55  * byte order of the response (as sent from the server) so that the
56  * client can determine the correct CRC32 hash code. jhrg 1/4/16
57  */
58 #undef INCLUDE_SOURCE_BYTE_ORDER
59 
60 namespace libdap {
61 
m_duplicate(const D4Group & g)62 void D4Group::m_duplicate(const D4Group &g)
63 {
64 	DBG(cerr << "In D4Group::m_duplicate for " << g.name() << endl);
65 
66 	// dims; deep copy, this is the parent
67 	if (g.d_dims) {
68 		d_dims = new D4Dimensions(*(g.d_dims));
69 		d_dims->set_parent(this);
70 
71 	    // Update all of the D4Dimension weak pointers in the Array objects.
72 	    // This is a hack - we know that Constructor::m_duplicate() has been
73 	    // called at this point and any Array instances have dimension pointers
74 	    // that reference the 'old' dimensions (g.d_dims) and not the 'new'
75 	    // dimensions made above. Scan every array and re-wire the weak pointers.
76 	    // jhrg 8/15/14
77 	    Vars_citer vi = d_vars.begin();
78 	    while (vi != d_vars.end()) {
79 	        if ((*vi)->type() == dods_array_c)
80 	            static_cast<Array*>(*vi)->update_dimension_pointers(g.d_dims, d_dims);
81 	        ++vi;
82 	    }
83 	}
84 
85 #if 0
86 	// Moved this block up inside the if because g.d_dims might be false. jhrg 9/14/15
87 	Vars_citer vi = d_vars.begin();
88 	while (vi != d_vars.end()) {
89 		if ((*vi)->type() == dods_array_c)
90 			static_cast<Array*>(*vi)->update_dimension_pointers(g.d_dims, d_dims);
91 		++vi;
92 	}
93 #endif
94 
95 	// enums; deep copy
96 	if (g.d_enum_defs) d_enum_defs = new D4EnumDefs(*g.d_enum_defs);
97 
98     // groups
99     groupsCIter i = g.d_groups.begin();
100     while(i != g.d_groups.end()) {
101         // Only D4Groups are in the d_groups container.
102         D4Group *g = static_cast<D4Group*>((*i++)->ptr_duplicate());
103         add_group_nocopy(g);
104     }
105 
106     DBG(cerr << "Exiting D4Group::m_duplicate" << endl);
107 }
108 
109 /** The D4Group constructor requires only the name of the variable
110     to be created. The name may be omitted, which will create a
111     nameless variable. This may be adequate for some applications.
112 
113     @note This type is available in DAP4 only.
114     See http://docs.opendap.org/index.php/DAP4:_Specification_Volume_1#Groups
115 
116 
117     @param n A string containing the name of the variable.
118 */
D4Group(const string & name)119 D4Group::D4Group(const string &name)
120     : Constructor(name, dods_group_c, /*is_dap4*/true), d_dims(0), d_enum_defs(0)
121 {}
122 
123 /** The D4Group server-side constructor requires the name of the variable
124     to be created and the dataset name from which this variable is being
125     created. Used on server-side handlers.
126 
127     @note This type is available in DAP4 only.
128     See http://docs.opendap.org/index.php/DAP4:_Specification_Volume_1#Groups
129 
130     @param n A string containing the name of the variable.
131     @param d A string containing the name of the dataset.
132 */
D4Group(const string & name,const string & dataset)133 D4Group::D4Group(const string &name, const string &dataset)
134     : Constructor(name, dataset, dods_group_c, /*is_dap4*/true), d_dims(0), d_enum_defs(0)
135 {}
136 
137 /** The D4Group copy constructor. */
D4Group(const D4Group & rhs)138 D4Group::D4Group(const D4Group &rhs) : Constructor(rhs), d_dims(0), d_enum_defs(0)
139 {
140 	DBG(cerr << "In D4Group::copy_ctor for " << rhs.name() << endl);
141     m_duplicate(rhs);
142 }
143 
~D4Group()144 D4Group::~D4Group()
145 {
146     delete d_dims;
147     delete d_enum_defs;
148 
149     groupsIter i = d_groups.begin();
150     while(i != d_groups.end())
151         delete *i++;
152 }
153 
154 #if 0
155 D4Group *
156 
157 // I think this was a mistake. jhrg 11/17/16
158 #endif
159 BaseType *
ptr_duplicate()160 D4Group::ptr_duplicate()
161 {
162     return new D4Group(*this);
163 }
164 
165 D4Group &
operator =(const D4Group & rhs)166 D4Group::operator=(const D4Group &rhs)
167 {
168     if (this == &rhs)
169         return *this;
170 
171     dynamic_cast<Constructor &>(*this) = rhs; // run Constructor=
172 
173     m_duplicate(rhs);
174 
175     return *this;
176 }
177 
178 /**
179  * Get the Fully Qualified Name for this Group, including the Group. This
180  * uses the name representation described in the DAP4 specification.
181  *
182  * @return The FQN in a string
183  */
184 string
FQN() const185 D4Group::FQN() const
186 {
187 	// The root group is named "/" (always)
188 	return (name() == "/") ? "/" : static_cast<D4Group*>(get_parent())->FQN() + name() + "/";
189 }
190 
191 // Note that in order for this to work the second argument must not be a reference.
192 // jhrg 8/20/13
193 static bool
name_eq(D4Group * g,const string name)194 name_eq(D4Group *g, const string name)
195 {
196 	return g->name() == name;
197 }
198 
199 D4Group *
find_child_grp(const string & grp_name)200 D4Group::find_child_grp(const string &grp_name)
201 {
202 	groupsIter g = find_if(grp_begin(), grp_end(), bind2nd(ptr_fun(name_eq), grp_name));
203 	return (g == grp_end()) ? 0: *g;
204 }
205 
206 // TODO Add constraint param? jhrg 11/17/13
207 BaseType *
find_first_var_that_uses_dimension(D4Dimension * dim)208 D4Group::find_first_var_that_uses_dimension(D4Dimension *dim)
209 {
210     // for each group, starting with the root group
211     //    for each variable in the group that is marked to send and is an array
212     //        return the btp if it uses the D4Dimension
213     //    if it contains child groups, search those
214     //        return the btp if it uses the D4Dimension
215     // return null
216 
217     // exhaustive breadth-first search for 'dim
218 
219     // root group
220     for (Vars_iter i = var_begin(), e = var_end(); i != e; ++i) {
221         if ((*i)->send_p() && (*i)->type() == dods_array_c) {
222             Array *a = static_cast<Array*>(*i);
223             for (Array::Dim_iter di = a->dim_begin(), de = a->dim_end(); di != de; ++di) {
224                 if (a->dimension_D4dim(di) == dim)
225                     return a;
226             }
227         }
228     }
229 
230     for (groupsIter i = grp_begin(), e = grp_end(); i != e; ++i) {
231         BaseType *btp = (*i)->find_first_var_that_uses_dimension(dim);
232         if (btp) return btp;
233     }
234 
235     return 0;
236 }
237 
238 BaseType *
find_first_var_that_uses_enumeration(D4EnumDef * enum_def)239 D4Group::find_first_var_that_uses_enumeration(D4EnumDef *enum_def)
240 {
241     // for each group, starting with the root group
242     //    for each variable in the group that is marked to send and is an array
243     //        return the btp if it uses the D4EnumDef
244     //    if it contains child groups, search those
245     //        return the btp if it uses the D4EnumDef
246     // return null
247 
248     // exhaustive breadth-first search for 'dim
249 
250     // root group
251     for (Vars_iter i = var_begin(), e = var_end(); i != e; ++i) {
252         if ((*i)->send_p() && (*i)->type() == dods_enum_c) {
253             D4Enum *e = static_cast<D4Enum*>(*i);
254             if (e->enumeration() == enum_def)
255                 return e;
256         }
257     }
258 
259     for (groupsIter i = grp_begin(), e = grp_end(); i != e; ++i) {
260         BaseType *btp = (*i)->find_first_var_that_uses_enumeration(enum_def);
261         if (btp) return btp;
262     }
263 
264     return 0;
265 }
266 
267 /**
268  * @brief Find the dimension using a path.
269  * Using the DAP4 name syntax, lookup a dimension. The dimension must
270  * be defined before it is used. The \c path argument may be either an
271  * absolute path or a relative path. Note that the name syntax does not
272  * provide for paths to contain an 'up one level' symbol.
273  * @param path The path to the dimension
274  * @return A pointer to the D4Dimension object.
275  */
276 D4Dimension *
find_dim(const string & path)277 D4Group::find_dim(const string &path)
278 {
279 	string lpath = path;		// get a mutable copy
280 
281 	// special-case for the root group
282 	if (lpath[0] == '/') {
283 		if (name() != "/")
284 			throw InternalErr(__FILE__, __LINE__, "Lookup of a FQN starting in non-root group.");
285 		else
286 			lpath = lpath.substr(1);
287 	}
288 
289 	string::size_type pos = lpath.find('/');
290 	if (pos == string::npos) {
291 		// name looks like 'bar'
292 		return dims()->find_dim(lpath);
293 	}
294 
295 	// name looks like foo/bar/baz where foo and bar must be groups
296 	string grp_name = lpath.substr(0, pos);
297 	lpath = lpath.substr(pos + 1);
298 
299 	D4Group *grp = find_child_grp(grp_name);
300 	return (grp == 0) ? 0: grp->find_dim(lpath);
301 }
302 
303 Array *
find_map_source(const string & path)304 D4Group::find_map_source(const string &path)
305 {
306 	BaseType *map_source = m_find_map_source_helper(path);
307 
308 	// TODO more complete semantic checking jhrg 10/16/13
309 	if (map_source && map_source->type() == dods_array_c) return static_cast<Array*>(map_source);
310 
311 	return 0;
312 }
313 
314 BaseType *
m_find_map_source_helper(const string & path)315 D4Group::m_find_map_source_helper(const string &path)
316 {
317 	string lpath = path;		// get a mutable copy
318 
319 	// special-case for the root group
320 	if (lpath[0] == '/') {
321 		if (name() != "/")
322 			throw InternalErr(__FILE__, __LINE__, "Lookup of a FQN starting in non-root group.");
323 		else
324 			lpath = lpath.substr(1);
325 	}
326 
327 	string::size_type pos = lpath.find('/');
328 	if (pos == string::npos) {
329 		// name looks like 'bar'
330 		return var(lpath);
331 	}
332 
333 	// name looks like foo/bar/baz where foo an bar must be groups
334 	string grp_name = lpath.substr(0, pos);
335 	lpath = lpath.substr(pos + 1);
336 
337 	D4Group *grp = find_child_grp(grp_name);
338 	return (grp == 0) ? 0: grp->var(lpath);
339 }
340 
341 D4EnumDef *
find_enum_def(const string & path)342 D4Group::find_enum_def(const string &path)
343 {
344     string lpath = path;        // get a mutable copy
345 
346     // special-case for the root group
347     if (lpath[0] == '/') {
348         if (name() != "/")
349             throw InternalErr(__FILE__, __LINE__, "Lookup of a FQN starting in non-root group.");
350         else
351             lpath = lpath.substr(1);
352     }
353 
354     string::size_type pos = lpath.find('/');
355     if (pos == string::npos) {
356         // name looks like 'bar'
357         return enum_defs()->find_enum_def(lpath);
358     }
359 
360     // name looks like foo/bar/baz where foo and bar must be groups
361     string grp_name = lpath.substr(0, pos);
362     lpath = lpath.substr(pos + 1);
363 
364     D4Group *grp = find_child_grp(grp_name);
365     return (grp == 0) ? 0: grp->enum_defs()->find_enum_def(lpath);
366 }
367 
368 /**
369  * Find a variable using it's FUlly Qualified Name (FQN). The leading '/' is optional.
370  *
371  * @param path The FQN to the variable
372  * @return A BaseType* to the variable of null if it was not found
373  * @see BaseType::FQN()
374  */
375 BaseType *
find_var(const string & path)376 D4Group::find_var(const string &path)
377 {
378     string lpath = path;        // get a mutable copy
379 
380     // special-case for the root group
381     if (lpath[0] == '/') {
382         if (name() != "/")
383             throw InternalErr(__FILE__, __LINE__, "Lookup of a FQN starting in non-root group.");
384         else
385             lpath = lpath.substr(1);
386     }
387 
388     string::size_type pos = lpath.find('/');
389     if (pos == string::npos) {
390         // name looks like 'bar' or bar.baz; lookup in the Constructor that's part of the Group
391     	return var(lpath);
392     }
393 
394     // name looks like foo/bar/baz where foo and bar must be groups
395     string grp_name = lpath.substr(0, pos);
396     lpath = lpath.substr(pos + 1);
397 
398     D4Group *grp = find_child_grp(grp_name);
399     return (grp == 0) ? 0 : grp->find_var(lpath);
400 }
401 
402 /** Compute the size of all of the variables in this group and it's children,
403  * in kilobytes
404  *
405  * @param constrained Should the current constraint be taken into account?
406  * @return The size in kilobytes
407  */
408 long
request_size(bool constrained)409 D4Group::request_size(bool constrained)
410 {
411     long long size = 0;
412     // variables
413     Constructor::Vars_iter v = var_begin();
414     while (v != var_end()) {
415         if (constrained) {
416             if ((*v)->send_p())
417                 size += (*v)->width(constrained);
418         }
419         else {
420             size += (*v)->width(constrained);
421         }
422 
423         ++v;
424     }
425 
426     // groups
427     groupsIter g = d_groups.begin();
428     while (g != d_groups.end())
429         size += (*g++)->request_size(constrained);
430 
431     return size / 1024;
432 }
433 
434 /**
435  * @brief Get the estimated size of a response in kilobytes.
436  * This method looks at the variables in the DDS and computes
437  * the number of bytes in the response.
438  *
439  *  @note This version of the method does a poor job with Sequences. A better
440  *  implementation would look at row-constraint-based limitations and use them
441  *  for size computations. If a row-constraint is missing, return an error.
442  *
443  *  @param constrained Should the size of the whole DDS be used or should the
444  *  current constraint be taken into account?
445  */
request_size_kb(bool constrained)446 uint64_t D4Group::request_size_kb(bool constrained)
447 {
448     uint64_t size = 0;
449     // variables
450     Constructor::Vars_iter v = var_begin();
451     while (v != var_end()) {
452         if (constrained) {
453             if ((*v)->send_p())
454                 size += (*v)->width(constrained);
455         }
456         else {
457             size += (*v)->width(constrained);
458         }
459         ++v;
460     }
461     // groups
462     groupsIter g = d_groups.begin();
463     while (g != d_groups.end())
464         size += (*g++)->request_size(constrained);
465 
466     return size / 1024;
467 }
468 
469 
470 void
set_read_p(bool state)471 D4Group::set_read_p(bool state)
472 {
473     groupsIter g = d_groups.begin();
474     while (g != d_groups.end())
475         (*g++)->set_read_p(state);
476 
477     Constructor::set_read_p(state);
478 }
479 
480 void
set_send_p(bool state)481 D4Group::set_send_p(bool state)
482 {
483     groupsIter g = d_groups.begin();
484     while (g != d_groups.end())
485         (*g++)->set_send_p(state);
486 
487     Constructor::set_send_p(state);
488 }
489 
490 void
intern_data()491 D4Group::intern_data(/*Crc32 &checksum, DMR &dmr, ConstraintEvaluator &eval*/)
492 {
493     groupsIter g = d_groups.begin();
494     while (g != d_groups.end())
495         (*g++)->intern_data(/*checksum, dmr, eval*/);
496 
497     // Specialize how the top-level variables in any Group are sent; include
498     // a checksum for them. A subset operation might make an interior set of
499     // variables, but the parent structure will still be present and the checksum
500     // will be computed for that structure. In other words, DAP4 does not try
501     // to sort out which variables are the 'real' top-level variables and instead
502     // simply computes the CRC for whatever appears as a variable in the root
503     // group.
504 	for (Vars_iter i = d_vars.begin(); i != d_vars.end(); i++) {
505 		// Only send the stuff in the current subset.
506 		if ((*i)->send_p()) {
507 #if 0
508 		    checksum.Reset();
509 #endif
510 			(*i)->intern_data(/*checksum, dmr, eval*/);
511 #if 0
512 			D4Attribute *a = new D4Attribute("DAP4_Checksum_CRC32", attr_str_c);
513 
514 			ostringstream oss;
515 		    oss.setf(ios::hex, ios::basefield);
516 		    oss << setfill('0') << setw(8) << checksum.GetCrc32();
517             a->add_value(oss.str());
518 #if INCLUDE_SOURCE_BYTE_ORDER
519 	        if (um.is_source_big_endian())
520 	            a->add_value("source:big-endian");
521 	        else
522 	            a->add_value("source:little-endian");
523 #endif
524 	        (*i)->attributes()->add_attribute_nocopy(a);
525 			DBG(cerr << "CRC32: " << oss.str() << " for " << (*i)->name() << endl);
526 #endif
527 		}
528 	}
529 }
530 
531 /**
532  * @brief Serialize a Group
533  * @param m The DAP4 Stream Marshaller. This object serializes the data values and
534  * writes checksums (using CRC32) for the top level variables in every Group for which
535  * one or more variables are sent. The DAP4 Marshaller object can be made so that only
536  * the checksums are written.
537  * @param dmr Unused
538  * @param eval Unused
539  * @param filter Unused
540  * @exception Error is thrown if the value needs to be read and that operation fails.
541  */
542 void
serialize(D4StreamMarshaller & m,DMR & dmr,bool filter)543 D4Group::serialize(D4StreamMarshaller &m, DMR &dmr, /*ConstraintEvaluator &eval,*/ bool filter)
544 {
545 #if 0
546     // This will call Constructor read which will, for everything but a Sequence,
547     // read all of the data in one shot. However, the serialize() methods for the
548     // Arrays, Structures, etc., also have read() calls in them and those can be
549     // used to control how long the data are in memory, e.g., limiting the lifetime
550     // of a large array and avoiding having overlapping arrays when they are not
551     // needed. For a sequence read() has different semantics. It is called once
552     // for every instance and the read_p flag is not used.
553     if (!read_p())
554         read();  // read() throws Error
555 #endif
556 
557     groupsIter g = d_groups.begin();
558     while (g != d_groups.end())
559         (*g++)->serialize(m, dmr, filter);
560 
561     // Specialize how the top-level variables in any Group are sent; include
562     // a checksum for them. A subset operation might make an interior set of
563     // variables, but the parent structure will still be present and the checksum
564     // will be computed for that structure. In other words, DAP4 does not try
565     // to sort out which variables are the 'real' top-level variables and instead
566     // simply computes the CRC for whatever appears as a variable in the root
567     // group.
568 	for (Vars_iter i = d_vars.begin(); i != d_vars.end(); i++) {
569 		// Only send the stuff in the current subset.
570 		if ((*i)->send_p()) {
571 			m.reset_checksum();
572 
573 	        DBG(cerr << "Serializing variable " << (*i)->type_name() << " " << (*i)->name() << endl);
574 			(*i)->serialize(m, dmr, filter);
575 
576 			DBG(cerr << "Wrote CRC32: " << m.get_checksum() << " for " << (*i)->name() << endl);
577 			m.put_checksum();
578 		}
579 	}
580 }
581 
deserialize(D4StreamUnMarshaller & um,DMR & dmr)582 void D4Group::deserialize(D4StreamUnMarshaller &um, DMR &dmr)
583 {
584 	groupsIter g = d_groups.begin();
585 	while (g != d_groups.end()) {
586         DBG(cerr << "Deserializing group " << (*g)->name() << endl);
587 		(*g++)->deserialize(um, dmr);
588 	}
589 	// Specialize how the top-level variables in any Group are received; read
590 	// their checksum and store the value in a magic attribute of the variable
591 	for (Vars_iter i = d_vars.begin(); i != d_vars.end(); i++) {
592         DBG(cerr << "Deserializing variable " << (*i)->type_name() << " " << (*i)->name() << endl);
593 		(*i)->deserialize(um, dmr);
594 
595 		D4Attribute *a = new D4Attribute("DAP4_Checksum_CRC32", attr_str_c);
596 		string crc = um.get_checksum_str();
597 		a->add_value(crc);
598 #if INCLUDE_SOURCE_BYTE_ORDER
599 		if (um.is_source_big_endian())
600 		    a->add_value("source:big-endian");
601 		else
602 		    a->add_value("source:little-endian");
603 #endif
604 		DBG(cerr << "Read CRC32: " << crc << " for " << (*i)->name() << endl);
605 		(*i)->attributes()->add_attribute_nocopy(a);
606 	}
607 }
608 
609 void
print_dap4(XMLWriter & xml,bool constrained)610 D4Group::print_dap4(XMLWriter &xml, bool constrained)
611 {
612     if (!name().empty() && name() != "/") {
613         // For named groups, if constrained is true only print if this group
614         // has variables that are marked for transmission. For the root group
615         // this test is not made.
616         if (constrained && !send_p())
617             return;
618 
619         if (xmlTextWriterStartElement(xml.get_writer(), (const xmlChar*) type_name().c_str()) < 0)
620             throw InternalErr(__FILE__, __LINE__, "Could not write " + type_name() + " element");
621 
622         if (xmlTextWriterWriteAttribute(xml.get_writer(), (const xmlChar*) "name", (const xmlChar*) name().c_str()) < 0)
623             throw InternalErr(__FILE__, __LINE__, "Could not write attribute for name");
624     }
625 
626     // dims
627     if (!dims()->empty())
628         dims()->print_dap4(xml, constrained);
629 
630     // enums
631     if (!enum_defs()->empty())
632         enum_defs()->print_dap4(xml, constrained);
633 
634     // variables
635     Constructor::Vars_iter v = var_begin();
636     while (v != var_end())
637         (*v++)->print_dap4(xml, constrained);
638 
639     // attributes
640     attributes()->print_dap4(xml);
641 
642     // groups
643     groupsIter g = d_groups.begin();
644     while (g != d_groups.end())
645         (*g++)->print_dap4(xml, constrained);
646 
647     if (!name().empty() && name() != "/") {
648         if (xmlTextWriterEndElement(xml.get_writer()) < 0)
649             throw InternalErr(__FILE__, __LINE__, "Could not end " + type_name() + " element");
650     }
651 }
652 #if 0
653 /** @brief DAP4 to DAP2 transform
654  *
655  * D4Group objects, with the exception of the root group, "disappear"
656  * into the names of their member variables. Specifically the Group
657  * name is add as a prefix followed by a "/" separator to the names
658  * of all of the Group's member groups variables. The Group attributes
659  * (metadata) are transfered to the parent_attr_table. The Group
660  * members are collected returned in vector.
661  *
662  * @param  The AttrTable pointer parent_attr_table is used by Groups, which disappear
663  * from the DAP2 representation. Their children are returned in the the BaseType vector
664  * their attributes are added to parent_attr_table;
665  * @return A pointer to a vector of BaseType pointers (right?). In this D4Group case the
666  * vector will contain DAP2 versions of all of the member variables of the D4Group instance.
667  * (ex: UInt64) the will return a NULL pointer and so this must be tested!
668  */
669 vector<BaseType *> *
670 D4Group::transform_to_dap2(AttrTable *parent_attr_table)
671 {
672     return transform_to_dap2(parent_attr_table, false);
673 }
674 #endif
675 /** @brief Transform the D4Group's variables to DAP2 variables
676  *
677  * For all of the variables in a D4Group, build a vector of DAP2 variables
678  * that can be directly added to DDS object. Extract the DAP4 variables'
679  * attributes and transfer them the the AttrTable object passed as the first
680  * argument.
681  *
682  * For all variables in the D4Group that are members of child groups (i.e,
683  * not the root group), the name of the group (or names of the groups) will
684  * be prepended to the name of the variable. Group names are separated using
685  * a '/' character.
686  *
687  * The Group attributes are transfered to the parent_attr_table.
688  *
689  * @todo Fix the comment.
690  *
691  * @param parent_attr_table The AttrTable pointer parent_attr_table is used by Groups, which disappear
692  * from the DAP2 representation. Their children are returned in the the BaseType vector
693  * their attributes are added to parent_attr_table;
694  * @return A pointer to a vector of BaseType pointers (right?). In this D4Group case the
695  * vector will contain DAP2 versions of all of the member variables of the D4Group instance.
696  * (ex: UInt64) the will return a NULL pointer and so this must be tested!
697  */
698 vector<BaseType *> *
transform_to_dap2(AttrTable * parent_attr_table)699 D4Group::transform_to_dap2(AttrTable *parent_attr_table)
700 {
701     DBG( cerr << __func__ << "() - BEGIN ("<< name() << ")" << endl);
702 
703     vector<BaseType *> *results = new vector<BaseType *>(); // LEAK
704 
705     // Get the D4Group's attributes
706 #if 0
707     AttrTable *group_attrs = attributes()->get_AttrTable(name());
708 #else
709     AttrTable *group_attrs = new AttrTable();
710     attributes()->transform_attrs_to_dap2(group_attrs);
711     group_attrs->set_name(name());
712 #endif
713 
714     // If this is the root group then copy all of its attributes into the parent_attr_table.
715     // The group_attrs AttrTable* above will be replaced by the parent_attr_table.
716     bool is_root = (name() == "/");
717 
718     if (is_root) {
719         assert(name() == "/");
720         for (AttrTable::Attr_iter i = group_attrs->attr_begin(), e = group_attrs->attr_end(); i != e; ++i) {
721             if ((*i)->type == Attr_container) {
722                 // copy the source container so that the DAS passed in can be
723                 // deleted after calling this method.
724                 AttrTable *at = new AttrTable(*(*i)->attributes);
725                 parent_attr_table->append_container(at, at->get_name());
726             }
727             else {
728                 parent_attr_table->append_attr((*i)->name, AttrType_to_String((*i)->type), (*i)->attr);
729             }
730         }
731         delete group_attrs;
732         group_attrs = parent_attr_table;
733     }
734 
735     // Now we process the child variables of this group
736 
737     vector<BaseType *> dropped_vars;
738     for (D4Group::Vars_citer i = var_begin(), e = var_end(); i != e; ++i) {
739 
740         DBG( cerr << __func__ << "() - Processing member variable '" << (*i)->name() <<
741             "' root: " << (is_root?"true":"false") << endl);
742 
743         vector<BaseType *> *new_vars = (*i)->transform_to_dap2(group_attrs);
744         if (new_vars) {  // Might be un-mappable
745             // It's not so game on..
746             for (vector<BaseType*>::iterator vi = new_vars->begin(), ve = new_vars->end(); vi != ve; vi++) {
747                 string new_name = (is_root ? "" : FQN()) + (*vi)->name();
748                 (*vi)->set_name(new_name);
749                 (*vi)->set_parent(NULL);
750                 results->push_back((*vi));
751 #if 0
752                 (*vi) = NULL;
753 #endif
754                 DBG( cerr << __func__ << "() - Added member variable '" << (*i)->name() << "' " <<
755                     "to results vector. root: "<< (is_root?"true":"false") << endl);
756             }
757 
758             delete new_vars;
759         }
760         else {
761             DBG( cerr << __func__ << "() - Dropping member variable " << (*i)->name() <<
762                 " root: " << (is_root?"true":"false") << endl);
763             // Got back a NULL, so we are dropping this var.
764             dropped_vars.push_back(*i);
765         }
766     }
767 
768     // Process dropped DAP4 vars
769     DBG( cerr << __func__ << "() - Processing " << dropped_vars.size() << " Dropped Variable(s)" << endl);
770 
771     AttrTable *dv_attr_table = make_dropped_vars_attr_table(&dropped_vars);
772     if (dv_attr_table) {
773         group_attrs->append_container(dv_attr_table, dv_attr_table->get_name());
774     }
775 
776     // Get all the child groups.
777     for (D4Group::groupsIter gi = grp_begin(), ge = grp_end(); gi != ge; ++gi) {
778         vector<BaseType *> *d2_vars = (*gi)->transform_to_dap2(group_attrs);
779         if (d2_vars) {
780             for (vector<BaseType *>::iterator i = d2_vars->begin(), e = d2_vars->end(); i != e; ++i) {
781                 results->push_back(*i);
782             }
783         }
784 	delete d2_vars;
785     }
786 
787     if (!is_root) {
788         group_attrs->set_name(name());
789         parent_attr_table->append_container(group_attrs, group_attrs->get_name());
790     }
791 
792     return results;
793 }
794 
795 
796 } /* namespace libdap */
797