/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
#include <tinyxml.h>
#include <UniquePtr.h>
#include "Log.h"
#include "GenericFactory.h"
#include "task/ModelBuilder.h"
static const int MAX_NO_CHILDREN = 8;
static const ModelBuilder::ChildInfo CASE_TABLE[] = {
{ TaskGeneric::ETaskSetup, true },
{ TaskGeneric::ETaskAction, true },
{ TaskGeneric::ETaskSave, false }
};
static const ModelBuilder::ChildInfo SETUP_TABLE[] = {
{ TaskGeneric::ETaskSound, false },
{ TaskGeneric::ETaskProcess, false },
{ TaskGeneric::ETaskDownload, false }
};
static const ModelBuilder::ChildInfo ACTION_TABLE[] = {
{ TaskGeneric::ETaskSequential, true }
};
static const ModelBuilder::ChildInfo SEQUENTIAL_TABLE[] = {
{ TaskGeneric::ETaskSequential, false },
{ TaskGeneric::ETaskInput, false },
{ TaskGeneric::ETaskOutput, false },
{ TaskGeneric::ETaskProcess, false },
{ TaskGeneric::ETaskMessage, false }
};
ModelBuilder::ParsingInfo ModelBuilder::mParsingTable[ModelBuilder::PARSING_TABLE_SIZE] = {
{ "case", TaskGeneric::ETaskCase, CASE_TABLE,
sizeof(CASE_TABLE)/sizeof(ModelBuilder::ChildInfo) },
{ "setup", TaskGeneric::ETaskSetup, SETUP_TABLE,
sizeof(SETUP_TABLE)/sizeof(ModelBuilder::ChildInfo) },
{ "action", TaskGeneric::ETaskAction, ACTION_TABLE,
sizeof(ACTION_TABLE)/sizeof(ModelBuilder::ChildInfo) },
{ "sequential", TaskGeneric::ETaskSequential, SEQUENTIAL_TABLE,
sizeof(SEQUENTIAL_TABLE)/sizeof(ModelBuilder::ChildInfo) },
{ "process", TaskGeneric::ETaskProcess, NULL, 0 },
{ "input", TaskGeneric::ETaskInput, NULL, 0 },
{ "output", TaskGeneric::ETaskOutput, NULL, 0 },
{ "sound", TaskGeneric::ETaskSound, NULL, 0 },
{ "save", TaskGeneric::ETaskSave, NULL, 0 },
{ "message", TaskGeneric::ETaskMessage, NULL, 0 },
{ "download", TaskGeneric::ETaskDownload, NULL, 0 }
};
ModelBuilder::ModelBuilder()
: mFactory(new GenericFactory())
{
}
ModelBuilder::ModelBuilder(GenericFactory* factory)
: mFactory(factory)
{
}
ModelBuilder::~ModelBuilder()
{
delete mFactory;
}
TaskGeneric* ModelBuilder::parseTestDescriptionXml(const android::String8& xmlFileName,
bool caseOnly)
{
TiXmlDocument doc(xmlFileName.string());
if (!doc.LoadFile()) {
LOGE("ModelBuilder::parseTestDescriptionXml cannot load file %s", xmlFileName.string());
return NULL;
}
const TiXmlElement* root;
if ((root = doc.FirstChildElement("case")) != NULL) {
return parseCase(*root);
} else if (!caseOnly && ((root = doc.FirstChildElement("batch")) != NULL)) {
return parseBatch(*root, xmlFileName);
} else {
LOGE("ModelBuilder::parseTestDescriptionXml wrong root element");
return NULL;
}
}
TaskGeneric* ModelBuilder::parseGeneric(const TiXmlElement& self, int tableIndex)
{
TaskGeneric::TaskType typeSelf(mParsingTable[tableIndex].type);
int Nchildren = mParsingTable[tableIndex].Nchildren;
UniquePtr<TaskGeneric> taskSelf(mFactory->createTask(typeSelf));
if (taskSelf.get() == NULL) {
return NULL;
}
if (!parseAttributes(self, *taskSelf.get())) {
return NULL;
}
// copy mandatory flags, and will be cleared once the item is found
bool mandatoryAbsence[MAX_NO_CHILDREN];
const ModelBuilder::ChildInfo* childTable = mParsingTable[tableIndex].allowedChildren;
for (int i = 0; i < Nchildren; i++) {
mandatoryAbsence[i] = childTable[i].mandatory;
}
// handle children
const TiXmlElement* child = self.FirstChildElement();
while (child != NULL) {
TaskGeneric::TaskType childType(TaskGeneric::ETaskInvalid);
int i;
// check if type is valid
for (i = 0; i < PARSING_TABLE_SIZE; i++) {
if (strcmp(child->Value(), mParsingTable[i].name) == 0) {
break;
}
}
if (i == PARSING_TABLE_SIZE) {
LOGE("ModelBuilder::parseGeneric unknown element %s", child->Value());
return NULL;
}
childType = mParsingTable[i].type;
int j;
// check if the type is allowed as child
for (j = 0; j < Nchildren; j++) {
if (childTable[j].type == childType) {
if (childTable[j].mandatory) {
mandatoryAbsence[j] = false;
}
break;
}
}
if (j == Nchildren) {
LOGE("ModelBuilder::parseGeneric unsupported child type %d for type %d", childType,
typeSelf);
return NULL;
}
UniquePtr<TaskGeneric> taskChild(parseGeneric(*child, i));
if (taskChild.get() == NULL) {
LOGE("ModelBuilder::parseGeneric failed in parsing child type %d for type %d",
childType, typeSelf);
return NULL;
}
if (!taskSelf.get()->addChild(taskChild.get())) {
LOGE("ModelBuilder::parseGeneric cannot add child type %d to type %d", childType,
typeSelf);
return NULL;
}
TaskGeneric* donotuse = taskChild.release();
child = child->NextSiblingElement();
}
for (int i = 0; i < Nchildren; i++) {
if (mandatoryAbsence[i]) {
LOGE("ModelBuilder::parseGeneric mandatory child type %d not present in type %d",
childTable[i].type, typeSelf);
return NULL;
}
}
return taskSelf.release();
}
TaskCase* ModelBuilder::parseCase(const TiXmlElement& root)
{
// position 0 of mParsingTable should be "case"
return reinterpret_cast<TaskCase*>(parseGeneric(root, 0));
}
TaskBatch* ModelBuilder::parseBatch(const TiXmlElement& root, const android::String8& xmlFileName)
{
UniquePtr<TaskBatch> batch(
reinterpret_cast<TaskBatch*>(mFactory->createTask(TaskGeneric::ETaskBatch)));
if (batch.get() == NULL) {
LOGE("ModelBuilder::handleBatch cannot create TaskBatch");
return NULL;
}
if (!parseAttributes(root, *batch.get())) {
return NULL;
}
const TiXmlElement* inc = root.FirstChildElement("include");
if (inc == NULL) {
LOGE("ModelBuilder::handleBatch no include inside batch");
return NULL;
}
android::String8 path = xmlFileName.getPathDir();
UniquePtr<TaskCase> testCase;
int i = 0;
while (1) {
if (inc == NULL) {
break;
}
if (strcmp(inc->Value(),"include") != 0) {
LOGE("ModelBuilder::handleBatch invalid element %s", inc->Value());
}
testCase.reset(parseInclude(*inc, path));
if (testCase.get() == NULL) {
LOGE("ModelBuilder::handleBatch cannot create test case from include");
return NULL;
}
if (!batch.get()->addChild(testCase.get())) {
return NULL;
}
TaskGeneric* donotuse = testCase.release(); // parent will take care of destruction.
inc = inc->NextSiblingElement();
i++;
}
if (i == 0) {
// at least one include should exist.
LOGE("ModelBuilder::handleBatch no include elements");
return NULL;
}
return batch.release();
}
TaskCase* ModelBuilder::parseInclude(const TiXmlElement& elem, const android::String8& path)
{
const char* fileName = elem.Attribute("file");
if (fileName == NULL) {
LOGE("ModelBuilder::handleBatch no include elements");
return NULL;
}
android::String8 incFile = path;
incFile.appendPath(fileName);
// again no dynamic_cast intentionally
return reinterpret_cast<TaskCase*>(parseTestDescriptionXml(incFile, true));
}
bool ModelBuilder::parseAttributes(const TiXmlElement& elem, TaskGeneric& task)
{
const TiXmlAttribute* attr = elem.FirstAttribute();
while (1) {
if (attr == NULL) {
break;
}
android::String8 name(attr->Name());
android::String8 value(attr->Value());
if (!task.parseAttribute(name, value)) {
LOGE("ModelBuilder::parseAttributes cannot parse attribute %s:%s for task type %d",
attr->Name(), attr->Value(), task.getType());
return false;
}
attr = attr->Next();
}
return true;
}