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