blob: 7cdf4869b2915b151a0c1f9e6701121d441d80c9 [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 )?;
Zhi Dou5aaeee32023-08-07 22:54:13 +000050 template.add_template(
51 "FakeFeatureFlagsImpl.java",
52 include_str!("../templates/FakeFeatureFlagsImpl.java.template"),
53 )?;
Zhi Dou4655c962023-06-12 15:56:03 +000054
55 let path: PathBuf = package.split('.').collect();
Zhi Dou5aaeee32023-08-07 22:54:13 +000056 ["Flags.java", "FeatureFlags.java", "FeatureFlagsImpl.java", "FakeFeatureFlagsImpl.java"]
Zhi Dou4655c962023-06-12 15:56:03 +000057 .iter()
58 .map(|file| {
59 Ok(OutputFile {
60 contents: template.render(file, &context)?.into(),
61 path: path.join(file),
62 })
63 })
64 .collect::<Result<Vec<OutputFile>>>()
Zhi Doueb744892023-05-10 04:02:33 +000065}
66
67#[derive(Serialize)]
68struct Context {
Zhi Doueb744892023-05-10 04:02:33 +000069 pub class_elements: Vec<ClassElement>,
Zhi Dou8ba6aa72023-06-26 21:03:40 +000070 pub is_test_mode: bool,
71 pub is_read_write: bool,
72 pub package_name: String,
Zhi Doueb744892023-05-10 04:02:33 +000073}
74
75#[derive(Serialize)]
76struct ClassElement {
Zhi Dou8ba6aa72023-06-26 21:03:40 +000077 pub default_value: bool,
Mårten Kongstad066575b2023-06-07 16:29:25 +020078 pub device_config_namespace: String,
79 pub device_config_flag: String,
Mårten Kongstada2e152a2023-06-19 16:11:33 +020080 pub flag_name_constant_suffix: String,
Zhi Dou4655c962023-06-12 15:56:03 +000081 pub is_read_write: bool,
82 pub method_name: String,
Zhi Doueb744892023-05-10 04:02:33 +000083}
84
Mårten Kongstad403658f2023-06-14 09:51:56 +020085fn create_class_element(package: &str, pf: &ProtoParsedFlag) -> ClassElement {
86 let device_config_flag = codegen::create_device_config_ident(package, pf.name())
87 .expect("values checked at flag parse time");
Zhi Doueb744892023-05-10 04:02:33 +000088 ClassElement {
Zhi Dou8ba6aa72023-06-26 21:03:40 +000089 default_value: pf.state() == ProtoFlagState::ENABLED,
Mårten Kongstad403658f2023-06-14 09:51:56 +020090 device_config_namespace: pf.namespace().to_string(),
Mårten Kongstad066575b2023-06-07 16:29:25 +020091 device_config_flag,
Mårten Kongstada2e152a2023-06-19 16:11:33 +020092 flag_name_constant_suffix: pf.name().to_ascii_uppercase(),
Mårten Kongstad403658f2023-06-14 09:51:56 +020093 is_read_write: pf.permission() == ProtoFlagPermission::READ_WRITE,
94 method_name: format_java_method_name(pf.name()),
Zhi Doueb744892023-05-10 04:02:33 +000095 }
96}
97
Zhi Douaf81e202023-06-14 20:38:20 +000098fn format_java_method_name(flag_name: &str) -> String {
99 flag_name
100 .split('_')
101 .filter(|&word| !word.is_empty())
102 .enumerate()
103 .map(|(index, word)| {
104 if index == 0 {
105 word.to_ascii_lowercase()
106 } else {
107 word[0..1].to_ascii_uppercase() + &word[1..].to_ascii_lowercase()
108 }
109 })
110 .collect::<Vec<String>>()
111 .join("")
112}
113
Zhi Doueb744892023-05-10 04:02:33 +0000114#[cfg(test)]
115mod tests {
116 use super::*;
Zhi Dou4655c962023-06-12 15:56:03 +0000117 use std::collections::HashMap;
Zhi Doueb744892023-05-10 04:02:33 +0000118
Zhi Doua7200112023-08-07 18:09:28 +0000119 const EXPECTED_FEATUREFLAGS_COMMON_CONTENT: &str = r#"
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000120 package com.android.aconfig.test;
121 public interface FeatureFlags {
122 boolean disabledRo();
123 boolean disabledRw();
Zhi Dou71f1b352023-08-21 22:49:46 +0000124 boolean enabledFixedRo();
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000125 boolean enabledRo();
126 boolean enabledRw();
Zhi Dou06a448f2023-08-15 19:33:27 +0000127 }
Zhi Doua7200112023-08-07 18:09:28 +0000128 "#;
Mårten Kongstada2e152a2023-06-19 16:11:33 +0200129
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000130 const EXPECTED_FLAG_COMMON_CONTENT: &str = r#"
131 package com.android.aconfig.test;
132 public final class Flags {
133 public static final String FLAG_DISABLED_RO = "com.android.aconfig.test.disabled_ro";
134 public static final String FLAG_DISABLED_RW = "com.android.aconfig.test.disabled_rw";
Zhi Dou71f1b352023-08-21 22:49:46 +0000135 public static final String FLAG_ENABLED_FIXED_RO = "com.android.aconfig.test.enabled_fixed_ro";
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000136 public static final String FLAG_ENABLED_RO = "com.android.aconfig.test.enabled_ro";
137 public static final String FLAG_ENABLED_RW = "com.android.aconfig.test.enabled_rw";
138
139 public static boolean disabledRo() {
140 return FEATURE_FLAGS.disabledRo();
Zhi Doueb744892023-05-10 04:02:33 +0000141 }
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000142 public static boolean disabledRw() {
143 return FEATURE_FLAGS.disabledRw();
144 }
Zhi Dou71f1b352023-08-21 22:49:46 +0000145 public static boolean enabledFixedRo() {
146 return FEATURE_FLAGS.enabledFixedRo();
147 }
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000148 public static boolean enabledRo() {
149 return FEATURE_FLAGS.enabledRo();
150 }
151 public static boolean enabledRw() {
152 return FEATURE_FLAGS.enabledRw();
153 }
154 "#;
155
Zhi Dou06a448f2023-08-15 19:33:27 +0000156 const EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT: &str = r#"
157 package com.android.aconfig.test;
158 import java.util.HashMap;
159 import java.util.Map;
160 public class FakeFeatureFlagsImpl implements FeatureFlags {
161 public FakeFeatureFlagsImpl() {
162 resetAll();
163 }
Zhi Dou5aaeee32023-08-07 22:54:13 +0000164 @Override
165 public boolean disabledRo() {
Zhi Dou06a448f2023-08-15 19:33:27 +0000166 return getFlag(Flags.FLAG_DISABLED_RO);
Zhi Dou5aaeee32023-08-07 22:54:13 +0000167 }
168 @Override
169 public boolean disabledRw() {
Zhi Dou06a448f2023-08-15 19:33:27 +0000170 return getFlag(Flags.FLAG_DISABLED_RW);
Zhi Dou5aaeee32023-08-07 22:54:13 +0000171 }
172 @Override
Zhi Dou71f1b352023-08-21 22:49:46 +0000173 public boolean enabledFixedRo() {
Zhi Dou06a448f2023-08-15 19:33:27 +0000174 return getFlag(Flags.FLAG_ENABLED_FIXED_RO);
Zhi Dou71f1b352023-08-21 22:49:46 +0000175 }
176 @Override
Zhi Dou5aaeee32023-08-07 22:54:13 +0000177 public boolean enabledRo() {
Zhi Dou06a448f2023-08-15 19:33:27 +0000178 return getFlag(Flags.FLAG_ENABLED_RO);
Zhi Dou5aaeee32023-08-07 22:54:13 +0000179 }
180 @Override
181 public boolean enabledRw() {
Zhi Dou06a448f2023-08-15 19:33:27 +0000182 return getFlag(Flags.FLAG_ENABLED_RW);
Zhi Dou5aaeee32023-08-07 22:54:13 +0000183 }
Zhi Dou06a448f2023-08-15 19:33:27 +0000184 public void setFlag(String flagName, boolean value) {
185 if (!this.mFlagMap.containsKey(flagName)) {
186 throw new IllegalArgumentException("no such flag " + flagName);
187 }
188 this.mFlagMap.put(flagName, value);
189 }
190 public void resetAll() {
191 for (Map.Entry entry : mFlagMap.entrySet()) {
192 entry.setValue(null);
193 }
194 }
195 private boolean getFlag(String flagName) {
196 Boolean value = this.mFlagMap.get(flagName);
197 if (value == null) {
198 throw new IllegalArgumentException(flagName + " is not set");
199 }
200 return value;
201 }
202 private Map<String, Boolean> mFlagMap = new HashMap<>(
203 Map.of(
204 Flags.FLAG_DISABLED_RO, false,
205 Flags.FLAG_DISABLED_RW, false,
206 Flags.FLAG_ENABLED_FIXED_RO, false,
207 Flags.FLAG_ENABLED_RO, false,
208 Flags.FLAG_ENABLED_RW, false
209 )
210 );
211 }
Zhi Dou5aaeee32023-08-07 22:54:13 +0000212 "#;
213
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000214 #[test]
215 fn test_generate_java_code_production() {
216 let parsed_flags = crate::test::parse_test_flags();
217 let generated_files = generate_java_code(
218 crate::test::TEST_PACKAGE,
219 parsed_flags.parsed_flag.iter(),
220 CodegenMode::Production,
221 )
222 .unwrap();
223 let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
224 + r#"
225 private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
226 }"#;
Zhi Dou06a448f2023-08-15 19:33:27 +0000227
Zhi Dou5aaeee32023-08-07 22:54:13 +0000228 let expect_featureflagsimpl_content = r#"
Zhi Dou4655c962023-06-12 15:56:03 +0000229 package com.android.aconfig.test;
230 import android.provider.DeviceConfig;
231 public final class FeatureFlagsImpl implements FeatureFlags {
232 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000233 public boolean disabledRo() {
Zhi Dou4655c962023-06-12 15:56:03 +0000234 return false;
235 }
236 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000237 public boolean disabledRw() {
Zhi Dou4655c962023-06-12 15:56:03 +0000238 return DeviceConfig.getBoolean(
239 "aconfig_test",
240 "com.android.aconfig.test.disabled_rw",
241 false
242 );
243 }
244 @Override
Zhi Dou71f1b352023-08-21 22:49:46 +0000245 public boolean enabledFixedRo() {
246 return true;
247 }
248 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000249 public boolean enabledRo() {
Zhi Dou4655c962023-06-12 15:56:03 +0000250 return true;
251 }
252 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000253 public boolean enabledRw() {
Zhi Dou4655c962023-06-12 15:56:03 +0000254 return DeviceConfig.getBoolean(
255 "aconfig_test",
256 "com.android.aconfig.test.enabled_rw",
257 true
258 );
259 }
260 }
261 "#;
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000262 let mut file_set = HashMap::from([
263 ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()),
Zhi Dou5aaeee32023-08-07 22:54:13 +0000264 ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_featureflagsimpl_content),
Zhi Dou06a448f2023-08-15 19:33:27 +0000265 ("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_COMMON_CONTENT),
Zhi Dou5aaeee32023-08-07 22:54:13 +0000266 (
267 "com/android/aconfig/test/FakeFeatureFlagsImpl.java",
Zhi Dou06a448f2023-08-15 19:33:27 +0000268 EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT,
Zhi Dou5aaeee32023-08-07 22:54:13 +0000269 ),
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000270 ]);
271
272 for file in generated_files {
273 let file_path = file.path.to_str().unwrap();
274 assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
275 assert_eq!(
276 None,
277 crate::test::first_significant_code_diff(
278 file_set.get(file_path).unwrap(),
279 &String::from_utf8(file.contents.clone()).unwrap()
280 ),
281 "File {} content is not correct",
282 file_path
283 );
284 file_set.remove(file_path);
285 }
286
287 assert!(file_set.is_empty());
288 }
289
290 #[test]
291 fn test_generate_java_code_test() {
292 let parsed_flags = crate::test::parse_test_flags();
293 let generated_files = generate_java_code(
294 crate::test::TEST_PACKAGE,
295 parsed_flags.parsed_flag.iter(),
296 CodegenMode::Test,
297 )
298 .unwrap();
Zhi Dou06a448f2023-08-15 19:33:27 +0000299
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000300 let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
301 + r#"
Zhi Dou5aaeee32023-08-07 22:54:13 +0000302 public static void setFeatureFlags(FeatureFlags featureFlags) {
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000303 Flags.FEATURE_FLAGS = featureFlags;
304 }
Zhi Dou5aaeee32023-08-07 22:54:13 +0000305 public static void unsetFeatureFlags() {
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000306 Flags.FEATURE_FLAGS = null;
307 }
308 private static FeatureFlags FEATURE_FLAGS;
309 }
310 "#;
Zhi Dou5aaeee32023-08-07 22:54:13 +0000311 let expect_featureflagsimpl_content = r#"
312 package com.android.aconfig.test;
Zhi Dou06a448f2023-08-15 19:33:27 +0000313 public final class FeatureFlagsImpl implements FeatureFlags {
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000314 @Override
315 public boolean disabledRo() {
Zhi Dou06a448f2023-08-15 19:33:27 +0000316 throw new UnsupportedOperationException(
317 "Method is not implemented.");
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000318 }
319 @Override
320 public boolean disabledRw() {
Zhi Dou06a448f2023-08-15 19:33:27 +0000321 throw new UnsupportedOperationException(
322 "Method is not implemented.");
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000323 }
324 @Override
Zhi Dou71f1b352023-08-21 22:49:46 +0000325 public boolean enabledFixedRo() {
Zhi Dou06a448f2023-08-15 19:33:27 +0000326 throw new UnsupportedOperationException(
327 "Method is not implemented.");
Zhi Dou71f1b352023-08-21 22:49:46 +0000328 }
329 @Override
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000330 public boolean enabledRo() {
Zhi Dou06a448f2023-08-15 19:33:27 +0000331 throw new UnsupportedOperationException(
332 "Method is not implemented.");
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000333 }
334 @Override
335 public boolean enabledRw() {
Zhi Dou06a448f2023-08-15 19:33:27 +0000336 throw new UnsupportedOperationException(
337 "Method is not implemented.");
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000338 }
Zhi Dou4655c962023-06-12 15:56:03 +0000339 }
340 "#;
Zhi Dou5aaeee32023-08-07 22:54:13 +0000341
Zhi Dou4655c962023-06-12 15:56:03 +0000342 let mut file_set = HashMap::from([
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000343 ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()),
Zhi Dou06a448f2023-08-15 19:33:27 +0000344 ("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_COMMON_CONTENT),
345 ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_featureflagsimpl_content),
Zhi Dou5aaeee32023-08-07 22:54:13 +0000346 (
347 "com/android/aconfig/test/FakeFeatureFlagsImpl.java",
Zhi Dou06a448f2023-08-15 19:33:27 +0000348 EXPECTED_FAKEFEATUREFLAGSIMPL_CONTENT,
Zhi Dou5aaeee32023-08-07 22:54:13 +0000349 ),
Zhi Dou4655c962023-06-12 15:56:03 +0000350 ]);
351
352 for file in generated_files {
353 let file_path = file.path.to_str().unwrap();
354 assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
355 assert_eq!(
356 None,
357 crate::test::first_significant_code_diff(
358 file_set.get(file_path).unwrap(),
359 &String::from_utf8(file.contents.clone()).unwrap()
360 ),
361 "File {} content is not correct",
362 file_path
363 );
364 file_set.remove(file_path);
365 }
366
367 assert!(file_set.is_empty());
Zhi Doueb744892023-05-10 04:02:33 +0000368 }
Zhi Douaf81e202023-06-14 20:38:20 +0000369
370 #[test]
371 fn test_format_java_method_name() {
372 let input = "____some_snake___name____";
373 let expected = "someSnakeName";
374 let formatted_name = format_java_method_name(input);
375 assert_eq!(expected, formatted_name);
376 }
Zhi Doueb744892023-05-10 04:02:33 +0000377}