1 /*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17 #include <tinyxml.h>
18
19 #include <UniquePtr.h>
20
21 #include "Log.h"
22 #include "GenericFactory.h"
23 #include "task/ModelBuilder.h"
24
25 static const int MAX_NO_CHILDREN = 8;
26 static const ModelBuilder::ChildInfo CASE_TABLE[] = {
27 { TaskGeneric::ETaskSetup, true },
28 { TaskGeneric::ETaskAction, true },
29 { TaskGeneric::ETaskSave, false }
30 };
31 static const ModelBuilder::ChildInfo SETUP_TABLE[] = {
32 { TaskGeneric::ETaskSound, false },
33 { TaskGeneric::ETaskProcess, false },
34 { TaskGeneric::ETaskDownload, false }
35 };
36 static const ModelBuilder::ChildInfo ACTION_TABLE[] = {
37 { TaskGeneric::ETaskSequential, true }
38 };
39 static const ModelBuilder::ChildInfo SEQUENTIAL_TABLE[] = {
40 { TaskGeneric::ETaskSequential, false },
41 { TaskGeneric::ETaskInput, false },
42 { TaskGeneric::ETaskOutput, false },
43 { TaskGeneric::ETaskProcess, false },
44 { TaskGeneric::ETaskMessage, false }
45 };
46
47
48 ModelBuilder::ParsingInfo ModelBuilder::mParsingTable[ModelBuilder::PARSING_TABLE_SIZE] = {
49 { "case", TaskGeneric::ETaskCase, CASE_TABLE,
50 sizeof(CASE_TABLE)/sizeof(ModelBuilder::ChildInfo) },
51 { "setup", TaskGeneric::ETaskSetup, SETUP_TABLE,
52 sizeof(SETUP_TABLE)/sizeof(ModelBuilder::ChildInfo) },
53 { "action", TaskGeneric::ETaskAction, ACTION_TABLE,
54 sizeof(ACTION_TABLE)/sizeof(ModelBuilder::ChildInfo) },
55 { "sequential", TaskGeneric::ETaskSequential, SEQUENTIAL_TABLE,
56 sizeof(SEQUENTIAL_TABLE)/sizeof(ModelBuilder::ChildInfo) },
57 { "process", TaskGeneric::ETaskProcess, NULL, 0 },
58 { "input", TaskGeneric::ETaskInput, NULL, 0 },
59 { "output", TaskGeneric::ETaskOutput, NULL, 0 },
60 { "sound", TaskGeneric::ETaskSound, NULL, 0 },
61 { "save", TaskGeneric::ETaskSave, NULL, 0 },
62 { "message", TaskGeneric::ETaskMessage, NULL, 0 },
63 { "download", TaskGeneric::ETaskDownload, NULL, 0 }
64 };
65
66
ModelBuilder()67 ModelBuilder::ModelBuilder()
68 : mFactory(new GenericFactory())
69 {
70
71 }
72
ModelBuilder(GenericFactory * factory)73 ModelBuilder::ModelBuilder(GenericFactory* factory)
74 : mFactory(factory)
75 {
76
77 }
~ModelBuilder()78 ModelBuilder::~ModelBuilder()
79 {
80 delete mFactory;
81 }
82
parseTestDescriptionXml(const android::String8 & xmlFileName,bool caseOnly)83 TaskGeneric* ModelBuilder::parseTestDescriptionXml(const android::String8& xmlFileName,
84 bool caseOnly)
85 {
86 TiXmlDocument doc(xmlFileName.string());
87 if (!doc.LoadFile()) {
88 LOGE("ModelBuilder::parseTestDescriptionXml cannot load file %s", xmlFileName.string());
89 return NULL;
90 }
91 const TiXmlElement* root;
92 if ((root = doc.FirstChildElement("case")) != NULL) {
93 return parseCase(*root);
94 } else if (!caseOnly && ((root = doc.FirstChildElement("batch")) != NULL)) {
95 return parseBatch(*root, xmlFileName);
96 } else {
97 LOGE("ModelBuilder::parseTestDescriptionXml wrong root element");
98 return NULL;
99 }
100 }
101
parseGeneric(const TiXmlElement & self,int tableIndex)102 TaskGeneric* ModelBuilder::parseGeneric(const TiXmlElement& self, int tableIndex)
103 {
104 TaskGeneric::TaskType typeSelf(mParsingTable[tableIndex].type);
105 int Nchildren = mParsingTable[tableIndex].Nchildren;
106 UniquePtr<TaskGeneric> taskSelf(mFactory->createTask(typeSelf));
107 if (taskSelf.get() == NULL) {
108 return NULL;
109 }
110 if (!parseAttributes(self, *taskSelf.get())) {
111 return NULL;
112 }
113 // copy mandatory flags, and will be cleared once the item is found
114 bool mandatoryAbsence[MAX_NO_CHILDREN];
115 const ModelBuilder::ChildInfo* childTable = mParsingTable[tableIndex].allowedChildren;
116 for (int i = 0; i < Nchildren; i++) {
117 mandatoryAbsence[i] = childTable[i].mandatory;
118 }
119
120 // handle children
121 const TiXmlElement* child = self.FirstChildElement();
122 while (child != NULL) {
123 TaskGeneric::TaskType childType(TaskGeneric::ETaskInvalid);
124 int i;
125 // check if type is valid
126 for (i = 0; i < PARSING_TABLE_SIZE; i++) {
127 if (strcmp(child->Value(), mParsingTable[i].name) == 0) {
128 break;
129 }
130 }
131 if (i == PARSING_TABLE_SIZE) {
132 LOGE("ModelBuilder::parseGeneric unknown element %s", child->Value());
133 return NULL;
134 }
135 childType = mParsingTable[i].type;
136 int j;
137 // check if the type is allowed as child
138 for (j = 0; j < Nchildren; j++) {
139 if (childTable[j].type == childType) {
140 if (childTable[j].mandatory) {
141 mandatoryAbsence[j] = false;
142 }
143 break;
144 }
145 }
146 if (j == Nchildren) {
147 LOGE("ModelBuilder::parseGeneric unsupported child type %d for type %d", childType,
148 typeSelf);
149 return NULL;
150 }
151 UniquePtr<TaskGeneric> taskChild(parseGeneric(*child, i));
152 if (taskChild.get() == NULL) {
153 LOGE("ModelBuilder::parseGeneric failed in parsing child type %d for type %d",
154 childType, typeSelf);
155 return NULL;
156 }
157 if (!taskSelf.get()->addChild(taskChild.get())) {
158 LOGE("ModelBuilder::parseGeneric cannot add child type %d to type %d", childType,
159 typeSelf);
160 return NULL;
161 }
162 TaskGeneric* donotuse = taskChild.release();
163
164 child = child->NextSiblingElement();
165 }
166 for (int i = 0; i < Nchildren; i++) {
167 if (mandatoryAbsence[i]) {
168 LOGE("ModelBuilder::parseGeneric mandatory child type %d not present in type %d",
169 childTable[i].type, typeSelf);
170 return NULL;
171 }
172 }
173
174 return taskSelf.release();
175 }
176
177
parseCase(const TiXmlElement & root)178 TaskCase* ModelBuilder::parseCase(const TiXmlElement& root)
179 {
180 // position 0 of mParsingTable should be "case"
181 return reinterpret_cast<TaskCase*>(parseGeneric(root, 0));
182 }
183
184
parseBatch(const TiXmlElement & root,const android::String8 & xmlFileName)185 TaskBatch* ModelBuilder::parseBatch(const TiXmlElement& root, const android::String8& xmlFileName)
186 {
187 UniquePtr<TaskBatch> batch(
188 reinterpret_cast<TaskBatch*>(mFactory->createTask(TaskGeneric::ETaskBatch)));
189 if (batch.get() == NULL) {
190 LOGE("ModelBuilder::handleBatch cannot create TaskBatch");
191 return NULL;
192 }
193 if (!parseAttributes(root, *batch.get())) {
194 return NULL;
195 }
196
197 const TiXmlElement* inc = root.FirstChildElement("include");
198 if (inc == NULL) {
199 LOGE("ModelBuilder::handleBatch no include inside batch");
200 return NULL;
201 }
202 android::String8 path = xmlFileName.getPathDir();
203
204 UniquePtr<TaskCase> testCase;
205 int i = 0;
206 while (1) {
207 if (inc == NULL) {
208 break;
209 }
210 if (strcmp(inc->Value(),"include") != 0) {
211 LOGE("ModelBuilder::handleBatch invalid element %s", inc->Value());
212 }
213 testCase.reset(parseInclude(*inc, path));
214 if (testCase.get() == NULL) {
215 LOGE("ModelBuilder::handleBatch cannot create test case from include");
216 return NULL;
217 }
218 if (!batch.get()->addChild(testCase.get())) {
219 return NULL;
220 }
221 TaskGeneric* donotuse = testCase.release(); // parent will take care of destruction.
222 inc = inc->NextSiblingElement();
223 i++;
224 }
225 if (i == 0) {
226 // at least one include should exist.
227 LOGE("ModelBuilder::handleBatch no include elements");
228 return NULL;
229 }
230
231 return batch.release();
232 }
233
parseInclude(const TiXmlElement & elem,const android::String8 & path)234 TaskCase* ModelBuilder::parseInclude(const TiXmlElement& elem, const android::String8& path)
235 {
236 const char* fileName = elem.Attribute("file");
237 if (fileName == NULL) {
238 LOGE("ModelBuilder::handleBatch no include elements");
239 return NULL;
240 }
241 android::String8 incFile = path;
242 incFile.appendPath(fileName);
243
244 // again no dynamic_cast intentionally
245 return reinterpret_cast<TaskCase*>(parseTestDescriptionXml(incFile, true));
246 }
247
parseAttributes(const TiXmlElement & elem,TaskGeneric & task)248 bool ModelBuilder::parseAttributes(const TiXmlElement& elem, TaskGeneric& task)
249 {
250 const TiXmlAttribute* attr = elem.FirstAttribute();
251 while (1) {
252 if (attr == NULL) {
253 break;
254 }
255 android::String8 name(attr->Name());
256 android::String8 value(attr->Value());
257 if (!task.parseAttribute(name, value)) {
258 LOGE("ModelBuilder::parseAttributes cannot parse attribute %s:%s for task type %d",
259 attr->Name(), attr->Value(), task.getType());
260 return false;
261 }
262 attr = attr->Next();
263 }
264 return true;
265 }
266