blob: 8ab6ffa8f76cf6ff5ac6b9bd98785e92bb603239 [file] [log] [blame]
Zhi Doueb744892023-05-10 04:02:33 +00001/*
2 * Copyright (C) 2023 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
17use anyhow::Result;
18use serde::Serialize;
Joe Onorato0c4ef0f2023-05-13 11:30:11 -070019use std::path::PathBuf;
Zhi Doueb744892023-05-10 04:02:33 +000020use tinytemplate::TinyTemplate;
21
Mårten Kongstad066575b2023-06-07 16:29:25 +020022use crate::codegen;
Zhi Dou8ba6aa72023-06-26 21:03:40 +000023use crate::commands::{CodegenMode, OutputFile};
Mårten Kongstad403658f2023-06-14 09:51:56 +020024use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
Zhi Doueb744892023-05-10 04:02:33 +000025
Zhi Dou8ba6aa72023-06-26 21:03:40 +000026pub fn generate_java_code<'a, I>(
27 package: &str,
28 parsed_flags_iter: I,
29 codegen_mode: CodegenMode,
30) -> Result<Vec<OutputFile>>
Mårten Kongstad403658f2023-06-14 09:51:56 +020031where
32 I: Iterator<Item = &'a ProtoParsedFlag>,
33{
Mårten Kongstad066575b2023-06-07 16:29:25 +020034 let class_elements: Vec<ClassElement> =
Mårten Kongstad403658f2023-06-14 09:51:56 +020035 parsed_flags_iter.map(|pf| create_class_element(package, pf)).collect();
36 let is_read_write = class_elements.iter().any(|elem| elem.is_read_write);
Zhi Dou8ba6aa72023-06-26 21:03:40 +000037 let is_test_mode = codegen_mode == CodegenMode::Test;
38 let context =
39 Context { class_elements, is_test_mode, is_read_write, package_name: package.to_string() };
Zhi Doueb744892023-05-10 04:02:33 +000040 let mut template = TinyTemplate::new();
Zhi Dou4655c962023-06-12 15:56:03 +000041 template.add_template("Flags.java", include_str!("../templates/Flags.java.template"))?;
42 template.add_template(
43 "FeatureFlagsImpl.java",
44 include_str!("../templates/FeatureFlagsImpl.java.template"),
45 )?;
46 template.add_template(
47 "FeatureFlags.java",
48 include_str!("../templates/FeatureFlags.java.template"),
49 )?;
50
51 let path: PathBuf = package.split('.').collect();
Mårten Kongstad403658f2023-06-14 09:51:56 +020052 ["Flags.java", "FeatureFlagsImpl.java", "FeatureFlags.java"]
Zhi Dou4655c962023-06-12 15:56:03 +000053 .iter()
54 .map(|file| {
55 Ok(OutputFile {
56 contents: template.render(file, &context)?.into(),
57 path: path.join(file),
58 })
59 })
60 .collect::<Result<Vec<OutputFile>>>()
Zhi Doueb744892023-05-10 04:02:33 +000061}
62
63#[derive(Serialize)]
64struct Context {
Zhi Doueb744892023-05-10 04:02:33 +000065 pub class_elements: Vec<ClassElement>,
Zhi Dou8ba6aa72023-06-26 21:03:40 +000066 pub is_test_mode: bool,
67 pub is_read_write: bool,
68 pub package_name: String,
Zhi Doueb744892023-05-10 04:02:33 +000069}
70
71#[derive(Serialize)]
72struct ClassElement {
Zhi Dou8ba6aa72023-06-26 21:03:40 +000073 pub default_value: bool,
Mårten Kongstad066575b2023-06-07 16:29:25 +020074 pub device_config_namespace: String,
75 pub device_config_flag: String,
Mårten Kongstada2e152a2023-06-19 16:11:33 +020076 pub flag_name_constant_suffix: String,
Zhi Dou4655c962023-06-12 15:56:03 +000077 pub is_read_write: bool,
78 pub method_name: String,
Zhi Doueb744892023-05-10 04:02:33 +000079}
80
Mårten Kongstad403658f2023-06-14 09:51:56 +020081fn create_class_element(package: &str, pf: &ProtoParsedFlag) -> ClassElement {
82 let device_config_flag = codegen::create_device_config_ident(package, pf.name())
83 .expect("values checked at flag parse time");
Zhi Doueb744892023-05-10 04:02:33 +000084 ClassElement {
Zhi Dou8ba6aa72023-06-26 21:03:40 +000085 default_value: pf.state() == ProtoFlagState::ENABLED,
Mårten Kongstad403658f2023-06-14 09:51:56 +020086 device_config_namespace: pf.namespace().to_string(),
Mårten Kongstad066575b2023-06-07 16:29:25 +020087 device_config_flag,
Mårten Kongstada2e152a2023-06-19 16:11:33 +020088 flag_name_constant_suffix: pf.name().to_ascii_uppercase(),
Mårten Kongstad403658f2023-06-14 09:51:56 +020089 is_read_write: pf.permission() == ProtoFlagPermission::READ_WRITE,
90 method_name: format_java_method_name(pf.name()),
Zhi Doueb744892023-05-10 04:02:33 +000091 }
92}
93
Zhi Douaf81e202023-06-14 20:38:20 +000094fn format_java_method_name(flag_name: &str) -> String {
95 flag_name
96 .split('_')
97 .filter(|&word| !word.is_empty())
98 .enumerate()
99 .map(|(index, word)| {
100 if index == 0 {
101 word.to_ascii_lowercase()
102 } else {
103 word[0..1].to_ascii_uppercase() + &word[1..].to_ascii_lowercase()
104 }
105 })
106 .collect::<Vec<String>>()
107 .join("")
108}
109
Zhi Doueb744892023-05-10 04:02:33 +0000110#[cfg(test)]
111mod tests {
112 use super::*;
Zhi Dou4655c962023-06-12 15:56:03 +0000113 use std::collections::HashMap;
Zhi Doueb744892023-05-10 04:02:33 +0000114
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000115 const EXPECTED_FEATUREFLAGS_CONTENT: &str = r#"
116 package com.android.aconfig.test;
117 public interface FeatureFlags {
118 boolean disabledRo();
119 boolean disabledRw();
120 boolean enabledRo();
121 boolean enabledRw();
122 }"#;
Mårten Kongstada2e152a2023-06-19 16:11:33 +0200123
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000124 const EXPECTED_FLAG_COMMON_CONTENT: &str = r#"
125 package com.android.aconfig.test;
126 public final class Flags {
127 public static final String FLAG_DISABLED_RO = "com.android.aconfig.test.disabled_ro";
128 public static final String FLAG_DISABLED_RW = "com.android.aconfig.test.disabled_rw";
129 public static final String FLAG_ENABLED_RO = "com.android.aconfig.test.enabled_ro";
130 public static final String FLAG_ENABLED_RW = "com.android.aconfig.test.enabled_rw";
131
132 public static boolean disabledRo() {
133 return FEATURE_FLAGS.disabledRo();
Zhi Doueb744892023-05-10 04:02:33 +0000134 }
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000135 public static boolean disabledRw() {
136 return FEATURE_FLAGS.disabledRw();
137 }
138 public static boolean enabledRo() {
139 return FEATURE_FLAGS.enabledRo();
140 }
141 public static boolean enabledRw() {
142 return FEATURE_FLAGS.enabledRw();
143 }
144 "#;
145
146 #[test]
147 fn test_generate_java_code_production() {
148 let parsed_flags = crate::test::parse_test_flags();
149 let generated_files = generate_java_code(
150 crate::test::TEST_PACKAGE,
151 parsed_flags.parsed_flag.iter(),
152 CodegenMode::Production,
153 )
154 .unwrap();
155 let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
156 + r#"
157 private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
158 }"#;
Zhi Dou4655c962023-06-12 15:56:03 +0000159 let expected_featureflagsimpl_content = r#"
160 package com.android.aconfig.test;
161 import android.provider.DeviceConfig;
162 public final class FeatureFlagsImpl implements FeatureFlags {
163 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000164 public boolean disabledRo() {
Zhi Dou4655c962023-06-12 15:56:03 +0000165 return false;
166 }
167 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000168 public boolean disabledRw() {
Zhi Dou4655c962023-06-12 15:56:03 +0000169 return DeviceConfig.getBoolean(
170 "aconfig_test",
171 "com.android.aconfig.test.disabled_rw",
172 false
173 );
174 }
175 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000176 public boolean enabledRo() {
Zhi Dou4655c962023-06-12 15:56:03 +0000177 return true;
178 }
179 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000180 public boolean enabledRw() {
Zhi Dou4655c962023-06-12 15:56:03 +0000181 return DeviceConfig.getBoolean(
182 "aconfig_test",
183 "com.android.aconfig.test.enabled_rw",
184 true
185 );
186 }
187 }
188 "#;
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000189 let mut file_set = HashMap::from([
190 ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()),
191 ("com/android/aconfig/test/FeatureFlagsImpl.java", expected_featureflagsimpl_content),
192 ("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_CONTENT),
193 ]);
194
195 for file in generated_files {
196 let file_path = file.path.to_str().unwrap();
197 assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
198 assert_eq!(
199 None,
200 crate::test::first_significant_code_diff(
201 file_set.get(file_path).unwrap(),
202 &String::from_utf8(file.contents.clone()).unwrap()
203 ),
204 "File {} content is not correct",
205 file_path
206 );
207 file_set.remove(file_path);
208 }
209
210 assert!(file_set.is_empty());
211 }
212
213 #[test]
214 fn test_generate_java_code_test() {
215 let parsed_flags = crate::test::parse_test_flags();
216 let generated_files = generate_java_code(
217 crate::test::TEST_PACKAGE,
218 parsed_flags.parsed_flag.iter(),
219 CodegenMode::Test,
220 )
221 .unwrap();
222 let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
223 + r#"
224 public static void setFeatureFlagsImpl(FeatureFlags featureFlags) {
225 Flags.FEATURE_FLAGS = featureFlags;
226 }
227 public static void unsetFeatureFlagsImpl() {
228 Flags.FEATURE_FLAGS = null;
229 }
230 private static FeatureFlags FEATURE_FLAGS;
231 }
232 "#;
233 let expected_featureflagsimpl_content = r#"
Zhi Dou4655c962023-06-12 15:56:03 +0000234 package com.android.aconfig.test;
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000235 import static java.util.stream.Collectors.toMap;
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000236 import java.util.HashMap;
Zhi Dou8d27cc32023-06-29 15:15:32 +0000237 import java.util.Map;
Zhi Doua41cc5e2023-06-29 15:01:56 +0000238 import java.util.stream.Stream;
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000239 public final class FeatureFlagsImpl implements FeatureFlags {
240 @Override
241 public boolean disabledRo() {
242 return getFlag(Flags.FLAG_DISABLED_RO);
243 }
244 @Override
245 public boolean disabledRw() {
246 return getFlag(Flags.FLAG_DISABLED_RW);
247 }
248 @Override
249 public boolean enabledRo() {
250 return getFlag(Flags.FLAG_ENABLED_RO);
251 }
252 @Override
253 public boolean enabledRw() {
254 return getFlag(Flags.FLAG_ENABLED_RW);
255 }
256 public void setFlag(String flagName, boolean value) {
257 if (!this.mFlagMap.containsKey(flagName)) {
258 throw new IllegalArgumentException("no such flag" + flagName);
259 }
260 this.mFlagMap.put(flagName, value);
261 }
Zhi Dou8d27cc32023-06-29 15:15:32 +0000262 public void resetAll() {
263 for (Map.Entry entry : mFlagMap.entrySet()) {
264 entry.setValue(null);
265 }
266 }
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000267 private boolean getFlag(String flagName) {
268 Boolean value = this.mFlagMap.get(flagName);
269 if (value == null) {
270 throw new IllegalArgumentException(flagName + " is not set");
271 }
272 return value;
273 }
274 private HashMap<String, Boolean> mFlagMap = Stream.of(
275 Flags.FLAG_DISABLED_RO,
276 Flags.FLAG_DISABLED_RW,
277 Flags.FLAG_ENABLED_RO,
278 Flags.FLAG_ENABLED_RW
279 )
280 .collect(
281 HashMap::new,
282 (map, elem) -> map.put(elem, null),
283 HashMap::putAll
284 );
Zhi Dou4655c962023-06-12 15:56:03 +0000285 }
286 "#;
287 let mut file_set = HashMap::from([
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000288 ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()),
Zhi Dou4655c962023-06-12 15:56:03 +0000289 ("com/android/aconfig/test/FeatureFlagsImpl.java", expected_featureflagsimpl_content),
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000290 ("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_CONTENT),
Zhi Dou4655c962023-06-12 15:56:03 +0000291 ]);
292
293 for file in generated_files {
294 let file_path = file.path.to_str().unwrap();
295 assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
296 assert_eq!(
297 None,
298 crate::test::first_significant_code_diff(
299 file_set.get(file_path).unwrap(),
300 &String::from_utf8(file.contents.clone()).unwrap()
301 ),
302 "File {} content is not correct",
303 file_path
304 );
305 file_set.remove(file_path);
306 }
307
308 assert!(file_set.is_empty());
Zhi Doueb744892023-05-10 04:02:33 +0000309 }
Zhi Douaf81e202023-06-14 20:38:20 +0000310
311 #[test]
312 fn test_format_java_method_name() {
313 let input = "____some_snake___name____";
314 let expected = "someSnakeName";
315 let formatted_name = format_java_method_name(input);
316 assert_eq!(expected, formatted_name);
317 }
Zhi Doueb744892023-05-10 04:02:33 +0000318}