blob: a11737034c60e037e53ecb44e0830d0c0a09a851 [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 Dou8ba6aa72023-06-26 21:03:40 +0000119 const EXPECTED_FEATUREFLAGS_CONTENT: &str = r#"
120 package com.android.aconfig.test;
121 public interface FeatureFlags {
122 boolean disabledRo();
123 boolean disabledRw();
124 boolean enabledRo();
125 boolean enabledRw();
126 }"#;
Mårten Kongstada2e152a2023-06-19 16:11:33 +0200127
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000128 const EXPECTED_FLAG_COMMON_CONTENT: &str = r#"
129 package com.android.aconfig.test;
130 public final class Flags {
131 public static final String FLAG_DISABLED_RO = "com.android.aconfig.test.disabled_ro";
132 public static final String FLAG_DISABLED_RW = "com.android.aconfig.test.disabled_rw";
133 public static final String FLAG_ENABLED_RO = "com.android.aconfig.test.enabled_ro";
134 public static final String FLAG_ENABLED_RW = "com.android.aconfig.test.enabled_rw";
135
136 public static boolean disabledRo() {
137 return FEATURE_FLAGS.disabledRo();
Zhi Doueb744892023-05-10 04:02:33 +0000138 }
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000139 public static boolean disabledRw() {
140 return FEATURE_FLAGS.disabledRw();
141 }
142 public static boolean enabledRo() {
143 return FEATURE_FLAGS.enabledRo();
144 }
145 public static boolean enabledRw() {
146 return FEATURE_FLAGS.enabledRw();
147 }
148 "#;
149
Zhi Dou5aaeee32023-08-07 22:54:13 +0000150 const EXPECTED_METHOD_NOT_IMPL_COMMON_CONTENT: &str = r#"
151 @Override
152 public boolean disabledRo() {
153 throw new UnsupportedOperationException(
154 "Method is not implemented.");
155 }
156 @Override
157 public boolean disabledRw() {
158 throw new UnsupportedOperationException(
159 "Method is not implemented.");
160 }
161 @Override
162 public boolean enabledRo() {
163 throw new UnsupportedOperationException(
164 "Method is not implemented.");
165 }
166 @Override
167 public boolean enabledRw() {
168 throw new UnsupportedOperationException(
169 "Method is not implemented.");
170 }
171 "#;
172
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000173 #[test]
174 fn test_generate_java_code_production() {
175 let parsed_flags = crate::test::parse_test_flags();
176 let generated_files = generate_java_code(
177 crate::test::TEST_PACKAGE,
178 parsed_flags.parsed_flag.iter(),
179 CodegenMode::Production,
180 )
181 .unwrap();
182 let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
183 + r#"
184 private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
185 }"#;
Zhi Dou5aaeee32023-08-07 22:54:13 +0000186 let expect_fakefeatureflagsimpl_content = r#"
187 package com.android.aconfig.test;
188 public class FakeFeatureFlagsImpl implements FeatureFlags {"#
189 .to_owned()
190 + EXPECTED_METHOD_NOT_IMPL_COMMON_CONTENT
191 + r#"
192 }
193 "#;
194 let expect_featureflagsimpl_content = r#"
Zhi Dou4655c962023-06-12 15:56:03 +0000195 package com.android.aconfig.test;
196 import android.provider.DeviceConfig;
197 public final class FeatureFlagsImpl implements FeatureFlags {
198 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000199 public boolean disabledRo() {
Zhi Dou4655c962023-06-12 15:56:03 +0000200 return false;
201 }
202 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000203 public boolean disabledRw() {
Zhi Dou4655c962023-06-12 15:56:03 +0000204 return DeviceConfig.getBoolean(
205 "aconfig_test",
206 "com.android.aconfig.test.disabled_rw",
207 false
208 );
209 }
210 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000211 public boolean enabledRo() {
Zhi Dou4655c962023-06-12 15:56:03 +0000212 return true;
213 }
214 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000215 public boolean enabledRw() {
Zhi Dou4655c962023-06-12 15:56:03 +0000216 return DeviceConfig.getBoolean(
217 "aconfig_test",
218 "com.android.aconfig.test.enabled_rw",
219 true
220 );
221 }
222 }
223 "#;
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000224 let mut file_set = HashMap::from([
225 ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()),
Zhi Dou5aaeee32023-08-07 22:54:13 +0000226 ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_featureflagsimpl_content),
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000227 ("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_CONTENT),
Zhi Dou5aaeee32023-08-07 22:54:13 +0000228 (
229 "com/android/aconfig/test/FakeFeatureFlagsImpl.java",
230 expect_fakefeatureflagsimpl_content.as_str(),
231 ),
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000232 ]);
233
234 for file in generated_files {
235 let file_path = file.path.to_str().unwrap();
236 assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
237 assert_eq!(
238 None,
239 crate::test::first_significant_code_diff(
240 file_set.get(file_path).unwrap(),
241 &String::from_utf8(file.contents.clone()).unwrap()
242 ),
243 "File {} content is not correct",
244 file_path
245 );
246 file_set.remove(file_path);
247 }
248
249 assert!(file_set.is_empty());
250 }
251
252 #[test]
253 fn test_generate_java_code_test() {
254 let parsed_flags = crate::test::parse_test_flags();
255 let generated_files = generate_java_code(
256 crate::test::TEST_PACKAGE,
257 parsed_flags.parsed_flag.iter(),
258 CodegenMode::Test,
259 )
260 .unwrap();
261 let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
262 + r#"
Zhi Dou5aaeee32023-08-07 22:54:13 +0000263 public static void setFeatureFlags(FeatureFlags featureFlags) {
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000264 Flags.FEATURE_FLAGS = featureFlags;
265 }
Zhi Dou5aaeee32023-08-07 22:54:13 +0000266 public static void unsetFeatureFlags() {
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000267 Flags.FEATURE_FLAGS = null;
268 }
269 private static FeatureFlags FEATURE_FLAGS;
270 }
271 "#;
Zhi Dou5aaeee32023-08-07 22:54:13 +0000272 let expect_featureflagsimpl_content = r#"
273 package com.android.aconfig.test;
274 public final class FeatureFlagsImpl implements FeatureFlags {"#
275 .to_owned()
276 + EXPECTED_METHOD_NOT_IMPL_COMMON_CONTENT
277 + r#"
278 }
279 "#;
280 let expect_fakefeatureflagsimpl_content = r#"
Zhi Dou4655c962023-06-12 15:56:03 +0000281 package com.android.aconfig.test;
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000282 import static java.util.stream.Collectors.toMap;
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000283 import java.util.HashMap;
Zhi Dou8d27cc32023-06-29 15:15:32 +0000284 import java.util.Map;
Zhi Doua41cc5e2023-06-29 15:01:56 +0000285 import java.util.stream.Stream;
Zhi Dou5aaeee32023-08-07 22:54:13 +0000286 public class FakeFeatureFlagsImpl implements FeatureFlags {
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000287 @Override
288 public boolean disabledRo() {
289 return getFlag(Flags.FLAG_DISABLED_RO);
290 }
291 @Override
292 public boolean disabledRw() {
293 return getFlag(Flags.FLAG_DISABLED_RW);
294 }
295 @Override
296 public boolean enabledRo() {
297 return getFlag(Flags.FLAG_ENABLED_RO);
298 }
299 @Override
300 public boolean enabledRw() {
301 return getFlag(Flags.FLAG_ENABLED_RW);
302 }
303 public void setFlag(String flagName, boolean value) {
304 if (!this.mFlagMap.containsKey(flagName)) {
305 throw new IllegalArgumentException("no such flag" + flagName);
306 }
307 this.mFlagMap.put(flagName, value);
308 }
Zhi Dou8d27cc32023-06-29 15:15:32 +0000309 public void resetAll() {
310 for (Map.Entry entry : mFlagMap.entrySet()) {
311 entry.setValue(null);
312 }
313 }
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000314 private boolean getFlag(String flagName) {
315 Boolean value = this.mFlagMap.get(flagName);
316 if (value == null) {
317 throw new IllegalArgumentException(flagName + " is not set");
318 }
319 return value;
320 }
321 private HashMap<String, Boolean> mFlagMap = Stream.of(
322 Flags.FLAG_DISABLED_RO,
323 Flags.FLAG_DISABLED_RW,
324 Flags.FLAG_ENABLED_RO,
325 Flags.FLAG_ENABLED_RW
326 )
327 .collect(
328 HashMap::new,
329 (map, elem) -> map.put(elem, null),
330 HashMap::putAll
331 );
Zhi Dou4655c962023-06-12 15:56:03 +0000332 }
333 "#;
Zhi Dou5aaeee32023-08-07 22:54:13 +0000334
Zhi Dou4655c962023-06-12 15:56:03 +0000335 let mut file_set = HashMap::from([
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000336 ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()),
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000337 ("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_CONTENT),
Zhi Dou5aaeee32023-08-07 22:54:13 +0000338 (
339 "com/android/aconfig/test/FeatureFlagsImpl.java",
340 expect_featureflagsimpl_content.as_str(),
341 ),
342 (
343 "com/android/aconfig/test/FakeFeatureFlagsImpl.java",
344 expect_fakefeatureflagsimpl_content,
345 ),
Zhi Dou4655c962023-06-12 15:56:03 +0000346 ]);
347
348 for file in generated_files {
349 let file_path = file.path.to_str().unwrap();
350 assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
351 assert_eq!(
352 None,
353 crate::test::first_significant_code_diff(
354 file_set.get(file_path).unwrap(),
355 &String::from_utf8(file.contents.clone()).unwrap()
356 ),
357 "File {} content is not correct",
358 file_path
359 );
360 file_set.remove(file_path);
361 }
362
363 assert!(file_set.is_empty());
Zhi Doueb744892023-05-10 04:02:33 +0000364 }
Zhi Douaf81e202023-06-14 20:38:20 +0000365
366 #[test]
367 fn test_format_java_method_name() {
368 let input = "____some_snake___name____";
369 let expected = "someSnakeName";
370 let formatted_name = format_java_method_name(input);
371 assert_eq!(expected, formatted_name);
372 }
Zhi Doueb744892023-05-10 04:02:33 +0000373}