Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / build / android / gyp / java_cpp_enum.py
1 #!/usr/bin/env python
2 #
3 # Copyright 2014 The Chromium Authors. All rights reserved.
4 # Use of this source code is governed by a BSD-style license that can be
5 # found in the LICENSE file.
6
7 import collections
8 import re
9 import optparse
10 import os
11 from string import Template
12 import sys
13
14 from util import build_utils
15
16 # List of C++ types that are compatible with the Java code generated by this
17 # script.
18 ENUM_FIXED_TYPE_WHITELIST = ['char', 'unsigned char',
19   'short', 'unsigned short',
20   'int', 'int8_t', 'int16_t', 'int32_t', 'uint8_t', 'uint16_t']
21
22 class EnumDefinition(object):
23   def __init__(self, original_enum_name=None, class_name_override=None,
24                enum_package=None, entries=None, fixed_type=None):
25     self.original_enum_name = original_enum_name
26     self.class_name_override = class_name_override
27     self.enum_package = enum_package
28     self.entries = collections.OrderedDict(entries or [])
29     self.prefix_to_strip = None
30     self.fixed_type = fixed_type
31
32   def AppendEntry(self, key, value):
33     if key in self.entries:
34       raise Exception('Multiple definitions of key %s found.' % key)
35     self.entries[key] = value
36
37   @property
38   def class_name(self):
39     return self.class_name_override or self.original_enum_name
40
41   def Finalize(self):
42     self._Validate()
43     self._AssignEntryIndices()
44     self._StripPrefix()
45
46   def _Validate(self):
47     assert self.class_name
48     assert self.enum_package
49     assert self.entries
50     if self.fixed_type and self.fixed_type not in ENUM_FIXED_TYPE_WHITELIST:
51       raise Exception('Fixed type %s for enum %s not whitelisted.' %
52           (self.fixed_type, self.class_name))
53
54   def _AssignEntryIndices(self):
55     # Enums, if given no value, are given the value of the previous enum + 1.
56     if not all(self.entries.values()):
57       prev_enum_value = -1
58       for key, value in self.entries.iteritems():
59         if not value:
60           self.entries[key] = prev_enum_value + 1
61         elif value in self.entries:
62           self.entries[key] = self.entries[value]
63         else:
64           try:
65             self.entries[key] = int(value)
66           except ValueError:
67             raise Exception('Could not interpret integer from enum value "%s" '
68                             'for key %s.' % (value, key))
69         prev_enum_value = self.entries[key]
70
71
72   def _StripPrefix(self):
73     prefix_to_strip = self.prefix_to_strip
74     if not prefix_to_strip:
75       prefix_to_strip = self.original_enum_name
76       prefix_to_strip = re.sub('(?!^)([A-Z]+)', r'_\1', prefix_to_strip).upper()
77       prefix_to_strip += '_'
78       if not all([w.startswith(prefix_to_strip) for w in self.entries.keys()]):
79         prefix_to_strip = ''
80
81     entries = collections.OrderedDict()
82     for (k, v) in self.entries.iteritems():
83       stripped_key = k.replace(prefix_to_strip, '', 1)
84       if isinstance(v, basestring):
85         stripped_value = v.replace(prefix_to_strip, '', 1)
86       else:
87         stripped_value = v
88       entries[stripped_key] = stripped_value
89
90     self.entries = entries
91
92 class DirectiveSet(object):
93   class_name_override_key = 'CLASS_NAME_OVERRIDE'
94   enum_package_key = 'ENUM_PACKAGE'
95   prefix_to_strip_key = 'PREFIX_TO_STRIP'
96
97   known_keys = [class_name_override_key, enum_package_key, prefix_to_strip_key]
98
99   def __init__(self):
100     self._directives = {}
101
102   def Update(self, key, value):
103     if key not in DirectiveSet.known_keys:
104       raise Exception("Unknown directive: " + key)
105     self._directives[key] = value
106
107   @property
108   def empty(self):
109     return len(self._directives) == 0
110
111   def UpdateDefinition(self, definition):
112     definition.class_name_override = self._directives.get(
113         DirectiveSet.class_name_override_key, '')
114     definition.enum_package = self._directives.get(
115         DirectiveSet.enum_package_key)
116     definition.prefix_to_strip = self._directives.get(
117         DirectiveSet.prefix_to_strip_key)
118
119
120 class HeaderParser(object):
121   single_line_comment_re = re.compile(r'\s*//')
122   multi_line_comment_start_re = re.compile(r'\s*/\*')
123   enum_line_re = re.compile(r'^\s*(\w+)(\s*\=\s*([^,\n]+))?,?')
124   enum_end_re = re.compile(r'^\s*}\s*;\.*$')
125   generator_directive_re = re.compile(
126       r'^\s*//\s+GENERATED_JAVA_(\w+)\s*:\s*([\.\w]+)$')
127
128   optional_class_or_struct_re = r'(class|struct)?'
129   enum_name_re = r'(\w+)'
130   optional_fixed_type_re = r'(\:\s*(\w+\s*\w+?))?'
131   enum_start_re = re.compile(r'^\s*enum\s+' + optional_class_or_struct_re +
132       '\s*' + enum_name_re + '\s*' + optional_fixed_type_re + '\s*{\s*$')
133
134   def __init__(self, lines):
135     self._lines = lines
136     self._enum_definitions = []
137     self._in_enum = False
138     self._current_definition = None
139     self._generator_directives = DirectiveSet()
140
141   def ParseDefinitions(self):
142     for line in self._lines:
143       self._ParseLine(line)
144     return self._enum_definitions
145
146   def _ParseLine(self, line):
147     if not self._in_enum:
148       self._ParseRegularLine(line)
149     else:
150       self._ParseEnumLine(line)
151
152   def _ParseEnumLine(self, line):
153     if HeaderParser.single_line_comment_re.match(line):
154       return
155     if HeaderParser.multi_line_comment_start_re.match(line):
156       raise Exception('Multi-line comments in enums are not supported.')
157     enum_end = HeaderParser.enum_end_re.match(line)
158     enum_entry = HeaderParser.enum_line_re.match(line)
159     if enum_end:
160       self._ApplyGeneratorDirectives()
161       self._current_definition.Finalize()
162       self._enum_definitions.append(self._current_definition)
163       self._in_enum = False
164     elif enum_entry:
165       enum_key = enum_entry.groups()[0]
166       enum_value = enum_entry.groups()[2]
167       self._current_definition.AppendEntry(enum_key, enum_value)
168
169   def _ApplyGeneratorDirectives(self):
170     self._generator_directives.UpdateDefinition(self._current_definition)
171     self._generator_directives = DirectiveSet()
172
173   def _ParseRegularLine(self, line):
174     enum_start = HeaderParser.enum_start_re.match(line)
175     generator_directive = HeaderParser.generator_directive_re.match(line)
176     if enum_start:
177       if self._generator_directives.empty:
178         return
179       self._current_definition = EnumDefinition(
180           original_enum_name=enum_start.groups()[1],
181           fixed_type=enum_start.groups()[3])
182       self._in_enum = True
183     elif generator_directive:
184       directive_name = generator_directive.groups()[0]
185       directive_value = generator_directive.groups()[1]
186       self._generator_directives.Update(directive_name, directive_value)
187
188
189 def GetScriptName():
190   script_components = os.path.abspath(sys.argv[0]).split(os.path.sep)
191   build_index = script_components.index('build')
192   return os.sep.join(script_components[build_index:])
193
194
195 def DoGenerate(options, source_paths):
196   output_paths = []
197   for source_path in source_paths:
198     enum_definitions = DoParseHeaderFile(source_path)
199     for enum_definition in enum_definitions:
200       package_path = enum_definition.enum_package.replace('.', os.path.sep)
201       file_name = enum_definition.class_name + '.java'
202       output_path = os.path.join(options.output_dir, package_path, file_name)
203       output_paths.append(output_path)
204       if not options.print_output_only:
205         build_utils.MakeDirectory(os.path.dirname(output_path))
206         DoWriteOutput(source_path, output_path, enum_definition)
207   return output_paths
208
209
210 def DoParseHeaderFile(path):
211   with open(path) as f:
212     return HeaderParser(f.readlines()).ParseDefinitions()
213
214
215 def GenerateOutput(source_path, enum_definition):
216   template = Template("""
217 // Copyright 2014 The Chromium Authors. All rights reserved.
218 // Use of this source code is governed by a BSD-style license that can be
219 // found in the LICENSE file.
220
221 // This file is autogenerated by
222 //     ${SCRIPT_NAME}
223 // From
224 //     ${SOURCE_PATH}
225
226 package ${PACKAGE};
227
228 public class ${CLASS_NAME} {
229 ${ENUM_ENTRIES}
230 }
231 """)
232
233   enum_template = Template('  public static final int ${NAME} = ${VALUE};')
234   enum_entries_string = []
235   for enum_name, enum_value in enum_definition.entries.iteritems():
236     values = {
237         'NAME': enum_name,
238         'VALUE': enum_value,
239     }
240     enum_entries_string.append(enum_template.substitute(values))
241   enum_entries_string = '\n'.join(enum_entries_string)
242
243   values = {
244       'CLASS_NAME': enum_definition.class_name,
245       'ENUM_ENTRIES': enum_entries_string,
246       'PACKAGE': enum_definition.enum_package,
247       'SCRIPT_NAME': GetScriptName(),
248       'SOURCE_PATH': source_path,
249   }
250   return template.substitute(values)
251
252
253 def DoWriteOutput(source_path, output_path, enum_definition):
254   with open(output_path, 'w') as out_file:
255     out_file.write(GenerateOutput(source_path, enum_definition))
256
257 def AssertFilesList(output_paths, assert_files_list):
258   actual = set(output_paths)
259   expected = set(assert_files_list)
260   if not actual == expected:
261     need_to_add = list(actual - expected)
262     need_to_remove = list(expected - actual)
263     raise Exception('Output files list does not match expectations. Please '
264                     'add %s and remove %s.' % (need_to_add, need_to_remove))
265
266 def DoMain(argv):
267   parser = optparse.OptionParser()
268
269   parser.add_option('--assert_file', action="append", default=[],
270                     dest="assert_files_list", help='Assert that the given '
271                     'file is an output. There can be multiple occurrences of '
272                     'this flag.')
273   parser.add_option('--output_dir', help='Base path for generated files.')
274   parser.add_option('--print_output_only', help='Only print output paths.',
275                     action='store_true')
276
277   options, args = parser.parse_args(argv)
278
279   output_paths = DoGenerate(options, args)
280
281   if options.assert_files_list:
282     AssertFilesList(output_paths, options.assert_files_list)
283
284   return " ".join(output_paths)
285
286 if __name__ == '__main__':
287   DoMain(sys.argv[1:])