Torque3D/Engine/source/persistence/taml/json/tamlJSONReader.cpp
2019-04-20 18:52:24 -05:00

687 lines
22 KiB
C++

//-----------------------------------------------------------------------------
// Copyright (c) 2013 GarageGames, LLC
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
#include "persistence/taml/json/tamlJSONReader.h"
#include "persistence/taml/json/tamlJSONWriter.h"
#include "core/stream/fileStream.h"
#include "core/strings/stringUnit.h"
#include "core/frameAllocator.h"
// Debug Profiling.
#include "platform/profiler.h"
//-----------------------------------------------------------------------------
SimObject* TamlJSONReader::read( FileStream& stream )
{
// Debug Profiling.
PROFILE_SCOPE(TamlJSONReader_Read);
// Read JSON file.
const U32 streamSize = stream.getStreamSize();
FrameTemp<char> jsonText( streamSize + 1 );
if ( !stream.read( streamSize, jsonText ) )
{
// Warn!
Con::warnf("TamlJSONReader::read() - Could not load Taml JSON file from stream.");
return NULL;
}
jsonText[streamSize] = '\0';
// Create JSON document.
rapidjson::Document document;
document.Parse<0>( jsonText );
// Check the document is valid.
if ( document.GetType() != rapidjson::kObjectType )
{
// Warn!
Con::warnf("TamlJSONReader::read() - Load Taml JSON file from stream but was invalid.");
return NULL;
}
// Parse root value.
SimObject* pSimObject = parseType( document.MemberBegin() );
// Reset parse.
resetParse();
return pSimObject;
}
//-----------------------------------------------------------------------------
void TamlJSONReader::resetParse( void )
{
// Debug Profiling.
PROFILE_SCOPE(TamlJSONReader_ResetParse);
// Clear object reference map.
mObjectReferenceMap.clear();
}
//-----------------------------------------------------------------------------
SimObject* TamlJSONReader::parseType( const rapidjson::Value::ConstMemberIterator& memberItr )
{
// Debug Profiling.
PROFILE_SCOPE(TamlJSONReader_ParseType);
// Fetch name and value.
const rapidjson::Value& typeName = memberItr->name;
const rapidjson::Value& typeValue = memberItr->value;
// Is value an object?
if ( !typeValue.IsObject() )
{
// No, so warn.
Con::warnf( "TamlJSONReader::parseType() - Cannot process type '%s' as it is not an object.", typeName.GetString() );
return NULL;
}
// Fetch engine type name (demangled).
StringTableEntry engineTypeName = getDemangledName( typeName.GetString() );
// Fetch reference to Id.
const U32 tamlRefToId = getTamlRefToId( typeValue );
// Do we have a reference to Id?
if ( tamlRefToId != 0 )
{
// Yes, so fetch reference.
typeObjectReferenceHash::Iterator referenceItr = mObjectReferenceMap.find( tamlRefToId );
// Did we find the reference?
if ( referenceItr == mObjectReferenceMap.end() )
{
// No, so warn.
Con::warnf( "TamlJSONReader::parseType() - Could not find a reference Id of '%d'", tamlRefToId );
return NULL;
}
// Return object.
return referenceItr->value;
}
// No, so fetch reference Id.
const U32 tamlRefId = getTamlRefId( typeValue );
// Create type.
SimObject* pSimObject = Taml::createType( engineTypeName, mpTaml );
// Finish if we couldn't create the type.
if ( pSimObject == NULL )
return NULL;
// Find Taml callbacks.
TamlCallbacks* pCallbacks = dynamic_cast<TamlCallbacks*>( pSimObject );
TamlCustomNodes customNodes;
// Are there any Taml callbacks?
if ( pCallbacks != NULL )
{
// Yes, so call it.
mpTaml->tamlPreRead( pCallbacks );
}
// Parse field members.
for( rapidjson::Value::ConstMemberIterator fieldMemberItr = typeValue.MemberBegin(); fieldMemberItr != typeValue.MemberEnd(); ++fieldMemberItr )
{
// Fetch value.
const rapidjson::Value& fieldValue = fieldMemberItr->value;
// Skip if not a field.
if ( fieldValue.IsObject() )
continue;
// Parse as field.
parseField( fieldMemberItr, pSimObject );
}
// Fetch object name.
StringTableEntry objectName = StringTable->insert( getTamlObjectName( typeValue ) );
// Does the object require a name?
if ( objectName == StringTable->EmptyString() )
{
// No, so just register anonymously.
pSimObject->registerObject();
}
else
{
// Yes, so register a named object.
pSimObject->registerObject( objectName );
// Was the name assigned?
if ( pSimObject->getName() != objectName )
{
// No, so warn that the name was rejected.
Con::warnf( "Taml::parseType() - Registered an instance of type '%s' but a request to name it '%s' was rejected. This is typically because an object of that name already exists.",
engineTypeName, objectName );
}
}
// Do we have a reference Id?
if ( tamlRefId != 0 )
{
// Yes, so insert reference.
mObjectReferenceMap.insertUnique( tamlRefId, pSimObject );
}
// Parse children and custom node members.
for( rapidjson::Value::ConstMemberIterator objectMemberItr = typeValue.MemberBegin(); objectMemberItr != typeValue.MemberEnd(); ++objectMemberItr )
{
// Fetch name and value.
const rapidjson::Value& objName = objectMemberItr->name;
const rapidjson::Value& objectValue = objectMemberItr->value;
// Skip if not an object.
if ( !objectValue.IsObject() )
continue;
// Find the period character in the name.
const char* pPeriod = dStrchr( objName.GetString(), '.' );
// Did we find the period?
if ( pPeriod == NULL )
{
// No, so parse child object.
parseChild( objectMemberItr, pSimObject );
continue;
}
// Yes, so parse custom object.
parseCustom( objectMemberItr, pSimObject, pPeriod+1, customNodes );
}
// Call custom read.
if ( pCallbacks )
{
mpTaml->tamlCustomRead( pCallbacks, customNodes );
mpTaml->tamlPostRead( pCallbacks, customNodes );
}
// Return object.
return pSimObject;
}
//-----------------------------------------------------------------------------
inline void TamlJSONReader::parseField( rapidjson::Value::ConstMemberIterator& memberItr, SimObject* pSimObject )
{
// Debug Profiling.
PROFILE_SCOPE(TamlJSONReader_ParseField);
// Fetch name and value.
const rapidjson::Value& name = memberItr->name;
const rapidjson::Value& value = memberItr->value;
// Insert the field name.
StringTableEntry fieldName = StringTable->insert( name.GetString() );
// Ignore if this is a Taml attribute.
if ( fieldName == tamlRefIdName ||
fieldName == tamlRefToIdName ||
fieldName == tamlNamedObjectName )
return;
// Get field value.
char valueBuffer[4096];
if ( !parseStringValue( valueBuffer, sizeof(valueBuffer), value, fieldName ) )
{
// Warn.
Con::warnf( "Taml::parseField() Could not interpret value for field '%s'", fieldName );
return;
}
// Set field.
pSimObject->setPrefixedDataField(fieldName, NULL, valueBuffer);
}
//-----------------------------------------------------------------------------
inline void TamlJSONReader::parseChild( rapidjson::Value::ConstMemberIterator& memberItr, SimObject* pSimObject )
{
// Debug Profiling.
PROFILE_SCOPE(TamlJSONReader_ParseChild);
// Fetch name.
const rapidjson::Value& name = memberItr->name;
// Fetch the Taml children.
TamlChildren* pChildren = dynamic_cast<TamlChildren*>( pSimObject );
// Is this a Taml child?
if ( pChildren == NULL )
{
// No, so warn.
Con::warnf("Taml::parseChild() - Child member '%s' found under parent '%s' but object cannot have children.",
name.GetString(),
pSimObject->getClassName() );
return;
}
// Fetch any container child class specifier.
AbstractClassRep* pContainerChildClass = pSimObject->getClassRep()->getContainerChildClass( true );
// Parse child member.
SimObject* pChildSimObject = parseType( memberItr );
// Finish if the child was not created.
if ( pChildSimObject == NULL )
return;
// Do we have a container child class?
if ( pContainerChildClass != NULL )
{
// Yes, so is the child object the correctly derived type?
if ( !pChildSimObject->getClassRep()->isClass( pContainerChildClass ) )
{
// No, so warn.
Con::warnf("Taml::parseChild() - Child element '%s' found under parent '%s' but object is restricted to children of type '%s'.",
pChildSimObject->getClassName(),
pSimObject->getClassName(),
pContainerChildClass->getClassName() );
// NOTE: We can't delete the object as it may be referenced elsewhere!
pChildSimObject = NULL;
return;
}
}
// Add child.
pChildren->addTamlChild( pChildSimObject );
// Find Taml callbacks for child.
TamlCallbacks* pChildCallbacks = dynamic_cast<TamlCallbacks*>( pChildSimObject );
// Do we have callbacks on the child?
if ( pChildCallbacks != NULL )
{
// Yes, so perform callback.
mpTaml->tamlAddParent( pChildCallbacks, pSimObject );
}
}
//-----------------------------------------------------------------------------
inline void TamlJSONReader::parseCustom( rapidjson::Value::ConstMemberIterator& memberItr, SimObject* pSimObject, const char* pCustomNodeName, TamlCustomNodes& customNodes )
{
// Debug Profiling.
PROFILE_SCOPE(TamlJSONReader_ParseCustom);
// Fetch value.
const rapidjson::Value& value = memberItr->value;
// Add custom node.
TamlCustomNode* pCustomNode = customNodes.addNode( pCustomNodeName );
// Iterate members.
for( rapidjson::Value::ConstMemberIterator customMemberItr = value.MemberBegin(); customMemberItr != value.MemberEnd(); ++customMemberItr )
{
// Fetch value.
const rapidjson::Value& customValue = customMemberItr->value;
// Is the member an object?
if ( !customValue.IsObject() && !customValue.IsArray() )
{
// No, so warn.
Con::warnf( "Taml::parseCustom() - Cannot process custom node name '%s' member as child value is not an object or array.", pCustomNodeName );
return;
}
// Parse custom node.
parseCustomNode( customMemberItr, pCustomNode );
}
}
//-----------------------------------------------------------------------------
inline void TamlJSONReader::parseCustomNode( rapidjson::Value::ConstMemberIterator& memberItr, TamlCustomNode* pCustomNode )
{
// Debug Profiling.
PROFILE_SCOPE(TamlJSONReader_ParseCustomNode);
// Fetch name and value.
const rapidjson::Value& name = memberItr->name;
const rapidjson::Value& value = memberItr->value;
// Is the value an object?
if ( value.IsObject() )
{
// Yes, so is the node a proxy object?
if ( getTamlRefId( value ) != 0 || getTamlRefToId( value ) != 0 )
{
// Yes, so parse proxy object.
SimObject* pProxyObject = parseType( memberItr );
// Add child node.
pCustomNode->addNode( pProxyObject );
return;
}
}
char valueBuffer[4096];
// Fetch the node name.
StringTableEntry nodeName = getDemangledName( name.GetString() );
// Yes, so add child node.
TamlCustomNode* pChildNode = pCustomNode->addNode( nodeName );
// Is the value an array?
if ( value.IsArray() )
{
// Yes, so does it have a single entry?
if ( value.Size() == 1 )
{
// Yes, so parse the node text.
if ( parseStringValue( valueBuffer, sizeof(valueBuffer), *value.Begin(), nodeName ) )
{
pChildNode->setNodeText( valueBuffer );
}
else
{
// Warn.
Con::warnf( "Taml::parseCustomNode() - Encountered text in the custom node '%s' but could not interpret the value.", nodeName );
}
}
else
{
// No, so warn.
Con::warnf( "Taml::parseCustomNode() - Encountered text in the custom node '%s' but more than a single element was found in the array.", nodeName );
}
return;
}
// Iterate child members.
for( rapidjson::Value::ConstMemberIterator childMemberItr = value.MemberBegin(); childMemberItr != value.MemberEnd(); ++childMemberItr )
{
// Fetch name and value.
const rapidjson::Value& childName = childMemberItr->name;
const rapidjson::Value& childValue = childMemberItr->value;
// Fetch the field name.
StringTableEntry fieldName = StringTable->insert( childName.GetString() );
// Is the value an array?
if ( childValue.IsArray() )
{
// Yes, so does it have a single entry?
if ( childValue.Size() == 1 )
{
// Yes, so parse the node text.
if ( parseStringValue( valueBuffer, sizeof(valueBuffer), *childValue.Begin(), fieldName ) )
{
// Yes, so add sub-child node.
TamlCustomNode* pSubChildNode = pChildNode->addNode( fieldName );
// Set sub-child text.
pSubChildNode->setNodeText( valueBuffer );
continue;
}
// Warn.
Con::warnf( "Taml::parseCustomNode() - Encountered text in the custom node '%s' but could not interpret the value.", fieldName );
return;
}
// No, so warn.
Con::warnf( "Taml::parseCustomNode() - Encountered text in the custom node '%s' but more than a single element was found in the array.", fieldName );
return;
}
// Is the member an object?
if ( childValue.IsObject() )
{
// Yes, so parse custom node.
parseCustomNode( childMemberItr, pChildNode );
continue;
}
// Ignore if this is a Taml attribute.
if ( fieldName == tamlRefIdName ||
fieldName == tamlRefToIdName ||
fieldName == tamlNamedObjectName )
continue;
// Parse string value.
if ( !parseStringValue( valueBuffer, sizeof(valueBuffer), childValue, childName.GetString() ) )
{
// Warn.
Con::warnf( "Taml::parseCustomNode() - Could not interpret value for field '%s'", fieldName );
continue;
}
// Add node field.
pChildNode->addField( fieldName, valueBuffer );
}
}
//-----------------------------------------------------------------------------
inline StringTableEntry TamlJSONReader::getDemangledName( const char* pMangledName )
{
// Debug Profiling.
PROFILE_SCOPE(TamlJSONReader_GetDemangledName);
// Is the type name mangled?
if ( StringUnit::getUnitCount( pMangledName, JSON_RFC4627_NAME_MANGLING_CHARACTERS ) > 1 )
{
// Yes, so fetch type name portion.
return StringTable->insert( StringUnit::getUnit( pMangledName, 0, JSON_RFC4627_NAME_MANGLING_CHARACTERS ) );
}
// No, so use all the type name.
return StringTable->insert( pMangledName );
}
//-----------------------------------------------------------------------------
inline bool TamlJSONReader::parseStringValue( char* pBuffer, const S32 bufferSize, const rapidjson::Value& value, const char* pName )
{
// Debug Profiling.
PROFILE_SCOPE(TamlJSONReader_ParseStringValue);
// Handle field value appropriately.
if ( value.IsString() )
{
dSprintf( pBuffer, bufferSize, "%s", value.GetString() );
return true;
}
if ( value.IsNumber() )
{
if ( value.IsInt() )
{
dSprintf( pBuffer, bufferSize, "%d", value.GetInt() );
return true;
}
if ( value.IsUint() )
{
dSprintf( pBuffer, bufferSize, "%d", value.GetUint() );
return true;
}
if ( value.IsInt64() )
{
dSprintf( pBuffer, bufferSize, "%d", value.GetInt64() );
return true;
}
if ( value.IsUint64() )
{
dSprintf( pBuffer, bufferSize, "%d", value.GetUint64() );
return true;
}
if ( value.IsDouble() )
{
dSprintf( pBuffer, bufferSize, "%f", value.GetDouble() );
return true;
}
}
if ( value.IsBool() )
{
dSprintf( pBuffer, bufferSize, "%d", value.GetBool() );
return true;
}
// Failed to get value type.
Con::warnf( "Taml: Encountered a field '%s' but its value is an unknown type.", pName );
return false;
}
//-----------------------------------------------------------------------------
inline U32 TamlJSONReader::getTamlRefId( const rapidjson::Value& value )
{
// Debug Profiling.
PROFILE_SCOPE(TamlJSONReader_GetTamlRefId);
// Is the value an object?
if ( !value.IsObject() )
{
// No, so warn.
Con::warnf( "Taml::getTamlRefId() - Cannot get '%s' member as value is not an object.", tamlRefIdName );
return 0;
}
// Iterate members.
for( rapidjson::Value::ConstMemberIterator memberItr = value.MemberBegin(); memberItr != value.MemberEnd(); ++memberItr )
{
// Insert member name.
StringTableEntry attributeName = StringTable->insert( memberItr->name.GetString() );
// Skip if not the correct attribute.
if ( attributeName != tamlRefIdName )
continue;
// Is the value an integer?
if ( !memberItr->value.IsInt() )
{
// No, so warn.
Con::warnf( "Taml::getTamlRefId() - Found '%s' member but it is not an integer.", tamlRefIdName );
return 0;
}
// Return it.
return (U32)memberItr->value.GetInt();
}
// Not found.
return 0;
}
//-----------------------------------------------------------------------------
inline U32 TamlJSONReader::getTamlRefToId( const rapidjson::Value& value )
{
// Debug Profiling.
PROFILE_SCOPE(TamlJSONReader_GetTamlRefToId);
// Is the value an object?
if ( !value.IsObject() )
{
// No, so warn.
Con::warnf( "Taml::getTamlRefToId() - Cannot get '%s' member as value is not an object.", tamlRefToIdName );
return 0;
}
// Iterate members.
for( rapidjson::Value::ConstMemberIterator memberItr = value.MemberBegin(); memberItr != value.MemberEnd(); ++memberItr )
{
// Insert member name.
StringTableEntry attributeName = StringTable->insert( memberItr->name.GetString() );
// Skip if not the correct attribute.
if ( attributeName != tamlRefToIdName )
continue;
// Is the value an integer?
if ( !memberItr->value.IsInt() )
{
// No, so warn.
Con::warnf( "Taml::getTamlRefToId() - Found '%s' member but it is not an integer.", tamlRefToIdName );
return 0;
}
// Return it.
return (U32)memberItr->value.GetInt();
}
// Not found.
return 0;
}
//-----------------------------------------------------------------------------
inline const char* TamlJSONReader::getTamlObjectName( const rapidjson::Value& value )
{
// Debug Profiling.
PROFILE_SCOPE(TamlJSONReader_GetTamlObjectName);
// Is the value an object?
if ( !value.IsObject() )
{
// No, so warn.
Con::warnf( "Taml::getTamlObjectName() - Cannot get '%s' member as value is not an object.", tamlNamedObjectName );
return 0;
}
// Iterate members.
for( rapidjson::Value::ConstMemberIterator memberItr = value.MemberBegin(); memberItr != value.MemberEnd(); ++memberItr )
{
// Insert member name.
StringTableEntry attributeName = StringTable->insert( memberItr->name.GetString() );
// Skip if not the correct attribute.
if ( attributeName != tamlNamedObjectName )
continue;
// Is the value an integer?
if ( !memberItr->value.IsString() )
{
// No, so warn.
Con::warnf( "Taml::getTamlObjectName() - Found '%s' member but it is not a string.", tamlNamedObjectName );
return NULL;
}
// Return it.
return memberItr->value.GetString();
}
// Not found.
return NULL;
}