/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef INCIDENT_HELPER_UTIL_H #define INCIDENT_HELPER_UTIL_H #include #include #include #include #include using namespace android::util; typedef std::vector header_t; typedef std::vector record_t; typedef std::string (*trans_func) (const std::string&); const std::string DEFAULT_WHITESPACE = " \t"; const std::string DEFAULT_NEWLINE = "\r\n"; const std::string TAB_DELIMITER = "\t"; const std::string COMMA_DELIMITER = ","; const std::string PIPE_DELIMITER = "|"; const std::string PARENTHESES_DELIMITER = "()"; // returns true if c is a-zA-Z0-9 or underscore bool isValidChar(char c); // trim the string with the given charset std::string trim(const std::string& s, const std::string& charset); /** * When a text has a table format like this * line 1: HeadA HeadB HeadC * line 2: v1 v2 v3 * line 3: v11 v12 v13 * * We want to parse the line in structure given the delimiter. * parseHeader is used to parse the firse line of the table and returns a list of strings in lower case * parseRecord is used to parse other lines and returns a list of strings * empty strings are skipped */ header_t parseHeader(const std::string& line, const std::string& delimiters = DEFAULT_WHITESPACE); record_t parseRecord(const std::string& line, const std::string& delimiters = DEFAULT_WHITESPACE); /** * Gets the list of end indices of each word in the line and places it in the given vector, * clearing out the vector beforehand. These indices can be used with parseRecordByColumns. * Will return false if there was a problem getting the indices. headerNames * must be NULL terminated. */ bool getColumnIndices(std::vector& indices, const char* headerNames[], const std::string& line); /** * When a text-format table aligns by its vertical position, it is not possible to split them by purely delimiters. * This function allows to parse record by its header's column position' indices, must in ascending order. * At the same time, it still looks at the char at index, if it doesn't belong to delimiters, moves forward to find the delimiters. */ record_t parseRecordByColumns(const std::string& line, const std::vector& indices, const std::string& delimiters = DEFAULT_WHITESPACE); /** Prints record_t to stderr */ void printRecord(const record_t& record); /** * When the line starts/ends with the given key, the function returns true * as well as the line argument is changed to the rest trimmed part of the original. * e.g. "ZRAM: 6828K physical used for 31076K in swap (524284K total swap)" becomes * "6828K physical used for 31076K in swap (524284K total swap)" when given key "ZRAM:", * otherwise the line is not changed. * * In order to prevent two values have same prefix which cause entering to incorrect conditions, * stripPrefix and stripSuffix can turn on a flag that requires the ending char in the line must not be a valid * character or digits, this feature is off by default. * i.e. ABC%some value, ABCD%other value */ bool stripPrefix(std::string* line, const char* key, bool endAtDelimiter = false); bool stripSuffix(std::string* line, const char* key, bool endAtDelimiter = false); /** * behead the given line by the cut, return the head and reassign the line to be the rest. */ std::string behead(std::string* line, const char cut); /** * Converts string to the desired type */ int toInt(const std::string& s); long long toLongLong(const std::string& s); double toDouble(const std::string& s); /** * Reader class reads data from given fd in streaming fashion. * The buffer size is controlled by capacity parameter. */ class Reader { public: explicit Reader(const int fd); ~Reader(); bool readLine(std::string* line); bool ok(std::string* error); private: FILE* mFile; std::string mStatus; }; /** * The Table class is constructed from two arrays generated by the given message with * option (stream_proto.stream_msg).enable_fields_mapping = true. * The names are each field's names in the message and must corresponding to the header/name of * the text to be parsed, and the ids are the streaming proto encoded field ids. * * This class then allows users to insert the table values to proto based on its header. * * Advance feature: if some fields in the message are enums, user must explicitly add the * mapping from enum name string to its enum values. */ class Message; class Table { friend class Message; public: Table(const char* names[], const uint64_t ids[], const int count); ~Table(); // Add enum names to values for parsing purpose. void addEnumTypeMap(const char* field, const char* enumNames[], const int enumValues[], const int enumSize); // Manually add enum names to values mapping, useful when an Enum type is used by // a number of fields, there must not be any enum name conflicts. void addEnumNameToValue(const char* enumName, const int enumValue); // Based on given name, find the right field id, parse the text value and insert to proto. // Return false if the given name can't be found. bool insertField(ProtoOutputStream* proto, const std::string& name, const std::string& value); private: std::map mFields; std::map> mEnums; std::map mEnumValuesByName; }; /** * Reconstructs a typical proto message given its message Table, adds submessage fields explicitly. * It allows user to insert nested proto values purely by the names. See insertField for detail. */ class Message { public: explicit Message(Table* table); ~Message(); // Reconstructs the typical proto message by adding its message fields. void addSubMessage(uint64_t fieldId, Message* fieldMsg); // Inserts value if the given name has the corresponding field in its message and return true. // It will recursively search the name in submessages and find the correct field to insert. // For example, when the name is dalvik_vm_heapsize, and the message's corresponding proto is: // message Properties { // message DalvikVm { // int32 heapsize = 1; // bool usejit = 2; // } // DalvikVm dalvik_vm = 1; // string hack_in = 2; // } // The value will be inserted into field heapsize in dalvik_vm submessage. // // Also value belongs to same submessage MUST be inserted contiguously. // For example, dalvik_vm_usejit must be inserted directly after dalvik_vm_heapsize, otherwise // if hack_in attempts to be inserted before dalvik_vm_usejit, value of usejit isn't added as expected. bool insertField(ProtoOutputStream* proto, const std::string& name, const std::string& value); // Starts a new message field proto session. void startSession(ProtoOutputStream* proto, const std::string& name); // Ends the previous message field proto session. void endSession(ProtoOutputStream* proto); private: Table* mTable; std::string mPreviousField; std::stack mTokens; std::map mSubMessages; }; #endif // INCIDENT_HELPER_UTIL_H