1#! /usr/bin/python3
2#
3# pylint: disable=line-too-long, missing-docstring, logging-format-interpolation, invalid-name
4
5#
6# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
9#
10#      http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17
18import argparse
19import re
20import sys
21import os
22import logging
23import xml.etree.ElementTree as ET
24from collections import OrderedDict
25import xml.dom.minidom as MINIDOM
26
27def parseArgs():
28    argparser = argparse.ArgumentParser(description="Parameter-Framework XML \
29        structure file generator.\n\
30        Exit with the number of (recoverable or not) error that occured.")
31    argparser.add_argument('--androidaudiobaseheader',
32                           help="Android Audio Base C header file, Mandatory.",
33                           metavar="ANDROID_AUDIO_BASE_HEADER",
34                           type=argparse.FileType('r'),
35                           required=True)
36    argparser.add_argument('--commontypesstructure',
37                           help="Structure XML base file. Mandatory.",
38                           metavar="STRUCTURE_FILE_IN",
39                           type=argparse.FileType('r'),
40                           required=True)
41    argparser.add_argument('--outputfile',
42                           help="Structure XML file. Mandatory.",
43                           metavar="STRUCTURE_FILE_OUT",
44                           type=argparse.FileType('w'),
45                           required=True)
46    argparser.add_argument('--verbose',
47                           action='store_true')
48
49    return argparser.parse_args()
50
51
52def findBitPos(decimal):
53    pos = 0
54    i = 1
55    while i != decimal:
56        i = i << 1
57        pos = pos + 1
58        if pos == 32:
59            return -1
60    return pos
61
62
63def generateXmlStructureFile(componentTypeDict, structureTypesFile, outputFile):
64
65    logging.info("Importing structureTypesFile {}".format(structureTypesFile))
66    component_types_in_tree = ET.parse(structureTypesFile)
67
68    component_types_root = component_types_in_tree.getroot()
69
70    for component_types_name, values_dict in componentTypeDict.items():
71        for component_type in component_types_root.findall('ComponentType'):
72            if component_type.get('Name') == component_types_name:
73                bitparameters_node = component_type.find("BitParameterBlock")
74                if bitparameters_node is not None:
75                    ordered_values = OrderedDict(sorted(values_dict.items(), key=lambda x: x[1]))
76                    for key, value in ordered_values.items():
77                        value_node = ET.SubElement(bitparameters_node, "BitParameter")
78                        value_node.set('Name', key)
79                        value_node.set('Size', "1")
80                        value_node.set('Pos', str(findBitPos(value)))
81
82                enum_parameter_node = component_type.find("EnumParameter")
83                if enum_parameter_node is not None:
84                    ordered_values = OrderedDict(sorted(values_dict.items(), key=lambda x: x[1]))
85                    for key, value in ordered_values.items():
86                        value_node = ET.SubElement(enum_parameter_node, "ValuePair")
87                        value_node.set('Literal', key)
88                        value_node.set('Numerical', str(value))
89
90    xmlstr = ET.tostring(component_types_root, encoding='utf8', method='xml')
91    reparsed = MINIDOM.parseString(xmlstr)
92    prettyXmlStr = reparsed.toprettyxml(indent="    ", newl='\n')
93    prettyXmlStr = os.linesep.join([s for s in prettyXmlStr.splitlines() if s.strip()])
94    outputFile.write(prettyXmlStr)
95
96
97def capitalizeLine(line):
98    return ' '.join((w.capitalize() for w in line.split(' ')))
99
100def parseAndroidAudioFile(androidaudiobaseheaderFile):
101    #
102    # Adaptation table between Android Enumeration prefix and Audio PFW Criterion type names
103    #
104    component_type_mapping_table = {
105        'AUDIO_STREAM' : "VolumeProfileType",
106        'AUDIO_DEVICE_OUT' : "OutputDevicesMask",
107        'AUDIO_DEVICE_IN' : "InputDevicesMask"}
108
109    all_component_types = {
110        'VolumeProfileType' : {},
111        'OutputDevicesMask' : {},
112        'InputDevicesMask' : {}
113    }
114
115    #
116    # _CNT, _MAX, _ALL and _NONE are prohibited values as ther are just helpers for enum users.
117    #
118    ignored_values = ['CNT', 'MAX', 'ALL', 'NONE']
119
120    criteria_pattern = re.compile(
121        r"\s*(?P<type>(?:"+'|'.join(component_type_mapping_table.keys()) + "))_" \
122        r"(?P<literal>(?!" + '|'.join(ignored_values) + ")\w*)\s*=\s*" \
123        r"(?P<values>(?:0[xX])?[0-9a-fA-F]+)")
124
125    logging.info("Checking Android Header file {}".format(androidaudiobaseheaderFile))
126
127    for line_number, line in enumerate(androidaudiobaseheaderFile):
128        match = criteria_pattern.match(line)
129        if match:
130            logging.debug("The following line is VALID: {}:{}\n{}".format(
131                androidaudiobaseheaderFile.name, line_number, line))
132
133            component_type_name = component_type_mapping_table[match.groupdict()['type']]
134            component_type_literal = match.groupdict()['literal'].lower()
135
136            component_type_numerical_value = match.groupdict()['values']
137
138            # for AUDIO_DEVICE_IN: need to remove sign bit / rename default to stub
139            if component_type_name == "InputDevicesMask":
140                component_type_numerical_value = str(int(component_type_numerical_value, 0) & ~2147483648)
141                if component_type_literal == "default":
142                    component_type_literal = "stub"
143
144            if component_type_name == "OutputDevicesMask":
145                if component_type_literal == "default":
146                    component_type_literal = "stub"
147
148            # Remove duplicated numerical values
149            if int(component_type_numerical_value, 0) in all_component_types[component_type_name].values():
150                logging.info("The value {}:{} is duplicated for criterion {}, KEEPING LATEST".format(component_type_numerical_value, component_type_literal, component_type_name))
151                for key in list(all_component_types[component_type_name]):
152                    if all_component_types[component_type_name][key] == int(component_type_numerical_value, 0):
153                        del all_component_types[component_type_name][key]
154
155            all_component_types[component_type_name][component_type_literal] = int(component_type_numerical_value, 0)
156
157            logging.debug("type:{}, literal:{}, values:{}.".format(component_type_name, component_type_literal, component_type_numerical_value))
158
159    # Transform input source in inclusive criterion
160    shift = len(all_component_types['OutputDevicesMask'])
161    if shift > 32:
162        logging.critical("OutputDevicesMask incompatible with criterion representation on 32 bits")
163        logging.info("EXIT ON FAILURE")
164        exit(1)
165
166    for component_types in all_component_types:
167        values = ','.join('{}:{}'.format(value, key) for key, value in all_component_types[component_types].items())
168        logging.info("{}: <{}>".format(component_types, values))
169
170    return all_component_types
171
172
173def main():
174    logging.root.setLevel(logging.INFO)
175    args = parseArgs()
176    route_criteria = 0
177
178    all_component_types = parseAndroidAudioFile(args.androidaudiobaseheader)
179
180    generateXmlStructureFile(all_component_types, args.commontypesstructure, args.outputfile)
181
182# If this file is directly executed
183if __name__ == "__main__":
184    sys.exit(main())
185