1/*
2 * Copyright (c) 1999-2000, Eric Moon.
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions, and the following disclaimer.
11 *
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions, and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * 3. The name of the author may not be used to endorse or promote products
17 *    derived from this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
27 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31
32// MessageIO.cpp
33
34#include "MessageIO.h"
35
36#include <BeBuild.h>
37#include <Debug.h>
38
39#include <cstdlib>
40#include <cstring>
41#include <cctype>
42
43#include <vector>
44#include <utility>
45
46using namespace std;
47
48__USE_CORTEX_NAMESPACE
49
50// -------------------------------------------------------- //
51// constants
52// -------------------------------------------------------- //
53
54const char* const MessageIO::s_element = "BMessage";
55
56const char* _boolEl			= "bool";
57const char* _int8El			= "int8";
58const char* _int16El		= "int16";
59const char* _int32El		= "int32";
60const char* _int64El		= "int64";
61const char* _floatEl		= "float";
62const char* _doubleEl		= "double";
63const char* _stringEl		= "string";
64const char* _pointEl		= "point";
65const char* _rectEl			= "rect";
66
67// -------------------------------------------------------- //
68// *** ctor/dtor/accessor
69// -------------------------------------------------------- //
70
71MessageIO::~MessageIO() {
72	if(m_ownMessage && m_message)
73		delete m_message;
74}
75
76MessageIO::MessageIO() :
77	m_ownMessage(true),
78	m_message(0) {}
79
80// When given a message to export, this object does NOT take
81// responsibility for deleting it.  It will, however, handle
82// deletion of an imported BMessage.
83
84MessageIO::MessageIO(
85	const BMessage*						message) :
86	m_ownMessage(false),
87	m_message(const_cast<BMessage*>(message)) {
88
89	ASSERT(m_message);
90}
91
92void MessageIO::setMessage(
93	BMessage*									message) {
94
95	if(m_ownMessage && m_message)
96		delete m_message;
97	m_ownMessage = false;
98	m_message = message;
99
100	ASSERT(m_message);
101}
102
103// -------------------------------------------------------- //
104// *** static setup method
105// -------------------------------------------------------- //
106// call this method to install hooks for the tags needed by
107// MessageIO into the given document type
108
109/*static*/
110void MessageIO::AddTo(
111	XML::DocumentType*				docType) {
112
113	docType->addMapping(new Mapping<MessageIO>(s_element));
114}
115
116// -------------------------------------------------------- //
117// EXPORT:
118// -------------------------------------------------------- //
119
120void MessageIO::xmlExportBegin(
121	ExportContext&						context) const {
122
123	if(!m_message) {
124		context.reportError("No message data to export.\n");
125		return;
126	}
127	context.beginElement(s_element);
128}
129
130void MessageIO::xmlExportAttributes(
131	ExportContext&						context) const {
132
133	if(m_message->what)
134		context.writeAttr("what", m_message->what);
135	if(m_name.Length())
136		context.writeAttr("name", m_name.String());
137}
138
139void MessageIO::xmlExportContent(
140	ExportContext&						context) const {
141
142
143	ASSERT(m_message);
144	status_t err;
145
146	// +++++ the approach:
147	// 1) build a list of field names
148	// 2) export fields sorted first by index, then name
149
150	typedef vector<BString> field_set;
151	field_set fields;
152
153#ifdef B_BEOS_VERSION_DANO
154	const
155#endif
156	char* name;
157	type_code type;
158	int32 count;
159	for(
160		int32 n = 0;
161		m_message->GetInfo(B_ANY_TYPE, n, &name, &type, &count) == B_OK;
162		++n) {
163		fields.push_back(name);
164	}
165
166	if(!fields.size())
167		return;
168
169	context.beginContent();
170
171	bool done = false;
172	for(int32 n = 0; !done; ++n) {
173
174		done = true;
175
176		for(
177			uint32 fieldIndex = 0;
178			fieldIndex < fields.size();
179			++fieldIndex) {
180
181			if(m_message->GetInfo(
182				fields[fieldIndex].String(),
183				&type,
184				&count) < B_OK || n >= count)
185				continue;
186
187			// found a field at the current index, so don't give up
188			done = false;
189
190			err = _exportField(
191				context,
192				m_message,
193				type,
194				fields[fieldIndex].String(),
195				n);
196
197			if(err < B_OK) {
198				BString errText;
199				errText << "Couldn't export field '" << fields[fieldIndex] <<
200					"' index " << n << ": " << strerror(err) << "\n";
201				context.reportError(errText.String());
202				return;
203			}
204		}
205	}
206}
207
208void MessageIO::xmlExportEnd(
209	ExportContext&						context) const {
210	context.endElement();
211}
212
213
214// -------------------------------------------------------- //
215// IMPORT:
216// -------------------------------------------------------- //
217
218void MessageIO::xmlImportBegin(
219	ImportContext&						context) {
220
221	// create the message
222	if(m_message) {
223		if(m_ownMessage)
224			delete m_message;
225	}
226	m_message = new BMessage();
227	m_name.SetTo("");
228}
229
230void MessageIO::xmlImportAttribute(
231	const char*								key,
232	const char*								value,
233	ImportContext&						context) {
234
235	ASSERT(m_message);
236
237	if(!strcmp(key, "what"))
238		m_message->what = atol(value);
239	else if(!strcmp(key, "name"))
240		m_name.SetTo(value);
241}
242
243void MessageIO::xmlImportContent(
244	const char*								data,
245	uint32										length,
246	ImportContext&						context) {}
247
248void MessageIO::xmlImportChild(
249	IPersistent*							child,
250	ImportContext&						context) {
251
252	ASSERT(m_message);
253
254	if(strcmp(context.element(), s_element) != 0) {
255		context.reportError("Unexpected child element.\n");
256		return;
257	}
258
259	MessageIO* childMessageIO = dynamic_cast<MessageIO*>(child);
260	ASSERT(childMessageIO);
261
262	m_message->AddMessage(
263		childMessageIO->m_name.String(),
264		childMessageIO->m_message);
265}
266
267void MessageIO::xmlImportComplete(
268	ImportContext&						context) {}
269
270void MessageIO::xmlImportChildBegin(
271	const char*								name,
272	ImportContext&						context) {
273
274	// sanity checks
275
276	ASSERT(m_message);
277
278	if(strcmp(context.parentElement(), s_element) != 0) {
279		context.reportError("Unexpected parent element.\n");
280		return;
281	}
282
283	if(!_isValidMessageElement(context.element())) {
284		context.reportError("Invalid message field element.\n");
285		return;
286	}
287
288	m_fieldData.SetTo("");
289}
290
291void MessageIO::xmlImportChildAttribute(
292	const char*								key,
293	const char*								value,
294	ImportContext&						context) {
295
296	if(!strcmp(key, "name"))
297		m_fieldName.SetTo(value);
298	if(!strcmp(key, "value"))
299		m_fieldData.SetTo(value);
300}
301
302void MessageIO::xmlImportChildContent(
303	const char*								data,
304	uint32										length,
305	ImportContext&						context) {
306
307	m_fieldData.Append(data, length);
308}
309
310void MessageIO::xmlImportChildComplete(
311	const char*								name,
312	ImportContext&						context) {
313
314	ASSERT(m_message);
315
316	status_t err = _importField(
317		m_message,
318		name,
319		m_fieldName.String(),
320		m_fieldData.String());
321	if(err < B_OK) {
322		context.reportWarning("Invalid field data.\n");
323	}
324}
325
326// -------------------------------------------------------- //
327// implementation
328// -------------------------------------------------------- //
329
330bool MessageIO::_isValidMessageElement(
331	const char*								element) const {
332
333	if(!strcmp(element, _boolEl)) return true;
334	if(!strcmp(element, _int8El)) return true;
335	if(!strcmp(element, _int16El)) return true;
336	if(!strcmp(element, _int32El)) return true;
337	if(!strcmp(element, _int64El)) return true;
338	if(!strcmp(element, _floatEl)) return true;
339	if(!strcmp(element, _doubleEl)) return true;
340	if(!strcmp(element, _stringEl)) return true;
341	if(!strcmp(element, _pointEl)) return true;
342	if(!strcmp(element, _rectEl)) return true;
343
344	return false;
345}
346
347status_t MessageIO::_importField(
348	BMessage*									message,
349	const char*								element,
350	const char*								name,
351	const char*								data) {
352
353	// skip leading whitespace
354	while(*data && isspace(*data)) ++data;
355
356	if(!strcmp(element, _boolEl)) {
357		bool v;
358		if(!strcmp(data, "true") || !strcmp(data, "1"))
359			v = true;
360		else if(!strcmp(data, "false") || !strcmp(data, "0"))
361			v = false;
362		else
363			return B_BAD_VALUE;
364		return message->AddBool(name, v);
365	}
366
367	if(!strcmp(element, _int8El)) {
368		int8 v = atoi(data);
369		return message->AddInt8(name, v);
370	}
371	if(!strcmp(element, _int16El)) {
372		int16 v = atoi(data);
373		return message->AddInt16(name, v);
374	}
375	if(!strcmp(element, _int32El)) {
376		int32 v = atol(data);
377		return message->AddInt32(name, v);
378	}
379	if(!strcmp(element, _int64El)) {
380//		int64 v = atoll(data);
381		int64 v = strtoll(data, 0, 10);
382		return message->AddInt64(name, v);
383	}
384	if(!strcmp(element, _floatEl)) {
385		float v = (float)atof(data);
386		return message->AddFloat(name, v);
387	}
388	if(!strcmp(element, _doubleEl)) {
389		double v = atof(data);
390		return message->AddDouble(name, v);
391	}
392
393	if(!strcmp(element, _stringEl)) {
394		// +++++ chomp leading/trailing whitespace?
395
396		return message->AddString(name, data);
397	}
398
399	if(!strcmp(element, _pointEl)) {
400		BPoint p;
401		const char* ystart = strchr(data, ',');
402		if(!ystart)
403			return B_BAD_VALUE;
404		++ystart;
405		if(!*ystart)
406			return B_BAD_VALUE;
407		p.x = (float)atof(data);
408		p.y = (float)atof(ystart);
409
410		return message->AddPoint(name, p);
411	}
412
413	if(!strcmp(element, _rectEl)) {
414		BRect r;
415		const char* topstart = strchr(data, ',');
416		if(!topstart)
417			return B_BAD_VALUE;
418		++topstart;
419		if(!*topstart)
420			return B_BAD_VALUE;
421
422		const char* rightstart = strchr(topstart, ',');
423		if(!rightstart)
424			return B_BAD_VALUE;
425		++rightstart;
426		if(!*rightstart)
427			return B_BAD_VALUE;
428
429		const char* bottomstart = strchr(rightstart, ',');
430		if(!bottomstart)
431			return B_BAD_VALUE;
432		++bottomstart;
433		if(!*bottomstart)
434			return B_BAD_VALUE;
435
436		r.left = (float)atof(data);
437		r.top = (float)atof(topstart);
438		r.right = (float)atof(rightstart);
439		r.bottom = (float)atof(bottomstart);
440
441		return message->AddRect(name, r);
442	}
443
444	return B_BAD_INDEX;
445}
446
447status_t MessageIO::_exportField(
448	ExportContext&						context,
449	BMessage*									message,
450	type_code									type,
451	const char*								name,
452	int32											index) const {
453
454	status_t err;
455	BString elementName;
456	BString content;
457
458	switch(type) {
459		case B_BOOL_TYPE: {
460			bool v;
461			err = message->FindBool(name, index, &v);
462			if(err < B_OK)
463				return err;
464			elementName = _boolEl;
465			content = (v ? "true" : "false");
466			break;
467		}
468
469		case B_INT8_TYPE: {
470			int8 v;
471			err = message->FindInt8(name, index, &v);
472			if(err < B_OK)
473				return err;
474			elementName = _int8El;
475			content << (int32)v;
476			break;
477		}
478
479		case B_INT16_TYPE: {
480			int16 v;
481			err = message->FindInt16(name, index, &v);
482			if(err < B_OK)
483				return err;
484			elementName = _int16El;
485			content << (int32)v;
486			break;
487		}
488
489		case B_INT32_TYPE: {
490			int32 v;
491			err = message->FindInt32(name, index, &v);
492			if(err < B_OK)
493				return err;
494			elementName = _int32El;
495			content << v;
496			break;
497		}
498
499		case B_INT64_TYPE: {
500			int64 v;
501			err = message->FindInt64(name, index, &v);
502			if(err < B_OK)
503				return err;
504			elementName = _int64El;
505			content << v;
506			break;
507		}
508
509		case B_FLOAT_TYPE: {
510			float v;
511			err = message->FindFloat(name, index, &v);
512			if(err < B_OK)
513				return err;
514			elementName = _floatEl;
515			content << v; // +++++ need adjustable precision!
516			break;
517		}
518
519		case B_DOUBLE_TYPE: {
520			double v;
521			err = message->FindDouble(name, index, &v);
522			if(err < B_OK)
523				return err;
524			elementName = _doubleEl;
525			content << (float)v; // +++++ need adjustable precision!
526			break;
527		}
528
529		case B_STRING_TYPE: {
530			const char* v;
531			err = message->FindString(name, index, &v);
532			if(err < B_OK)
533				return err;
534			elementName = _stringEl;
535			content = v;
536			break;
537		}
538
539		case B_POINT_TYPE: {
540			BPoint v;
541			err = message->FindPoint(name, index, &v);
542			if(err < B_OK)
543				return err;
544			elementName = _pointEl;
545			content << v.x << ", " << v.y;
546			break;
547		}
548
549		case B_RECT_TYPE: {
550			BRect v;
551			err = message->FindRect(name, index, &v);
552			if(err < B_OK)
553				return err;
554			elementName = _rectEl;
555			content << v.left << ", " << v.top << ", " <<
556				v.right << ", " << v.bottom;
557			break;
558		}
559
560		case B_MESSAGE_TYPE: {
561			BMessage m;
562			err = message->FindMessage(name, index, &m);
563			if(err < B_OK)
564				return err;
565
566			// write child message
567			MessageIO io(&m);
568			io.m_name = name;
569			return context.writeObject(&io);
570		}
571
572		default:
573			return B_BAD_TYPE;
574	}
575
576	// spew the element
577	context.beginElement(elementName.String());
578	context.writeAttr("name", name);
579	context.writeAttr("value", content.String());
580//	context.beginContent();
581//	context.writeString(content);
582	context.endElement();
583
584	return B_OK;
585}
586// END -- MessageIO.cpp --
587