blob: be0ec976b2fc4e30caa0c364b24cf82c1c88dbb0 [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 Doua7200112023-08-07 18:09:28 +0000127 "#;
Mårten Kongstada2e152a2023-06-19 16:11:33 +0200128
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000129 const EXPECTED_FLAG_COMMON_CONTENT: &str = r#"
130 package com.android.aconfig.test;
131 public final class Flags {
132 public static final String FLAG_DISABLED_RO = "com.android.aconfig.test.disabled_ro";
133 public static final String FLAG_DISABLED_RW = "com.android.aconfig.test.disabled_rw";
Zhi Dou71f1b352023-08-21 22:49:46 +0000134 public static final String FLAG_ENABLED_FIXED_RO = "com.android.aconfig.test.enabled_fixed_ro";
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000135 public static final String FLAG_ENABLED_RO = "com.android.aconfig.test.enabled_ro";
136 public static final String FLAG_ENABLED_RW = "com.android.aconfig.test.enabled_rw";
137
138 public static boolean disabledRo() {
139 return FEATURE_FLAGS.disabledRo();
Zhi Doueb744892023-05-10 04:02:33 +0000140 }
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000141 public static boolean disabledRw() {
142 return FEATURE_FLAGS.disabledRw();
143 }
Zhi Dou71f1b352023-08-21 22:49:46 +0000144 public static boolean enabledFixedRo() {
145 return FEATURE_FLAGS.enabledFixedRo();
146 }
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000147 public static boolean enabledRo() {
148 return FEATURE_FLAGS.enabledRo();
149 }
150 public static boolean enabledRw() {
151 return FEATURE_FLAGS.enabledRw();
152 }
153 "#;
154
Zhi Dou5aaeee32023-08-07 22:54:13 +0000155 const EXPECTED_METHOD_NOT_IMPL_COMMON_CONTENT: &str = r#"
156 @Override
157 public boolean disabledRo() {
158 throw new UnsupportedOperationException(
159 "Method is not implemented.");
160 }
161 @Override
162 public boolean disabledRw() {
163 throw new UnsupportedOperationException(
164 "Method is not implemented.");
165 }
166 @Override
Zhi Dou71f1b352023-08-21 22:49:46 +0000167 public boolean enabledFixedRo() {
168 throw new UnsupportedOperationException(
169 "Method is not implemented.");
170 }
171 @Override
Zhi Dou5aaeee32023-08-07 22:54:13 +0000172 public boolean enabledRo() {
173 throw new UnsupportedOperationException(
174 "Method is not implemented.");
175 }
176 @Override
177 public boolean enabledRw() {
178 throw new UnsupportedOperationException(
179 "Method is not implemented.");
180 }
181 "#;
182
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000183 #[test]
184 fn test_generate_java_code_production() {
185 let parsed_flags = crate::test::parse_test_flags();
186 let generated_files = generate_java_code(
187 crate::test::TEST_PACKAGE,
188 parsed_flags.parsed_flag.iter(),
189 CodegenMode::Production,
190 )
191 .unwrap();
Zhi Doua7200112023-08-07 18:09:28 +0000192 let expect_featureflags_content = EXPECTED_FEATUREFLAGS_COMMON_CONTENT.to_string()
193 + r#"
194 }"#;
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000195 let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
196 + r#"
197 private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
198 }"#;
Zhi Dou5aaeee32023-08-07 22:54:13 +0000199 let expect_fakefeatureflagsimpl_content = r#"
200 package com.android.aconfig.test;
201 public class FakeFeatureFlagsImpl implements FeatureFlags {"#
202 .to_owned()
203 + EXPECTED_METHOD_NOT_IMPL_COMMON_CONTENT
204 + r#"
205 }
206 "#;
207 let expect_featureflagsimpl_content = r#"
Zhi Dou4655c962023-06-12 15:56:03 +0000208 package com.android.aconfig.test;
209 import android.provider.DeviceConfig;
210 public final class FeatureFlagsImpl implements FeatureFlags {
211 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000212 public boolean disabledRo() {
Zhi Dou4655c962023-06-12 15:56:03 +0000213 return false;
214 }
215 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000216 public boolean disabledRw() {
Zhi Dou4655c962023-06-12 15:56:03 +0000217 return DeviceConfig.getBoolean(
218 "aconfig_test",
219 "com.android.aconfig.test.disabled_rw",
220 false
221 );
222 }
223 @Override
Zhi Dou71f1b352023-08-21 22:49:46 +0000224 public boolean enabledFixedRo() {
225 return true;
226 }
227 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000228 public boolean enabledRo() {
Zhi Dou4655c962023-06-12 15:56:03 +0000229 return true;
230 }
231 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000232 public boolean enabledRw() {
Zhi Dou4655c962023-06-12 15:56:03 +0000233 return DeviceConfig.getBoolean(
234 "aconfig_test",
235 "com.android.aconfig.test.enabled_rw",
236 true
237 );
238 }
239 }
240 "#;
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000241 let mut file_set = HashMap::from([
242 ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()),
Zhi Dou5aaeee32023-08-07 22:54:13 +0000243 ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_featureflagsimpl_content),
Zhi Doua7200112023-08-07 18:09:28 +0000244 ("com/android/aconfig/test/FeatureFlags.java", expect_featureflags_content.as_str()),
Zhi Dou5aaeee32023-08-07 22:54:13 +0000245 (
246 "com/android/aconfig/test/FakeFeatureFlagsImpl.java",
247 expect_fakefeatureflagsimpl_content.as_str(),
248 ),
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000249 ]);
250
251 for file in generated_files {
252 let file_path = file.path.to_str().unwrap();
253 assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
254 assert_eq!(
255 None,
256 crate::test::first_significant_code_diff(
257 file_set.get(file_path).unwrap(),
258 &String::from_utf8(file.contents.clone()).unwrap()
259 ),
260 "File {} content is not correct",
261 file_path
262 );
263 file_set.remove(file_path);
264 }
265
266 assert!(file_set.is_empty());
267 }
268
269 #[test]
270 fn test_generate_java_code_test() {
271 let parsed_flags = crate::test::parse_test_flags();
272 let generated_files = generate_java_code(
273 crate::test::TEST_PACKAGE,
274 parsed_flags.parsed_flag.iter(),
275 CodegenMode::Test,
276 )
277 .unwrap();
Zhi Doua7200112023-08-07 18:09:28 +0000278 let expect_featureflags_content = EXPECTED_FEATUREFLAGS_COMMON_CONTENT.to_string()
279 + r#"
280 public void setFlag(String flagName, boolean value);
281 public void resetAll();
282 }"#;
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000283 let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
284 + r#"
Zhi Dou5aaeee32023-08-07 22:54:13 +0000285 public static void setFeatureFlags(FeatureFlags featureFlags) {
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000286 Flags.FEATURE_FLAGS = featureFlags;
287 }
Zhi Dou5aaeee32023-08-07 22:54:13 +0000288 public static void unsetFeatureFlags() {
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000289 Flags.FEATURE_FLAGS = null;
290 }
291 private static FeatureFlags FEATURE_FLAGS;
292 }
293 "#;
Zhi Dou5aaeee32023-08-07 22:54:13 +0000294 let expect_featureflagsimpl_content = r#"
295 package com.android.aconfig.test;
296 public final class FeatureFlagsImpl implements FeatureFlags {"#
297 .to_owned()
298 + EXPECTED_METHOD_NOT_IMPL_COMMON_CONTENT
299 + r#"
Zhi Doua7200112023-08-07 18:09:28 +0000300 @Override
301 public void setFlag(String flagName, boolean value) {
302 throw new UnsupportedOperationException(
303 "Method is not implemented.");
304 }
305 @Override
306 public void resetAll() {
307 throw new UnsupportedOperationException(
308 "Method is not implemented.");
309 }
Zhi Dou5aaeee32023-08-07 22:54:13 +0000310 }
311 "#;
312 let expect_fakefeatureflagsimpl_content = r#"
Zhi Dou4655c962023-06-12 15:56:03 +0000313 package com.android.aconfig.test;
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000314 import static java.util.stream.Collectors.toMap;
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000315 import java.util.HashMap;
Zhi Dou8d27cc32023-06-29 15:15:32 +0000316 import java.util.Map;
Zhi Doua41cc5e2023-06-29 15:01:56 +0000317 import java.util.stream.Stream;
Zhi Dou5aaeee32023-08-07 22:54:13 +0000318 public class FakeFeatureFlagsImpl implements FeatureFlags {
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000319 @Override
320 public boolean disabledRo() {
321 return getFlag(Flags.FLAG_DISABLED_RO);
322 }
323 @Override
324 public boolean disabledRw() {
325 return getFlag(Flags.FLAG_DISABLED_RW);
326 }
327 @Override
Zhi Dou71f1b352023-08-21 22:49:46 +0000328 public boolean enabledFixedRo() {
329 return getFlag(Flags.FLAG_ENABLED_FIXED_RO);
330 }
331 @Override
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000332 public boolean enabledRo() {
333 return getFlag(Flags.FLAG_ENABLED_RO);
334 }
335 @Override
336 public boolean enabledRw() {
337 return getFlag(Flags.FLAG_ENABLED_RW);
338 }
Zhi Doua7200112023-08-07 18:09:28 +0000339 @Override
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000340 public void setFlag(String flagName, boolean value) {
341 if (!this.mFlagMap.containsKey(flagName)) {
342 throw new IllegalArgumentException("no such flag" + flagName);
343 }
344 this.mFlagMap.put(flagName, value);
345 }
Zhi Doua7200112023-08-07 18:09:28 +0000346 @Override
Zhi Dou8d27cc32023-06-29 15:15:32 +0000347 public void resetAll() {
348 for (Map.Entry entry : mFlagMap.entrySet()) {
349 entry.setValue(null);
350 }
351 }
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000352 private boolean getFlag(String flagName) {
353 Boolean value = this.mFlagMap.get(flagName);
354 if (value == null) {
355 throw new IllegalArgumentException(flagName + " is not set");
356 }
357 return value;
358 }
359 private HashMap<String, Boolean> mFlagMap = Stream.of(
360 Flags.FLAG_DISABLED_RO,
361 Flags.FLAG_DISABLED_RW,
Zhi Dou71f1b352023-08-21 22:49:46 +0000362 Flags.FLAG_ENABLED_FIXED_RO,
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000363 Flags.FLAG_ENABLED_RO,
364 Flags.FLAG_ENABLED_RW
365 )
366 .collect(
367 HashMap::new,
368 (map, elem) -> map.put(elem, null),
369 HashMap::putAll
370 );
Zhi Dou4655c962023-06-12 15:56:03 +0000371 }
372 "#;
Zhi Dou5aaeee32023-08-07 22:54:13 +0000373
Zhi Dou4655c962023-06-12 15:56:03 +0000374 let mut file_set = HashMap::from([
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000375 ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()),
Zhi Doua7200112023-08-07 18:09:28 +0000376 ("com/android/aconfig/test/FeatureFlags.java", expect_featureflags_content.as_str()),
Zhi Dou5aaeee32023-08-07 22:54:13 +0000377 (
378 "com/android/aconfig/test/FeatureFlagsImpl.java",
379 expect_featureflagsimpl_content.as_str(),
380 ),
381 (
382 "com/android/aconfig/test/FakeFeatureFlagsImpl.java",
383 expect_fakefeatureflagsimpl_content,
384 ),
Zhi Dou4655c962023-06-12 15:56:03 +0000385 ]);
386
387 for file in generated_files {
388 let file_path = file.path.to_str().unwrap();
389 assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
390 assert_eq!(
391 None,
392 crate::test::first_significant_code_diff(
393 file_set.get(file_path).unwrap(),
394 &String::from_utf8(file.contents.clone()).unwrap()
395 ),
396 "File {} content is not correct",
397 file_path
398 );
399 file_set.remove(file_path);
400 }
401
402 assert!(file_set.is_empty());
Zhi Doueb744892023-05-10 04:02:33 +0000403 }
Zhi Douaf81e202023-06-14 20:38:20 +0000404
405 #[test]
406 fn test_format_java_method_name() {
407 let input = "____some_snake___name____";
408 let expected = "someSnakeName";
409 let formatted_name = format_java_method_name(input);
410 assert_eq!(expected, formatted_name);
411 }
Zhi Doueb744892023-05-10 04:02:33 +0000412}