blob: 2c9dcf5a488ab95f2985be12dbbb23649649bdcd [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();
124 boolean enabledRo();
125 boolean enabledRw();
Zhi Doua7200112023-08-07 18:09:28 +0000126 "#;
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();
Zhi Doua7200112023-08-07 18:09:28 +0000182 let expect_featureflags_content = EXPECTED_FEATUREFLAGS_COMMON_CONTENT.to_string()
183 + r#"
184 }"#;
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000185 let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
186 + r#"
187 private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
188 }"#;
Zhi Dou5aaeee32023-08-07 22:54:13 +0000189 let expect_fakefeatureflagsimpl_content = r#"
190 package com.android.aconfig.test;
191 public class FakeFeatureFlagsImpl implements FeatureFlags {"#
192 .to_owned()
193 + EXPECTED_METHOD_NOT_IMPL_COMMON_CONTENT
194 + r#"
195 }
196 "#;
197 let expect_featureflagsimpl_content = r#"
Zhi Dou4655c962023-06-12 15:56:03 +0000198 package com.android.aconfig.test;
199 import android.provider.DeviceConfig;
200 public final class FeatureFlagsImpl implements FeatureFlags {
201 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000202 public boolean disabledRo() {
Zhi Dou4655c962023-06-12 15:56:03 +0000203 return false;
204 }
205 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000206 public boolean disabledRw() {
Zhi Dou4655c962023-06-12 15:56:03 +0000207 return DeviceConfig.getBoolean(
208 "aconfig_test",
209 "com.android.aconfig.test.disabled_rw",
210 false
211 );
212 }
213 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000214 public boolean enabledRo() {
Zhi Dou4655c962023-06-12 15:56:03 +0000215 return true;
216 }
217 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000218 public boolean enabledRw() {
Zhi Dou4655c962023-06-12 15:56:03 +0000219 return DeviceConfig.getBoolean(
220 "aconfig_test",
221 "com.android.aconfig.test.enabled_rw",
222 true
223 );
224 }
225 }
226 "#;
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000227 let mut file_set = HashMap::from([
228 ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()),
Zhi Dou5aaeee32023-08-07 22:54:13 +0000229 ("com/android/aconfig/test/FeatureFlagsImpl.java", expect_featureflagsimpl_content),
Zhi Doua7200112023-08-07 18:09:28 +0000230 ("com/android/aconfig/test/FeatureFlags.java", expect_featureflags_content.as_str()),
Zhi Dou5aaeee32023-08-07 22:54:13 +0000231 (
232 "com/android/aconfig/test/FakeFeatureFlagsImpl.java",
233 expect_fakefeatureflagsimpl_content.as_str(),
234 ),
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000235 ]);
236
237 for file in generated_files {
238 let file_path = file.path.to_str().unwrap();
239 assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
240 assert_eq!(
241 None,
242 crate::test::first_significant_code_diff(
243 file_set.get(file_path).unwrap(),
244 &String::from_utf8(file.contents.clone()).unwrap()
245 ),
246 "File {} content is not correct",
247 file_path
248 );
249 file_set.remove(file_path);
250 }
251
252 assert!(file_set.is_empty());
253 }
254
255 #[test]
256 fn test_generate_java_code_test() {
257 let parsed_flags = crate::test::parse_test_flags();
258 let generated_files = generate_java_code(
259 crate::test::TEST_PACKAGE,
260 parsed_flags.parsed_flag.iter(),
261 CodegenMode::Test,
262 )
263 .unwrap();
Zhi Doua7200112023-08-07 18:09:28 +0000264 let expect_featureflags_content = EXPECTED_FEATUREFLAGS_COMMON_CONTENT.to_string()
265 + r#"
266 public void setFlag(String flagName, boolean value);
267 public void resetAll();
268 }"#;
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000269 let expect_flags_content = EXPECTED_FLAG_COMMON_CONTENT.to_string()
270 + r#"
Zhi Dou5aaeee32023-08-07 22:54:13 +0000271 public static void setFeatureFlags(FeatureFlags featureFlags) {
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000272 Flags.FEATURE_FLAGS = featureFlags;
273 }
Zhi Dou5aaeee32023-08-07 22:54:13 +0000274 public static void unsetFeatureFlags() {
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000275 Flags.FEATURE_FLAGS = null;
276 }
277 private static FeatureFlags FEATURE_FLAGS;
278 }
279 "#;
Zhi Dou5aaeee32023-08-07 22:54:13 +0000280 let expect_featureflagsimpl_content = r#"
281 package com.android.aconfig.test;
282 public final class FeatureFlagsImpl implements FeatureFlags {"#
283 .to_owned()
284 + EXPECTED_METHOD_NOT_IMPL_COMMON_CONTENT
285 + r#"
Zhi Doua7200112023-08-07 18:09:28 +0000286 @Override
287 public void setFlag(String flagName, boolean value) {
288 throw new UnsupportedOperationException(
289 "Method is not implemented.");
290 }
291 @Override
292 public void resetAll() {
293 throw new UnsupportedOperationException(
294 "Method is not implemented.");
295 }
Zhi Dou5aaeee32023-08-07 22:54:13 +0000296 }
297 "#;
298 let expect_fakefeatureflagsimpl_content = r#"
Zhi Dou4655c962023-06-12 15:56:03 +0000299 package com.android.aconfig.test;
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000300 import static java.util.stream.Collectors.toMap;
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000301 import java.util.HashMap;
Zhi Dou8d27cc32023-06-29 15:15:32 +0000302 import java.util.Map;
Zhi Doua41cc5e2023-06-29 15:01:56 +0000303 import java.util.stream.Stream;
Zhi Dou5aaeee32023-08-07 22:54:13 +0000304 public class FakeFeatureFlagsImpl implements FeatureFlags {
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000305 @Override
306 public boolean disabledRo() {
307 return getFlag(Flags.FLAG_DISABLED_RO);
308 }
309 @Override
310 public boolean disabledRw() {
311 return getFlag(Flags.FLAG_DISABLED_RW);
312 }
313 @Override
314 public boolean enabledRo() {
315 return getFlag(Flags.FLAG_ENABLED_RO);
316 }
317 @Override
318 public boolean enabledRw() {
319 return getFlag(Flags.FLAG_ENABLED_RW);
320 }
Zhi Doua7200112023-08-07 18:09:28 +0000321 @Override
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000322 public void setFlag(String flagName, boolean value) {
323 if (!this.mFlagMap.containsKey(flagName)) {
324 throw new IllegalArgumentException("no such flag" + flagName);
325 }
326 this.mFlagMap.put(flagName, value);
327 }
Zhi Doua7200112023-08-07 18:09:28 +0000328 @Override
Zhi Dou8d27cc32023-06-29 15:15:32 +0000329 public void resetAll() {
330 for (Map.Entry entry : mFlagMap.entrySet()) {
331 entry.setValue(null);
332 }
333 }
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000334 private boolean getFlag(String flagName) {
335 Boolean value = this.mFlagMap.get(flagName);
336 if (value == null) {
337 throw new IllegalArgumentException(flagName + " is not set");
338 }
339 return value;
340 }
341 private HashMap<String, Boolean> mFlagMap = Stream.of(
342 Flags.FLAG_DISABLED_RO,
343 Flags.FLAG_DISABLED_RW,
344 Flags.FLAG_ENABLED_RO,
345 Flags.FLAG_ENABLED_RW
346 )
347 .collect(
348 HashMap::new,
349 (map, elem) -> map.put(elem, null),
350 HashMap::putAll
351 );
Zhi Dou4655c962023-06-12 15:56:03 +0000352 }
353 "#;
Zhi Dou5aaeee32023-08-07 22:54:13 +0000354
Zhi Dou4655c962023-06-12 15:56:03 +0000355 let mut file_set = HashMap::from([
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000356 ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()),
Zhi Doua7200112023-08-07 18:09:28 +0000357 ("com/android/aconfig/test/FeatureFlags.java", expect_featureflags_content.as_str()),
Zhi Dou5aaeee32023-08-07 22:54:13 +0000358 (
359 "com/android/aconfig/test/FeatureFlagsImpl.java",
360 expect_featureflagsimpl_content.as_str(),
361 ),
362 (
363 "com/android/aconfig/test/FakeFeatureFlagsImpl.java",
364 expect_fakefeatureflagsimpl_content,
365 ),
Zhi Dou4655c962023-06-12 15:56:03 +0000366 ]);
367
368 for file in generated_files {
369 let file_path = file.path.to_str().unwrap();
370 assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
371 assert_eq!(
372 None,
373 crate::test::first_significant_code_diff(
374 file_set.get(file_path).unwrap(),
375 &String::from_utf8(file.contents.clone()).unwrap()
376 ),
377 "File {} content is not correct",
378 file_path
379 );
380 file_set.remove(file_path);
381 }
382
383 assert!(file_set.is_empty());
Zhi Doueb744892023-05-10 04:02:33 +0000384 }
Zhi Douaf81e202023-06-14 20:38:20 +0000385
386 #[test]
387 fn test_format_java_method_name() {
388 let input = "____some_snake___name____";
389 let expected = "someSnakeName";
390 let formatted_name = format_java_method_name(input);
391 assert_eq!(expected, formatted_name);
392 }
Zhi Doueb744892023-05-10 04:02:33 +0000393}