1*38e8c45fSAndroid Build Coastguard Worker /*
2*38e8c45fSAndroid Build Coastguard Worker * Copyright (C) 2008 The Android Open Source Project
3*38e8c45fSAndroid Build Coastguard Worker *
4*38e8c45fSAndroid Build Coastguard Worker * Licensed under the Apache License, Version 2.0 (the "License");
5*38e8c45fSAndroid Build Coastguard Worker * you may not use this file except in compliance with the License.
6*38e8c45fSAndroid Build Coastguard Worker * You may obtain a copy of the License at
7*38e8c45fSAndroid Build Coastguard Worker *
8*38e8c45fSAndroid Build Coastguard Worker * http://www.apache.org/licenses/LICENSE-2.0
9*38e8c45fSAndroid Build Coastguard Worker *
10*38e8c45fSAndroid Build Coastguard Worker * Unless required by applicable law or agreed to in writing, software
11*38e8c45fSAndroid Build Coastguard Worker * distributed under the License is distributed on an "AS IS" BASIS,
12*38e8c45fSAndroid Build Coastguard Worker * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*38e8c45fSAndroid Build Coastguard Worker * See the License for the specific language governing permissions and
14*38e8c45fSAndroid Build Coastguard Worker * limitations under the License.
15*38e8c45fSAndroid Build Coastguard Worker */
16*38e8c45fSAndroid Build Coastguard Worker
17*38e8c45fSAndroid Build Coastguard Worker #define LOG_TAG "PropertyMap"
18*38e8c45fSAndroid Build Coastguard Worker
19*38e8c45fSAndroid Build Coastguard Worker #include <cstdlib>
20*38e8c45fSAndroid Build Coastguard Worker
21*38e8c45fSAndroid Build Coastguard Worker #include <input/PropertyMap.h>
22*38e8c45fSAndroid Build Coastguard Worker #include <log/log.h>
23*38e8c45fSAndroid Build Coastguard Worker
24*38e8c45fSAndroid Build Coastguard Worker // Enables debug output for the parser.
25*38e8c45fSAndroid Build Coastguard Worker #define DEBUG_PARSER 0
26*38e8c45fSAndroid Build Coastguard Worker
27*38e8c45fSAndroid Build Coastguard Worker // Enables debug output for parser performance.
28*38e8c45fSAndroid Build Coastguard Worker #define DEBUG_PARSER_PERFORMANCE 0
29*38e8c45fSAndroid Build Coastguard Worker
30*38e8c45fSAndroid Build Coastguard Worker namespace android {
31*38e8c45fSAndroid Build Coastguard Worker
32*38e8c45fSAndroid Build Coastguard Worker static const char* WHITESPACE = " \t\r";
33*38e8c45fSAndroid Build Coastguard Worker static const char* WHITESPACE_OR_PROPERTY_DELIMITER = " \t\r=";
34*38e8c45fSAndroid Build Coastguard Worker
35*38e8c45fSAndroid Build Coastguard Worker // --- PropertyMap ---
36*38e8c45fSAndroid Build Coastguard Worker
PropertyMap()37*38e8c45fSAndroid Build Coastguard Worker PropertyMap::PropertyMap() {}
38*38e8c45fSAndroid Build Coastguard Worker
~PropertyMap()39*38e8c45fSAndroid Build Coastguard Worker PropertyMap::~PropertyMap() {}
40*38e8c45fSAndroid Build Coastguard Worker
clear()41*38e8c45fSAndroid Build Coastguard Worker void PropertyMap::clear() {
42*38e8c45fSAndroid Build Coastguard Worker mProperties.clear();
43*38e8c45fSAndroid Build Coastguard Worker }
44*38e8c45fSAndroid Build Coastguard Worker
addProperty(const std::string & key,const std::string & value)45*38e8c45fSAndroid Build Coastguard Worker void PropertyMap::addProperty(const std::string& key, const std::string& value) {
46*38e8c45fSAndroid Build Coastguard Worker mProperties.emplace(key, value);
47*38e8c45fSAndroid Build Coastguard Worker }
48*38e8c45fSAndroid Build Coastguard Worker
getKeysWithPrefix(const std::string & prefix) const49*38e8c45fSAndroid Build Coastguard Worker std::unordered_set<std::string> PropertyMap::getKeysWithPrefix(const std::string& prefix) const {
50*38e8c45fSAndroid Build Coastguard Worker std::unordered_set<std::string> keys;
51*38e8c45fSAndroid Build Coastguard Worker for (const auto& [key, _] : mProperties) {
52*38e8c45fSAndroid Build Coastguard Worker if (key.starts_with(prefix)) {
53*38e8c45fSAndroid Build Coastguard Worker keys.insert(key);
54*38e8c45fSAndroid Build Coastguard Worker }
55*38e8c45fSAndroid Build Coastguard Worker }
56*38e8c45fSAndroid Build Coastguard Worker return keys;
57*38e8c45fSAndroid Build Coastguard Worker }
58*38e8c45fSAndroid Build Coastguard Worker
hasProperty(const std::string & key) const59*38e8c45fSAndroid Build Coastguard Worker bool PropertyMap::hasProperty(const std::string& key) const {
60*38e8c45fSAndroid Build Coastguard Worker return mProperties.find(key) != mProperties.end();
61*38e8c45fSAndroid Build Coastguard Worker }
62*38e8c45fSAndroid Build Coastguard Worker
getString(const std::string & key) const63*38e8c45fSAndroid Build Coastguard Worker std::optional<std::string> PropertyMap::getString(const std::string& key) const {
64*38e8c45fSAndroid Build Coastguard Worker auto it = mProperties.find(key);
65*38e8c45fSAndroid Build Coastguard Worker return it != mProperties.end() ? std::make_optional(it->second) : std::nullopt;
66*38e8c45fSAndroid Build Coastguard Worker }
67*38e8c45fSAndroid Build Coastguard Worker
getBool(const std::string & key) const68*38e8c45fSAndroid Build Coastguard Worker std::optional<bool> PropertyMap::getBool(const std::string& key) const {
69*38e8c45fSAndroid Build Coastguard Worker std::optional<int32_t> intValue = getInt(key);
70*38e8c45fSAndroid Build Coastguard Worker return intValue.has_value() ? std::make_optional(*intValue != 0) : std::nullopt;
71*38e8c45fSAndroid Build Coastguard Worker }
72*38e8c45fSAndroid Build Coastguard Worker
getInt(const std::string & key) const73*38e8c45fSAndroid Build Coastguard Worker std::optional<int32_t> PropertyMap::getInt(const std::string& key) const {
74*38e8c45fSAndroid Build Coastguard Worker std::optional<std::string> stringValue = getString(key);
75*38e8c45fSAndroid Build Coastguard Worker if (!stringValue.has_value() || stringValue->length() == 0) {
76*38e8c45fSAndroid Build Coastguard Worker return std::nullopt;
77*38e8c45fSAndroid Build Coastguard Worker }
78*38e8c45fSAndroid Build Coastguard Worker
79*38e8c45fSAndroid Build Coastguard Worker char* end;
80*38e8c45fSAndroid Build Coastguard Worker int32_t value = static_cast<int32_t>(strtol(stringValue->c_str(), &end, 10));
81*38e8c45fSAndroid Build Coastguard Worker if (*end != '\0') {
82*38e8c45fSAndroid Build Coastguard Worker ALOGW("Property key '%s' has invalid value '%s'. Expected an integer.", key.c_str(),
83*38e8c45fSAndroid Build Coastguard Worker stringValue->c_str());
84*38e8c45fSAndroid Build Coastguard Worker return std::nullopt;
85*38e8c45fSAndroid Build Coastguard Worker }
86*38e8c45fSAndroid Build Coastguard Worker return value;
87*38e8c45fSAndroid Build Coastguard Worker }
88*38e8c45fSAndroid Build Coastguard Worker
getFloat(const std::string & key) const89*38e8c45fSAndroid Build Coastguard Worker std::optional<float> PropertyMap::getFloat(const std::string& key) const {
90*38e8c45fSAndroid Build Coastguard Worker std::optional<std::string> stringValue = getString(key);
91*38e8c45fSAndroid Build Coastguard Worker if (!stringValue.has_value() || stringValue->length() == 0) {
92*38e8c45fSAndroid Build Coastguard Worker return std::nullopt;
93*38e8c45fSAndroid Build Coastguard Worker }
94*38e8c45fSAndroid Build Coastguard Worker
95*38e8c45fSAndroid Build Coastguard Worker char* end;
96*38e8c45fSAndroid Build Coastguard Worker float value = strtof(stringValue->c_str(), &end);
97*38e8c45fSAndroid Build Coastguard Worker if (*end != '\0') {
98*38e8c45fSAndroid Build Coastguard Worker ALOGW("Property key '%s' has invalid value '%s'. Expected a float.", key.c_str(),
99*38e8c45fSAndroid Build Coastguard Worker stringValue->c_str());
100*38e8c45fSAndroid Build Coastguard Worker return std::nullopt;
101*38e8c45fSAndroid Build Coastguard Worker }
102*38e8c45fSAndroid Build Coastguard Worker return value;
103*38e8c45fSAndroid Build Coastguard Worker }
104*38e8c45fSAndroid Build Coastguard Worker
getDouble(const std::string & key) const105*38e8c45fSAndroid Build Coastguard Worker std::optional<double> PropertyMap::getDouble(const std::string& key) const {
106*38e8c45fSAndroid Build Coastguard Worker std::optional<std::string> stringValue = getString(key);
107*38e8c45fSAndroid Build Coastguard Worker if (!stringValue.has_value() || stringValue->length() == 0) {
108*38e8c45fSAndroid Build Coastguard Worker return std::nullopt;
109*38e8c45fSAndroid Build Coastguard Worker }
110*38e8c45fSAndroid Build Coastguard Worker
111*38e8c45fSAndroid Build Coastguard Worker char* end;
112*38e8c45fSAndroid Build Coastguard Worker double value = strtod(stringValue->c_str(), &end);
113*38e8c45fSAndroid Build Coastguard Worker if (*end != '\0') {
114*38e8c45fSAndroid Build Coastguard Worker ALOGW("Property key '%s' has invalid value '%s'. Expected a double.", key.c_str(),
115*38e8c45fSAndroid Build Coastguard Worker stringValue->c_str());
116*38e8c45fSAndroid Build Coastguard Worker return std::nullopt;
117*38e8c45fSAndroid Build Coastguard Worker }
118*38e8c45fSAndroid Build Coastguard Worker return value;
119*38e8c45fSAndroid Build Coastguard Worker }
120*38e8c45fSAndroid Build Coastguard Worker
addAll(const PropertyMap * map)121*38e8c45fSAndroid Build Coastguard Worker void PropertyMap::addAll(const PropertyMap* map) {
122*38e8c45fSAndroid Build Coastguard Worker for (const auto& [key, value] : map->mProperties) {
123*38e8c45fSAndroid Build Coastguard Worker mProperties.emplace(key, value);
124*38e8c45fSAndroid Build Coastguard Worker }
125*38e8c45fSAndroid Build Coastguard Worker }
126*38e8c45fSAndroid Build Coastguard Worker
load(const char * filename)127*38e8c45fSAndroid Build Coastguard Worker android::base::Result<std::unique_ptr<PropertyMap>> PropertyMap::load(const char* filename) {
128*38e8c45fSAndroid Build Coastguard Worker std::unique_ptr<PropertyMap> outMap = std::make_unique<PropertyMap>();
129*38e8c45fSAndroid Build Coastguard Worker if (outMap == nullptr) {
130*38e8c45fSAndroid Build Coastguard Worker return android::base::Error(NO_MEMORY) << "Error allocating property map.";
131*38e8c45fSAndroid Build Coastguard Worker }
132*38e8c45fSAndroid Build Coastguard Worker
133*38e8c45fSAndroid Build Coastguard Worker Tokenizer* rawTokenizer;
134*38e8c45fSAndroid Build Coastguard Worker status_t status = Tokenizer::open(String8(filename), &rawTokenizer);
135*38e8c45fSAndroid Build Coastguard Worker if (status) {
136*38e8c45fSAndroid Build Coastguard Worker return android::base::Error(-status) << "Could not open file: " << filename;
137*38e8c45fSAndroid Build Coastguard Worker }
138*38e8c45fSAndroid Build Coastguard Worker #if DEBUG_PARSER_PERFORMANCE
139*38e8c45fSAndroid Build Coastguard Worker nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
140*38e8c45fSAndroid Build Coastguard Worker #endif
141*38e8c45fSAndroid Build Coastguard Worker std::unique_ptr<Tokenizer> tokenizer(rawTokenizer);
142*38e8c45fSAndroid Build Coastguard Worker Parser parser(outMap.get(), tokenizer.get());
143*38e8c45fSAndroid Build Coastguard Worker status = parser.parse();
144*38e8c45fSAndroid Build Coastguard Worker #if DEBUG_PARSER_PERFORMANCE
145*38e8c45fSAndroid Build Coastguard Worker nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
146*38e8c45fSAndroid Build Coastguard Worker ALOGD("Parsed property file '%s' %d lines in %0.3fms.", tokenizer->getFilename().string(),
147*38e8c45fSAndroid Build Coastguard Worker tokenizer->getLineNumber(), elapsedTime / 1000000.0);
148*38e8c45fSAndroid Build Coastguard Worker #endif
149*38e8c45fSAndroid Build Coastguard Worker if (status) {
150*38e8c45fSAndroid Build Coastguard Worker return android::base::Error(BAD_VALUE) << "Could not parse " << filename;
151*38e8c45fSAndroid Build Coastguard Worker }
152*38e8c45fSAndroid Build Coastguard Worker
153*38e8c45fSAndroid Build Coastguard Worker return std::move(outMap);
154*38e8c45fSAndroid Build Coastguard Worker }
155*38e8c45fSAndroid Build Coastguard Worker
156*38e8c45fSAndroid Build Coastguard Worker // --- PropertyMap::Parser ---
157*38e8c45fSAndroid Build Coastguard Worker
Parser(PropertyMap * map,Tokenizer * tokenizer)158*38e8c45fSAndroid Build Coastguard Worker PropertyMap::Parser::Parser(PropertyMap* map, Tokenizer* tokenizer)
159*38e8c45fSAndroid Build Coastguard Worker : mMap(map), mTokenizer(tokenizer) {}
160*38e8c45fSAndroid Build Coastguard Worker
~Parser()161*38e8c45fSAndroid Build Coastguard Worker PropertyMap::Parser::~Parser() {}
162*38e8c45fSAndroid Build Coastguard Worker
parse()163*38e8c45fSAndroid Build Coastguard Worker status_t PropertyMap::Parser::parse() {
164*38e8c45fSAndroid Build Coastguard Worker while (!mTokenizer->isEof()) {
165*38e8c45fSAndroid Build Coastguard Worker #if DEBUG_PARSER
166*38e8c45fSAndroid Build Coastguard Worker ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().c_str(),
167*38e8c45fSAndroid Build Coastguard Worker mTokenizer->peekRemainderOfLine().c_str());
168*38e8c45fSAndroid Build Coastguard Worker #endif
169*38e8c45fSAndroid Build Coastguard Worker
170*38e8c45fSAndroid Build Coastguard Worker mTokenizer->skipDelimiters(WHITESPACE);
171*38e8c45fSAndroid Build Coastguard Worker
172*38e8c45fSAndroid Build Coastguard Worker if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
173*38e8c45fSAndroid Build Coastguard Worker String8 keyToken = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER);
174*38e8c45fSAndroid Build Coastguard Worker if (keyToken.empty()) {
175*38e8c45fSAndroid Build Coastguard Worker ALOGE("%s: Expected non-empty property key.", mTokenizer->getLocation().c_str());
176*38e8c45fSAndroid Build Coastguard Worker return BAD_VALUE;
177*38e8c45fSAndroid Build Coastguard Worker }
178*38e8c45fSAndroid Build Coastguard Worker
179*38e8c45fSAndroid Build Coastguard Worker mTokenizer->skipDelimiters(WHITESPACE);
180*38e8c45fSAndroid Build Coastguard Worker
181*38e8c45fSAndroid Build Coastguard Worker if (mTokenizer->nextChar() != '=') {
182*38e8c45fSAndroid Build Coastguard Worker ALOGE("%s: Expected '=' between property key and value.",
183*38e8c45fSAndroid Build Coastguard Worker mTokenizer->getLocation().c_str());
184*38e8c45fSAndroid Build Coastguard Worker return BAD_VALUE;
185*38e8c45fSAndroid Build Coastguard Worker }
186*38e8c45fSAndroid Build Coastguard Worker
187*38e8c45fSAndroid Build Coastguard Worker mTokenizer->skipDelimiters(WHITESPACE);
188*38e8c45fSAndroid Build Coastguard Worker
189*38e8c45fSAndroid Build Coastguard Worker String8 valueToken = mTokenizer->nextToken(WHITESPACE);
190*38e8c45fSAndroid Build Coastguard Worker if (valueToken.find("\\", 0) >= 0 || valueToken.find("\"", 0) >= 0) {
191*38e8c45fSAndroid Build Coastguard Worker ALOGE("%s: Found reserved character '\\' or '\"' in property value.",
192*38e8c45fSAndroid Build Coastguard Worker mTokenizer->getLocation().c_str());
193*38e8c45fSAndroid Build Coastguard Worker return BAD_VALUE;
194*38e8c45fSAndroid Build Coastguard Worker }
195*38e8c45fSAndroid Build Coastguard Worker
196*38e8c45fSAndroid Build Coastguard Worker mTokenizer->skipDelimiters(WHITESPACE);
197*38e8c45fSAndroid Build Coastguard Worker if (!mTokenizer->isEol()) {
198*38e8c45fSAndroid Build Coastguard Worker ALOGE("%s: Expected end of line, got '%s'.", mTokenizer->getLocation().c_str(),
199*38e8c45fSAndroid Build Coastguard Worker mTokenizer->peekRemainderOfLine().c_str());
200*38e8c45fSAndroid Build Coastguard Worker return BAD_VALUE;
201*38e8c45fSAndroid Build Coastguard Worker }
202*38e8c45fSAndroid Build Coastguard Worker
203*38e8c45fSAndroid Build Coastguard Worker if (mMap->hasProperty(keyToken.c_str())) {
204*38e8c45fSAndroid Build Coastguard Worker ALOGE("%s: Duplicate property value for key '%s'.",
205*38e8c45fSAndroid Build Coastguard Worker mTokenizer->getLocation().c_str(), keyToken.c_str());
206*38e8c45fSAndroid Build Coastguard Worker return BAD_VALUE;
207*38e8c45fSAndroid Build Coastguard Worker }
208*38e8c45fSAndroid Build Coastguard Worker
209*38e8c45fSAndroid Build Coastguard Worker mMap->addProperty(keyToken.c_str(), valueToken.c_str());
210*38e8c45fSAndroid Build Coastguard Worker }
211*38e8c45fSAndroid Build Coastguard Worker
212*38e8c45fSAndroid Build Coastguard Worker mTokenizer->nextLine();
213*38e8c45fSAndroid Build Coastguard Worker }
214*38e8c45fSAndroid Build Coastguard Worker return OK;
215*38e8c45fSAndroid Build Coastguard Worker }
216*38e8c45fSAndroid Build Coastguard Worker
217*38e8c45fSAndroid Build Coastguard Worker } // namespace android
218