nimbus_fml/backends/kotlin/gen_structs/
mod.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use askama::Template;
6use std::collections::HashSet;
7
8use crate::intermediate_representation::PropDef;
9use crate::{
10    backends::{CodeDeclaration, CodeOracle, CodeType, TypeIdentifier},
11    intermediate_representation::{FeatureDef, FeatureManifest, GeckoPrefDef, TypeFinder},
12};
13
14mod bundled;
15mod common;
16mod enum_;
17mod feature;
18mod filters;
19mod imports;
20mod object;
21mod primitives;
22mod structural;
23#[derive(Template)]
24#[template(syntax = "kt", escape = "none", path = "FeatureManifestTemplate.kt")]
25pub struct FeatureManifestDeclaration<'a> {
26    fm: &'a FeatureManifest,
27    oracle: ConcreteCodeOracle,
28}
29impl<'a> FeatureManifestDeclaration<'a> {
30    pub fn new(fm: &'a FeatureManifest) -> Self {
31        Self {
32            fm,
33            oracle: Default::default(),
34        }
35    }
36
37    pub fn members(&self) -> Vec<Box<dyn CodeDeclaration + 'a>> {
38        let fm = self.fm;
39
40        fm.iter_feature_defs()
41            .map(|inner| {
42                Box::new(feature::FeatureCodeDeclaration::new(fm, inner))
43                    as Box<dyn CodeDeclaration>
44            })
45            .chain(fm.iter_enum_defs().map(|inner| {
46                Box::new(enum_::EnumCodeDeclaration::new(fm, inner)) as Box<dyn CodeDeclaration>
47            }))
48            .chain(fm.iter_object_defs().map(|inner| {
49                Box::new(object::ObjectCodeDeclaration::new(fm, inner)) as Box<dyn CodeDeclaration>
50            }))
51            .chain(fm.iter_imported_files().into_iter().map(|inner| {
52                Box::new(imports::ImportedModuleInitialization::new(inner))
53                    as Box<dyn CodeDeclaration>
54            }))
55            .collect()
56    }
57
58    pub fn feature_properties(&self) -> Vec<PropDef> {
59        let fm = self.fm;
60
61        fm.iter_feature_defs()
62            .flat_map(|feature| feature.props())
63            .chain(
64                fm.iter_object_defs()
65                    .flat_map(|object| object.props.clone()),
66            )
67            .chain(fm.iter_imported_files().into_iter().flat_map(|inner| {
68                inner
69                    .fm
70                    .iter_feature_defs()
71                    .flat_map(|feature| feature.props())
72            }))
73            .collect()
74    }
75
76    pub fn iter_feature_defs(&self) -> Vec<&FeatureDef> {
77        self.fm.iter_feature_defs().collect::<_>()
78    }
79
80    #[allow(unused)]
81    pub fn iter_gecko_prefs(&self) -> Vec<&GeckoPrefDef> {
82        self.fm.iter_gecko_prefs().collect::<_>()
83    }
84
85    #[allow(unused)]
86    pub fn iter_features_with_prefs(&self) -> Vec<(String, Vec<(String, &GeckoPrefDef)>)> {
87        self.fm.iter_features_with_prefs().collect::<_>()
88    }
89
90    pub fn initialization_code(&self) -> Vec<String> {
91        let oracle = &self.oracle;
92        self.members()
93            .into_iter()
94            .filter_map(|member| member.initialization_code(oracle))
95            .collect()
96    }
97
98    pub fn declaration_code(&self) -> Vec<String> {
99        let oracle = &self.oracle;
100        self.members()
101            .into_iter()
102            .filter_map(|member| member.definition_code(oracle))
103            .collect()
104    }
105
106    pub fn imports(&self) -> Vec<String> {
107        let oracle = &self.oracle;
108        // We'll filter out objects from the package we're in.
109        let my_package = format!(
110            "{}.*",
111            self.fm.about.nimbus_package_name().unwrap_or_default()
112        );
113        let mut imports: Vec<String> = self
114            .members()
115            .into_iter()
116            .filter_map(|member| member.imports(oracle))
117            .flatten()
118            .chain(
119                self.fm
120                    .all_types()
121                    .into_iter()
122                    .filter_map(|type_| self.oracle.find(&type_).imports(oracle))
123                    .flatten(),
124            )
125            .chain(vec![
126                "org.mozilla.experiments.nimbus.Variables".to_string(),
127                "org.mozilla.experiments.nimbus.internal.FeatureHolder".to_string(),
128                "org.mozilla.experiments.nimbus.internal.FeatureManifestInterface".to_string(),
129                "org.mozilla.experiments.nimbus.internal.GeckoPref".to_string(),
130                "org.mozilla.experiments.nimbus.FeaturesInterface".to_string(),
131                "org.json.JSONObject".to_string(),
132                "android.content.SharedPreferences".to_string(),
133            ])
134            .filter(|i| i != &my_package)
135            .collect::<HashSet<String>>()
136            .into_iter()
137            .collect();
138
139        let include_r: bool = self
140            .feature_properties()
141            .into_iter()
142            .any(|prop| self.oracle.find(&prop.typ()).is_resource_id(&prop.default));
143        if include_r {
144            imports.push(format!("{}.R", self.fm.about.resource_package_name()))
145        }
146
147        imports.sort();
148        imports
149    }
150}
151
152#[derive(Default, Clone)]
153pub struct ConcreteCodeOracle;
154
155impl ConcreteCodeOracle {
156    fn create_code_type(&self, type_: TypeIdentifier) -> Box<dyn CodeType> {
157        match type_ {
158            TypeIdentifier::Boolean => Box::new(primitives::BooleanCodeType),
159            TypeIdentifier::String | TypeIdentifier::StringAlias(_) => {
160                Box::new(primitives::StringCodeType)
161            }
162            TypeIdentifier::Int => Box::new(primitives::IntCodeType),
163
164            TypeIdentifier::BundleText => Box::new(bundled::TextCodeType),
165            TypeIdentifier::BundleImage => Box::new(bundled::ImageCodeType),
166
167            TypeIdentifier::Enum(id) => Box::new(enum_::EnumCodeType::new(id)),
168            TypeIdentifier::Object(id) => Box::new(object::ObjectCodeType::new(id)),
169
170            TypeIdentifier::Option(ref inner) => Box::new(structural::OptionalCodeType::new(inner)),
171            TypeIdentifier::List(ref inner) => Box::new(structural::ListCodeType::new(inner)),
172            TypeIdentifier::StringMap(ref v_type) => {
173                let k_type = &TypeIdentifier::String;
174                Box::new(structural::MapCodeType::new(k_type, v_type))
175            }
176            TypeIdentifier::EnumMap(ref k_type, ref v_type) => {
177                Box::new(structural::MapCodeType::new(k_type, v_type))
178            }
179        }
180    }
181}
182
183impl CodeOracle for ConcreteCodeOracle {
184    fn find(&self, type_: &TypeIdentifier) -> Box<dyn CodeType> {
185        self.create_code_type(type_.clone())
186    }
187}