/* * 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; }