1 //
2 // Copyright (C) 2017 The Android Open Source Project
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of 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,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16 
17 #include <property_info_serializer/property_info_serializer.h>
18 
19 #include <android-base/strings.h>
20 
21 #include "space_tokenizer.h"
22 
23 using android::base::Join;
24 using android::base::Split;
25 using android::base::StartsWith;
26 using android::base::Trim;
27 
28 namespace android {
29 namespace properties {
30 
31 namespace {
32 
IsTypeValid(const std::vector<std::string> & type_strings)33 bool IsTypeValid(const std::vector<std::string>& type_strings) {
34   if (type_strings.empty()) {
35     return false;
36   }
37 
38   // There must be at least one string following 'enum'
39   if (type_strings[0] == "enum") {
40     return type_strings.size() > 1;
41   }
42 
43   // There should not be any string following any other types.
44   if (type_strings.size() != 1) {
45     return false;
46   }
47 
48   // Check that the type matches one of remaining valid types.
49   static const char* const no_parameter_types[] = {"string", "bool",   "int",
50                                                    "uint",   "double", "size"};
51   for (const auto& type : no_parameter_types) {
52     if (type_strings[0] == type) {
53       return true;
54     }
55   }
56   return false;
57 }
58 
ParsePropertyInfoLine(const std::string & line,bool require_prefix_or_exact,PropertyInfoEntry * out,std::string * error)59 bool ParsePropertyInfoLine(const std::string& line, bool require_prefix_or_exact,
60                            PropertyInfoEntry* out, std::string* error) {
61   auto tokenizer = SpaceTokenizer(line);
62 
63   auto property = tokenizer.GetNext();
64   if (property.empty()) {
65     *error = "Did not find a property entry in '" + line + "'";
66     return false;
67   }
68 
69   auto context = tokenizer.GetNext();
70   if (context.empty()) {
71     *error = "Did not find a context entry in '" + line + "'";
72     return false;
73   }
74 
75   // It is not an error to not find exact_match or a type, as older files will not contain them.
76   auto match_operation = tokenizer.GetNext();
77   // We reformat type to be space deliminated regardless of the input whitespace for easier storage
78   // and subsequent parsing.
79   auto type_strings = std::vector<std::string>{};
80   auto type = tokenizer.GetNext();
81   while (!type.empty()) {
82     type_strings.emplace_back(type);
83     type = tokenizer.GetNext();
84   }
85 
86   bool exact_match = false;
87   if (match_operation == "exact") {
88     exact_match = true;
89   } else if (match_operation != "prefix" && match_operation != "" && require_prefix_or_exact) {
90     *error = "Match operation '" + match_operation +
91              "' is not valid: must be either 'prefix' or 'exact'";
92     return false;
93   }
94 
95   if (!type_strings.empty() && !IsTypeValid(type_strings)) {
96     *error = "Type '" + Join(type_strings, " ") + "' is not valid";
97     return false;
98   }
99 
100   *out = {property, context, Join(type_strings, " "), exact_match};
101   return true;
102 }
103 
104 }  // namespace
105 
ParsePropertyInfoFile(const std::string & file_contents,bool require_prefix_or_exact,std::vector<PropertyInfoEntry> * property_infos,std::vector<std::string> * errors)106 void ParsePropertyInfoFile(const std::string& file_contents, bool require_prefix_or_exact,
107                            std::vector<PropertyInfoEntry>* property_infos,
108                            std::vector<std::string>* errors) {
109   // Do not clear property_infos to allow this function to be called on multiple files, with
110   // their results concatenated.
111   errors->clear();
112 
113   for (const auto& line : Split(file_contents, "\n")) {
114     auto trimmed_line = Trim(line);
115     if (trimmed_line.empty() || StartsWith(trimmed_line, "#")) {
116       continue;
117     }
118 
119     auto property_info_entry = PropertyInfoEntry{};
120     auto parse_error = std::string{};
121     if (!ParsePropertyInfoLine(trimmed_line, require_prefix_or_exact, &property_info_entry,
122                                &parse_error)) {
123       errors->emplace_back(parse_error);
124       continue;
125     }
126 
127     property_infos->emplace_back(property_info_entry);
128   }
129 }
130 
131 }  // namespace properties
132 }  // namespace android
133