1 /* 2 * Copyright (C) 2021 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 package com.android.csuite.core; 18 19 import com.android.tradefed.config.IConfiguration; 20 import com.android.tradefed.config.Option; 21 import com.android.tradefed.config.Option.Importance; 22 23 import com.google.common.annotations.VisibleForTesting; 24 import com.google.common.base.Preconditions; 25 import com.google.common.io.Resources; 26 27 import java.io.IOException; 28 import java.nio.charset.StandardCharsets; 29 import java.nio.file.Path; 30 import java.util.ArrayList; 31 import java.util.HashMap; 32 import java.util.List; 33 import java.util.Map; 34 35 public final class ModuleTemplate { 36 @VisibleForTesting static final String XML_FILE_EXTENSION = ".xml"; 37 @VisibleForTesting static final String TEMPLATE_FILE_EXTENSION = ".xml.template"; 38 39 @VisibleForTesting 40 static final String MODULE_TEMPLATE_PROVIDER_OBJECT_TYPE = "MODULE_TEMPLATE_PROVIDER"; 41 42 @VisibleForTesting static final String DEFAULT_TEMPLATE_OPTION = "default-template"; 43 @VisibleForTesting static final String EXTRA_TEMPLATES_OPTION = "extra-templates"; 44 @VisibleForTesting static final String TEMPLATE_ROOT_OPTION = "template-root"; 45 46 @Option( 47 name = DEFAULT_TEMPLATE_OPTION, 48 description = "The default module config template resource path.", 49 importance = Importance.ALWAYS) 50 private String mDefaultTemplate; 51 52 @Option( 53 name = TEMPLATE_ROOT_OPTION, 54 description = "The root path of the template files.", 55 importance = Importance.ALWAYS) 56 private String mTemplateRoot; 57 58 @Option( 59 name = EXTRA_TEMPLATES_OPTION, 60 description = "Extra module config template resource paths.", 61 importance = Importance.NEVER) 62 private List<String> mExtraTemplates = new ArrayList<>(); 63 64 private final ResourceLoader mResourceLoader; 65 private String mDefaultTemplateContent; 66 private Map<String, String> mTemplateContentMap; 67 private Map<String, String> mTemplateMapping; 68 69 /** 70 * Load the ModuleTemplate object from a suite configuration. 71 * 72 * <p>An error will be thrown if there's no such objects or more than one objects. 73 * 74 * @param configuration The suite configuration. 75 * @return A ModuleTemplate object. 76 * @throws IOException 77 */ loadFrom(IConfiguration configuration)78 public static ModuleTemplate loadFrom(IConfiguration configuration) throws IOException { 79 List<?> moduleTemplates = 80 configuration.getConfigurationObjectList(MODULE_TEMPLATE_PROVIDER_OBJECT_TYPE); 81 Preconditions.checkNotNull( 82 moduleTemplates, "Missing " + MODULE_TEMPLATE_PROVIDER_OBJECT_TYPE); 83 Preconditions.checkArgument( 84 moduleTemplates.size() == 1, 85 "Only one module template object is expected. Found " + moduleTemplates.size()); 86 ModuleTemplate moduleTemplate = (ModuleTemplate) moduleTemplates.get(0); 87 moduleTemplate.init(configuration); 88 return moduleTemplate; 89 } 90 ModuleTemplate()91 public ModuleTemplate() { 92 this(new ClassResourceLoader()); 93 } 94 95 @VisibleForTesting ModuleTemplate(ResourceLoader resourceLoader)96 ModuleTemplate(ResourceLoader resourceLoader) { 97 mResourceLoader = resourceLoader; 98 } 99 100 @SuppressWarnings("MustBeClosedChecker") init(IConfiguration configuration)101 private void init(IConfiguration configuration) throws IOException { 102 if (mDefaultTemplateContent != null) { // Already loaded. 103 return; 104 } 105 106 mTemplateContentMap = new HashMap<>(); 107 108 String defaultTemplateContent = mResourceLoader.load(mDefaultTemplate); 109 mDefaultTemplateContent = defaultTemplateContent; 110 mTemplateContentMap.put( 111 getTemplateNameFromTemplateFile(mDefaultTemplate), defaultTemplateContent); 112 113 for (String extraTemplate : mExtraTemplates) { 114 mTemplateContentMap.put( 115 getTemplateNameFromTemplateFile(extraTemplate), 116 mResourceLoader.load(extraTemplate)); 117 } 118 119 mTemplateMapping = new HashMap<>(); 120 121 List<?> templateMappingObjects = 122 configuration.getConfigurationObjectList( 123 TemplateMappingProvider.TEMPLATE_MAPPING_PROVIDER_OBJECT_TYPE); 124 125 if (templateMappingObjects == null) { // No mapping objects found. 126 return; 127 } 128 129 for (Object provider : templateMappingObjects) { 130 ((TemplateMappingProvider) provider) 131 .get() 132 .forEach( 133 entry -> { 134 String moduleName = entry.getKey(); 135 String templateName = 136 getTemplateNameFromTemplateMapping(entry.getValue()); 137 138 Preconditions.checkArgument( 139 !mTemplateMapping.containsKey(moduleName), 140 "Duplicated module template map key: " + moduleName); 141 Preconditions.checkArgument( 142 mTemplateContentMap.containsKey(templateName), 143 "The template specified in module template map does not" 144 + " exist: " 145 + templateName); 146 147 mTemplateMapping.put(moduleName, templateName); 148 }); 149 } 150 } 151 getTemplateNameFromTemplateMapping(String name)152 private String getTemplateNameFromTemplateMapping(String name) { 153 String fileName = Path.of(name).toString(); 154 if (fileName.toLowerCase().endsWith(XML_FILE_EXTENSION)) { 155 return fileName.substring(0, fileName.length() - XML_FILE_EXTENSION.length()); 156 } 157 return fileName; 158 } 159 getTemplateNameFromTemplateFile(String path)160 private String getTemplateNameFromTemplateFile(String path) { 161 Preconditions.checkArgument( 162 path.endsWith(TEMPLATE_FILE_EXTENSION), 163 "Unexpected file extension for template path: " + path); 164 String fileName = Path.of(mTemplateRoot).relativize(Path.of(path)).toString(); 165 return fileName.substring(0, fileName.length() - TEMPLATE_FILE_EXTENSION.length()); 166 } 167 substitute(String moduleName, Map<String, String> replacementPairs)168 public String substitute(String moduleName, Map<String, String> replacementPairs) { 169 Preconditions.checkNotNull( 170 mDefaultTemplateContent, "The module template object is not fully loaded."); 171 return replacementPairs.keySet().stream() 172 .reduce( 173 getTemplateContent(moduleName), 174 (res, placeholder) -> 175 res.replace(placeholder, replacementPairs.get(placeholder))); 176 } 177 getTemplateContent(String moduleName)178 private String getTemplateContent(String moduleName) { 179 if (!mTemplateMapping.containsKey(moduleName)) { 180 return mDefaultTemplateContent; 181 } 182 183 return mTemplateContentMap.get(mTemplateMapping.get(moduleName)); 184 } 185 186 public interface ResourceLoader { load(String resourceName)187 String load(String resourceName) throws IOException; 188 } 189 190 public static final class ClassResourceLoader implements ResourceLoader { 191 @Override load(String resourceName)192 public String load(String resourceName) throws IOException { 193 return Resources.toString( 194 getClass().getClassLoader().getResource(resourceName), StandardCharsets.UTF_8); 195 } 196 } 197 } 198