aconfig: follow Java conventions for Java file paths

Update codegen_java to write the generated Java file(s) to
"java/package/File.java" instead of just "File.java".

Also generalize codegen_java::GeneratedFile to commands::OutputFile in
preparation for the upcoming C++ and Rust codegen.

Also change Java package name to 'com.android.internal.aconfig'.

Bug: 279485059
Test: atest aconfig.test
Change-Id: I13978697e35010fe6be8637aa495d4b852dbed7e
diff --git a/tools/aconfig/src/codegen_java.rs b/tools/aconfig/src/codegen_java.rs
index 08b762a..bbf1272 100644
--- a/tools/aconfig/src/codegen_java.rs
+++ b/tools/aconfig/src/codegen_java.rs
@@ -20,13 +20,9 @@
 
 use crate::aconfig::{FlagState, Permission};
 use crate::cache::{Cache, Item};
+use crate::commands::OutputFile;
 
-pub struct GeneratedFile {
-    pub file_content: String,
-    pub file_name: String,
-}
-
-pub fn generate_java_code(cache: &Cache) -> Result<GeneratedFile> {
+pub fn generate_java_code(cache: &Cache) -> Result<OutputFile> {
     let class_elements: Vec<ClassElement> = cache.iter().map(create_class_element).collect();
     let readwrite = class_elements.iter().any(|item| item.readwrite);
     let namespace = uppercase_first_letter(
@@ -35,8 +31,9 @@
     let context = Context { namespace: namespace.clone(), readwrite, class_elements };
     let mut template = TinyTemplate::new();
     template.add_template("java_code_gen", include_str!("../templates/java.template"))?;
-    let file_content = template.render("java_code_gen", &context)?;
-    Ok(GeneratedFile { file_content, file_name: format!("{}.java", namespace) })
+    let contents = template.render("java_code_gen", &context)?;
+    let path = ["com", "android", "internal", "aconfig", &(namespace + ".java")].iter().collect();
+    Ok(OutputFile { contents: contents.into(), path })
 }
 
 #[derive(Serialize)]
@@ -123,7 +120,7 @@
                 },
             )
             .unwrap();
-        let expect_content = "package com.android.aconfig;
+        let expect_content = r#"package com.android.internal.aconfig;
 
         import android.provider.DeviceConfig;
 
@@ -135,17 +132,19 @@
 
             public static boolean test2() {
                 return DeviceConfig.getBoolean(
-                    \"Testflag\",
-                    \"test2__test2\",
+                    "Testflag",
+                    "test2__test2",
                     false
                 );
             }
 
         }
-        ";
-        let expected_file_name = format!("{}.java", uppercase_first_letter(namespace));
-        let generated_file = generate_java_code(&cache).unwrap();
-        assert_eq!(expected_file_name, generated_file.file_name);
-        assert_eq!(expect_content.replace(' ', ""), generated_file.file_content.replace(' ', ""));
+        "#;
+        let file = generate_java_code(&cache).unwrap();
+        assert_eq!("com/android/internal/aconfig/Testflag.java", file.path.to_str().unwrap());
+        assert_eq!(
+            expect_content.replace(' ', ""),
+            String::from_utf8(file.contents).unwrap().replace(' ', "")
+        );
     }
 }
diff --git a/tools/aconfig/src/commands.rs b/tools/aconfig/src/commands.rs
index c02fe07..475d9b8 100644
--- a/tools/aconfig/src/commands.rs
+++ b/tools/aconfig/src/commands.rs
@@ -20,10 +20,11 @@
 use serde::{Deserialize, Serialize};
 use std::fmt;
 use std::io::Read;
+use std::path::PathBuf;
 
 use crate::aconfig::{FlagDeclarations, FlagValue};
 use crate::cache::Cache;
-use crate::codegen_java::{generate_java_code, GeneratedFile};
+use crate::codegen_java::generate_java_code;
 use crate::protos::ProtoParsedFlags;
 
 #[derive(Serialize, Deserialize, Clone, Debug)]
@@ -47,6 +48,11 @@
     pub reader: Box<dyn Read>,
 }
 
+pub struct OutputFile {
+    pub path: PathBuf, // relative to some root directory only main knows about
+    pub contents: Vec<u8>,
+}
+
 pub fn create_cache(
     namespace: &str,
     declarations: Vec<Input>,
@@ -85,7 +91,7 @@
     Ok(cache)
 }
 
-pub fn generate_code(cache: &Cache) -> Result<GeneratedFile> {
+pub fn generate_code(cache: &Cache) -> Result<OutputFile> {
     generate_java_code(cache)
 }
 
diff --git a/tools/aconfig/src/main.rs b/tools/aconfig/src/main.rs
index 4abcb90..513e313 100644
--- a/tools/aconfig/src/main.rs
+++ b/tools/aconfig/src/main.rs
@@ -16,11 +16,12 @@
 
 //! `aconfig` is a build time tool to manage build time configurations, such as feature flags.
 
-use anyhow::Result;
+use anyhow::{anyhow, ensure, Result};
 use clap::{builder::ArgAction, builder::EnumValueParser, Arg, ArgMatches, Command};
 use std::fs;
 use std::io;
 use std::io::Write;
+use std::path::{Path, PathBuf};
 
 mod aconfig;
 mod cache;
@@ -29,7 +30,7 @@
 mod protos;
 
 use crate::cache::Cache;
-use commands::{Input, Source};
+use commands::{Input, OutputFile, Source};
 
 fn cli() -> Command {
     Command::new("aconfig")
@@ -68,6 +69,22 @@
     Ok(opened_files)
 }
 
+fn write_output_file_realtive_to_dir(root: &Path, output_file: &OutputFile) -> Result<()> {
+    ensure!(
+        root.is_dir(),
+        "output directory {} does not exist or is not a directory",
+        root.display()
+    );
+    let path = root.join(output_file.path.clone());
+    let parent = path
+        .parent()
+        .ok_or(anyhow!("unable to locate parent of output file {}", path.display()))?;
+    fs::create_dir_all(parent)?;
+    let mut file = fs::File::create(path)?;
+    file.write_all(&output_file.contents)?;
+    Ok(())
+}
+
 fn main() -> Result<()> {
     let matches = cli().get_matches();
     match matches.subcommand() {
@@ -84,12 +101,9 @@
             let path = sub_matches.get_one::<String>("cache").unwrap();
             let file = fs::File::open(path)?;
             let cache = Cache::read_from_reader(file)?;
-            let out = sub_matches.get_one::<String>("out").unwrap();
+            let dir = PathBuf::from(sub_matches.get_one::<String>("out").unwrap());
             let generated_file = commands::generate_code(&cache).unwrap();
-            fs::write(
-                format!("{}/{}", out, generated_file.file_name),
-                generated_file.file_content,
-            )?;
+            write_output_file_realtive_to_dir(&dir, &generated_file)?;
         }
         Some(("dump", sub_matches)) => {
             let path = sub_matches.get_one::<String>("cache").unwrap();
diff --git a/tools/aconfig/templates/java.template b/tools/aconfig/templates/java.template
index 3854579..ebcd607 100644
--- a/tools/aconfig/templates/java.template
+++ b/tools/aconfig/templates/java.template
@@ -1,4 +1,4 @@
-package com.android.aconfig;
+package com.android.internal.aconfig;
 {{ if readwrite }}
 import android.provider.DeviceConfig;
 {{ endif }}