blob: 15eb2d65ba5d5a6adf8e2e4c4e92e98c26f8a526 [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;
236 import java.util.stream.Stream;
237 import java.util.HashMap;
238 public final class FeatureFlagsImpl implements FeatureFlags {
239 @Override
240 public boolean disabledRo() {
241 return getFlag(Flags.FLAG_DISABLED_RO);
242 }
243 @Override
244 public boolean disabledRw() {
245 return getFlag(Flags.FLAG_DISABLED_RW);
246 }
247 @Override
248 public boolean enabledRo() {
249 return getFlag(Flags.FLAG_ENABLED_RO);
250 }
251 @Override
252 public boolean enabledRw() {
253 return getFlag(Flags.FLAG_ENABLED_RW);
254 }
255 public void setFlag(String flagName, boolean value) {
256 if (!this.mFlagMap.containsKey(flagName)) {
257 throw new IllegalArgumentException("no such flag" + flagName);
258 }
259 this.mFlagMap.put(flagName, value);
260 }
261 private boolean getFlag(String flagName) {
262 Boolean value = this.mFlagMap.get(flagName);
263 if (value == null) {
264 throw new IllegalArgumentException(flagName + " is not set");
265 }
266 return value;
267 }
268 private HashMap<String, Boolean> mFlagMap = Stream.of(
269 Flags.FLAG_DISABLED_RO,
270 Flags.FLAG_DISABLED_RW,
271 Flags.FLAG_ENABLED_RO,
272 Flags.FLAG_ENABLED_RW
273 )
274 .collect(
275 HashMap::new,
276 (map, elem) -> map.put(elem, null),
277 HashMap::putAll
278 );
Zhi Dou4655c962023-06-12 15:56:03 +0000279 }
280 "#;
281 let mut file_set = HashMap::from([
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000282 ("com/android/aconfig/test/Flags.java", expect_flags_content.as_str()),
Zhi Dou4655c962023-06-12 15:56:03 +0000283 ("com/android/aconfig/test/FeatureFlagsImpl.java", expected_featureflagsimpl_content),
Zhi Dou8ba6aa72023-06-26 21:03:40 +0000284 ("com/android/aconfig/test/FeatureFlags.java", EXPECTED_FEATUREFLAGS_CONTENT),
Zhi Dou4655c962023-06-12 15:56:03 +0000285 ]);
286
287 for file in generated_files {
288 let file_path = file.path.to_str().unwrap();
289 assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
290 assert_eq!(
291 None,
292 crate::test::first_significant_code_diff(
293 file_set.get(file_path).unwrap(),
294 &String::from_utf8(file.contents.clone()).unwrap()
295 ),
296 "File {} content is not correct",
297 file_path
298 );
299 file_set.remove(file_path);
300 }
301
302 assert!(file_set.is_empty());
Zhi Doueb744892023-05-10 04:02:33 +0000303 }
Zhi Douaf81e202023-06-14 20:38:20 +0000304
305 #[test]
306 fn test_format_java_method_name() {
307 let input = "____some_snake___name____";
308 let expected = "someSnakeName";
309 let formatted_name = format_java_method_name(input);
310 assert_eq!(expected, formatted_name);
311 }
Zhi Doueb744892023-05-10 04:02:33 +0000312}