1#!/usr/bin/env python
2#
3# Copyright 2019, The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17import os
18
19class ResourceLocation:
20    def __init__(self, file, line=None):
21        self.file = file
22        self.line = line
23
24    def __str__(self):
25        if self.line is not None:
26            return self.file + ':' + str(self.line)
27        else:
28            return self.file
29
30class Resource:
31    def __init__(self, name, type, location=None):
32        self.name = name
33        self.type = type
34        self.locations = []
35        if location is not None:
36            self.locations.append(location)
37
38    def __eq__(self, other):
39        if isinstance(other, _Grab):
40            return other == self
41        return self.name == other.name and self.type == other.type
42
43    def __ne__(self, other):
44        if isinstance(other, _Grab):
45            return other != self
46        return self.name != other.name or self.type != other.type
47
48    def __hash__(self):
49        return hash((self.name, self.type))
50
51    def __str__(self):
52        result = ''
53        for location in self.locations:
54            result += str(location) + ': '
55        result += '<'+self.type+' name="'+self.name+'"'
56
57        return result + '>'
58
59    def __repr__(self):
60        return str(self)
61
62def get_all_resources(resDir):
63    allResDirs = [f for f in os.listdir(resDir) if os.path.isdir(os.path.join(resDir, f))]
64    valuesDirs = [f for f in allResDirs if f.startswith('values')]
65    fileDirs = [f for f in allResDirs if not f.startswith('values')]
66
67    resources = set()
68
69    # Get the filenames of the all the files in all the fileDirs
70    for dir in fileDirs:
71        type = dir.split('-')[0]
72        for file in os.listdir(os.path.join(resDir, dir)):
73            if file.endswith('.xml'):
74                add_resource_to_set(resources,
75                                    Resource(file[:-4], type,
76                                             ResourceLocation(os.path.join(resDir, dir, file))))
77
78    for dir in valuesDirs:
79        for file in os.listdir(os.path.join(resDir, dir)):
80            if file.endswith('.xml'):
81                for resource in get_resources_from_single_file(os.path.join(resDir, dir, file)):
82                    add_resource_to_set(resources, resource)
83
84    return resources
85
86def get_resources_from_single_file(filename):
87    # defer importing lxml to here so that people who aren't editing chassis don't have to have
88    # lxml installed
89    import lxml.etree as etree
90    doc = etree.parse(filename)
91    resourceTag = doc.getroot()
92    result = set()
93    for resource in resourceTag:
94        if resource.tag == 'declare-styleable' or resource.tag is etree.Comment:
95            continue
96
97        if resource.tag == 'item' or resource.tag == 'public':
98            add_resource_to_set(result, Resource(resource.get('name'), resource.get('type'),
99                                                 ResourceLocation(filename, resource.sourceline)))
100        else:
101            add_resource_to_set(result, Resource(resource.get('name'), resource.tag,
102                                                 ResourceLocation(filename, resource.sourceline)))
103    return result
104
105def remove_layout_resources(resourceSet):
106    result = set()
107    for resource in resourceSet:
108        if resource.type != 'layout':
109            result.add(resource)
110    return result
111
112# Used to get objects out of sets
113class _Grab:
114    def __init__(self, value):
115        self.search_value = value
116    def __hash__(self):
117        return hash(self.search_value)
118    def __eq__(self, other):
119        if self.search_value == other:
120            self.actual_value = other
121            return True
122        return False
123
124def add_resource_to_set(resourceset, resource):
125    grabber = _Grab(resource)
126    if grabber in resourceset:
127        grabber.actual_value.locations.extend(resource.locations)
128    else:
129        resourceset.update([resource])
130
131def merge_resources(set1, set2):
132    for resource in set2:
133        add_resource_to_set(set1, resource)
134