| #!/usr/bin/env python3 | |
| # Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | |
| # for details. All rights reserved. Use of this source code is governed by a | |
| # BSD-style license that can be found in the LICENSE file. | |
| """Module to manage IDL files.""" | |
| import copy | |
| import pickle | |
| import logging | |
| import os | |
| import os.path | |
| import shutil | |
| import idlnode | |
| import idlrenderer | |
| from generator import IsDartCollectionType, IsPureInterface | |
| _logger = logging.getLogger('database') | |
| class Database(object): | |
| """The Database class manages a collection of IDL files stored | |
| inside a directory. | |
| Each IDL is describing a single interface. The IDL files are written in the | |
| FremontCut syntax, which is derived from the Web IDL syntax and includes | |
| annotations. | |
| Database operations include adding, updating and removing IDL files. | |
| """ | |
| def __init__(self, root_dir): | |
| """Initializes a Database over a given directory. | |
| Args: | |
| root_dir -- a directory. If directory does not exist, it will | |
| be created. | |
| """ | |
| self._root_dir = root_dir | |
| if not os.path.exists(root_dir): | |
| _logger.debug('creating root directory %s' % root_dir) | |
| os.makedirs(root_dir) | |
| self._all_interfaces = {} | |
| self._interfaces_to_delete = [] | |
| self._enums = {} | |
| self._all_dictionaries = {} | |
| # TODO(terry): Hack to remember all typedef unions. | |
| self._all_type_defs = {} | |
| def Clone(self): | |
| new_database = Database(self._root_dir) | |
| new_database._all_interfaces = copy.deepcopy(self._all_interfaces) | |
| new_database._interfaces_to_delete = copy.deepcopy( | |
| self._interfaces_to_delete) | |
| new_database._enums = copy.deepcopy(self._enums) | |
| new_database._all_dictionaries = copy.deepcopy(self._all_dictionaries) | |
| new_database._all_type_defs = copy.deepcopy(self._all_type_defs) | |
| return new_database | |
| def Delete(self): | |
| """Deletes the database by deleting its directory""" | |
| if os.path.exists(self._root_dir): | |
| shutil.rmtree(self._root_dir) | |
| # reset in-memory constructs | |
| self._all_interfaces = {} | |
| def _ScanForInterfaces(self): | |
| """Iterates over the database files and lists all interface names. | |
| Return: | |
| A list of interface names. | |
| """ | |
| res = [] | |
| def Visitor(_, dirname, names): | |
| for name in names: | |
| if os.path.isfile(os.path.join(dirname, name)): | |
| root, ext = os.path.splitext(name) | |
| if ext == '.idl': | |
| res.append(root) | |
| os.path.walk(self._root_dir, Visitor, None) | |
| return res | |
| def _FilePath(self, interface_name): | |
| """Calculates the file path that a given interface should | |
| be saved to. | |
| Args: | |
| interface_name -- the name of the interface. | |
| """ | |
| return os.path.join(self._root_dir, '%s.idl' % interface_name) | |
| def _LoadInterfaceFile(self, interface_name): | |
| """Loads an interface from the database. | |
| Returns: | |
| An IDLInterface instance or None if the interface is not found. | |
| Args: | |
| interface_name -- the name of the interface. | |
| """ | |
| file_name = self._FilePath(interface_name) | |
| _logger.info('loading %s' % file_name) | |
| if not os.path.exists(file_name): | |
| return None | |
| f = open(file_name, 'r') | |
| content = f.read() | |
| f.close() | |
| # Parse file: | |
| idl_file = idlnode.IDLFile(self._idlparser.parse(content), file_name) | |
| if not idl_file.interfaces: | |
| raise RuntimeError('No interface found in %s' % file_name) | |
| elif len(idl_file.interfaces) > 1: | |
| raise RuntimeError('Expected one interface in %s' % file_name) | |
| interface = idl_file.interfaces[0] | |
| self._all_interfaces[interface_name] = interface | |
| return interface | |
| def Load(self): | |
| """Loads all interfaces into memory. | |
| """ | |
| # FIXME: Speed this up by multi-threading. | |
| for (interface_name) in self._ScanForInterfaces(): | |
| self._LoadInterfaceFile(interface_name) | |
| self.Cache() | |
| def Cache(self): | |
| """Serialize the database using pickle for faster startup in the future | |
| """ | |
| output_file = open(os.path.join(self._root_dir, 'cache.pickle'), 'wb') | |
| pickle.dump(self._all_interfaces, output_file) | |
| pickle.dump(self._interfaces_to_delete, output_file) | |
| def LoadFromCache(self): | |
| """Deserialize the database using pickle for fast startup | |
| """ | |
| input_file_name = os.path.join(self._root_dir, 'cache.pickle') | |
| if not os.path.isfile(input_file_name): | |
| self.Load() | |
| return | |
| input_file = open(input_file_name, 'rb') | |
| self._all_interfaces = pickle.load(input_file) | |
| self._interfaces_to_delete = pickle.load(input_file) | |
| input_file.close() | |
| def Save(self): | |
| """Saves all in-memory interfaces into files.""" | |
| for interface in list(self._all_interfaces.values()): | |
| self._SaveInterfaceFile(interface) | |
| for interface_name in self._interfaces_to_delete: | |
| self._DeleteInterfaceFile(interface_name) | |
| def _SaveInterfaceFile(self, interface): | |
| """Saves an interface into the database. | |
| Args: | |
| interface -- an IDLInterface instance. | |
| """ | |
| interface_name = interface.id | |
| # Actual saving | |
| file_path = self._FilePath(interface_name) | |
| _logger.debug('writing %s' % file_path) | |
| dir_name = os.path.dirname(file_path) | |
| if not os.path.exists(dir_name): | |
| _logger.debug('creating directory %s' % dir_name) | |
| os.mkdir(dir_name) | |
| # Render the IDLInterface object into text. | |
| text = idlrenderer.render(interface) | |
| f = open(file_path, 'w') | |
| f.write(text) | |
| f.close() | |
| def HasInterface(self, interface_name): | |
| """Returns True if the interface is in memory""" | |
| return interface_name in self._all_interfaces | |
| def GetInterface(self, interface_name): | |
| """Returns an IDLInterface corresponding to the interface_name | |
| from memory. | |
| Args: | |
| interface_name -- the name of the interface. | |
| """ | |
| if interface_name not in self._all_interfaces: | |
| raise RuntimeError('Interface %s is not loaded' % interface_name) | |
| return self._all_interfaces[interface_name] | |
| def AddInterface(self, interface): | |
| """Returns an IDLInterface corresponding to the interface_name | |
| from memory. | |
| Args: | |
| interface -- the name of the interface. | |
| """ | |
| interface_name = interface.id | |
| if interface_name in self._all_interfaces: | |
| raise RuntimeError('Interface %s already exists' % interface_name) | |
| self._all_interfaces[interface_name] = interface | |
| def GetInterfaces(self): | |
| """Returns a list of all loaded interfaces.""" | |
| res = [] | |
| for _, interface in sorted(self._all_interfaces.items()): | |
| res.append(interface) | |
| return res | |
| def DeleteInterface(self, interface_name): | |
| """Deletes an interface from the database. File is deleted when | |
| Save() is called. | |
| Args: | |
| interface_name -- the name of the interface. | |
| """ | |
| if interface_name not in self._all_interfaces: | |
| raise RuntimeError('Interface %s not found' % interface_name) | |
| self._interfaces_to_delete.append(interface_name) | |
| del self._all_interfaces[interface_name] | |
| def _DeleteInterfaceFile(self, interface_name): | |
| """Actual file deletion""" | |
| file_path = self._FilePath(interface_name) | |
| if os.path.exists(file_path): | |
| _logger.debug('deleting %s' % file_path) | |
| os.remove(file_path) | |
| def Hierarchy(self, interface): | |
| yield interface | |
| for parent in interface.parents: | |
| parent_name = parent.type.id | |
| if not self.HasInterface(parent.type.id): | |
| continue | |
| for parent_interface in self.Hierarchy( | |
| self.GetInterface(parent.type.id)): | |
| yield parent_interface | |
| def HasEnum(self, enum_name): | |
| return enum_name in self._enums | |
| def GetEnum(self, enum_name): | |
| return self._enums[enum_name] | |
| def AddEnum(self, enum): | |
| self._enums[enum.id] = enum | |
| def HasDictionary(self, dictionary_name): | |
| """Returns True if the dictionary is in memory""" | |
| return dictionary_name in self._all_dictionaries | |
| def GetDictionary(self, dictionary_name): | |
| """Returns an IDLDictionary corresponding to the dictionary_name | |
| from memory. | |
| Args: | |
| dictionary_name -- the name of the dictionary. | |
| """ | |
| if dictionary_name not in self._all_dictionaries: | |
| raise RuntimeError('Dictionary %s is not loaded' % dictionary_name) | |
| return self._all_dictionaries[dictionary_name] | |
| def AddDictionary(self, dictionary): | |
| """Returns an IDLDictionary corresponding to the dictionary_name | |
| from memory. | |
| Args: | |
| dictionary -- the name of the dictionary. | |
| """ | |
| dictionary_name = dictionary.id | |
| if dictionary_name in self._all_dictionaries: | |
| raise RuntimeError('Dictionary %s already exists' % dictionary_name) | |
| self._all_dictionaries[dictionary_name] = dictionary | |
| def GetDictionaries(self): | |
| """Returns a list of all loaded dictionaries.""" | |
| res = [] | |
| for _, dictionary in sorted(self._all_dictionaries.items()): | |
| res.append(dictionary) | |
| return res | |
| def HasTypeDef(self, type_def_name): | |
| """Returns True if the typedef is in memory""" | |
| return type_def_name in self._all_type_defs | |
| def GetTypeDef(self, type_def_name): | |
| """Returns an IDLTypeDef corresponding to the type_def_name | |
| from memory. | |
| Args: | |
| type_def_name -- the name of the typedef. | |
| """ | |
| if type_def_name not in self._all_type_defs: | |
| raise RuntimeError('Typedef %s is not loaded' % type_def_name) | |
| return self._all_type_defs[type_def_name] | |
| def AddTypeDef(self, type_def): | |
| """Add only a typedef that a unions they map to any (no type).""" | |
| type_def_name = type_def.id | |
| if type_def_name in self._all_type_defs: | |
| raise RuntimeError('Typedef %s already exists' % type_def_name) | |
| self._all_type_defs[type_def_name] = type_def | |
| print(' Added typedef %s' % type_def_name) | |
| def TransitiveSecondaryParents(self, interface, propagate_event_target): | |
| """Returns a list of all non-primary parents. | |
| The list contains the interface objects for interfaces defined in the | |
| database, and the name for undefined interfaces. | |
| """ | |
| def walk(parents, walk_result): | |
| for parent in parents: | |
| parent_name = parent.type.id | |
| if IsDartCollectionType(parent_name): | |
| if not (parent_name in walk_result): | |
| walk_result.append(parent_name) | |
| continue | |
| if self.HasInterface(parent_name): | |
| parent_interface = self.GetInterface(parent_name) | |
| if not (parent_interface in walk_result): | |
| # Interface has multi-inherited don't add interfaces more than once | |
| # to our parent result list. | |
| walk_result.append(parent_interface) | |
| walk(parent_interface.parents, walk_result) | |
| return walk_result | |
| result = [] | |
| if interface.parents: | |
| parent = interface.parents[0] | |
| if (IsPureInterface(parent.type.id, self) or | |
| (propagate_event_target and parent.type.id == 'EventTarget')): | |
| result = walk(interface.parents, []) | |
| else: | |
| result = walk(interface.parents[1:], []) | |
| return result |