| /* | |
| * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| #include <set> | |
| #include <fstream> | |
| #include <algorithm> | |
| #include "CfgFile.h" | |
| #include "SysInfo.h" | |
| #include "Log.h" | |
| #include "Toolbox.h" | |
| #include "FileUtils.h" | |
| #include "ErrorHandling.h" | |
| #include "StringProcessing.h" | |
| const CfgFile::Properties& CfgFile::getProperties( | |
| const SectionName& sectionName) const { | |
| const PropertyMap::const_iterator entry = data.find(sectionName); | |
| if (entry != data.end()) { | |
| return entry->second; | |
| } | |
| return empty; | |
| } | |
| CfgFile& CfgFile::setPropertyValue(const SectionName& sectionName, | |
| const PropertyName& name, const tstring_array& value) { | |
| PropertyMap::iterator entry = data.find(sectionName); | |
| if (entry != data.end()) { | |
| entry->second[name] = value; | |
| } else { | |
| Properties props; | |
| props[name] = value; | |
| data[sectionName] = props; | |
| } | |
| return *this; | |
| } | |
| namespace { | |
| template <typename CfgFileType, typename OpType> | |
| void iterateProperties(CfgFileType& cfgFile, OpType& op) { | |
| for (auto mapIt = cfgFile.begin(), mapEnd = cfgFile.end(); mapIt != mapEnd; ++mapIt) { | |
| for (auto propertyIt = mapIt->second.begin(), propertyEnd = mapIt->second.end(); propertyIt != propertyEnd; ++propertyIt) { | |
| for (auto strIt = propertyIt->second.begin(), strEnd = propertyIt->second.end(); strIt != strEnd; ++strIt) { | |
| op(strIt); | |
| } | |
| } | |
| } | |
| } | |
| struct tokenize_strings { | |
| void operator () (tstring_array::const_iterator& strIt) { | |
| const auto tokens = StringProcessing::tokenize(*strIt); | |
| values.push_back(tokens); | |
| } | |
| std::set<tstring> variableNames() const { | |
| std::set<tstring> allVariableNames; | |
| for (auto it = values.begin(), end = values.end(); it != end; ++it) { | |
| const auto variableNames = StringProcessing::extractVariableNames(*it); | |
| allVariableNames.insert(variableNames.begin(), variableNames.end()); | |
| } | |
| return allVariableNames; | |
| } | |
| std::vector<StringProcessing::TokenizedString> values; | |
| }; | |
| class expand_macros { | |
| public: | |
| expand_macros(const CfgFile::Macros& m, | |
| std::vector<StringProcessing::TokenizedString>::iterator iter) : macros(m), curTokenizedString(iter) { | |
| } | |
| void operator () (tstring_array::iterator& strIt) { | |
| StringProcessing::expandVariables(*curTokenizedString, macros); | |
| auto newStr = StringProcessing::stringify(*curTokenizedString); | |
| ++curTokenizedString; | |
| if (*strIt != newStr) { | |
| LOG_TRACE(tstrings::any() << "Map [" << *strIt << "] into [" << newStr << "]"); | |
| } | |
| strIt->swap(newStr); | |
| } | |
| private: | |
| const CfgFile::Macros& macros; | |
| std::vector<StringProcessing::TokenizedString>::iterator curTokenizedString; | |
| }; | |
| } // namespace | |
| CfgFile CfgFile::expandMacros(const Macros& macros) const { | |
| CfgFile copyCfgFile = *this; | |
| tokenize_strings tokenizedStrings; | |
| iterateProperties(static_cast<const CfgFile::PropertyMap&>(copyCfgFile.data), tokenizedStrings); | |
| Macros allMacros(macros); | |
| const auto variableNames = tokenizedStrings.variableNames(); | |
| for (auto it = variableNames.begin(), end = variableNames.end(); it != end; ++it) { | |
| if (macros.find(*it) == macros.end()) { | |
| // Not one of the reserved macro names. Assuming an environment variable. | |
| const auto envVarName = *it; | |
| if (SysInfo::isEnvVariableSet(envVarName)) { | |
| const auto envVarValue = SysInfo::getEnvVariable(envVarName); | |
| allMacros[envVarName] = envVarValue; | |
| } | |
| } | |
| } | |
| expand_macros expandMacros(allMacros, tokenizedStrings.values.begin()); | |
| iterateProperties(copyCfgFile.data, expandMacros); | |
| return copyCfgFile; | |
| } | |
| namespace { | |
| const CfgFile::SectionName* getSectionName(const tstring& str); | |
| const CfgFile::PropertyName* getPropertyName(const tstring& str); | |
| const CfgFile::SectionName UnknownSection = CfgFile::SectionName(_T("")); | |
| class PurgeSection { | |
| public: | |
| PurgeSection(CfgFile::SectionName& sectionName, | |
| CfgFile::Properties& sectionData, | |
| CfgFile::PropertyMap& cfgFileData): | |
| sectionName(sectionName), sectionData(sectionData), | |
| cfgFileData(cfgFileData) { | |
| } | |
| void operator ()() { | |
| if (sectionName != UnknownSection && !sectionData.empty()) { | |
| std::swap(cfgFileData[sectionName], sectionData); | |
| sectionName = UnknownSection; | |
| sectionData.clear(); | |
| } | |
| } | |
| private: | |
| CfgFile::SectionName& sectionName; | |
| CfgFile::Properties& sectionData; | |
| CfgFile::PropertyMap& cfgFileData; | |
| }; | |
| class AddProperty { | |
| public: | |
| AddProperty(const CfgFile::SectionName& sectionName, | |
| CfgFile::Properties& sectionData): sectionName(sectionName), | |
| sectionData(sectionData) { | |
| } | |
| void operator ()(const tstring& name, const tstring& value) { | |
| if (sectionName != UnknownSection) { | |
| const CfgFile::PropertyName *known = getPropertyName(name); | |
| if (known) { | |
| sectionData[*known].push_back(value); | |
| } | |
| } | |
| } | |
| private: | |
| const CfgFile::SectionName& sectionName; | |
| CfgFile::Properties& sectionData; | |
| }; | |
| } // namespace | |
| CfgFile CfgFile::load(const tstring& path) { | |
| std::ifstream input(path.c_str()); | |
| if (!input.good()) { | |
| JP_THROW(tstrings::any() << "Error opening \"" << path << "\" file: " | |
| << lastCRTError()); | |
| } | |
| CfgFile cfgFile; | |
| SectionName sectionName = UnknownSection; | |
| Properties sectionData; | |
| PurgeSection purgeSection(sectionName, sectionData, cfgFile.data); | |
| AddProperty addProperty(sectionName, sectionData); | |
| std::string utf8line; | |
| int lineno = 0; | |
| while (std::getline(input, utf8line)) { | |
| ++lineno; | |
| const tstring line = tstrings::any(utf8line).tstr(); | |
| if (line.empty() || _T(';') == *line.begin()) { | |
| // Empty line or comment, ignore. | |
| continue; | |
| } | |
| if (_T('[') == *line.begin()) { | |
| const size_t endIdx = line.find_last_of(_T(']')); | |
| if (endIdx == tstring::npos) { | |
| JP_THROW(tstrings::any() << "Error parsing [" << path | |
| << "] file at " << lineno << ": Missing ']' character"); | |
| } | |
| purgeSection(); | |
| // Section begin. | |
| const SectionName *knownName = getSectionName(line.substr(1, endIdx - 1)); | |
| if (knownName) { | |
| sectionName = *knownName; | |
| } else { | |
| sectionName = UnknownSection; | |
| } | |
| continue; | |
| } | |
| size_t sepIdx = 0; | |
| do { | |
| sepIdx = line.find_first_of(_T('='), sepIdx); | |
| if (sepIdx == tstring::npos) { | |
| addProperty(line, tstring()); | |
| break; | |
| } | |
| if (sepIdx != 0 && line[sepIdx - 1] == '\\') { | |
| sepIdx++; | |
| continue; | |
| } | |
| addProperty(line.substr(0, sepIdx), line.substr(sepIdx + 1)); | |
| break; | |
| } while (true); | |
| } | |
| if (!input.eof()) { | |
| // Failed to process file up to the end. | |
| JP_THROW(tstrings::any() << "Failed to read \"" << path | |
| << "\" file up to the end: " << lastCRTError()); | |
| } | |
| purgeSection(); | |
| return cfgFile; | |
| } | |
| tstring join(const tstring_array& values, const tstring::value_type delimiter) { | |
| return tstrings::join(values.begin(), values.end(), tstring(1, delimiter)); | |
| } | |
| tstring CfgFile::asString(Properties::const_reference property) { | |
| return *property.second.rbegin(); | |
| } | |
| bool CfgFile::asBoolean(Properties::const_reference property) { | |
| const auto str = asString(property); | |
| if (tstrings::equals(str, _T("true"), tstrings::IGNORE_CASE)) { | |
| return true; | |
| } | |
| // Parse integer. | |
| tistringstream iss(str); | |
| int intValue = 0; | |
| iss >> intValue; | |
| return intValue != 0; | |
| } | |
| tstring CfgFile::asPathList(Properties::const_reference property) { | |
| return join(property.second, FileUtils::pathSeparator); | |
| } | |
| #define JP_ALL_SECTIONS \ | |
| JP_SECTION(Application); \ | |
| JP_SECTION(JavaOptions); \ | |
| JP_SECTION(AppCDSJavaOptions); \ | |
| JP_SECTION(AppCDSGenerateCacheJavaOptions); \ | |
| JP_SECTION(ArgOptions); | |
| namespace SectionName { | |
| #define JP_SECTION(name) const CfgFile::SectionName name(_T(#name)) | |
| JP_ALL_SECTIONS | |
| #undef JP_SECTION | |
| } // namespace SectionName | |
| namespace { | |
| const CfgFile::SectionName* getSectionName(const tstring& str) { | |
| #define JP_SECTION(name) while (str == _T(#name)) { return &SectionName::name; } | |
| JP_ALL_SECTIONS | |
| #undef JP_SECTION | |
| return 0; | |
| } | |
| } | |
| #undef JP_ALL_SECTIONS | |
| #define JP_ALL_PROPERTIES \ | |
| JP_PROPERTY(version, "app.version"); \ | |
| JP_PROPERTY(mainjar, "app.mainjar"); \ | |
| JP_PROPERTY(mainmodule, "app.mainmodule"); \ | |
| JP_PROPERTY(mainclass, "app.mainclass"); \ | |
| JP_PROPERTY(classpath, "app.classpath"); \ | |
| JP_PROPERTY(modulepath, "app.modulepath"); \ | |
| JP_PROPERTY(runtime, "app.runtime"); \ | |
| JP_PROPERTY(splash, "app.splash"); \ | |
| JP_PROPERTY(memory, "app.memory"); \ | |
| JP_PROPERTY(arguments, "arguments"); \ | |
| JP_PROPERTY(javaOptions, "java-options"); \ | |
| JP_PROPERTY(winNorestart, "win.norestart"); \ | |
| namespace PropertyName { | |
| #define JP_PROPERTY(varName, name) const CfgFile::PropertyName varName(_T(name)) | |
| JP_ALL_PROPERTIES | |
| #undef JP_PROPERTY | |
| } // namespace PropertyName | |
| namespace { | |
| const CfgFile::PropertyName* getPropertyName(const tstring& str) { | |
| #define JP_PROPERTY(varName, name) while (str == _T(name)) { return &PropertyName::varName; } | |
| JP_ALL_PROPERTIES | |
| #undef JP_PROPERTY | |
| return 0; | |
| } | |
| } // namespace | |
| #undef JP_ALL_PROPERTIES |