blob: 47516b76c9da9ce6bc80a795fdafe4816f012de9 [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;
Mårten Kongstadd42eeeb2023-05-12 10:01:00 +020023use crate::commands::OutputFile;
Mårten Kongstad403658f2023-06-14 09:51:56 +020024use crate::protos::{ProtoFlagPermission, ProtoFlagState, ProtoParsedFlag};
Zhi Doueb744892023-05-10 04:02:33 +000025
Mårten Kongstad403658f2023-06-14 09:51:56 +020026pub fn generate_java_code<'a, I>(package: &str, parsed_flags_iter: I) -> Result<Vec<OutputFile>>
27where
28 I: Iterator<Item = &'a ProtoParsedFlag>,
29{
Mårten Kongstad066575b2023-06-07 16:29:25 +020030 let class_elements: Vec<ClassElement> =
Mårten Kongstad403658f2023-06-14 09:51:56 +020031 parsed_flags_iter.map(|pf| create_class_element(package, pf)).collect();
32 let is_read_write = class_elements.iter().any(|elem| elem.is_read_write);
Zhi Dou4655c962023-06-12 15:56:03 +000033 let context = Context { package_name: package.to_string(), is_read_write, class_elements };
Zhi Doueb744892023-05-10 04:02:33 +000034 let mut template = TinyTemplate::new();
Zhi Dou4655c962023-06-12 15:56:03 +000035 template.add_template("Flags.java", include_str!("../templates/Flags.java.template"))?;
36 template.add_template(
37 "FeatureFlagsImpl.java",
38 include_str!("../templates/FeatureFlagsImpl.java.template"),
39 )?;
40 template.add_template(
41 "FeatureFlags.java",
42 include_str!("../templates/FeatureFlags.java.template"),
43 )?;
44
45 let path: PathBuf = package.split('.').collect();
Mårten Kongstad403658f2023-06-14 09:51:56 +020046 ["Flags.java", "FeatureFlagsImpl.java", "FeatureFlags.java"]
Zhi Dou4655c962023-06-12 15:56:03 +000047 .iter()
48 .map(|file| {
49 Ok(OutputFile {
50 contents: template.render(file, &context)?.into(),
51 path: path.join(file),
52 })
53 })
54 .collect::<Result<Vec<OutputFile>>>()
Zhi Doueb744892023-05-10 04:02:33 +000055}
56
57#[derive(Serialize)]
58struct Context {
Zhi Dou4655c962023-06-12 15:56:03 +000059 pub package_name: String,
60 pub is_read_write: bool,
Zhi Doueb744892023-05-10 04:02:33 +000061 pub class_elements: Vec<ClassElement>,
62}
63
64#[derive(Serialize)]
65struct ClassElement {
Zhi Doueb744892023-05-10 04:02:33 +000066 pub default_value: String,
Mårten Kongstad066575b2023-06-07 16:29:25 +020067 pub device_config_namespace: String,
68 pub device_config_flag: String,
Mårten Kongstada2e152a2023-06-19 16:11:33 +020069 pub flag_name_constant_suffix: String,
Zhi Dou4655c962023-06-12 15:56:03 +000070 pub is_read_write: bool,
71 pub method_name: String,
Zhi Doueb744892023-05-10 04:02:33 +000072}
73
Mårten Kongstad403658f2023-06-14 09:51:56 +020074fn create_class_element(package: &str, pf: &ProtoParsedFlag) -> ClassElement {
75 let device_config_flag = codegen::create_device_config_ident(package, pf.name())
76 .expect("values checked at flag parse time");
Zhi Doueb744892023-05-10 04:02:33 +000077 ClassElement {
Mårten Kongstad403658f2023-06-14 09:51:56 +020078 default_value: if pf.state() == ProtoFlagState::ENABLED {
Zhi Doueb744892023-05-10 04:02:33 +000079 "true".to_string()
80 } else {
81 "false".to_string()
82 },
Mårten Kongstad403658f2023-06-14 09:51:56 +020083 device_config_namespace: pf.namespace().to_string(),
Mårten Kongstad066575b2023-06-07 16:29:25 +020084 device_config_flag,
Mårten Kongstada2e152a2023-06-19 16:11:33 +020085 flag_name_constant_suffix: pf.name().to_ascii_uppercase(),
Mårten Kongstad403658f2023-06-14 09:51:56 +020086 is_read_write: pf.permission() == ProtoFlagPermission::READ_WRITE,
87 method_name: format_java_method_name(pf.name()),
Zhi Doueb744892023-05-10 04:02:33 +000088 }
89}
90
Zhi Douaf81e202023-06-14 20:38:20 +000091fn format_java_method_name(flag_name: &str) -> String {
92 flag_name
93 .split('_')
94 .filter(|&word| !word.is_empty())
95 .enumerate()
96 .map(|(index, word)| {
97 if index == 0 {
98 word.to_ascii_lowercase()
99 } else {
100 word[0..1].to_ascii_uppercase() + &word[1..].to_ascii_lowercase()
101 }
102 })
103 .collect::<Vec<String>>()
104 .join("")
105}
106
Zhi Doueb744892023-05-10 04:02:33 +0000107#[cfg(test)]
108mod tests {
109 use super::*;
Zhi Dou4655c962023-06-12 15:56:03 +0000110 use std::collections::HashMap;
Zhi Doueb744892023-05-10 04:02:33 +0000111
112 #[test]
113 fn test_generate_java_code() {
Mårten Kongstad403658f2023-06-14 09:51:56 +0200114 let parsed_flags = crate::test::parse_test_flags();
115 let generated_files =
116 generate_java_code(crate::test::TEST_PACKAGE, parsed_flags.parsed_flag.iter()).unwrap();
Zhi Dou4655c962023-06-12 15:56:03 +0000117 let expect_flags_content = r#"
118 package com.android.aconfig.test;
Joe Onorato0c4ef0f2023-05-13 11:30:11 -0700119 public final class Flags {
Mårten Kongstada2e152a2023-06-19 16:11:33 +0200120 public static final String FLAG_DISABLED_RO = "com.android.aconfig.test.disabled_ro";
121 public static final String FLAG_DISABLED_RW = "com.android.aconfig.test.disabled_rw";
122 public static final String FLAG_ENABLED_RO = "com.android.aconfig.test.enabled_ro";
123 public static final String FLAG_ENABLED_RW = "com.android.aconfig.test.enabled_rw";
124
Zhi Douaf81e202023-06-14 20:38:20 +0000125 public static boolean disabledRo() {
126 return FEATURE_FLAGS.disabledRo();
Zhi Doueb744892023-05-10 04:02:33 +0000127 }
Zhi Douaf81e202023-06-14 20:38:20 +0000128 public static boolean disabledRw() {
129 return FEATURE_FLAGS.disabledRw();
Zhi Doueb744892023-05-10 04:02:33 +0000130 }
Zhi Douaf81e202023-06-14 20:38:20 +0000131 public static boolean enabledRo() {
132 return FEATURE_FLAGS.enabledRo();
Zhi Dou4655c962023-06-12 15:56:03 +0000133 }
Zhi Douaf81e202023-06-14 20:38:20 +0000134 public static boolean enabledRw() {
135 return FEATURE_FLAGS.enabledRw();
Zhi Dou4655c962023-06-12 15:56:03 +0000136 }
137 private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
Zhi Doueb744892023-05-10 04:02:33 +0000138 }
Mårten Kongstadd42eeeb2023-05-12 10:01:00 +0200139 "#;
Zhi Dou4655c962023-06-12 15:56:03 +0000140 let expected_featureflagsimpl_content = r#"
141 package com.android.aconfig.test;
142 import android.provider.DeviceConfig;
143 public final class FeatureFlagsImpl implements FeatureFlags {
144 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000145 public boolean disabledRo() {
Zhi Dou4655c962023-06-12 15:56:03 +0000146 return false;
147 }
148 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000149 public boolean disabledRw() {
Zhi Dou4655c962023-06-12 15:56:03 +0000150 return DeviceConfig.getBoolean(
151 "aconfig_test",
152 "com.android.aconfig.test.disabled_rw",
153 false
154 );
155 }
156 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000157 public boolean enabledRo() {
Zhi Dou4655c962023-06-12 15:56:03 +0000158 return true;
159 }
160 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000161 public boolean enabledRw() {
Zhi Dou4655c962023-06-12 15:56:03 +0000162 return DeviceConfig.getBoolean(
163 "aconfig_test",
164 "com.android.aconfig.test.enabled_rw",
165 true
166 );
167 }
168 }
169 "#;
170 let expected_featureflags_content = r#"
171 package com.android.aconfig.test;
172 public interface FeatureFlags {
Zhi Douaf81e202023-06-14 20:38:20 +0000173 boolean disabledRo();
174 boolean disabledRw();
175 boolean enabledRo();
176 boolean enabledRw();
Zhi Dou4655c962023-06-12 15:56:03 +0000177 }
178 "#;
179 let mut file_set = HashMap::from([
180 ("com/android/aconfig/test/Flags.java", expect_flags_content),
181 ("com/android/aconfig/test/FeatureFlagsImpl.java", expected_featureflagsimpl_content),
182 ("com/android/aconfig/test/FeatureFlags.java", expected_featureflags_content),
183 ]);
184
185 for file in generated_files {
186 let file_path = file.path.to_str().unwrap();
187 assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
188 assert_eq!(
189 None,
190 crate::test::first_significant_code_diff(
191 file_set.get(file_path).unwrap(),
192 &String::from_utf8(file.contents.clone()).unwrap()
193 ),
194 "File {} content is not correct",
195 file_path
196 );
197 file_set.remove(file_path);
198 }
199
200 assert!(file_set.is_empty());
Zhi Doueb744892023-05-10 04:02:33 +0000201 }
Zhi Douaf81e202023-06-14 20:38:20 +0000202
203 #[test]
204 fn test_format_java_method_name() {
205 let input = "____some_snake___name____";
206 let expected = "someSnakeName";
207 let formatted_name = format_java_method_name(input);
208 assert_eq!(expected, formatted_name);
209 }
Zhi Doueb744892023-05-10 04:02:33 +0000210}