blob: 0d1b281696cc699089f88b503bdc543f337702d6 [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,
Zhi Dou4655c962023-06-12 15:56:03 +000069 pub is_read_write: bool,
70 pub method_name: String,
Zhi Doueb744892023-05-10 04:02:33 +000071}
72
Mårten Kongstad403658f2023-06-14 09:51:56 +020073fn create_class_element(package: &str, pf: &ProtoParsedFlag) -> ClassElement {
74 let device_config_flag = codegen::create_device_config_ident(package, pf.name())
75 .expect("values checked at flag parse time");
Zhi Doueb744892023-05-10 04:02:33 +000076 ClassElement {
Mårten Kongstad403658f2023-06-14 09:51:56 +020077 default_value: if pf.state() == ProtoFlagState::ENABLED {
Zhi Doueb744892023-05-10 04:02:33 +000078 "true".to_string()
79 } else {
80 "false".to_string()
81 },
Mårten Kongstad403658f2023-06-14 09:51:56 +020082 device_config_namespace: pf.namespace().to_string(),
Mårten Kongstad066575b2023-06-07 16:29:25 +020083 device_config_flag,
Mårten Kongstad403658f2023-06-14 09:51:56 +020084 is_read_write: pf.permission() == ProtoFlagPermission::READ_WRITE,
85 method_name: format_java_method_name(pf.name()),
Zhi Doueb744892023-05-10 04:02:33 +000086 }
87}
88
Zhi Douaf81e202023-06-14 20:38:20 +000089fn format_java_method_name(flag_name: &str) -> String {
90 flag_name
91 .split('_')
92 .filter(|&word| !word.is_empty())
93 .enumerate()
94 .map(|(index, word)| {
95 if index == 0 {
96 word.to_ascii_lowercase()
97 } else {
98 word[0..1].to_ascii_uppercase() + &word[1..].to_ascii_lowercase()
99 }
100 })
101 .collect::<Vec<String>>()
102 .join("")
103}
104
Zhi Doueb744892023-05-10 04:02:33 +0000105#[cfg(test)]
106mod tests {
107 use super::*;
Zhi Dou4655c962023-06-12 15:56:03 +0000108 use std::collections::HashMap;
Zhi Doueb744892023-05-10 04:02:33 +0000109
110 #[test]
111 fn test_generate_java_code() {
Mårten Kongstad403658f2023-06-14 09:51:56 +0200112 let parsed_flags = crate::test::parse_test_flags();
113 let generated_files =
114 generate_java_code(crate::test::TEST_PACKAGE, parsed_flags.parsed_flag.iter()).unwrap();
Zhi Dou4655c962023-06-12 15:56:03 +0000115 let expect_flags_content = r#"
116 package com.android.aconfig.test;
Joe Onorato0c4ef0f2023-05-13 11:30:11 -0700117 public final class Flags {
Zhi Douaf81e202023-06-14 20:38:20 +0000118 public static boolean disabledRo() {
119 return FEATURE_FLAGS.disabledRo();
Zhi Doueb744892023-05-10 04:02:33 +0000120 }
Zhi Douaf81e202023-06-14 20:38:20 +0000121 public static boolean disabledRw() {
122 return FEATURE_FLAGS.disabledRw();
Zhi Doueb744892023-05-10 04:02:33 +0000123 }
Zhi Douaf81e202023-06-14 20:38:20 +0000124 public static boolean enabledRo() {
125 return FEATURE_FLAGS.enabledRo();
Zhi Dou4655c962023-06-12 15:56:03 +0000126 }
Zhi Douaf81e202023-06-14 20:38:20 +0000127 public static boolean enabledRw() {
128 return FEATURE_FLAGS.enabledRw();
Zhi Dou4655c962023-06-12 15:56:03 +0000129 }
130 private static FeatureFlags FEATURE_FLAGS = new FeatureFlagsImpl();
Zhi Doueb744892023-05-10 04:02:33 +0000131 }
Mårten Kongstadd42eeeb2023-05-12 10:01:00 +0200132 "#;
Zhi Dou4655c962023-06-12 15:56:03 +0000133 let expected_featureflagsimpl_content = r#"
134 package com.android.aconfig.test;
135 import android.provider.DeviceConfig;
136 public final class FeatureFlagsImpl implements FeatureFlags {
137 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000138 public boolean disabledRo() {
Zhi Dou4655c962023-06-12 15:56:03 +0000139 return false;
140 }
141 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000142 public boolean disabledRw() {
Zhi Dou4655c962023-06-12 15:56:03 +0000143 return DeviceConfig.getBoolean(
144 "aconfig_test",
145 "com.android.aconfig.test.disabled_rw",
146 false
147 );
148 }
149 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000150 public boolean enabledRo() {
Zhi Dou4655c962023-06-12 15:56:03 +0000151 return true;
152 }
153 @Override
Zhi Douaf81e202023-06-14 20:38:20 +0000154 public boolean enabledRw() {
Zhi Dou4655c962023-06-12 15:56:03 +0000155 return DeviceConfig.getBoolean(
156 "aconfig_test",
157 "com.android.aconfig.test.enabled_rw",
158 true
159 );
160 }
161 }
162 "#;
163 let expected_featureflags_content = r#"
164 package com.android.aconfig.test;
165 public interface FeatureFlags {
Zhi Douaf81e202023-06-14 20:38:20 +0000166 boolean disabledRo();
167 boolean disabledRw();
168 boolean enabledRo();
169 boolean enabledRw();
Zhi Dou4655c962023-06-12 15:56:03 +0000170 }
171 "#;
172 let mut file_set = HashMap::from([
173 ("com/android/aconfig/test/Flags.java", expect_flags_content),
174 ("com/android/aconfig/test/FeatureFlagsImpl.java", expected_featureflagsimpl_content),
175 ("com/android/aconfig/test/FeatureFlags.java", expected_featureflags_content),
176 ]);
177
178 for file in generated_files {
179 let file_path = file.path.to_str().unwrap();
180 assert!(file_set.contains_key(file_path), "Cannot find {}", file_path);
181 assert_eq!(
182 None,
183 crate::test::first_significant_code_diff(
184 file_set.get(file_path).unwrap(),
185 &String::from_utf8(file.contents.clone()).unwrap()
186 ),
187 "File {} content is not correct",
188 file_path
189 );
190 file_set.remove(file_path);
191 }
192
193 assert!(file_set.is_empty());
Zhi Doueb744892023-05-10 04:02:33 +0000194 }
Zhi Douaf81e202023-06-14 20:38:20 +0000195
196 #[test]
197 fn test_format_java_method_name() {
198 let input = "____some_snake___name____";
199 let expected = "someSnakeName";
200 let formatted_name = format_java_method_name(input);
201 assert_eq!(expected, formatted_name);
202 }
Zhi Doueb744892023-05-10 04:02:33 +0000203}