// Copyright 2015 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package java

// This file contains the module types for compiling Java for Android, and converts the properties
// into the flags and filenames necessary to pass to the Module.  The final creation of the rules
// is handled in builder.go

import (
	"fmt"
	"maps"
	"path/filepath"
	"slices"
	"sort"
	"strings"

	"android/soong/remoteexec"

	"github.com/google/blueprint"
	"github.com/google/blueprint/depset"
	"github.com/google/blueprint/proptools"

	"android/soong/android"
	"android/soong/cc"
	"android/soong/dexpreopt"
	"android/soong/java/config"
	"android/soong/tradefed"
)

//go:generate go run ../../blueprint/gobtools/codegen/gob_gen.go

func init() {
	registerJavaBuildComponents(android.InitRegistrationContext)

	RegisterJavaSdkMemberTypes()
}

func registerJavaBuildComponents(ctx android.RegistrationContext) {
	ctx.RegisterModuleType("java_defaults", DefaultsFactory)

	ctx.RegisterModuleType("java_library", LibraryFactory)
	ctx.RegisterModuleType("java_library_static", LibraryStaticFactory)
	ctx.RegisterModuleType("java_library_host", LibraryHostFactory)
	ctx.RegisterModuleType("java_binary", BinaryFactory)
	ctx.RegisterModuleType("java_binary_host", BinaryHostFactory)
	ctx.RegisterModuleType("java_test", TestFactory)
	ctx.RegisterModuleType("java_test_helper_library", TestHelperLibraryFactory)
	ctx.RegisterModuleType("java_test_host", TestHostFactory)
	ctx.RegisterModuleType("java_test_import", JavaTestImportFactory)
	ctx.RegisterModuleType("java_import", ImportFactory)
	ctx.RegisterModuleType("java_import_host", ImportFactoryHost)
	ctx.RegisterModuleType("java_device_for_host", DeviceForHostFactory)
	ctx.RegisterModuleType("java_host_for_device", HostForDeviceFactory)
	ctx.RegisterModuleType("dex_import", DexImportFactory)
	ctx.RegisterModuleType("java_api_library", ApiLibraryFactory)
	ctx.RegisterModuleType("java_api_contribution", ApiContributionFactory)
	ctx.RegisterModuleType("java_api_contribution_import", ApiContributionImportFactory)
	ctx.RegisterModuleType("java_genrule_combiner", GenruleCombinerFactory)

	// This mutator registers dependencies on dex2oat for modules that should be
	// dexpreopted. This is done late when the final variants have been
	// established, to not get the dependencies split into the wrong variants and
	// to support the checks in dexpreoptDisabled().
	ctx.FinalDepsMutators(func(ctx android.RegisterMutatorsContext) {
		ctx.BottomUp("dexpreopt_tool_deps", dexpreoptToolDepsMutator)
		// needs access to ApexInfoProvider which is available after variant creation
		ctx.BottomUp("jacoco_deps", jacocoDepsMutator)
	})

	ctx.RegisterParallelSingletonType("kythe_java_extract", kytheExtractJavaFactory)
}

func RegisterJavaSdkMemberTypes() {
	// Register sdk member types.
	android.RegisterSdkMemberType(javaHeaderLibsSdkMemberType)
	android.RegisterSdkMemberType(javaLibsSdkMemberType)
	android.RegisterSdkMemberType(JavaBootLibsSdkMemberType)
	android.RegisterSdkMemberType(JavaSystemserverLibsSdkMemberType)
	android.RegisterSdkMemberType(javaTestSdkMemberType)
}

type StubsLinkType int

const (
	Unknown StubsLinkType = iota
	Stubs
	Implementation
)

var (
	// Supports adding java header libraries to module_exports and sdk.
	javaHeaderLibsSdkMemberType = &librarySdkMemberType{
		android.SdkMemberTypeBase{
			PropertyName: "java_header_libs",
			SupportsSdk:  true,
		},
		func(_ android.SdkMemberContext, j android.ModuleProxy, javaInfo *JavaInfo) android.Path {
			headerJars := javaInfo.HeaderJars
			if len(headerJars) != 1 {
				panic(fmt.Errorf("there must be only one header jar from %q", j.Name()))
			}

			return headerJars[0]
		},
		sdkSnapshotFilePathForJar,
		copyEverythingToSnapshot,
	}

	// Export implementation classes jar as part of the sdk.
	exportImplementationClassesJar = func(_ android.SdkMemberContext, j android.ModuleProxy, javaInfo *JavaInfo) android.Path {
		implementationJars := javaInfo.ImplementationAndResourcesJars
		if len(implementationJars) != 1 {
			panic(fmt.Errorf("there must be only one implementation jar from %q", j.Name()))
		}
		return implementationJars[0]
	}

	// Supports adding java implementation libraries to module_exports but not sdk.
	javaLibsSdkMemberType = &librarySdkMemberType{
		android.SdkMemberTypeBase{
			PropertyName: "java_libs",
		},
		exportImplementationClassesJar,
		sdkSnapshotFilePathForJar,
		copyEverythingToSnapshot,
	}

	snapshotRequiresImplementationJar = func(ctx android.SdkMemberContext) bool {
		// In the S build the build will break if updatable-media does not provide a full implementation
		// jar. That issue was fixed in Tiramisu by b/229932396.
		if ctx.IsTargetBuildBeforeTiramisu() && ctx.Name() == "updatable-media" {
			return true
		}

		return false
	}

	// Supports adding java boot libraries to module_exports and sdk.
	//
	// The build has some implicit dependencies (via the boot jars configuration) on a number of
	// modules, e.g. core-oj, apache-xml, that are part of the java boot class path and which are
	// provided by mainline modules (e.g. art, conscrypt, runtime-i18n) but which are not otherwise
	// used outside those mainline modules.
	//
	// As they are not needed outside the mainline modules adding them to the sdk/module-exports as
	// either java_libs, or java_header_libs would end up exporting more information than was strictly
	// necessary. The java_boot_libs property to allow those modules to be exported as part of the
	// sdk/module_exports without exposing any unnecessary information.
	JavaBootLibsSdkMemberType = &librarySdkMemberType{
		android.SdkMemberTypeBase{
			PropertyName: "java_boot_libs",
			SupportsSdk:  true,
		},
		func(ctx android.SdkMemberContext, j android.ModuleProxy, javaInfo *JavaInfo) android.Path {
			if snapshotRequiresImplementationJar(ctx) {
				return exportImplementationClassesJar(ctx, j, javaInfo)
			}

			// Java boot libs are only provided in the SDK to provide access to their dex implementation
			// jar for use by dexpreopting and boot jars package check. They do not need to provide an
			// actual implementation jar but the java_import will need a file that exists so just copy an
			// empty file. Any attempt to use that file as a jar will cause a build error.
			return ctx.SnapshotBuilder().EmptyFile()
		},
		func(ctx android.SdkMemberContext, osPrefix, name string) string {
			if snapshotRequiresImplementationJar(ctx) {
				return sdkSnapshotFilePathForJar(ctx, osPrefix, name)
			}

			// Create a special name for the implementation jar to try and provide some useful information
			// to a developer that attempts to compile against this.
			// TODO(b/175714559): Provide a proper error message in Soong not ninja.
			return filepath.Join(osPrefix, "java_boot_libs", "snapshot", "jars", "are", "invalid", name+jarFileSuffix)
		},
		onlyCopyJarToSnapshot,
	}

	// Supports adding java systemserver libraries to module_exports and sdk.
	//
	// The build has some implicit dependencies (via the systemserver jars configuration) on a number
	// of modules that are part of the java systemserver classpath and which are provided by mainline
	// modules but which are not otherwise used outside those mainline modules.
	//
	// As they are not needed outside the mainline modules adding them to the sdk/module-exports as
	// either java_libs, or java_header_libs would end up exporting more information than was strictly
	// necessary. The java_systemserver_libs property to allow those modules to be exported as part of
	// the sdk/module_exports without exposing any unnecessary information.
	JavaSystemserverLibsSdkMemberType = &librarySdkMemberType{
		android.SdkMemberTypeBase{
			PropertyName: "java_systemserver_libs",
			SupportsSdk:  true,

			// This was only added in Tiramisu.
			SupportedBuildReleaseSpecification: "Tiramisu+",
		},
		func(ctx android.SdkMemberContext, _ android.ModuleProxy, _ *JavaInfo) android.Path {
			// Java systemserver libs are only provided in the SDK to provide access to their dex
			// implementation jar for use by dexpreopting. They do not need to provide an actual
			// implementation jar but the java_import will need a file that exists so just copy an empty
			// file. Any attempt to use that file as a jar will cause a build error.
			return ctx.SnapshotBuilder().EmptyFile()
		},
		func(_ android.SdkMemberContext, osPrefix, name string) string {
			// Create a special name for the implementation jar to try and provide some useful information
			// to a developer that attempts to compile against this.
			// TODO(b/175714559): Provide a proper error message in Soong not ninja.
			return filepath.Join(osPrefix, "java_systemserver_libs", "snapshot", "jars", "are", "invalid", name+jarFileSuffix)
		},
		onlyCopyJarToSnapshot,
	}

	// Supports adding java test libraries to module_exports but not sdk.
	javaTestSdkMemberType = &testSdkMemberType{
		SdkMemberTypeBase: android.SdkMemberTypeBase{
			PropertyName: "java_tests",
		},
	}

	// Rule for generating device binary default wrapper
	deviceBinaryWrapper = pctx.StaticRule("deviceBinaryWrapper", blueprint.RuleParams{
		Command: `printf '#!/system/bin/sh\n` +
			`export CLASSPATH=/system/framework/$jar_name\n` +
			`exec app_process /$partition/bin $main_class "$$@"\n'> ${out}`,
		Description: "Generating device binary wrapper ${jar_name}",
	}, "jar_name", "partition", "main_class")
)

// @auto-generate: gob
type ProguardSpecInfo struct {
	// If true, proguard flags files will be exported to reverse dependencies across libs edges
	// If false, proguard flags files will only be exported to reverse dependencies across
	// static_libs edges.
	Export_proguard_flags_files bool

	// TransitiveDepsProguardSpecFiles is a depset of paths to proguard flags files that are exported from
	// all transitive deps. This list includes all proguard flags files from transitive static dependencies,
	// and all proguard flags files from transitive libs dependencies which set `export_proguard_spec: true`.
	ProguardFlagsFiles depset.DepSet[android.Path]

	// implementation detail to store transitive proguard flags files from exporting shared deps
	UnconditionallyExportedProguardFlags depset.DepSet[android.Path]
}

var ProguardSpecInfoProvider = blueprint.NewProvider[ProguardSpecInfo]()

type AndroidLibraryDependencyInfo struct {
	ExportPackage       android.Path
	ResourcesNodeDepSet depset.DepSet[*resourcesNode]
	RRODirsDepSet       depset.DepSet[rroDir]
	ManifestsDepSet     depset.DepSet[android.Path]
}

type UsesLibraryDependencyInfo struct {
	DexJarInstallPath   android.Path
	ClassLoaderContexts dexpreopt.ClassLoaderContextMap
}

// Return a deep copy of ClassLoaderContexts.
func (u *UsesLibraryDependencyInfo) GetClassLoaderContexts() dexpreopt.ClassLoaderContextMap {
	return u.ClassLoaderContexts.DeepCopy()
}

type ProvidesUsesLibInfo struct {
	ProvidesUsesLib *string
}

type ModuleWithUsesLibraryInfo struct {
	UsesLibrary *usesLibrary
}

type ModuleWithSdkDepInfo struct {
	SdkLinkType sdkLinkType
	Stubs       bool
}

type ApexDependencyInfo struct {
	// These fields can be different from the ones in JavaInfo, for example, for sdk_library
	// the following fields are set since sdk_library inherits the implementations of
	// ApexDependency from base, but the same-named fields are not set in JavaInfo.
	HeaderJars                     android.Paths
	ImplementationAndResourcesJars android.Paths
}

// JavaInfo contains information about a java module for use by modules that depend on it.
type JavaInfo struct {
	// HeaderJars is a list of jars that can be passed as the javac classpath in order to link
	// against this module.  If empty, ImplementationJars should be used instead.
	// Unlike LocalHeaderJars, HeaderJars includes classes from static dependencies.
	HeaderJars android.Paths

	RepackagedHeaderJars android.Paths

	// list of header jars that have not been jarjared.  This should only be used as part of
	// handling header_jar_override, where we need to use this as the header jars for this implementation of
	// The only place this is needed is when `header_jar_override` on another module references this module.
	LocalHeaderJarsPreJarjar android.Paths

	// set of header jars for all transitive libs deps
	TransitiveLibsHeaderJarsForR8 depset.DepSet[android.Path]

	// set of header jars for all transitive static libs deps
	TransitiveStaticLibsHeaderJarsForR8 depset.DepSet[android.Path]

	// depset of header jars for this module and all transitive static dependencies
	TransitiveStaticLibsHeaderJars depset.DepSet[android.Path]

	// depset of implementation jars for this module and all transitive static dependencies
	TransitiveStaticLibsImplementationJars depset.DepSet[android.Path]

	// depset of resource jars for this module and all transitive static dependencies
	TransitiveStaticLibsResourceJars depset.DepSet[android.Path]

	// ImplementationAndResourceJars is a list of jars that contain the implementations of classes
	// in the module as well as any resources included in the module.
	ImplementationAndResourcesJars android.Paths

	// ImplementationJars is a list of jars that contain the implementations of classes in the
	// module.
	ImplementationJars android.Paths

	// ResourceJars is a list of jars that contain the resources included in the module.
	ResourceJars android.Paths

	// LocalHeaderJars is a list of jars that contain classes from this module, but not from any static dependencies.
	LocalHeaderJars android.Paths

	// KotlinHeaderJars is a jar that only contains Kotlin classes from this module, but not from any static dependencies.
	KotlinHeaderJars android.Paths

	// AidlIncludeDirs is a list of directories that should be passed to the aidl tool when
	// depending on this module.
	AidlIncludeDirs android.Paths

	// SrcJarArgs is a list of arguments to pass to soong_zip to package the sources of this
	// module.
	SrcJarArgs []string

	// SrcJarDeps is a list of paths to depend on when packaging the sources of this module.
	SrcJarDeps android.Paths

	KSnapshotFiles map[string]android.Path

	// The source files of this module and all its transitive static dependencies.
	TransitiveSrcFiles depset.DepSet[android.Path]

	// ExportedPlugins is a list of paths that should be used as annotation processors for any
	// module that depends on this module.
	ExportedPlugins android.Paths

	// ExportedPluginClasses is a list of classes that should be run as annotation processors for
	// any module that depends on this module.
	ExportedPluginClasses []string

	// ExportedPluginDisableTurbine is true if this module's annotation processors generate APIs,
	// requiring disbling turbine for any modules that depend on it.
	ExportedPluginDisableTurbine bool

	// JacocoInfo contains the path to a jar containing uninstrumented classes that will be
	// instrumented by jacoco.
	JacocoInfo JacocoInfo

	// StubsLinkType provides information about whether the provided jars are stub jars or
	// implementation jars. If the provider is set by java_sdk_library, the link type is "unknown"
	// and selection between the stub jar vs implementation jar is deferred to SdkLibrary.sdkJars(...)
	StubsLinkType StubsLinkType

	// AconfigIntermediateCacheOutputPaths is a path to the cache files collected from the
	// java_aconfig_library modules that are statically linked to this module.
	AconfigIntermediateCacheOutputPaths android.Paths

	SdkVersion android.SdkSpec

	// output file of the module, which may be a classes jar or a dex jar
	OutputFile android.Path

	ExtraOutputFiles android.Paths

	AndroidLibraryDependencyInfo *AndroidLibraryDependencyInfo

	UsesLibraryDependencyInfo *UsesLibraryDependencyInfo

	ProvidesUsesLibInfo *ProvidesUsesLibInfo

	MissingOptionalUsesLibs []string

	ModuleWithSdkDepInfo *ModuleWithSdkDepInfo

	// output file containing classes.dex and resources
	DexJarFile OptionalDexJarPath

	// installed file for binary dependency
	InstallFile android.Path

	// The path to the dex jar that is in the boot class path. If this is unset then the associated
	// module is not a boot jar, but could be one of the <x>-hiddenapi modules that provide additional
	// annotations for the <x> boot dex jar but which do not actually provide a boot dex jar
	// themselves.
	//
	// This must be the path to the unencoded dex jar as the encoded dex jar indirectly depends on
	// this file so using the encoded dex jar here would result in a cycle in the ninja rules.
	BootDexJarPath OptionalDexJarPath

	// The paths to the classes jars that contain classes and class members annotated with
	// the UnsupportedAppUsage annotation that need to be extracted as part of the hidden API
	// processing.
	HiddenapiClassesJarPaths android.Paths

	// The compressed state of the dex file being encoded. This is used to ensure that the encoded
	// dex file has the same state.
	UncompressDexState *bool

	// True if the module containing this structure contributes to the hiddenapi information or has
	// that information encoded within it.
	Active bool

	BuiltInstalled string

	// The config is used for two purposes:
	// - Passing dexpreopt information about libraries from Soong to Make. This is needed when
	//   a <uses-library> is defined in Android.bp, but used in Android.mk (see dex_preopt_config_merger.py).
	//   Note that dexpreopt.config might be needed even if dexpreopt is disabled for the library itself.
	// - Dexpreopt post-processing (using dexpreopt artifacts from a prebuilt system image to incrementally
	//   dexpreopt another partition).
	ConfigPath android.WritablePath

	LogtagsSrcs android.Paths

	ProguardDictionary android.OptionalPath

	ProguardUsageZip android.OptionalPath

	LinterReports android.Paths

	// installed file for hostdex copy
	HostdexInstallFile android.InstallPath

	// Additional srcJars tacked in by GeneratedJavaLibraryModule
	GeneratedSrcjars []android.Path

	// True if profile-guided optimization is actually enabled.
	ProfileGuided bool

	Stem string

	DexJarBuildPath OptionalDexJarPath

	DexpreopterInfo *DexpreopterInfo

	XrefJavaFiles            android.Paths
	XrefKotlinFiles          android.Paths
	HasOverrideMinSdkVersion bool
	CompileDex               *bool
	SystemModules            string
	Installable              bool
	ApexDependencyInfo       *ApexDependencyInfo

	MaxSdkVersion android.ApiLevel
}

var JavaInfoProvider = blueprint.NewProvider[*JavaInfo]()

type DexpreopterInfo struct {
	// The path to the profile on host that dexpreopter generates. This is used as the input for
	// dex2oat.
	OutputProfilePathOnHost android.Path
	// If the java module is to be installed into an APEX, this list contains information about the
	// dexpreopt outputs to be installed on devices. Note that these dexpreopt outputs are installed
	// outside of the APEX.
	ApexSystemServerDexpreoptInstalls []DexpreopterInstall

	// ApexSystemServerDexJars returns the list of dex jars if this is an apex system server jar.
	ApexSystemServerDexJars android.Paths
}

type JavaLibraryInfo struct {
	Prebuilt          bool
	PermittedPackages []string
}

var JavaLibraryInfoProvider = blueprint.NewProvider[JavaLibraryInfo]()

type JavaDexImportInfo struct{}

var JavaDexImportInfoProvider = blueprint.NewProvider[JavaDexImportInfo]()

// SyspropPublicStubInfo contains info about the sysprop public stub library that corresponds to
// the sysprop implementation library.
type SyspropPublicStubInfo struct {
	// JavaInfo is the JavaInfoProvider of the sysprop public stub library that corresponds to
	// the sysprop implementation library.
	JavaInfo *JavaInfo
}

var SyspropPublicStubInfoProvider = blueprint.NewProvider[SyspropPublicStubInfo]()

// Methods that need to be implemented for a module that is added to apex java_libs property.
type ApexDependency interface {
	HeaderJars() android.Paths
	ImplementationAndResourcesJars() android.Paths
}

// Provides build path and install path to DEX jars.
type UsesLibraryDependency interface {
	DexJarBuildPath(ctx android.ModuleErrorfContext) OptionalDexJarPath
	DexJarInstallPath() android.Path
	ClassLoaderContexts() dexpreopt.ClassLoaderContextMap
}

// TODO(jungjw): Move this to kythe.go once it's created.
type xref interface {
	XrefJavaFiles() android.Paths
	XrefKotlinFiles() android.Paths
}

func (j *Module) XrefJavaFiles() android.Paths {
	return j.kytheFiles
}

func (j *Module) XrefKotlinFiles() android.Paths {
	return j.kytheKotlinFiles
}

func (d dependencyTag) PropagateAconfigValidation() bool {
	return d.static
}

var _ android.PropagateAconfigValidationDependencyTag = dependencyTag{}

type dependencyTag struct {
	blueprint.BaseDependencyTag
	name string

	// True if the dependency is relinked at runtime.
	runtimeLinked bool

	// True if the dependency is a toolchain, for example an annotation processor.
	toolchain bool

	static bool

	installable bool
}

var _ android.InstallNeededDependencyTag = (*dependencyTag)(nil)

func (d dependencyTag) InstallDepNeeded() bool {
	return d.installable
}

func (d dependencyTag) LicenseAnnotations() []android.LicenseAnnotation {
	if d.runtimeLinked {
		return []android.LicenseAnnotation{android.LicenseAnnotationSharedDependency}
	} else if d.toolchain {
		return []android.LicenseAnnotation{android.LicenseAnnotationToolchain}
	}
	return nil
}

var _ android.LicenseAnnotationsDependencyTag = dependencyTag{}

type usesLibraryDependencyTag struct {
	dependencyTag
	sdkVersion int  // SDK version in which the library appared as a standalone library.
	optional   bool // If the dependency is optional or required.
}

func makeUsesLibraryDependencyTag(sdkVersion int, optional bool) usesLibraryDependencyTag {
	return usesLibraryDependencyTag{
		dependencyTag: dependencyTag{
			name:          fmt.Sprintf("uses-library-%d", sdkVersion),
			runtimeLinked: true,
		},
		sdkVersion: sdkVersion,
		optional:   optional,
	}
}

func IsJniDepTag(depTag blueprint.DependencyTag) bool {
	return depTag == jniLibTag || depTag == jniInstallTag
}

func IsOptionalUsesLibraryDepTag(depTag blueprint.DependencyTag) bool {
	if tag, ok := depTag.(usesLibraryDependencyTag); ok {
		return tag.optional
	}
	return depTag == r8LibraryJarTag
}

// A tag that is used for staging the dependencies of a module, for populating uses libraries
// dependencies.
type usesLibStagingTagStruct struct {
	blueprint.BaseDependencyTag
}

// Mark this tag so dependencies that use it are excluded from APEX contents.
func (t usesLibStagingTagStruct) ExcludeFromApexContents() {}

var _ android.ExcludeFromApexContentsTag = (*usesLibStagingTagStruct)(nil)

var (
	dataNativeBinsTag          = dependencyTag{name: "dataNativeBins"}
	dataDeviceBinsTag          = dependencyTag{name: "dataDeviceBins"}
	staticLibTag               = dependencyTag{name: "staticlib", static: true}
	libTag                     = dependencyTag{name: "javalib", runtimeLinked: true}
	sdkLibTag                  = dependencyTag{name: "sdklib", runtimeLinked: true}
	java9LibTag                = dependencyTag{name: "java9lib", runtimeLinked: true}
	pluginTag                  = dependencyTag{name: "plugin", toolchain: true}
	errorpronePluginTag        = dependencyTag{name: "errorprone-plugin", toolchain: true}
	exportedPluginTag          = dependencyTag{name: "exported-plugin", toolchain: true}
	bootClasspathTag           = dependencyTag{name: "bootclasspath", runtimeLinked: true}
	systemModulesTag           = dependencyTag{name: "system modules", runtimeLinked: true}
	frameworkResTag            = dependencyTag{name: "framework-res"}
	kotlinPluginTag            = dependencyTag{name: "kotlin-plugin", toolchain: true}
	composeEmbeddablePluginTag = dependencyTag{name: "compose-embeddable-plugin", toolchain: true}
	composePluginTag           = dependencyTag{name: "compose-plugin", toolchain: true}
	proguardRaiseTag           = dependencyTag{name: "proguard-raise"}
	certificateTag             = dependencyTag{name: "certificate"}
	headerJarOverrideTag       = dependencyTag{name: "header-jar-override"}
	instrumentationForTag      = dependencyTag{name: "instrumentation_for"}
	extraLintCheckTag          = dependencyTag{name: "extra-lint-check", toolchain: true}
	jniLibTag                  = dependencyTag{name: "jnilib", runtimeLinked: true}
	r8LibraryJarTag            = dependencyTag{name: "r8-libraryjar", runtimeLinked: true}
	traceReferencesTag         = dependencyTag{name: "trace-references"}
	syspropPublicStubDepTag    = dependencyTag{name: "sysprop public stub"}
	javaApiContributionTag     = dependencyTag{name: "java-api-contribution"}
	aconfigDeclarationTag      = dependencyTag{name: "aconfig-declaration"}
	jniInstallTag              = dependencyTag{name: "jni install", runtimeLinked: true, installable: true}
	usesLibReqTag              = makeUsesLibraryDependencyTag(dexpreopt.AnySdkVersion, false)
	usesLibOptTag              = makeUsesLibraryDependencyTag(dexpreopt.AnySdkVersion, true)
	usesLibCompat28OptTag      = makeUsesLibraryDependencyTag(28, true)
	usesLibCompat29ReqTag      = makeUsesLibraryDependencyTag(29, false)
	usesLibCompat30OptTag      = makeUsesLibraryDependencyTag(30, true)
	usesLibStagingTag          = usesLibStagingTagStruct{}
)

// A list of tags for deps used for compiling a module.
// Any dependency tags that modifies the following properties of `deps` in `Module.collectDeps` should be
// added to this list:
// - bootClasspath
// - classpath
// - java9Classpath
// - systemModules
// - kotlin deps...
var (
	compileDependencyTags = []blueprint.DependencyTag{
		sdkLibTag,
		libTag,
		staticLibTag,
		bootClasspathTag,
		systemModulesTag,
		java9LibTag,
		composePluginTag,
		composeEmbeddablePluginTag,
		kotlinPluginTag,
		syspropPublicStubDepTag,
		instrumentationForTag,
		traceReferencesTag,
	}
)

func IsLibDepTag(depTag blueprint.DependencyTag) bool {
	return depTag == libTag
}

func IsStaticLibDepTag(depTag blueprint.DependencyTag) bool {
	return depTag == staticLibTag
}

type sdkDep struct {
	useModule, useFiles, invalidVersion bool

	// The modules that will be added to the bootclasspath when targeting 1.8 or lower
	bootclasspath []string

	// The default system modules to use. Will be an empty string if no system
	// modules are to be used.
	systemModules string

	// The modules that will be added to the classpath regardless of the Java language level targeted
	classpath []string

	// The modules that will be added ot the classpath when targeting 1.9 or higher
	// (normally these will be on the bootclasspath when targeting 1.8 or lower)
	java9Classpath []string

	frameworkResModule string

	jars android.Paths
	aidl android.OptionalPath

	noStandardLibs, noFrameworksLibs bool
}

func (s sdkDep) hasStandardLibs() bool {
	return !s.noStandardLibs
}

func (s sdkDep) hasFrameworkLibs() bool {
	return !s.noStandardLibs && !s.noFrameworksLibs
}

type jniLib struct {
	name           string
	path           android.Path
	target         android.Target
	coverageFile   android.OptionalPath
	unstrippedFile android.Path
	partition      string
	installPaths   android.InstallPaths
}

func sdkDeps(ctx android.BottomUpMutatorContext, sdkContext android.SdkContext, d dexer) {
	sdkDep := decodeSdkDep(ctx, sdkContext)
	if sdkDep.useModule {
		ctx.AddVariationDependencies(nil, bootClasspathTag, sdkDep.bootclasspath...)
		ctx.AddVariationDependencies(nil, java9LibTag, sdkDep.java9Classpath...)
		ctx.AddVariationDependencies(nil, sdkLibTag, sdkDep.classpath...)
		if d.effectiveOptimizeEnabled(ctx) && sdkDep.hasStandardLibs() {
			ctx.AddVariationDependencies(nil, proguardRaiseTag,
				config.LegacyCorePlatformBootclasspathLibraries...,
			)
		}
		if d.effectiveOptimizeEnabled(ctx) && sdkDep.hasFrameworkLibs() {
			ctx.AddVariationDependencies(nil, proguardRaiseTag, config.FrameworkLibraries...)
		}
	}
	if sdkDep.systemModules != "" {
		ctx.AddVariationDependencies(nil, systemModulesTag, sdkDep.systemModules)
	}
}

type deps struct {
	// bootClasspath is the list of jars that form the boot classpath (generally the java.* and
	// android.* classes) for tools that still use it.  javac targeting 1.9 or higher uses
	// systemModules and java9Classpath instead.
	bootClasspath classpath

	// classpath is the list of jars that form the classpath for javac and kotlinc rules.  It
	// contains header jars for all static and non-static dependencies.
	classpath classpath

	// dexClasspath is the list of jars that form the classpath for d8 and r8 rules.  It contains
	// header jars for all non-static dependencies.  Static dependencies have already been
	// combined into the program jar.
	dexClasspath classpath

	// java9Classpath is the list of jars that will be added to the classpath when targeting
	// 1.9 or higher.  It generally contains the android.* classes, while the java.* classes
	// are provided by systemModules.
	java9Classpath classpath

	processorPath           classpath ``
	errorProneProcessorPath classpath
	processorClasses        []string
	staticJars              android.Paths
	staticHeaderJars        android.Paths
	staticResourceJars      android.Paths
	aidlIncludeDirs         android.Paths
	srcs                    android.Paths
	srcJars                 android.Paths
	systemModules           *systemModules
	aidlPreprocess          android.OptionalPath
	composeEmbeddablePlugin android.OptionalPath
	composePlugin           android.OptionalPath
	kSnapshotFiles          map[string]android.Path
	kotlinPlugins           android.Paths
	aconfigProtoFiles       android.Paths

	headerJarOverride          android.OptionalPath
	headerJarOverridePreJarjar android.OptionalPath

	disableTurbine bool

	transitiveStaticLibsHeaderJars         []depset.DepSet[android.Path]
	transitiveStaticLibsImplementationJars []depset.DepSet[android.Path]
	transitiveStaticLibsResourceJars       []depset.DepSet[android.Path]
}

func checkProducesJars(ctx android.ModuleContext, dep android.SourceFilesInfo, module android.ModuleProxy) {
	for _, f := range dep.Srcs {
		if f.Ext() != ".jar" {
			ctx.ModuleErrorf("genrule %q must generate files ending with .jar to be used as a libs or static_libs dependency",
				ctx.OtherModuleName(module))
		}
	}
}

func getJavaVersion(ctx android.ModuleContext, javaVersion string, sdkContext android.SdkContext) javaVersion {
	if javaVersion != "" {
		return normalizeJavaVersion(ctx, javaVersion)
	} else if ctx.Device() {
		return defaultJavaLanguageVersion(ctx, sdkContext.SdkVersion(ctx))
	} else if ctx.Config().TargetsJava21() {
		// Build flag that controls whether Java 21 is used as the default
		// target version, or Java 17.
		return JAVA_VERSION_21
	} else {
		return JAVA_VERSION_17
	}
}

// Java version for stubs generation
func getStubsJavaVersion() javaVersion {
	return JAVA_VERSION_8
}

type javaVersion int

const (
	JAVA_VERSION_UNSUPPORTED = 0
	JAVA_VERSION_6           = 6
	JAVA_VERSION_7           = 7
	JAVA_VERSION_8           = 8
	JAVA_VERSION_9           = 9
	JAVA_VERSION_11          = 11
	JAVA_VERSION_17          = 17
	JAVA_VERSION_21          = 21
	JAVA_VERSION_25          = 25
)

func (v javaVersion) String() string {
	switch v {
	case JAVA_VERSION_6:
		// Java version 1.6 no longer supported, bumping to 1.8
		return "1.8"
	case JAVA_VERSION_7:
		// Java version 1.7 no longer supported, bumping to 1.8
		return "1.8"
	case JAVA_VERSION_8:
		return "1.8"
	case JAVA_VERSION_9:
		return "1.9"
	case JAVA_VERSION_11:
		return "11"
	case JAVA_VERSION_17:
		return "17"
	case JAVA_VERSION_21:
		return "21"
	case JAVA_VERSION_25:
		return "25"
	default:
		return "unsupported"
	}
}

func (v javaVersion) StringForKotlinc() string {
	// $ ./external/kotlinc/bin/kotlinc -jvm-target foo
	// error: unknown JVM target version: foo
	// Supported versions: 1.8, 9, 10, 11, 12, 13, 14, 15, 16, 17
	switch v {
	case JAVA_VERSION_6:
		return "1.8"
	case JAVA_VERSION_7:
		return "1.8"
	case JAVA_VERSION_9:
		return "9"
	default:
		return v.String()
	}
}

// Returns true if javac targeting this version uses system modules instead of a bootclasspath.
func (v javaVersion) usesJavaModules() bool {
	return v >= 9
}

func normalizeJavaVersion(ctx android.BaseModuleContext, javaVersion string) javaVersion {
	switch javaVersion {
	case "1.6", "6":
		// Java version 1.6 no longer supported, bumping to 1.8
		return JAVA_VERSION_8
	case "1.7", "7":
		// Java version 1.7 no longer supported, bumping to 1.8
		return JAVA_VERSION_8
	case "1.8", "8":
		return JAVA_VERSION_8
	case "1.9", "9":
		return JAVA_VERSION_9
	case "11":
		return JAVA_VERSION_11
	case "17":
		return JAVA_VERSION_17
	case "21":
		return JAVA_VERSION_21
	case "25":
		return JAVA_VERSION_25
	case "10", "12", "13", "14", "15", "16":
		ctx.PropertyErrorf("java_version", "Java language level %s is not supported", javaVersion)
		return JAVA_VERSION_UNSUPPORTED
	default:
		ctx.PropertyErrorf("java_version", "Unrecognized Java language level")
		return JAVA_VERSION_UNSUPPORTED
	}
}

//
// Java libraries (.jar file)
//

type Library struct {
	Module

	combinedExportedProguardFlagsFile android.Path

	InstallMixin func(ctx android.ModuleContext, installPath android.Path) (extraInstallDeps android.InstallPaths)

	apiXmlFile android.WritablePath
}

var _ android.ApexModule = (*Library)(nil)

func (j *Library) CheckDepsMinSdkVersion(ctx android.ModuleContext) {
	CheckMinSdkVersion(ctx, j)
}

// Provides access to the list of permitted packages from apex boot jars.
type PermittedPackagesForUpdatableBootJars interface {
	PermittedPackagesForUpdatableBootJars() []string
}

var _ PermittedPackagesForUpdatableBootJars = (*Library)(nil)

func (j *Library) PermittedPackagesForUpdatableBootJars() []string {
	return j.properties.Permitted_packages
}

func shouldUncompressDex(ctx android.ModuleContext, libName string, dexpreopter *dexpreopter) bool {
	// Store uncompressed (and aligned) any dex files from jars in APEXes.
	if apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider); !apexInfo.IsForPlatform() {
		return true
	}

	// Store uncompressed (and do not strip) dex files from boot class path jars.
	if inList(ctx.ModuleName(), ctx.Config().BootJars()) {
		return true
	}

	// Store uncompressed dex files that are preopted on /system or /system_other.
	if !dexpreopter.dexpreoptDisabled(ctx, libName) {
		return true
	}

	if ctx.Config().UncompressPrivAppDex() &&
		inList(ctx.ModuleName(), ctx.Config().ModulesLoadedByPrivilegedModules()) {
		return true
	}

	return false
}

// Sets `dexer.dexProperties.Uncompress_dex` to the proper value.
func setUncompressDex(ctx android.ModuleContext, dexpreopter *dexpreopter, dexer *dexer) {
	if dexer.dexProperties.Uncompress_dex == nil {
		// If the value was not force-set by the user, use reasonable default based on the module.
		dexer.dexProperties.Uncompress_dex = proptools.BoolPtr(shouldUncompressDex(ctx, android.RemoveOptionalPrebuiltPrefix(ctx.ModuleName()), dexpreopter))
	}
}

// list of java_library modules that set platform_apis: true
// this property is a no-op for java_library
// TODO (b/215379393): Remove this allowlist
var (
	aospPlatformApiAllowlist = map[string]bool{
		"adservices-test-scenarios":                         true,
		"aidl-cpp-java-test-interface-java":                 true,
		"aidl-test-extras-java":                             true,
		"aidl-test-interface-java":                          true,
		"aidl-test-interface-permission-java":               true,
		"aidl_test_java_client_permission":                  true,
		"aidl_test_java_client_sdk1":                        true,
		"aidl_test_java_client_sdk29":                       true,
		"aidl_test_java_client":                             true,
		"aidl_test_java_service_permission":                 true,
		"aidl_test_java_service_sdk1":                       true,
		"aidl_test_java_service_sdk29":                      true,
		"aidl_test_java_service":                            true,
		"aidl_test_loggable_interface-java":                 true,
		"aidl_test_nonvintf_parcelable-V1-java":             true,
		"aidl_test_nonvintf_parcelable-V2-java":             true,
		"aidl_test_unstable_parcelable-java":                true,
		"aidl_test_vintf_parcelable-V1-java":                true,
		"aidl_test_vintf_parcelable-V2-java":                true,
		"android.aidl.test.trunk-V1-java":                   true,
		"android.aidl.test.trunk-V2-java":                   true,
		"android.frameworks.location.altitude-V1-java":      true,
		"android.frameworks.location.altitude-V2-java":      true,
		"android.frameworks.stats-V1-java":                  true,
		"android.frameworks.stats-V2-java":                  true,
		"android.frameworks.stats-V3-java":                  true,
		"android.hardware.authsecret-V1-java":               true,
		"android.hardware.authsecret-V2-java":               true,
		"android.hardware.biometrics.common-V1-java":        true,
		"android.hardware.biometrics.common-V2-java":        true,
		"android.hardware.biometrics.common-V3-java":        true,
		"android.hardware.biometrics.common-V4-java":        true,
		"android.hardware.biometrics.face-V1-java":          true,
		"android.hardware.biometrics.face-V2-java":          true,
		"android.hardware.biometrics.face-V3-java":          true,
		"android.hardware.biometrics.face-V4-java":          true,
		"android.hardware.biometrics.fingerprint-V1-java":   true,
		"android.hardware.biometrics.fingerprint-V2-java":   true,
		"android.hardware.biometrics.fingerprint-V3-java":   true,
		"android.hardware.biometrics.fingerprint-V4-java":   true,
		"android.hardware.bluetooth.lmp_event-V1-java":      true,
		"android.hardware.confirmationui-V1-java":           true,
		"android.hardware.confirmationui-V2-java":           true,
		"android.hardware.gatekeeper-V1-java":               true,
		"android.hardware.gatekeeper-V2-java":               true,
		"android.hardware.gnss-V1-java":                     true,
		"android.hardware.gnss-V2-java":                     true,
		"android.hardware.gnss-V3-java":                     true,
		"android.hardware.gnss-V4-java":                     true,
		"android.hardware.graphics.common-V1-java":          true,
		"android.hardware.graphics.common-V2-java":          true,
		"android.hardware.graphics.common-V3-java":          true,
		"android.hardware.graphics.common-V4-java":          true,
		"android.hardware.graphics.common-V5-java":          true,
		"android.hardware.identity-V1-java":                 true,
		"android.hardware.identity-V2-java":                 true,
		"android.hardware.identity-V3-java":                 true,
		"android.hardware.identity-V4-java":                 true,
		"android.hardware.identity-V5-java":                 true,
		"android.hardware.identity-V6-java":                 true,
		"android.hardware.keymaster-V1-java":                true,
		"android.hardware.keymaster-V2-java":                true,
		"android.hardware.keymaster-V3-java":                true,
		"android.hardware.keymaster-V4-java":                true,
		"android.hardware.keymaster-V5-java":                true,
		"android.hardware.oemlock-V1-java":                  true,
		"android.hardware.oemlock-V2-java":                  true,
		"android.hardware.power.stats-V1-java":              true,
		"android.hardware.power.stats-V2-java":              true,
		"android.hardware.power.stats-V3-java":              true,
		"android.hardware.power-V1-java":                    true,
		"android.hardware.power-V2-java":                    true,
		"android.hardware.power-V3-java":                    true,
		"android.hardware.power-V4-java":                    true,
		"android.hardware.power-V5-java":                    true,
		"android.hardware.rebootescrow-V1-java":             true,
		"android.hardware.rebootescrow-V2-java":             true,
		"android.hardware.security.authgraph-V1-java":       true,
		"android.hardware.security.keymint-V1-java":         true,
		"android.hardware.security.keymint-V2-java":         true,
		"android.hardware.security.keymint-V3-java":         true,
		"android.hardware.security.keymint-V4-java":         true,
		"android.hardware.security.secretkeeper-V1-java":    true,
		"android.hardware.security.secureclock-V1-java":     true,
		"android.hardware.security.secureclock-V2-java":     true,
		"android.hardware.thermal-V1-java":                  true,
		"android.hardware.thermal-V2-java":                  true,
		"android.hardware.threadnetwork-V1-java":            true,
		"android.hardware.weaver-V1-java":                   true,
		"android.hardware.weaver-V2-java":                   true,
		"android.hardware.weaver-V3-java":                   true,
		"android.security.attestationmanager-java":          true,
		"android.security.authorization-java":               true,
		"android.security.compat-java":                      true,
		"android.security.legacykeystore-java":              true,
		"android.security.maintenance-java":                 true,
		"android.security.metrics-java":                     true,
		"android.system.keystore2-V1-java":                  true,
		"android.system.keystore2-V2-java":                  true,
		"android.system.keystore2-V3-java":                  true,
		"android.system.keystore2-V4-java":                  true,
		"binderReadParcelIface-java":                        true,
		"binderRecordReplayTestIface-java":                  true,
		"car-experimental-api-static-lib":                   true,
		"collector-device-lib-platform":                     true,
		"com.android.car.oem":                               true,
		"com.google.hardware.pixel.display-V10-java":        true,
		"com.google.hardware.pixel.display-V1-java":         true,
		"com.google.hardware.pixel.display-V2-java":         true,
		"com.google.hardware.pixel.display-V3-java":         true,
		"com.google.hardware.pixel.display-V4-java":         true,
		"com.google.hardware.pixel.display-V5-java":         true,
		"com.google.hardware.pixel.display-V6-java":         true,
		"com.google.hardware.pixel.display-V7-java":         true,
		"com.google.hardware.pixel.display-V8-java":         true,
		"com.google.hardware.pixel.display-V9-java":         true,
		"conscrypt-support":                                 true,
		"cts-keystore-test-util":                            true,
		"cts-keystore-user-auth-helper-library":             true,
		"ctsmediautil":                                      true,
		"CtsNetTestsNonUpdatableLib":                        true,
		"DpmWrapper":                                        true,
		"flickerlib-apphelpers":                             true,
		"flickerlib-helpers":                                true,
		"flickerlib-parsers":                                true,
		"flickerlib":                                        true,
		"hardware.google.bluetooth.ccc-V1-java":             true,
		"hardware.google.bluetooth.sar-V1-java":             true,
		"monet":                                             true,
		"pixel-power-ext-V1-java":                           true,
		"pixel-power-ext-V2-java":                           true,
		"pixel_stateresidency_provider_aidl_interface-java": true,
		"pixel-thermal-ext-V1-java":                         true,
		"protolog-lib":                                      true,
		"RkpRegistrationCheck":                              true,
		"rotary-service-javastream-protos":                  true,
		"service_based_camera_extensions":                   true,
		"statsd-helper-test":                                true,
		"statsd-helper":                                     true,
		"test-piece-2-V1-java":                              true,
		"test-piece-2-V2-java":                              true,
		"test-piece-3-V1-java":                              true,
		"test-piece-3-V2-java":                              true,
		"test-piece-3-V3-java":                              true,
		"test-piece-4-V1-java":                              true,
		"test-piece-4-V2-java":                              true,
		"test-root-package-V1-java":                         true,
		"test-root-package-V2-java":                         true,
		"test-root-package-V3-java":                         true,
		"test-root-package-V4-java":                         true,
		"testServiceIface-java":                             true,
		"wm-flicker-common-app-helpers":                     true,
		"wm-flicker-common-assertions":                      true,
		"wm-shell-flicker-utils":                            true,
		"wycheproof-keystore":                               true,
	}

	// Union of aosp and internal allowlists
	PlatformApiAllowlist = map[string]bool{}
)

func init() {
	for k, v := range aospPlatformApiAllowlist {
		PlatformApiAllowlist[k] = v
	}
}

func (j *Library) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	if disableSourceApexVariant(ctx) {
		// Prebuilts are active, do not create the installation rules for the source javalib.
		// Even though the source javalib is not used, we need to hide it to prevent duplicate installation rules.
		// TODO (b/331665856): Implement a principled solution for this.
		j.HideFromMake()
		j.SkipInstall()
	}
	j.provideHiddenAPIPropertyInfo(ctx)

	j.sdkVersion = j.SdkVersion(ctx)
	j.minSdkVersion = j.MinSdkVersion(ctx)
	j.maxSdkVersion = j.MaxSdkVersion(ctx)

	// Check min_sdk_version of the transitive dependencies if this module is created from
	// java_sdk_library.
	overridableMinSdkVersion := j.overridableProperties.Min_sdk_version.Get(ctx)
	if overridableMinSdkVersion.IsPresent() && j.SdkLibraryName() != nil {
		j.CheckDepsMinSdkVersion(ctx)
	}

	// SdkLibrary.GenerateAndroidBuildActions(ctx) sets the stubsLinkType to Unknown.
	// If the stubsLinkType has already been set to Unknown, the stubsLinkType should
	// not be overridden.
	if j.stubsLinkType != Unknown {
		if proptools.Bool(j.properties.Is_stubs_module) {
			j.stubsLinkType = Stubs
		} else {
			j.stubsLinkType = Implementation
		}
	}

	j.stem = proptools.StringDefault(j.overridableProperties.Stem, ctx.ModuleName())

	proguardSpecInfo := j.collectProguardSpecInfo(ctx)
	android.SetProvider(ctx, ProguardSpecInfoProvider, proguardSpecInfo)
	exportedProguardFlagsFiles := proguardSpecInfo.ProguardFlagsFiles.ToList()
	j.extraProguardFlagsFiles = append(j.extraProguardFlagsFiles, exportedProguardFlagsFiles...)

	combinedExportedProguardFlagFile := android.PathForModuleOut(ctx, "export_proguard_flags")
	writeCombinedProguardFlagsFile(ctx, combinedExportedProguardFlagFile, exportedProguardFlagsFiles)
	j.combinedExportedProguardFlagsFile = combinedExportedProguardFlagFile

	apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)
	if !apexInfo.IsForPlatform() {
		j.HideFromMake()
	}

	j.checkSdkVersions(ctx)
	j.checkHeadersOnly(ctx)
	if ctx.Device() {
		libName := j.Name()
		if j.SdkLibraryName() != nil && strings.HasSuffix(libName, ".impl") {
			libName = proptools.String(j.SdkLibraryName())
		}
		j.dexpreopter.installPath = j.dexpreopter.getInstallPath(
			ctx, libName, android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar"))
		j.dexpreopter.isSDKLibrary = j.deviceProperties.IsSDKLibrary
		setUncompressDex(ctx, &j.dexpreopter, &j.dexer)
		j.dexpreopter.uncompressedDex = *j.dexProperties.Uncompress_dex
		j.classLoaderContexts = j.usesLibrary.classLoaderContextForUsesLibDeps(ctx)
		if j.usesLibrary.shouldDisableDexpreopt {
			j.dexpreopter.disableDexpreopt()
		}
	}
	javaInfo := j.compile(ctx)

	j.setInstallRules(ctx)

	android.SetProvider(ctx, android.TestOnlyProviderKey, android.TestModuleInformation{
		TestOnly:       Bool(j.sourceProperties.Test_only),
		TopLevelTarget: j.sourceProperties.Top_level_test_target,
	})

	android.SetProvider(ctx, JavaLibraryInfoProvider, JavaLibraryInfo{
		Prebuilt:          false,
		PermittedPackages: j.properties.Permitted_packages,
	})

	if javaInfo != nil {
		setExtraJavaInfo(ctx, j, javaInfo)
		javaInfo.ExtraOutputFiles = j.extraOutputFiles
		javaInfo.DexJarFile = j.dexJarFile
		javaInfo.InstallFile = j.installFile
		javaInfo.Active = j.active
		javaInfo.BuiltInstalled = j.builtInstalled
		javaInfo.ConfigPath = j.configPath
		javaInfo.LogtagsSrcs = j.logtagsSrcs
		javaInfo.ProguardDictionary = j.proguardDictionary
		javaInfo.ProguardUsageZip = j.proguardUsageZip
		javaInfo.LinterReports = j.reports
		javaInfo.HostdexInstallFile = j.hostdexInstallFile
		javaInfo.GeneratedSrcjars = j.properties.Generated_srcjars
		javaInfo.ProfileGuided = j.dexpreopter.dexpreoptProperties.Dex_preopt_result.Profile_guided

		android.SetProvider(ctx, JavaInfoProvider, javaInfo)
	}

	setOutputFiles(ctx, j.Module)

	j.javaLibraryModuleInfoJSON(ctx)

	buildComplianceMetadata(ctx)

	j.createApiXmlFile(ctx)

	if j.dexer.proguardDictionary.Valid() {
		android.SetProvider(ctx, ProguardProvider, ProguardInfo{
			ModuleName:         android.ModuleNameWithPossibleOverride(ctx),
			Class:              "JAVA_LIBRARIES",
			ProguardDictionary: j.dexer.proguardDictionary.Path(),
			ProguardUsageZip:   j.dexer.proguardUsageZip.Path(),
			ClassesJar:         j.implementationAndResourcesJar,
		})
	}

	if ctx.Os() == android.Windows {
		// Make does not support Windows Java modules
		j.HideFromMake()
	} else if !j.ApexModuleBase.AvailableFor(android.AvailableToPlatform) && !j.hostDexNeeded() {
		// Platform variant.  If not available for the platform, we don't need Make module, unless
		// hostdex is enabled, in which case only the hostdex variant is visible to make.
		j.HideFromMake()
	} else if proptools.Bool(j.properties.Headers_only) {
		// If generating headers only then don't expose to Make.
		j.HideFromMake()
	}
}

func (j *Library) javaLibraryModuleInfoJSON(ctx android.ModuleContext) *android.ModuleInfoJSON {
	moduleInfoJSON := ctx.ModuleInfoJSON()
	moduleInfoJSON.Class = []string{"JAVA_LIBRARIES"}
	if j.implementationAndResourcesJar != nil {
		moduleInfoJSON.ClassesJar = []string{j.implementationAndResourcesJar.String()}
	}
	moduleInfoJSON.SystemSharedLibs = []string{"none"}

	if j.hostDexNeeded() {
		hostDexModuleInfoJSON := ctx.ExtraModuleInfoJSON()
		hostDexModuleInfoJSON.SubName = "-hostdex"
		hostDexModuleInfoJSON.Class = []string{"JAVA_LIBRARIES"}
		if j.implementationAndResourcesJar != nil {
			hostDexModuleInfoJSON.ClassesJar = []string{j.implementationAndResourcesJar.String()}
		}
		hostDexModuleInfoJSON.SystemSharedLibs = []string{"none"}
		hostDexModuleInfoJSON.SupportedVariantsOverride = []string{"HOST"}
	}

	return moduleInfoJSON
}

func buildComplianceMetadata(ctx android.ModuleContext) {
	// Dump metadata that can not be done in android/compliance-metadata.go
	complianceMetadataInfo := ctx.ComplianceMetadataInfo()
	builtFiles := ctx.GetOutputFiles().DefaultOutputFiles.Strings()
	for _, paths := range ctx.GetOutputFiles().TaggedOutputFiles {
		builtFiles = append(builtFiles, paths.Strings()...)
	}
	complianceMetadataInfo.AddBuiltFiles(builtFiles...)

	// Static deps
	staticDepNames := make([]string, 0)
	staticDepFiles := android.Paths{}
	ctx.VisitDirectDepsProxyWithTag(staticLibTag, func(module android.ModuleProxy) {
		if dep, ok := android.OtherModuleProvider(ctx, module, JavaInfoProvider); ok {
			staticDepNames = append(staticDepNames, module.Name())
			staticDepFiles = append(staticDepFiles, dep.ImplementationJars...)
			staticDepFiles = append(staticDepFiles, dep.HeaderJars...)
			staticDepFiles = append(staticDepFiles, dep.ResourceJars...)
		}
	})
	complianceMetadataInfo.SetListValue(android.ComplianceMetadataProp.STATIC_DEPS, android.SortedUniqueStrings(staticDepNames))
	complianceMetadataInfo.SetListValue(android.ComplianceMetadataProp.STATIC_DEP_FILES, android.SortedUniqueStrings(staticDepFiles.Strings()))
}

func (j *Library) getJarInstallDir(ctx android.ModuleContext) android.InstallPath {
	var installDir android.InstallPath
	if ctx.InstallInTestcases() {
		var archDir string
		if !ctx.Host() {
			archDir = ctx.DeviceConfig().DeviceArch()
		}
		installModuleName := ctx.ModuleName()
		// If this module is an impl library created from java_sdk_library,
		// install the files under the java_sdk_library module outdir instead of this module outdir.
		if j.SdkLibraryName() != nil && strings.HasSuffix(j.Name(), ".impl") {
			installModuleName = proptools.String(j.SdkLibraryName())
		}
		installDir = android.PathForModuleInstall(ctx, installModuleName, archDir)
	} else {
		installDir = android.PathForModuleInstall(ctx, "framework")
	}
	return installDir
}

func (j *Library) setInstallRules(ctx android.ModuleContext) {
	apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)

	if (Bool(j.properties.Installable) || ctx.Host()) && apexInfo.IsForPlatform() {
		var extraInstallDeps android.InstallPaths
		if j.InstallMixin != nil {
			extraInstallDeps = j.InstallMixin(ctx, j.outputFile)
		}
		hostDexNeeded := Bool(j.deviceProperties.Hostdex) && !ctx.Host()
		if hostDexNeeded {
			j.hostdexInstallFile = ctx.InstallFileWithoutCheckbuild(
				android.PathForHostDexInstall(ctx, "framework"),
				j.Stem()+"-hostdex.jar", j.outputFile)
		}
		j.installFile = ctx.InstallFileWithoutCheckbuild(j.getJarInstallDir(ctx), j.Stem()+".jar", j.outputFile, extraInstallDeps...)
	}
}

func (j *Library) DepsMutator(ctx android.BottomUpMutatorContext) {
	j.usesLibrary.deps(ctx, false)
	j.deps(ctx)

	if j.properties.Header_jar_override != "" {
		ctx.AddVariationDependencies(nil, headerJarOverrideTag, j.properties.Header_jar_override)
	}
	if j.SdkLibraryName() != nil && strings.HasSuffix(j.Name(), ".impl") {
		if dexpreopt.IsDex2oatNeeded(ctx) {
			dexpreopt.RegisterToolDeps(ctx)
		}
		prebuiltSdkLibExists := ctx.OtherModuleExists(android.PrebuiltNameFromSource(proptools.String(j.SdkLibraryName())))
		if prebuiltSdkLibExists && ctx.OtherModuleExists("all_apex_contributions") {
			ctx.AddDependency(ctx.Module(), android.AcDepTag, "all_apex_contributions")
		}
	}
}

var apiXMLGeneratingApiSurfaces = []android.SdkKind{
	android.SdkPublic,
	android.SdkSystem,
	android.SdkModule,
	android.SdkSystemServer,
	android.SdkTest,
}

func (j *Library) createApiXmlFile(ctx android.ModuleContext) {
	if kind, ok := android.JavaLibraryNameToSdkKind(ctx.ModuleName()); ok && android.InList(kind, apiXMLGeneratingApiSurfaces) {
		scopePrefix := AllApiScopes.matchingScopeFromSdkKind(kind).apiFilePrefix
		j.apiXmlFile = android.PathForModuleOut(ctx, fmt.Sprintf("%sapi.xml", scopePrefix))
		ctx.Build(pctx, android.BuildParams{
			Rule: generateApiXMLRule,
			// LOCAL_SOONG_CLASSES_JAR
			Input:  j.implementationAndResourcesJar,
			Output: j.apiXmlFile,
		})
		ctx.DistForGoal("dist_files", j.apiXmlFile)
		ctx.SetOutputFiles(android.Paths{j.apiXmlFile}, ".api.xml")
	}
}

const (
	aidlIncludeDir   = "aidl"
	javaDir          = "java"
	jarFileSuffix    = ".jar"
	testConfigSuffix = "-AndroidTest.xml"
)

// path to the jar file of a java library. Relative to <sdk_root>/<api_dir>
func sdkSnapshotFilePathForJar(_ android.SdkMemberContext, osPrefix, name string) string {
	return sdkSnapshotFilePathForMember(osPrefix, name, jarFileSuffix)
}

func sdkSnapshotFilePathForMember(osPrefix, name string, suffix string) string {
	return filepath.Join(javaDir, osPrefix, name+suffix)
}

type librarySdkMemberType struct {
	android.SdkMemberTypeBase

	// Function to retrieve the appropriate output jar (implementation or header) from
	// the library.
	jarToExportGetter func(ctx android.SdkMemberContext, j android.ModuleProxy, javaInfo *JavaInfo) android.Path

	// Function to compute the snapshot relative path to which the named library's
	// jar should be copied.
	snapshotPathGetter func(ctx android.SdkMemberContext, osPrefix, name string) string

	// True if only the jar should be copied to the snapshot, false if the jar plus any additional
	// files like aidl files should also be copied.
	onlyCopyJarToSnapshot bool
}

const (
	onlyCopyJarToSnapshot    = true
	copyEverythingToSnapshot = false
)

func (mt *librarySdkMemberType) AddDependencies(ctx android.SdkDependencyContext, dependencyTag blueprint.DependencyTag, names []string) {
	ctx.AddVariationDependencies(nil, dependencyTag, names...)
}

func (mt *librarySdkMemberType) IsInstance(ctx android.ModuleContext, module android.ModuleProxy) bool {
	info, ok := android.OtherModuleProvider(ctx, module, JavaLibraryInfoProvider)
	return ok && !info.Prebuilt
}

func (mt *librarySdkMemberType) AddPrebuiltModule(ctx android.SdkMemberContext, member android.SdkMember) android.BpModule {
	return ctx.SnapshotBuilder().AddPrebuiltModule(member, "java_import")
}

func (mt *librarySdkMemberType) CreateVariantPropertiesStruct() android.SdkMemberProperties {
	return &librarySdkMemberProperties{}
}

type librarySdkMemberProperties struct {
	android.SdkMemberPropertiesBase

	JarToExport     android.Path `android:"arch_variant"`
	AidlIncludeDirs android.Paths

	// The list of permitted packages that need to be passed to the prebuilts as they are used to
	// create the updatable-bcp-packages.txt file.
	PermittedPackages []string

	// The value of the min_sdk_version property, translated into a number where possible.
	MinSdkVersion *string `supported_build_releases:"Tiramisu+"`

	DexPreoptProfileGuided *bool `supported_build_releases:"UpsideDownCake+"`
}

func (p *librarySdkMemberProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.ModuleProxy) {
	//j := variant.(*Library)
	mctx := ctx.SdkModuleContext()
	commonInfo := android.OtherModulePointerProviderOrDefault(mctx, variant, android.CommonModuleInfoProvider)
	javaInfo := android.OtherModulePointerProviderOrDefault(mctx, variant, JavaInfoProvider)
	libraryInfo := android.OtherModuleProviderOrDefault(mctx, variant, JavaLibraryInfoProvider)

	p.JarToExport = ctx.MemberType().(*librarySdkMemberType).jarToExportGetter(ctx, variant, javaInfo)

	p.AidlIncludeDirs = javaInfo.AidlIncludeDirs

	p.PermittedPackages = libraryInfo.PermittedPackages

	// If the min_sdk_version was set then add the canonical representation of the API level to the
	// snapshot.
	if javaInfo.HasOverrideMinSdkVersion {
		canonical, err := android.ReplaceFinalizedCodenames(mctx.Config(), commonInfo.MinSdkVersion.ApiLevel.String())
		if err != nil {
			ctx.ModuleErrorf("%s", err)
		}
		p.MinSdkVersion = proptools.StringPtr(canonical)
	}

	if javaInfo.ProfileGuided {
		p.DexPreoptProfileGuided = proptools.BoolPtr(true)
	}
}

func (p *librarySdkMemberProperties) AddToPropertySet(ctx android.SdkMemberContext, propertySet android.BpPropertySet) {
	builder := ctx.SnapshotBuilder()

	memberType := ctx.MemberType().(*librarySdkMemberType)

	exportedJar := p.JarToExport
	if exportedJar != nil {
		// Delegate the creation of the snapshot relative path to the member type.
		snapshotRelativeJavaLibPath := memberType.snapshotPathGetter(ctx, p.OsPrefix(), ctx.Name())

		// Copy the exported jar to the snapshot.
		builder.CopyToSnapshot(exportedJar, snapshotRelativeJavaLibPath)

		propertySet.AddProperty("jars", []string{snapshotRelativeJavaLibPath})
	}

	if p.MinSdkVersion != nil {
		propertySet.AddProperty("min_sdk_version", *p.MinSdkVersion)
	}

	if len(p.PermittedPackages) > 0 {
		propertySet.AddProperty("permitted_packages", p.PermittedPackages)
	}

	dexPreoptSet := propertySet.AddPropertySet("dex_preopt")
	if p.DexPreoptProfileGuided != nil {
		dexPreoptSet.AddProperty("profile_guided", proptools.Bool(p.DexPreoptProfileGuided))
	}

	// Do not copy anything else to the snapshot.
	if memberType.onlyCopyJarToSnapshot {
		return
	}

	aidlIncludeDirs := p.AidlIncludeDirs
	if len(aidlIncludeDirs) != 0 {
		sdkModuleContext := ctx.SdkModuleContext()
		for _, dir := range aidlIncludeDirs {
			// TODO(jiyong): copy parcelable declarations only
			aidlFiles, _ := sdkModuleContext.GlobWithDeps(dir.String()+"/**/*.aidl", nil)
			for _, file := range aidlFiles {
				builder.CopyToSnapshot(android.PathForSource(sdkModuleContext, file), filepath.Join(aidlIncludeDir, file))
			}
		}

		// TODO(b/151933053) - add aidl include dirs property
	}
}

// java_library builds and links sources into a `.jar` file for the device, and possibly for the host as well.
//
// By default, a java_library has a single variant that produces a `.jar` file containing `.class` files that were
// compiled against the device bootclasspath.  This jar is not suitable for installing on a device, but can be used
// as a `static_libs` dependency of another module.
//
// Specifying `installable: true` will product a `.jar` file containing `classes.dex` files, suitable for installing on
// a device.
//
// Specifying `host_supported: true` will produce two variants, one compiled against the device bootclasspath and one
// compiled against the host bootclasspath.
func LibraryFactory() android.Module {
	module := &Library{}

	module.addHostAndDeviceProperties()
	module.AddProperties(&module.sourceProperties)

	module.initModuleAndImport(module)

	android.InitApexModule(module)
	InitJavaModule(module, android.HostAndDeviceSupported)
	return module
}

// java_library_static is an obsolete alias for java_library.
func LibraryStaticFactory() android.Module {
	return LibraryFactory()
}

// java_library_host builds and links sources into a `.jar` file for the host.
//
// A java_library_host has a single variant that produces a `.jar` file containing `.class` files that were
// compiled against the host bootclasspath.
func LibraryHostFactory() android.Module {
	module := &Library{}

	module.addHostProperties()

	module.Module.properties.Installable = proptools.BoolPtr(true)

	android.InitApexModule(module)
	InitJavaModule(module, android.HostSupported)
	return module
}

//
// Java Tests
//

// Test option struct.
type TestOptions struct {
	android.CommonTestOptions

	// a list of extra test configuration files that should be installed with the module.
	Extra_test_configs []string `android:"path,arch_variant"`

	// Extra <option> tags to add to the auto generated test xml file. The "key"
	// is optional in each of these.
	Tradefed_options []tradefed.Option

	// Extra <option> tags to add to the auto generated test xml file under the test runner, e.g., AndroidJunitTest.
	// The "key" is optional in each of these.
	Test_runner_options []tradefed.Option
}

type testProperties struct {
	// list of compatibility suites (for example "cts", "vts") that the module should be
	// installed into.
	Test_suites []string `android:"arch_variant"`

	// the name of the test configuration (for example "AndroidTest.xml") that should be
	// installed with the module.
	Test_config *string `android:"path,arch_variant"`

	// the name of the test configuration template (for example "AndroidTestTemplate.xml") that
	// should be installed with the module.
	Test_config_template *string `android:"path,arch_variant"`

	// list of files or filegroup modules that provide data that should be installed alongside
	// the test
	Data []string `android:"path"`

	// Same as data, but will add dependencies on modules using the device's os variation and
	// the common arch variation. Useful for a host test that wants to embed a module built for
	// device.
	Device_common_data []string `android:"path_device_common"`

	// same as data, but adds dependencies using the device's os variation and the device's first
	// architecture's variation. Can be used to add a module built for device to the data of a
	// host test.
	Device_first_data []string `android:"path_device_first"`

	// same as data, but adds dependencies using the device's os variation and the device's first
	// 32-bit architecture's variation. If a 32-bit arch doesn't exist for this device, it will use
	// a 64 bit arch instead. Can be used to add a module built for device to the data of a
	// host test.
	Device_first_prefer32_data []string `android:"path_device_first_prefer32"`

	// Same as data, but will add dependencies on modules using the host's os variation and
	// the common arch variation. Useful for a device test that wants to depend on a host
	// module, for example to include a custom Tradefed test runner.
	Host_common_data []string `android:"path_host_common"`

	// Same as data, but will add dependencies on modules using the host's os variation and
	// the host's arch variation. Useful for a device test that wants to depend on a host
	// non-java module.
	Host_first_data []string `android:"path_host_first"`

	// Flag to indicate whether or not to create test config automatically. If AndroidTest.xml
	// doesn't exist next to the Android.bp, this attribute doesn't need to be set to true
	// explicitly.
	Auto_gen_config *bool

	// Add parameterized mainline modules to auto generated test config. The options will be
	// handled by TradeFed to do downloading and installing the specified modules on the device.
	Test_mainline_modules []string

	// Test options.
	Test_options TestOptions

	// Names of modules containing JNI libraries that should be installed alongside the test.
	Jni_libs proptools.Configurable[[]string]

	// Install the test into a folder named for the module in all test suites.
	Per_testcase_directory *bool
}

type hostTestProperties struct {
	// list of native binary modules that should be installed alongside the test
	Data_native_bins []string `android:"arch_variant"`

	// list of device binary modules that should be installed alongside the test
	// This property only adds the first variant of the dependency
	Data_device_bins_first []string `android:"arch_variant"`

	// list of device binary modules that should be installed alongside the test
	// This property adds 64bit AND 32bit variants of the dependency
	Data_device_bins_both []string `android:"arch_variant"`

	// list of device binary modules that should be installed alongside the test
	// This property only adds 64bit variants of the dependency
	Data_device_bins_64 []string `android:"arch_variant"`

	// list of device binary modules that should be installed alongside the test
	// This property adds 32bit variants of the dependency if available, or else
	// defaults to the 64bit variant
	Data_device_bins_prefer32 []string `android:"arch_variant"`

	// list of device binary modules that should be installed alongside the test
	// This property only adds 32bit variants of the dependency
	Data_device_bins_32 []string `android:"arch_variant"`
}

type testHelperLibraryProperties struct {
	// list of compatibility suites (for example "cts", "vts") that the module should be
	// installed into.
	Test_suites []string `android:"arch_variant"`

	// Install the test into a folder named for the module in all test suites.
	Per_testcase_directory *bool
}

type prebuiltTestProperties struct {
	// list of compatibility suites (for example "cts", "vts") that the module should be
	// installed into.
	Test_suites []string `android:"arch_variant"`

	// the name of the test configuration (for example "AndroidTest.xml") that should be
	// installed with the module.
	Test_config *string `android:"path,arch_variant"`
}

type Test struct {
	Library

	testProperties testProperties

	testConfig       android.Path
	extraTestConfigs android.Paths
	data             android.Paths
}

type TestHost struct {
	Test

	testHostProperties hostTestProperties
}

type TestHelperLibrary struct {
	Library

	testHelperLibraryProperties testHelperLibraryProperties
}

type JavaTestImport struct {
	Import

	prebuiltTestProperties prebuiltTestProperties

	testConfig android.Path
	dexJarFile android.Path
}

type JavaTestInfo struct {
	TestConfig android.Path
}

var JavaTestInfoProvider = blueprint.NewProvider[JavaTestInfo]()

func (j *Test) InstallInTestcases() bool {
	// Host java tests install into $(HOST_OUT_JAVA_LIBRARIES), and then are copied into
	// testcases by base_rules.mk.
	return !j.Host()
}

func (j *TestHelperLibrary) InstallInTestcases() bool {
	return true
}

func (j *JavaTestImport) InstallInTestcases() bool {
	return true
}

func (j *TestHost) IsNativeCoverageNeeded(ctx cc.IsNativeCoverageNeededContext) bool {
	return ctx.DeviceConfig().NativeCoverageEnabled()
}

func (j *TestHost) addDataDeviceBinsDeps(ctx android.BottomUpMutatorContext) {
	if len(j.testHostProperties.Data_device_bins_first) > 0 {
		deviceVariations := ctx.Config().AndroidFirstDeviceTarget.Variations()
		ctx.AddFarVariationDependencies(deviceVariations, dataDeviceBinsTag, j.testHostProperties.Data_device_bins_first...)
	}

	var maybeAndroid32Target *android.Target
	var maybeAndroid64Target *android.Target
	android32TargetList := android.FirstTarget(ctx.Config().Targets[android.Android], "lib32")
	android64TargetList := android.FirstTarget(ctx.Config().Targets[android.Android], "lib64")
	if len(android32TargetList) > 0 {
		maybeAndroid32Target = &android32TargetList[0]
	}
	if len(android64TargetList) > 0 {
		maybeAndroid64Target = &android64TargetList[0]
	}

	if len(j.testHostProperties.Data_device_bins_both) > 0 {
		if maybeAndroid32Target == nil && maybeAndroid64Target == nil {
			ctx.PropertyErrorf("data_device_bins_both", "no device targets available. Targets: %q", ctx.Config().Targets)
			return
		}
		if maybeAndroid32Target != nil {
			ctx.AddFarVariationDependencies(
				maybeAndroid32Target.Variations(),
				dataDeviceBinsTag,
				j.testHostProperties.Data_device_bins_both...,
			)
		}
		if maybeAndroid64Target != nil {
			ctx.AddFarVariationDependencies(
				maybeAndroid64Target.Variations(),
				dataDeviceBinsTag,
				j.testHostProperties.Data_device_bins_both...,
			)
		}
	}

	if len(j.testHostProperties.Data_device_bins_prefer32) > 0 {
		if maybeAndroid32Target != nil {
			ctx.AddFarVariationDependencies(
				maybeAndroid32Target.Variations(),
				dataDeviceBinsTag,
				j.testHostProperties.Data_device_bins_prefer32...,
			)
		} else {
			if maybeAndroid64Target == nil {
				ctx.PropertyErrorf("data_device_bins_prefer32", "no device targets available. Targets: %q", ctx.Config().Targets)
				return
			}
			ctx.AddFarVariationDependencies(
				maybeAndroid64Target.Variations(),
				dataDeviceBinsTag,
				j.testHostProperties.Data_device_bins_prefer32...,
			)
		}
	}

	if len(j.testHostProperties.Data_device_bins_32) > 0 {
		if maybeAndroid32Target == nil {
			ctx.PropertyErrorf("data_device_bins_32", "cannot find 32bit device target. Targets: %q", ctx.Config().Targets)
			return
		}
		deviceVariations := maybeAndroid32Target.Variations()
		ctx.AddFarVariationDependencies(deviceVariations, dataDeviceBinsTag, j.testHostProperties.Data_device_bins_32...)
	}

	if len(j.testHostProperties.Data_device_bins_64) > 0 {
		if maybeAndroid64Target == nil {
			ctx.PropertyErrorf("data_device_bins_64", "cannot find 64bit device target. Targets: %q", ctx.Config().Targets)
			return
		}
		deviceVariations := maybeAndroid64Target.Variations()
		ctx.AddFarVariationDependencies(deviceVariations, dataDeviceBinsTag, j.testHostProperties.Data_device_bins_64...)
	}
}

func (j *TestHost) DepsMutator(ctx android.BottomUpMutatorContext) {
	if len(j.testHostProperties.Data_native_bins) > 0 {
		for _, target := range ctx.MultiTargets() {
			ctx.AddVariationDependencies(target.Variations(), dataNativeBinsTag, j.testHostProperties.Data_native_bins...)
		}
	}

	jniLibs := j.testProperties.Jni_libs.GetOrDefault(ctx, nil)
	if len(jniLibs) > 0 {
		for _, target := range ctx.MultiTargets() {
			sharedLibVariations := append(target.Variations(), blueprint.Variation{Mutator: "link", Variation: "shared"})
			ctx.AddFarVariationDependencies(sharedLibVariations, jniLibTag, jniLibs...)
		}
	}

	j.addDataDeviceBinsDeps(ctx)
	j.deps(ctx)
}

func (j *TestHost) AddExtraResource(p android.Path) {
	j.extraResources = append(j.extraResources, p)
}

func (j *TestHost) dataDeviceBins() []string {
	ret := make([]string, 0,
		len(j.testHostProperties.Data_device_bins_first)+
			len(j.testHostProperties.Data_device_bins_both)+
			len(j.testHostProperties.Data_device_bins_prefer32)+
			len(j.testHostProperties.Data_device_bins_32)+
			len(j.testHostProperties.Data_device_bins_64),
	)

	ret = append(ret, j.testHostProperties.Data_device_bins_first...)
	ret = append(ret, j.testHostProperties.Data_device_bins_both...)
	ret = append(ret, j.testHostProperties.Data_device_bins_prefer32...)
	ret = append(ret, j.testHostProperties.Data_device_bins_32...)
	ret = append(ret, j.testHostProperties.Data_device_bins_64...)

	return ret
}

func (j *TestHost) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	var configs []tradefed.Config
	dataDeviceBins := j.dataDeviceBins()
	if len(dataDeviceBins) > 0 {
		// add Tradefed configuration to push device bins to device for testing
		remoteDir := filepath.Join("/data/local/tests/unrestricted/", j.Name())
		options := []tradefed.Option{{Name: "cleanup", Value: "true"}}
		for _, bin := range dataDeviceBins {
			fullPath := filepath.Join(remoteDir, bin)
			options = append(options, tradefed.Option{Name: "push-file", Key: bin, Value: fullPath})
		}
		configs = append(configs, tradefed.Object{
			Type:    "target_preparer",
			Class:   "com.android.tradefed.targetprep.PushFilePreparer",
			Options: options,
		})
	}

	j.Test.generateAndroidBuildActionsWithConfig(ctx, configs)
	j.Test.javaTestSetTestsuiteInfo(ctx)
	android.SetProvider(ctx, tradefed.BaseTestProviderKey, tradefed.BaseTestProviderData{
		TestcaseRelDataFiles: testcaseRel(j.data),
		OutputFile:           j.outputFile,
		TestConfig:           j.testConfig,
		RequiredModuleNames:  j.RequiredModuleNames(ctx),
		TestSuites:           j.testProperties.Test_suites,
		IsHost:               true,
		LocalSdkVersion:      j.sdkVersion.String(),
		IsUnitTest:           Bool(j.testProperties.Test_options.Unit_test),
		MkInclude:            "$(BUILD_SYSTEM)/soong_java_prebuilt.mk",
		MkAppClass:           "JAVA_LIBRARIES",
	})

	moduleInfoJSON := ctx.ModuleInfoJSON()
	if proptools.Bool(j.testProperties.Test_options.Unit_test) {
		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "host-unit-tests")
	}
}

func (j *Test) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	checkMinSdkVersionMts(ctx, j.MinSdkVersion(ctx))
	j.generateAndroidBuildActionsWithConfig(ctx, nil)
	j.javaTestSetTestsuiteInfo(ctx)
}

func (j *Test) generateAndroidBuildActionsWithConfig(ctx android.ModuleContext, configs []tradefed.Config) {
	if j.testProperties.Test_options.Unit_test == nil && ctx.Host() {
		// TODO(b/): Clean temporary heuristic to avoid unexpected onboarding.
		defaultUnitTest := !inList("tradefed", j.properties.Libs) && !inList("cts", j.testProperties.Test_suites)
		j.testProperties.Test_options.Unit_test = proptools.BoolPtr(defaultUnitTest)
	}
	j.testConfig = tradefed.AutoGenTestConfig(ctx, tradefed.AutoGenTestConfigOptions{
		TestConfigProp:          j.testProperties.Test_config,
		TestConfigTemplateProp:  j.testProperties.Test_config_template,
		TestSuites:              j.testProperties.Test_suites,
		Config:                  configs,
		OptionsForAutogenerated: j.testProperties.Test_options.Tradefed_options,
		TestRunnerOptions:       j.testProperties.Test_options.Test_runner_options,
		AutoGenConfig:           j.testProperties.Auto_gen_config,
		UnitTest:                j.testProperties.Test_options.Unit_test,
		DeviceTemplate:          "${JavaTestConfigTemplate}",
		HostTemplate:            "${JavaHostTestConfigTemplate}",
		HostUnitTestTemplate:    "${JavaHostUnitTestConfigTemplate}",
	})

	j.data = android.PathsForModuleSrc(ctx, j.testProperties.Data)
	j.data = append(j.data, android.PathsForModuleSrc(ctx, j.testProperties.Device_common_data)...)
	j.data = append(j.data, android.PathsForModuleSrc(ctx, j.testProperties.Device_first_data)...)
	j.data = append(j.data, android.PathsForModuleSrc(ctx, j.testProperties.Device_first_prefer32_data)...)
	j.data = append(j.data, android.PathsForModuleSrc(ctx, j.testProperties.Host_common_data)...)
	j.data = append(j.data, android.PathsForModuleSrc(ctx, j.testProperties.Host_first_data)...)

	j.extraTestConfigs = android.PathsForModuleSrc(ctx, j.testProperties.Test_options.Extra_test_configs)

	ctx.VisitDirectDepsProxyWithTag(dataNativeBinsTag, func(dep android.ModuleProxy) {
		j.data = append(j.data, android.OutputFileForModule(ctx, dep, ""))
	})

	ctx.VisitDirectDepsProxyWithTag(dataDeviceBinsTag, func(dep android.ModuleProxy) {
		j.data = append(j.data, android.OutputFileForModule(ctx, dep, ""))
	})

	var directImplementationDeps android.Paths
	var transitiveImplementationDeps []depset.DepSet[android.Path]
	ctx.VisitDirectDepsProxyWithTag(jniLibTag, func(dep android.ModuleProxy) {
		sharedLibInfo, _ := android.OtherModuleProvider(ctx, dep, cc.SharedLibraryInfoProvider)
		if sharedLibInfo.SharedLibrary != nil {
			// Copy to an intermediate output directory to append "lib[64]" to the path,
			// so that it's compatible with the default rpath values.
			var relPath string
			if sharedLibInfo.Target.Arch.ArchType.Multilib == "lib64" {
				relPath = filepath.Join("lib64", sharedLibInfo.SharedLibrary.Base())
			} else {
				relPath = filepath.Join("lib", sharedLibInfo.SharedLibrary.Base())
			}
			relocatedLib := android.PathForModuleOut(ctx, "relocated").Join(ctx, relPath)
			ctx.Build(pctx, android.BuildParams{
				Rule:   android.Cp,
				Input:  sharedLibInfo.SharedLibrary,
				Output: relocatedLib,
			})
			j.data = append(j.data, relocatedLib)

			directImplementationDeps = append(directImplementationDeps, android.OutputFileForModule(ctx, dep, ""))
			if info, ok := android.OtherModuleProvider(ctx, dep, cc.ImplementationDepInfoProvider); ok {
				transitiveImplementationDeps = append(transitiveImplementationDeps, info.ImplementationDeps)
			}
		} else {
			ctx.PropertyErrorf("jni_libs", "%q of type %q is not supported", dep.Name(), ctx.OtherModuleType(dep))
		}
	})

	android.SetProvider(ctx, cc.ImplementationDepInfoProvider, &cc.ImplementationDepInfo{
		ImplementationDeps: depset.New(depset.PREORDER, directImplementationDeps, transitiveImplementationDeps),
	})

	j.Library.GenerateAndroidBuildActions(ctx)

	moduleInfoJSON := ctx.ModuleInfoJSON()
	// LOCAL_MODULE_TAGS
	moduleInfoJSON.Tags = append(moduleInfoJSON.Tags, "tests")
	var allTestConfigs android.Paths
	if j.testConfig != nil {
		allTestConfigs = append(allTestConfigs, j.testConfig)
	}
	allTestConfigs = append(allTestConfigs, j.extraTestConfigs...)
	if len(allTestConfigs) > 0 {
		moduleInfoJSON.TestConfig = allTestConfigs.Strings()
	} else {
		optionalConfig := android.ExistentPathForSource(ctx, ctx.ModuleDir(), "AndroidTest.xml")
		if optionalConfig.Valid() {
			moduleInfoJSON.TestConfig = append(moduleInfoJSON.TestConfig, optionalConfig.String())
		}
	}
	if len(j.testProperties.Test_suites) > 0 {
		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, j.testProperties.Test_suites...)
	} else {
		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "null-suite")
	}
	if _, ok := j.testConfig.(android.WritablePath); ok {
		moduleInfoJSON.AutoTestConfig = []string{"true"}
	}
	if proptools.Bool(j.testProperties.Test_options.Unit_test) {
		moduleInfoJSON.IsUnitTest = "true"
		if ctx.Host() {
			moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "host-unit-tests")
		}
	}
	moduleInfoJSON.TestMainlineModules = append(moduleInfoJSON.TestMainlineModules, j.testProperties.Test_mainline_modules...)
}

func (j *Test) javaTestSetTestsuiteInfo(ctx android.ModuleContext) {
	// Install test deps
	var testData []android.DataPath
	for _, data := range j.data {
		dataPath := android.DataPath{SrcPath: data}
		testData = append(testData, dataPath)
	}
	ctx.SetTestSuiteInfo(android.TestSuiteInfo{
		TestSuites:           j.testProperties.Test_suites,
		MainFile:             j.outputFile,
		MainFileStem:         j.Stem(),
		MainFileExt:          ".jar",
		ConfigFile:           j.testConfig,
		ExtraConfigs:         j.extraTestConfigs,
		NeedsArchFolder:      ctx.Device(),
		NonArchData:          testData,
		PerTestcaseDirectory: proptools.Bool(j.testProperties.Per_testcase_directory),
		IsUnitTest:           Bool(j.testProperties.Test_options.Unit_test),
	})

	android.SetProvider(ctx, JavaTestInfoProvider, JavaTestInfo{
		TestConfig: j.testConfig,
	})
}

func (j *TestHelperLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	j.Library.GenerateAndroidBuildActions(ctx)

	moduleInfoJSON := ctx.ModuleInfoJSON()
	moduleInfoJSON.Tags = append(moduleInfoJSON.Tags, "tests")
	if len(j.testHelperLibraryProperties.Test_suites) > 0 {
		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, j.testHelperLibraryProperties.Test_suites...)
	} else {
		moduleInfoJSON.CompatibilitySuites = append(moduleInfoJSON.CompatibilitySuites, "null-suite")
	}
	optionalConfig := android.ExistentPathForSource(ctx, ctx.ModuleDir(), "AndroidTest.xml")
	var config android.Path
	if optionalConfig.Valid() {
		config = optionalConfig.Path()
		moduleInfoJSON.TestConfig = append(moduleInfoJSON.TestConfig, optionalConfig.String())
	}

	ctx.SetTestSuiteInfo(android.TestSuiteInfo{
		TestSuites:           j.testHelperLibraryProperties.Test_suites,
		MainFile:             j.outputFile,
		MainFileStem:         j.Stem(),
		MainFileExt:          ".jar",
		ConfigFile:           config,
		NeedsArchFolder:      ctx.Device(),
		PerTestcaseDirectory: proptools.Bool(j.testHelperLibraryProperties.Per_testcase_directory),
	})
}

func (j *JavaTestImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	j.testConfig = tradefed.AutoGenTestConfig(ctx, tradefed.AutoGenTestConfigOptions{
		TestConfigProp:       j.prebuiltTestProperties.Test_config,
		TestSuites:           j.prebuiltTestProperties.Test_suites,
		DeviceTemplate:       "${JavaTestConfigTemplate}",
		HostTemplate:         "${JavaHostTestConfigTemplate}",
		HostUnitTestTemplate: "${JavaHostUnitTestConfigTemplate}",
	})

	j.Import.GenerateAndroidBuildActions(ctx)
}

type testSdkMemberType struct {
	android.SdkMemberTypeBase
}

func (mt *testSdkMemberType) AddDependencies(ctx android.SdkDependencyContext, dependencyTag blueprint.DependencyTag, names []string) {
	ctx.AddVariationDependencies(nil, dependencyTag, names...)
}

func (mt *testSdkMemberType) IsInstance(ctx android.ModuleContext, module android.ModuleProxy) bool {
	_, ok := android.OtherModuleProvider(ctx, module, JavaTestInfoProvider)
	return ok
}

func (mt *testSdkMemberType) AddPrebuiltModule(ctx android.SdkMemberContext, member android.SdkMember) android.BpModule {
	return ctx.SnapshotBuilder().AddPrebuiltModule(member, "java_test_import")
}

func (mt *testSdkMemberType) CreateVariantPropertiesStruct() android.SdkMemberProperties {
	return &testSdkMemberProperties{}
}

type testSdkMemberProperties struct {
	android.SdkMemberPropertiesBase

	JarToExport android.Path
	TestConfig  android.Path
}

func (p *testSdkMemberProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.ModuleProxy) {
	implementationJars := android.OtherModulePointerProviderOrDefault(ctx.SdkModuleContext(), variant, JavaInfoProvider).ImplementationJars
	if len(implementationJars) != 1 {
		panic(fmt.Errorf("there must be only one implementation jar from %q", variant.Name()))
	}

	p.JarToExport = implementationJars[0]
	p.TestConfig = android.OtherModuleProviderOrDefault(ctx.SdkModuleContext(), variant, JavaTestInfoProvider).TestConfig
}

func (p *testSdkMemberProperties) AddToPropertySet(ctx android.SdkMemberContext, propertySet android.BpPropertySet) {
	builder := ctx.SnapshotBuilder()

	exportedJar := p.JarToExport
	if exportedJar != nil {
		snapshotRelativeJavaLibPath := sdkSnapshotFilePathForJar(ctx, p.OsPrefix(), ctx.Name())
		builder.CopyToSnapshot(exportedJar, snapshotRelativeJavaLibPath)

		propertySet.AddProperty("jars", []string{snapshotRelativeJavaLibPath})
	}

	testConfig := p.TestConfig
	if testConfig != nil {
		snapshotRelativeTestConfigPath := sdkSnapshotFilePathForMember(p.OsPrefix(), ctx.Name(), testConfigSuffix)
		builder.CopyToSnapshot(testConfig, snapshotRelativeTestConfigPath)
		propertySet.AddProperty("test_config", snapshotRelativeTestConfigPath)
	}
}

// java_test builds a and links sources into a `.jar` file for the device, and possibly for the host as well, and
// creates an `AndroidTest.xml` file to allow running the test with `atest` or a `TEST_MAPPING` file.
//
// By default, a java_test has a single variant that produces a `.jar` file containing `classes.dex` files that were
// compiled against the device bootclasspath.
//
// Specifying `host_supported: true` will produce two variants, one compiled against the device bootclasspath and one
// compiled against the host bootclasspath.
func TestFactory() android.Module {
	module := &Test{}

	module.addHostAndDeviceProperties()
	module.AddProperties(&module.testProperties)

	module.Module.properties.Installable = proptools.BoolPtr(true)
	module.Module.dexpreopter.isTest = true
	module.Module.linter.properties.Lint.Test_module_type = proptools.BoolPtr(true)
	module.Module.sourceProperties.Test_only = proptools.BoolPtr(true)
	module.Module.sourceProperties.Top_level_test_target = true

	InitJavaModule(module, android.HostAndDeviceSupported)
	return module
}

// java_test_helper_library creates a java library and makes sure that it is added to the appropriate test suite.
func TestHelperLibraryFactory() android.Module {
	module := &TestHelperLibrary{}

	module.addHostAndDeviceProperties()
	module.AddProperties(&module.testHelperLibraryProperties)

	module.Module.properties.Installable = proptools.BoolPtr(true)
	module.Module.dexpreopter.isTest = true
	module.Module.linter.properties.Lint.Test_module_type = proptools.BoolPtr(true)
	module.Module.sourceProperties.Test_only = proptools.BoolPtr(true)

	InitJavaModule(module, android.HostAndDeviceSupported)
	return module
}

// java_test_import imports one or more `.jar` files into the build graph as if they were built by a java_test module
// and makes sure that it is added to the appropriate test suite.
//
// By default, a java_test_import has a single variant that expects a `.jar` file containing `.class` files that were
// compiled against an Android classpath.
//
// Specifying `host_supported: true` will produce two variants, one for use as a dependency of device modules and one
// for host modules.
func JavaTestImportFactory() android.Module {
	module := &JavaTestImport{}

	module.AddProperties(
		&module.Import.properties,
		&module.prebuiltTestProperties)

	module.Import.properties.Installable = proptools.BoolPtr(true)

	android.InitPrebuiltModule(module, &module.properties.Jars)
	android.InitApexModule(module)
	InitJavaModule(module, android.HostAndDeviceSupported)
	return module
}

// java_test_host builds a and links sources into a `.jar` file for the host, and creates an `AndroidTest.xml` file to
// allow running the test with `atest` or a `TEST_MAPPING` file.
//
// A java_test_host has a single variant that produces a `.jar` file containing `.class` files that were
// compiled against the host bootclasspath.
func TestHostFactory() android.Module {
	module := &TestHost{}

	module.addHostProperties()
	module.AddProperties(&module.testProperties)
	module.AddProperties(&module.testHostProperties)

	InitTestHost(
		module,
		proptools.BoolPtr(true),
		nil,
		nil)

	InitJavaModuleMultiTargets(module, android.HostSupported)

	return module
}

func InitTestHost(th *TestHost, installable *bool, testSuites []string, autoGenConfig *bool) {
	th.properties.Installable = installable
	th.testProperties.Auto_gen_config = autoGenConfig
	th.testProperties.Test_suites = testSuites
	th.sourceProperties.Test_only = proptools.BoolPtr(true)
	th.sourceProperties.Top_level_test_target = true
}

//
// Java Binaries (.jar file plus wrapper script)
//

type binaryProperties struct {
	// installable script to execute the resulting jar
	Wrapper *string `android:"path,arch_variant"`

	// Name of the class containing main to be inserted into the manifest as Main-Class.
	Main_class *string

	// Names of modules containing JNI libraries that should be installed alongside the binary.
	Jni_libs []string `android:"arch_variant"`
}

type Binary struct {
	Library

	binaryProperties binaryProperties

	wrapperFile android.Path
	binaryFile  android.InstallPath

	androidMkNamesOfJniLibs []string
}

func (a *Binary) IsNativeCoverageNeeded(ctx cc.IsNativeCoverageNeededContext) bool {
	return ctx.Device() && ctx.DeviceConfig().NativeCoverageEnabled()
}

var _ cc.UseCoverage = &Binary{}

func (j *Binary) HostToolPath() android.OptionalPath {
	return android.OptionalPathForPath(j.binaryFile)
}

func (j *Binary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	j.stem = proptools.StringDefault(j.overridableProperties.Stem, ctx.ModuleName())

	// Handle the binary wrapper. This comes before compiling the jar so that the wrapper
	// is the first PackagingSpec
	if j.binaryProperties.Wrapper != nil {
		j.wrapperFile = android.PathForModuleSrc(ctx, *j.binaryProperties.Wrapper)
	} else {
		if ctx.Windows() {
			ctx.PropertyErrorf("wrapper", "wrapper is required for Windows")
		}

		if ctx.Device() {
			// device binary should have a main_class property if it does not
			// have a specific wrapper, so that a default wrapper can
			// be generated for it.
			if j.binaryProperties.Main_class == nil {
				ctx.PropertyErrorf("main_class", "main_class property "+
					"is required for device binary if no default wrapper is assigned")
			} else {
				wrapper := android.PathForModuleOut(ctx, ctx.ModuleName()+".sh")
				jarName := j.Stem() + ".jar"
				partition := j.PartitionTag(ctx.DeviceConfig())
				ctx.Build(pctx, android.BuildParams{
					Rule:   deviceBinaryWrapper,
					Output: wrapper,
					Args: map[string]string{
						"jar_name":   jarName,
						"partition":  partition,
						"main_class": String(j.binaryProperties.Main_class),
					},
				})
				j.wrapperFile = wrapper
			}
		} else {
			j.wrapperFile = android.PathForSource(ctx, "build/soong/scripts/jar-wrapper.sh")
		}
	}

	ext := ""
	if ctx.Windows() {
		ext = ".bat"
	}

	// The host installation rules make the installed wrapper depend on all the dependencies
	// of the wrapper variant, which will include the common variant's jar file and any JNI
	// libraries.  This is verified by TestBinary. Also make it depend on the jar file so that
	// the binary file timestamp will update when the jar file timestamp does. The jar file is
	// built later on, in j.Library.GenerateAndroidBuildActions, so we have to create an identical
	// installpath representing it here.
	j.binaryFile = ctx.InstallExecutable(android.PathForModuleInstall(ctx, "bin"),
		ctx.ModuleName()+ext, j.wrapperFile, j.getJarInstallDir(ctx).Join(ctx, j.Stem()+".jar"))

	// Set the jniLibs of this binary.
	// These will be added to `LOCAL_REQUIRED_MODULES`, and the kati packaging system will
	// install these alongside the java binary.
	ctx.VisitDirectDepsProxyWithTag(jniInstallTag, func(jni android.ModuleProxy) {
		// Use the BaseModuleName of the dependency (without any prebuilt_ prefix)
		commonInfo := android.OtherModulePointerProviderOrDefault(ctx, jni, android.CommonModuleInfoProvider)
		j.androidMkNamesOfJniLibs = append(j.androidMkNamesOfJniLibs, commonInfo.BaseModuleName+":"+commonInfo.Target.Arch.ArchType.Bitness())
	})
	// Check that native libraries are not listed in `required`. Prompt users to use `jni_libs` instead.
	ctx.VisitDirectDepsProxyWithTag(android.RequiredDepTag, func(dep android.ModuleProxy) {
		if _, hasSharedLibraryInfo := android.OtherModuleProvider(ctx, dep, cc.SharedLibraryInfoProvider); hasSharedLibraryInfo {
			ctx.ModuleErrorf("cc_library %s is no longer supported in `required` of java_binary modules. Please use jni_libs instead.", dep.Name())
		}
	})

	// Compile the jar
	if j.binaryProperties.Main_class != nil {
		if j.properties.Manifest != nil {
			ctx.PropertyErrorf("main_class", "main_class cannot be used when manifest is set")
		}
		manifestFile := android.PathForModuleOut(ctx, "manifest.txt")
		GenerateMainClassManifest(ctx, manifestFile, String(j.binaryProperties.Main_class))
		j.overrideManifest = android.OptionalPathForPath(manifestFile)
	}

	j.Library.GenerateAndroidBuildActions(ctx)
}

func (j *Binary) DepsMutator(ctx android.BottomUpMutatorContext) {
	j.deps(ctx)
	// These dependencies ensure the installation rules will install the jar file when the
	// wrapper is installed, and the jni libraries when the wrapper is installed.
	if ctx.Os().Class == android.Host {
		ctx.AddVariationDependencies(ctx.Config().BuildOSTarget.Variations(), jniInstallTag, j.binaryProperties.Jni_libs...)
	} else if ctx.Os().Class == android.Device {
		ctx.AddVariationDependencies(ctx.Config().AndroidFirstDeviceTarget.Variations(), jniInstallTag, j.binaryProperties.Jni_libs...)
	} else {
		ctx.ModuleErrorf("Unknown os class")
	}
}

// java_binary builds a `.jar` file and a shell script that executes it for the device, and possibly for the host
// as well.
//
// By default, a java_binary has a single variant that produces a `.jar` file containing `classes.dex` files that were
// compiled against the device bootclasspath.
//
// Specifying `host_supported: true` will produce two variants, one compiled against the device bootclasspath and one
// compiled against the host bootclasspath.
func BinaryFactory() android.Module {
	module := &Binary{}

	module.addHostAndDeviceProperties()
	module.AddProperties(&module.binaryProperties, &module.sourceProperties)

	module.Module.properties.Installable = proptools.BoolPtr(true)

	android.InitAndroidArchModule(module, android.HostAndDeviceSupported, android.MultilibCommon)
	android.InitDefaultableModule(module)

	return module
}

// java_binary_host builds a `.jar` file and a shell script that executes it for the host.
//
// A java_binary_host has a single variant that produces a `.jar` file containing `.class` files that were
// compiled against the host bootclasspath.
func BinaryHostFactory() android.Module {
	module := &Binary{}

	module.addHostProperties()
	module.AddProperties(&module.binaryProperties)

	module.Module.properties.Installable = proptools.BoolPtr(true)

	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibCommon)
	android.InitDefaultableModule(module)
	return module
}

type JavaApiContribution struct {
	android.ModuleBase
	android.DefaultableModuleBase
	embeddableInModuleAndImport

	properties struct {
		// name of the API surface
		Api_surface *string

		// relative path to the API signature text file
		Api_file *string `android:"path"`
	}
}

func ApiContributionFactory() android.Module {
	module := &JavaApiContribution{}
	android.InitAndroidModule(module)
	android.InitDefaultableModule(module)
	module.AddProperties(&module.properties)
	module.initModuleAndImport(module)
	return module
}

type JavaApiImportInfo struct {
	ApiFile    android.Path
	ApiSurface string
}

var JavaApiImportProvider = blueprint.NewProvider[JavaApiImportInfo]()

func (ap *JavaApiContribution) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	var apiFile android.Path = nil
	if apiFileString := ap.properties.Api_file; apiFileString != nil {
		apiFile = android.PathForModuleSrc(ctx, String(apiFileString))
	}

	android.SetProvider(ctx, JavaApiImportProvider, JavaApiImportInfo{
		ApiFile:    apiFile,
		ApiSurface: proptools.String(ap.properties.Api_surface),
	})
}

func (ap *JavaApiContribution) DepsMutator(ctx android.BottomUpMutatorContext) {
	ap.EmbeddableSdkLibraryComponent.setComponentDependencyInfoProvider(ctx)
}

type ApiLibrary struct {
	android.ModuleBase
	android.DefaultableModuleBase

	hiddenAPI
	dexer
	embeddableInModuleAndImport

	properties JavaApiLibraryProperties

	stubsSrcJar               android.WritablePath
	stubsJar                  android.WritablePath
	stubsJarWithoutStaticLibs android.WritablePath
	extractedSrcJar           android.WritablePath
	// .dex of stubs, used for hiddenapi processing
	dexJarFile OptionalDexJarPath

	validationPaths android.Paths

	stubsType StubsType

	aconfigProtoFiles android.Paths

	kSnapshotFiles map[string]android.Path
}

func (al ApiLibrary) JarToSnapshotMap() map[string]android.Path {
	return al.kSnapshotFiles
}

var _ KSnapshotContainer = ApiLibrary{}

type JavaApiLibraryProperties struct {
	// name of the API surface
	Api_surface *string

	// list of Java API contribution modules that consists this API surface
	// This is a list of Soong modules
	Api_contributions []string

	// List of flags to be passed to the javac compiler to generate jar file
	Javacflags []string

	// List of shared java libs that this module has dependencies to and
	// should be passed as classpath in javac invocation
	Libs proptools.Configurable[[]string]

	// List of java libs that this module has static dependencies to and will be
	// merge zipped after metalava invocation
	Static_libs proptools.Configurable[[]string]

	// Version of previously released API file for compatibility check.
	Previous_api *string `android:"path"`

	// java_system_modules module providing the jar to be added to the
	// bootclasspath when compiling the stubs.
	// The jar will also be passed to metalava as a classpath to
	// generate compilable stubs.
	System_modules *string

	// If true, the module runs validation on the API signature files provided
	// by the modules passed via api_contributions by checking if the files are
	// in sync with the source Java files. However, the environment variable
	// DISABLE_STUB_VALIDATION has precedence over this property.
	Enable_validation *bool

	// Type of stubs the module should generate. Must be one of "everything", "runtime" or
	// "exportable". Defaults to "everything".
	// - "everything" stubs include all non-flagged apis and flagged apis, regardless of the state
	// of the flag.
	// - "runtime" stubs include all non-flagged apis and flagged apis that are ENABLED or
	// READ_WRITE, and all other flagged apis are stripped.
	// - "exportable" stubs include all non-flagged apis and flagged apis that are ENABLED and
	// READ_ONLY, and all other flagged apis are stripped.
	Stubs_type *string

	// List of aconfig_declarations module names that the stubs generated in this module
	// depend on.
	Aconfig_declarations []string

	// List of hard coded filegroups containing Metalava config files that are passed to every
	// Metalava invocation that this module performs. See addMetalavaConfigFilesToCmd.
	ConfigFiles []string `android:"path" blueprint:"mutated"`

	// If not blank, set to the version of the sdk to compile against.
	// Defaults to an empty string, which compiles the module against the private platform APIs.
	// Values are of one of the following forms:
	// 1) numerical API level, "current", "none", or "core_platform"
	// 2) An SDK kind with an API level: "<sdk kind>_<API level>"
	// See build/soong/android/sdk_version.go for the complete and up to date list of SDK kinds.
	// If the SDK kind is empty, it will be set to public.
	Sdk_version *string
}

func ApiLibraryFactory() android.Module {
	module := &ApiLibrary{}
	module.AddProperties(&module.properties)
	module.properties.ConfigFiles = getMetalavaConfigFilegroupReference()
	android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibCommon)
	module.initModuleAndImport(module)
	android.InitDefaultableModule(module)
	return module
}

func (al *ApiLibrary) ApiSurface() *string {
	return al.properties.Api_surface
}

func (al *ApiLibrary) StubsJar() android.Path {
	return al.stubsJar
}

func metalavaStubCmd(ctx android.ModuleContext, rule *android.RuleBuilder,
	srcs android.Paths, homeDir android.WritablePath,
	classpath android.Paths, configFiles android.Paths, apiSurface *string) *android.RuleBuilderCommand {
	rule.Command().Text("rm -rf").Flag(homeDir.String())
	rule.Command().Text("mkdir -p").Flag(homeDir.String())

	cmd := rule.Command()
	cmd.FlagWithArg("ANDROID_PREFS_ROOT=", homeDir.String())

	if metalavaUseRewrapper(ctx) {
		rule.Remoteable(android.RemoteRuleSupports{RBE: true})
		execStrategy := ctx.Config().GetenvWithDefault("RBE_METALAVA_EXEC_STRATEGY", remoteexec.LocalExecStrategy)
		labels := map[string]string{"type": "tool", "name": "metalava"}

		pool := ctx.Config().GetenvWithDefault("RBE_METALAVA_POOL", "java16")
		rule.Rewrapper(&remoteexec.REParams{
			Labels:          labels,
			ExecStrategy:    execStrategy,
			ToolchainInputs: []string{config.JavaCmd(ctx).String()},
			Platform:        map[string]string{remoteexec.PoolKey: pool},
		})
	}

	cmd.BuiltTool("metalava").ImplicitTool(ctx.Config().HostJavaToolPath(ctx, "metalava.jar")).
		Flag(config.JavacVmFlags).
		Flag("-J--add-opens=java.base/java.util=ALL-UNNAMED").
		FlagWithInputList("--source-files ", srcs, " ")

	cmd.Flag("--color").
		Flag("--quiet").
		Flag("--include-annotations").
		// The flag makes nullability issues as warnings rather than errors by replacing
		// @Nullable/@NonNull in the listed packages APIs with @RecentlyNullable/@RecentlyNonNull,
		// and these packages are meant to have everything annotated
		// @RecentlyNullable/@RecentlyNonNull.
		FlagWithArg("--force-convert-to-warning-nullability-annotations ", "+*:-android.*:+android.icu.*:-dalvik.*").
		FlagWithArg("--repeat-errors-max ", "10").
		FlagWithArg("--hide ", "UnresolvedImport").
		FlagWithArg("--hide ", "InvalidNullabilityOverride").
		FlagWithArg("--hide ", "ChangedDefault")

	addMetalavaConfigFilesToCmd(cmd, configFiles)

	addOptionalApiSurfaceToCmd(cmd, apiSurface)

	if len(classpath) == 0 {
		// The main purpose of the `--api-class-resolution api` option is to force metalava to ignore
		// classes on the classpath when an API file contains missing classes. However, as this command
		// does not specify `--classpath` this is not needed for that. However, this is also used as a
		// signal to the special metalava code for generating stubs from text files that it needs to add
		// some additional items into the API (e.g. default constructors).
		cmd.FlagWithArg("--api-class-resolution ", "api")
	} else {
		cmd.FlagWithArg("--api-class-resolution ", "api:classpath")
		cmd.FlagWithInputList("--classpath ", classpath, ":")
	}

	return cmd
}

func (al *ApiLibrary) HeaderJars() android.Paths {
	return android.Paths{al.stubsJar}
}

func (al *ApiLibrary) OutputDirAndDeps() (android.Path, android.Paths) {
	return nil, nil
}

func (al *ApiLibrary) stubsFlags(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, stubsDir android.OptionalPath) {
	if stubsDir.Valid() {
		cmd.FlagWithArg("--stubs ", stubsDir.String())
	}
}

func (al *ApiLibrary) addValidation(ctx android.ModuleContext, cmd *android.RuleBuilderCommand, validationPaths android.Paths) {
	for _, validationPath := range validationPaths {
		cmd.Validation(validationPath)
	}
}

func (al *ApiLibrary) DepsMutator(ctx android.BottomUpMutatorContext) {
	apiContributions := al.properties.Api_contributions
	addValidations := !ctx.Config().IsEnvTrue("DISABLE_STUB_VALIDATION") &&
		!ctx.Config().IsEnvTrue("WITHOUT_CHECK_API") &&
		proptools.BoolDefault(al.properties.Enable_validation, true)
	for _, apiContributionName := range apiContributions {
		ctx.AddDependency(ctx.Module(), javaApiContributionTag, apiContributionName)

		// Add the java_api_contribution module generating droidstubs module
		// as dependency when validation adding conditions are met and
		// the java_api_contribution module name has ".api.contribution" suffix.
		// All droidstubs-generated modules possess the suffix in the name,
		// but there is no such guarantee for tests.
		if addValidations {
			if strings.HasSuffix(apiContributionName, ".api.contribution") {
				ctx.AddDependency(ctx.Module(), metalavaCurrentApiTimestampTag, strings.TrimSuffix(apiContributionName, ".api.contribution"))
			} else {
				ctx.ModuleErrorf("Validation is enabled for module %s but a "+
					"current timestamp provider is not found for the api "+
					"contribution %s",
					ctx.ModuleName(),
					apiContributionName,
				)
			}
		}
	}
	if ctx.Device() {
		sdkDep := decodeSdkDep(ctx, android.SdkContext(al))
		if sdkDep.useModule {
			ctx.AddVariationDependencies(nil, systemModulesTag, sdkDep.systemModules)
			ctx.AddVariationDependencies(nil, libTag, sdkDep.classpath...)
			ctx.AddVariationDependencies(nil, bootClasspathTag, sdkDep.bootclasspath...)

		}
	}
	ctx.AddVariationDependencies(nil, libTag, al.properties.Libs.GetOrDefault(ctx, nil)...)
	ctx.AddVariationDependencies(nil, staticLibTag, al.properties.Static_libs.GetOrDefault(ctx, nil)...)

	for _, aconfigDeclarationsName := range al.properties.Aconfig_declarations {
		ctx.AddDependency(ctx.Module(), aconfigDeclarationTag, aconfigDeclarationsName)
	}

	al.EmbeddableSdkLibraryComponent.setComponentDependencyInfoProvider(ctx)
}

// Map where key is the api scope name and value is the int value
// representing the order of the api scope, narrowest to the widest
var scopeOrderMap = AllApiScopes.MapToIndex(
	func(s *apiScope) string { return s.name })

// Add some extra entries into scopeOrderMap for some special api surface names needed by libcore,
// external/conscrypt and external/icu and java/core-libraries.
func init() {
	count := len(scopeOrderMap)
	scopeOrderMap["core"] = count + 1
	scopeOrderMap["core-platform"] = count + 2
	scopeOrderMap["intra-core"] = count + 3
	scopeOrderMap["core-platform-plus-public"] = count + 4
	scopeOrderMap["core-platform-legacy"] = count + 5
}

func (al *ApiLibrary) sortApiFilesByApiScope(ctx android.ModuleContext, srcFilesInfo []JavaApiImportInfo) []JavaApiImportInfo {
	for _, srcFileInfo := range srcFilesInfo {
		if srcFileInfo.ApiSurface == "" {
			ctx.ModuleErrorf("Api surface not defined for the associated api file %s", srcFileInfo.ApiFile)
		}
	}
	sort.Slice(srcFilesInfo, func(i, j int) bool {
		return scopeOrderMap[srcFilesInfo[i].ApiSurface] < scopeOrderMap[srcFilesInfo[j].ApiSurface]
	})

	return srcFilesInfo
}

var validstubsType = []StubsType{Everything, Runtime, Exportable}

func (al *ApiLibrary) validateProperties(ctx android.ModuleContext) {
	if al.properties.Stubs_type == nil {
		ctx.ModuleErrorf("java_api_library module type must specify stubs_type property.")
	} else {
		al.stubsType = StringToStubsType(proptools.String(al.properties.Stubs_type))
	}

	if !android.InList(al.stubsType, validstubsType) {
		ctx.PropertyErrorf("stubs_type", "%s is not a valid stubs_type property value. "+
			"Must be one of %s.", proptools.String(al.properties.Stubs_type), validstubsType)
	}
}

func (al *ApiLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	al.validateProperties(ctx)

	al.kSnapshotFiles = make(map[string]android.Path)

	rule := android.NewRuleBuilder(pctx, ctx)

	rule.Sbox(android.PathForModuleOut(ctx, "metalava"),
		android.PathForModuleOut(ctx, "metalava.sbox.textproto")).
		SandboxInputs()

	stubsDir := android.OptionalPathForPath(android.PathForModuleOut(ctx, "metalava", "stubsDir"))
	rule.Command().Text("rm -rf").Text(stubsDir.String())
	rule.Command().Text("mkdir -p").Text(stubsDir.String())

	homeDir := android.PathForModuleOut(ctx, "metalava", "home")

	var srcFilesInfo []JavaApiImportInfo
	var classPaths android.Paths
	var bootclassPaths android.Paths
	var staticLibs android.Paths
	var systemModulesPaths android.Paths
	ctx.VisitDirectDepsProxy(func(dep android.ModuleProxy) {
		tag := ctx.OtherModuleDependencyTag(dep)
		switch tag {
		case javaApiContributionTag:
			provider, _ := android.OtherModuleProvider(ctx, dep, JavaApiImportProvider)
			if provider.ApiFile == nil && !ctx.Config().AllowMissingDependencies() {
				ctx.ModuleErrorf("Error: %s has an empty api file.", dep.Name())
			}
			srcFilesInfo = append(srcFilesInfo, provider)
		case libTag:
			if provider, ok := android.OtherModuleProvider(ctx, dep, JavaInfoProvider); ok {
				classPaths = append(classPaths, provider.HeaderJars...)
				al.aconfigProtoFiles = append(al.aconfigProtoFiles, provider.AconfigIntermediateCacheOutputPaths...)
				maps.Copy(al.kSnapshotFiles, provider.KSnapshotFiles)
			}
		case bootClasspathTag:
			if provider, ok := android.OtherModuleProvider(ctx, dep, JavaInfoProvider); ok {
				bootclassPaths = append(bootclassPaths, provider.HeaderJars...)
				al.aconfigProtoFiles = append(al.aconfigProtoFiles, provider.AconfigIntermediateCacheOutputPaths...)
				maps.Copy(al.kSnapshotFiles, provider.KSnapshotFiles)
			}
		case staticLibTag:
			if provider, ok := android.OtherModuleProvider(ctx, dep, JavaInfoProvider); ok {
				staticLibs = append(staticLibs, provider.HeaderJars...)
				al.aconfigProtoFiles = append(al.aconfigProtoFiles, provider.AconfigIntermediateCacheOutputPaths...)
				maps.Copy(al.kSnapshotFiles, provider.KSnapshotFiles)
			}
		case systemModulesTag:
			if sm, ok := android.OtherModuleProvider(ctx, dep, SystemModulesProvider); ok {
				systemModulesPaths = append(systemModulesPaths, sm.HeaderJars...)
			}
		case metalavaCurrentApiTimestampTag:
			if currentApiTimestampProvider, ok := android.OtherModuleProvider(ctx, dep, DroidStubsInfoProvider); ok {
				al.validationPaths = append(al.validationPaths, currentApiTimestampProvider.CurrentApiTimestamp)
			}
		case aconfigDeclarationTag:
			if provider, ok := android.OtherModuleProvider(ctx, dep, android.AconfigDeclarationsProviderKey); ok {
				al.aconfigProtoFiles = append(al.aconfigProtoFiles, provider.IntermediateCacheOutputPath)
			} else if provider, ok := android.OtherModuleProvider(ctx, dep, android.CodegenInfoProvider); ok {
				al.aconfigProtoFiles = append(al.aconfigProtoFiles, provider.IntermediateCacheOutputPaths...)
			} else {
				ctx.ModuleErrorf("Only aconfig_declarations and aconfig_declarations_group "+
					"module type is allowed for flags_packages property, but %s is neither "+
					"of these supported module types",
					dep.Name(),
				)
			}
		}
	})

	srcFilesInfo = al.sortApiFilesByApiScope(ctx, srcFilesInfo)
	var srcFiles android.Paths
	for _, srcFileInfo := range srcFilesInfo {
		srcFiles = append(srcFiles, android.PathForSource(ctx, srcFileInfo.ApiFile.String()))
	}

	if srcFiles == nil && !ctx.Config().AllowMissingDependencies() {
		ctx.ModuleErrorf("Error: %s has an empty api file.", ctx.ModuleName())
	}

	configFiles := android.PathsForModuleSrc(ctx, al.properties.ConfigFiles)

	combinedPaths := append(([]android.Path)(nil), systemModulesPaths...)
	combinedPaths = append(combinedPaths, classPaths...)
	combinedPaths = append(combinedPaths, bootclassPaths...)
	cmd := metalavaStubCmd(ctx, rule, srcFiles, homeDir, combinedPaths, configFiles, al.properties.Api_surface)

	al.stubsFlags(ctx, cmd, stubsDir)

	previousApi := String(al.properties.Previous_api)
	if previousApi != "" {
		previousApiFiles := android.PathsForModuleSrc(ctx, []string{previousApi})
		cmd.FlagForEachInput("--migrate-nullness ", previousApiFiles)
	}

	// While we could use SOONG_USE_PARTIAL_COMPILE in the validation's rule, that would unduly
	// complicate the code for minimal benefit.  Instead, add a phony
	// target to let the developer manually run the validation if they so
	// desire.
	ctx.Phony("stub-validation", al.validationPaths...)
	ctx.Phony(ctx.ModuleName()+"-stub-validation", al.validationPaths...)
	if !ctx.Config().PartialCompileFlags().Disable_stub_validation {
		al.addValidation(ctx, cmd, al.validationPaths)
	}

	generateRevertAnnotationArgs(ctx, cmd, al.stubsType, al.aconfigProtoFiles)

	al.stubsSrcJar = android.PathForModuleOut(ctx, "metalava", ctx.ModuleName()+"-"+"stubs.srcjar")
	al.stubsJarWithoutStaticLibs = android.PathForModuleOut(ctx, "metalava", "stubs.jar")
	al.stubsJar = android.PathForModuleOut(ctx, ctx.ModuleName(), fmt.Sprintf("%s.jar", ctx.ModuleName()))

	rule.Command().
		BuiltTool("soong_zip").
		Flag("-write_if_changed").
		Flag("-jar").
		FlagWithOutput("-o ", al.stubsSrcJar).
		FlagWithArg("-C ", stubsDir.String()).
		FlagWithArg("-D ", stubsDir.String())

	rule.Build("metalava", "metalava merged text")

	javacFlags := javaBuilderFlags{
		javaVersion:   getStubsJavaVersion(),
		javacFlags:    strings.Join(al.properties.Javacflags, " "),
		classpath:     classpath(classPaths),
		bootClasspath: classpath(append(systemModulesPaths, bootclassPaths...)),
	}

	annoSrcJar := android.PathForModuleOut(ctx, ctx.ModuleName(), "anno.srcjar")

	TransformJavaToClasses(ctx, al.stubsJarWithoutStaticLibs, 0, android.Paths{},
		android.Paths{al.stubsSrcJar}, annoSrcJar, javacFlags, android.Paths{}, nil)

	builder := android.NewRuleBuilder(pctx, ctx)
	builder.Command().
		BuiltTool("merge_zips").
		Output(al.stubsJar).
		Inputs(android.Paths{al.stubsJarWithoutStaticLibs}).
		Inputs(staticLibs)
	builder.Build("merge_zips", "merge jar files")
	al.addKSnapshot(ctx, al.stubsJar)

	// compile stubs to .dex for hiddenapi processing
	dexParams := &compileDexParams{
		flags:         javacFlags,
		sdkVersion:    al.SdkVersion(ctx),
		minSdkVersion: al.MinSdkVersion(ctx),
		classesJar:    al.stubsJar,
		jarName:       ctx.ModuleName() + ".jar",
	}
	dexOutputFile, _ := al.dexer.compileDex(ctx, dexParams)
	uncompressed := true
	al.initHiddenAPI(ctx, makeDexJarPathFromPath(dexOutputFile), al.stubsJar, &uncompressed)
	dexOutputFile = al.hiddenAPIEncodeDex(ctx, dexOutputFile)
	al.dexJarFile = makeDexJarPathFromPath(dexOutputFile)

	ctx.Phony(ctx.ModuleName(), al.stubsJar)

	al.addKSnapshot(ctx, al.stubsJar)

	javaInfo := &JavaInfo{
		HeaderJars:                             android.PathsIfNonNil(al.stubsJar),
		LocalHeaderJars:                        android.PathsIfNonNil(al.stubsJar),
		TransitiveStaticLibsHeaderJars:         depset.New(depset.PREORDER, android.PathsIfNonNil(al.stubsJar), nil),
		TransitiveStaticLibsImplementationJars: depset.New(depset.PREORDER, android.PathsIfNonNil(al.stubsJar), nil),
		ImplementationAndResourcesJars:         android.PathsIfNonNil(al.stubsJar),
		ImplementationJars:                     android.PathsIfNonNil(al.stubsJar),
		AidlIncludeDirs:                        android.Paths{},
		StubsLinkType:                          Stubs,
		// No aconfig libraries on api libraries
	}
	setExtraJavaInfo(ctx, al, javaInfo)
	android.SetProvider(ctx, JavaInfoProvider, javaInfo)

	moduleInfoJSON := ctx.ModuleInfoJSON()
	moduleInfoJSON.Class = []string{"JAVA_LIBRARIES"}
	if al.stubsJar != nil {
		moduleInfoJSON.ClassesJar = []string{al.stubsJar.String()}
	}
	moduleInfoJSON.SystemSharedLibs = []string{"none"}
}

func (al *ApiLibrary) addKSnapshot(ctx android.ModuleContext, jarFile android.Path) {
	if jarFile == nil {
		return
	}
	if _, exists := al.kSnapshotFiles[jarFile.String()]; !exists {
		snapshot := SnapshotJarForKotlin(ctx, jarFile.(android.WritablePath))
		al.kSnapshotFiles[jarFile.String()] = snapshot
	}
}

func (al *ApiLibrary) DexJarBuildPath(ctx android.ModuleErrorfContext) OptionalDexJarPath {
	return al.dexJarFile
}

func (al *ApiLibrary) DexJarInstallPath() android.Path {
	return al.dexJarFile.Path()
}

func (al *ApiLibrary) ClassLoaderContexts() dexpreopt.ClassLoaderContextMap {
	return nil
}

// Most java_api_library constitues the sdk, but there are some java_api_library that
// does not contribute to the api surface. Such modules are allowed to set sdk_version
// other than "none"
func (al *ApiLibrary) SdkVersion(ctx android.ConfigContext) android.SdkSpec {
	return android.SdkSpecFrom(ctx, proptools.String(al.properties.Sdk_version))
}

// java_api_library is always at "current". Return FutureApiLevel
func (al *ApiLibrary) MinSdkVersion(ctx android.MinSdkVersionFromValueContext) android.ApiLevel {
	return al.SdkVersion(ctx).ApiLevel
}

func (al *ApiLibrary) ReplaceMaxSdkVersionPlaceholder(ctx android.EarlyModuleContext) android.ApiLevel {
	return al.SdkVersion(ctx).ApiLevel
}

func (al *ApiLibrary) SystemModules() string {
	return proptools.String(al.properties.System_modules)
}

func (al *ApiLibrary) TargetSdkVersion(ctx android.EarlyModuleContext) android.ApiLevel {
	return al.SdkVersion(ctx).ApiLevel
}

func (al *ApiLibrary) IDEInfo(ctx android.BaseModuleContext, i *android.IdeInfo) {
	i.Deps = append(i.Deps, al.ideDeps(ctx)...)
	i.Libs = append(i.Libs, al.properties.Libs.GetOrDefault(ctx, nil)...)
	i.Static_libs = append(i.Static_libs, al.properties.Static_libs.GetOrDefault(ctx, nil)...)
	i.SrcJars = append(i.SrcJars, al.stubsSrcJar.String())
}

// deps of java_api_library for module_bp_java_deps.json
func (al *ApiLibrary) ideDeps(ctx android.BaseModuleContext) []string {
	ret := []string{}
	ret = append(ret, al.properties.Libs.GetOrDefault(ctx, nil)...)
	ret = append(ret, al.properties.Static_libs.GetOrDefault(ctx, nil)...)
	if proptools.StringDefault(al.properties.System_modules, "none") != "none" {
		ret = append(ret, proptools.String(al.properties.System_modules))
	}
	// Other non java_library dependencies like java_api_contribution are ignored for now.
	return ret
}

// implement the following interfaces for hiddenapi processing
var _ hiddenAPIModule = (*ApiLibrary)(nil)
var _ UsesLibraryDependency = (*ApiLibrary)(nil)
var _ android.SdkContext = (*ApiLibrary)(nil)

// implement the following interface for IDE completion.
var _ android.IDEInfo = (*ApiLibrary)(nil)

//
// Java prebuilts
//

type ImportProperties struct {
	Jars []string `android:"path,arch_variant"`

	// The version of the SDK that the source prebuilt file was built against. Defaults to the
	// current version if not specified.
	Sdk_version *string

	// The minimum version of the SDK that this module supports. Defaults to sdk_version if not
	// specified.
	Min_sdk_version proptools.Configurable[string] `android:"replace_instead_of_append"`

	// The max sdk version placeholder used to replace maxSdkVersion attributes on permission
	// and uses-permission tags in manifest_fixer.
	Replace_max_sdk_version_placeholder *string

	Installable *bool

	// If not empty, classes are restricted to the specified packages and their sub-packages.
	Permitted_packages []string

	// List of shared java libs that this module has dependencies to
	Libs []string

	// List of static java libs that this module has dependencies to
	Static_libs proptools.Configurable[[]string]

	// List of files to remove from the jar file(s)
	Exclude_files []string

	// List of directories to remove from the jar file(s)
	Exclude_dirs []string

	// if set to true, run Jetifier against .jar file. Defaults to false.
	Jetifier *bool

	// set the name of the output
	Stem *string

	Aidl struct {
		// directories that should be added as include directories for any aidl sources of modules
		// that depend on this module, as well as to aidl for this module.
		Export_include_dirs []string
	}

	// Name of the source soong module that gets shadowed by this prebuilt
	// If unspecified, follows the naming convention that the source module of
	// the prebuilt is Name() without "prebuilt_" prefix
	Source_module_name *string

	// Non-nil if this java_import module was dynamically created by a java_sdk_library_import
	// The name is the undecorated name of the java_sdk_library as it appears in the blueprint file
	// (without any prebuilt_ prefix)
	Created_by_java_sdk_library_name *string `blueprint:"mutated"`

	// Property signifying whether the module provides stubs jar or not.
	Is_stubs_module *bool
}

type Import struct {
	android.ModuleBase
	android.DefaultableModuleBase
	android.ApexModuleBase
	prebuilt android.Prebuilt

	// Functionality common to Module and Import.
	embeddableInModuleAndImport

	hiddenAPI
	dexer
	dexpreopter

	properties ImportProperties

	// output file containing classes.dex and resources
	dexJarFile        OptionalDexJarPath
	dexJarFileErr     error
	dexJarInstallFile android.Path

	combinedImplementationFile android.Path
	combinedHeaderFile         android.Path
	classLoaderContexts        dexpreopt.ClassLoaderContextMap
	exportAidlIncludeDirs      android.Paths

	kSnapshotFiles map[string]android.Path

	sdkVersion    android.SdkSpec
	minSdkVersion android.ApiLevel

	stubsLinkType StubsLinkType
}

var _ PermittedPackagesForUpdatableBootJars = (*Import)(nil)

func (j *Import) PermittedPackagesForUpdatableBootJars() []string {
	return j.properties.Permitted_packages
}

func (j *Import) SdkVersion(ctx android.ConfigContext) android.SdkSpec {
	return android.SdkSpecFrom(ctx, String(j.properties.Sdk_version))
}

func (j *Import) SystemModules() string {
	return "none"
}

func (j *Import) MinSdkVersion(ctx android.MinSdkVersionFromValueContext) android.ApiLevel {
	minSdkVersion := j.properties.Min_sdk_version.Get(j.ConfigurableEvaluator(ctx))
	if minSdkVersion.IsPresent() {
		return android.ApiLevelFrom(ctx, minSdkVersion.Get())
	}
	return j.SdkVersion(ctx).ApiLevel
}

func (j *Import) ReplaceMaxSdkVersionPlaceholder(ctx android.EarlyModuleContext) android.ApiLevel {
	if j.properties.Replace_max_sdk_version_placeholder != nil {
		return android.ApiLevelFrom(ctx, *j.properties.Replace_max_sdk_version_placeholder)
	}
	// Default is PrivateApiLevel
	return android.SdkSpecPrivate.ApiLevel
}

func (j *Import) TargetSdkVersion(ctx android.EarlyModuleContext) android.ApiLevel {
	return j.SdkVersion(ctx).ApiLevel
}

func (j *Import) Prebuilt() *android.Prebuilt {
	return &j.prebuilt
}

func (j *Import) PrebuiltSrcs() []string {
	return j.properties.Jars
}

func (j *Import) BaseModuleName() string {
	return proptools.StringDefault(j.properties.Source_module_name, j.ModuleBase.Name())
}

func (j *Import) Name() string {
	return j.prebuilt.Name(j.ModuleBase.Name())
}

func (j *Import) Stem() string {
	return proptools.StringDefault(j.properties.Stem, j.BaseModuleName())
}

func (j *Import) CreatedByJavaSdkLibraryName() *string {
	return j.properties.Created_by_java_sdk_library_name
}

func (j *Import) DepsMutator(ctx android.BottomUpMutatorContext) {
	ctx.AddVariationDependencies(nil, libTag, j.properties.Libs...)
	ctx.AddVariationDependencies(nil, staticLibTag, j.properties.Static_libs.GetOrDefault(ctx, nil)...)

	if ctx.Device() && Bool(j.dexProperties.Compile_dex) {
		sdkDeps(ctx, android.SdkContext(j), j.dexer)
	}

	j.EmbeddableSdkLibraryComponent.setComponentDependencyInfoProvider(ctx)
}

func (j *Import) commonBuildActions(ctx android.ModuleContext) {
	j.sdkVersion = j.SdkVersion(ctx)
	j.minSdkVersion = j.MinSdkVersion(ctx)

	apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)
	if !apexInfo.IsForPlatform() {
		j.HideFromMake()
	}

	if ctx.Windows() {
		j.HideFromMake()
	}

	if proptools.Bool(j.properties.Is_stubs_module) {
		j.stubsLinkType = Stubs
	} else {
		j.stubsLinkType = Implementation
	}
}

func (j *Import) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	j.commonBuildActions(ctx)

	j.classLoaderContexts = make(dexpreopt.ClassLoaderContextMap)

	j.kSnapshotFiles = make(map[string]android.Path)

	var flags javaBuilderFlags

	var transitiveClasspathHeaderJars []depset.DepSet[android.Path]
	var transitiveBootClasspathHeaderJars []depset.DepSet[android.Path]
	var transitiveStaticLibsHeaderJars []depset.DepSet[android.Path]
	var transitiveStaticLibsImplementationJars []depset.DepSet[android.Path]
	var transitiveStaticLibsResourceJars []depset.DepSet[android.Path]

	j.collectTransitiveHeaderJarsForR8(ctx)
	var staticJars android.Paths
	var staticResourceJars android.Paths
	var staticHeaderJars android.Paths
	ctx.VisitDirectDepsProxy(func(module android.ModuleProxy) {
		tag := ctx.OtherModuleDependencyTag(module)
		if dep, ok := android.OtherModuleProvider(ctx, module, JavaInfoProvider); ok {
			maps.Copy(j.kSnapshotFiles, dep.KSnapshotFiles)
			switch tag {
			case libTag, sdkLibTag:
				flags.classpath = append(flags.classpath, dep.HeaderJars...)
				flags.dexClasspath = append(flags.dexClasspath, dep.HeaderJars...)
				transitiveClasspathHeaderJars = append(transitiveClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
			case staticLibTag:
				flags.classpath = append(flags.classpath, dep.HeaderJars...)
				staticJars = append(staticJars, dep.ImplementationJars...)
				staticResourceJars = append(staticResourceJars, dep.ResourceJars...)
				staticHeaderJars = append(staticHeaderJars, dep.HeaderJars...)
				transitiveClasspathHeaderJars = append(transitiveClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
				transitiveStaticLibsHeaderJars = append(transitiveStaticLibsHeaderJars, dep.TransitiveStaticLibsHeaderJars)
				transitiveStaticLibsImplementationJars = append(transitiveStaticLibsImplementationJars, dep.TransitiveStaticLibsImplementationJars)
				transitiveStaticLibsResourceJars = append(transitiveStaticLibsResourceJars, dep.TransitiveStaticLibsResourceJars)
			case bootClasspathTag:
				flags.bootClasspath = append(flags.bootClasspath, dep.HeaderJars...)
				transitiveBootClasspathHeaderJars = append(transitiveBootClasspathHeaderJars, dep.TransitiveStaticLibsHeaderJars)
			}
		} else if _, ok := android.OtherModuleProvider(ctx, module, SdkLibraryInfoProvider); ok {
			switch tag {
			case libTag, sdkLibTag:
				sdkInfo, _ := android.OtherModuleProvider(ctx, module, SdkLibraryInfoProvider)
				generatingLibsString := android.PrettyConcat(
					getGeneratingLibs(ctx, j.SdkVersion(ctx), module.Name(), sdkInfo), true, "or")
				ctx.ModuleErrorf("cannot depend directly on java_sdk_library %q; try depending on %s instead", module.Name(), generatingLibsString)
			}
		}

		addCLCFromDep(ctx, module, j.classLoaderContexts)
	})

	localJars := android.PathsForModuleSrc(ctx, j.properties.Jars)
	jarName := j.Stem() + ".jar"

	// Combine only the local jars together for use in transitive classpaths.
	// Always pass input jar through TransformJarsToJar to strip module-info.class from prebuilts.
	localCombinedHeaderJar := android.PathForModuleOut(ctx, "local-combined", jarName)
	TransformJarsToJar(ctx, localCombinedHeaderJar, "combine local prebuilt implementation jars", localJars, android.OptionalPath{},
		false, j.properties.Exclude_files, j.properties.Exclude_dirs)
	localStrippedJars := android.Paths{localCombinedHeaderJar}

	j.addKSnapshot(ctx, localCombinedHeaderJar)

	completeStaticLibsHeaderJars := depset.New(depset.PREORDER, localStrippedJars, transitiveStaticLibsHeaderJars)
	completeStaticLibsImplementationJars := depset.New(depset.PREORDER, localStrippedJars, transitiveStaticLibsImplementationJars)
	completeStaticLibsResourceJars := depset.New(depset.PREORDER, nil, transitiveStaticLibsResourceJars)

	// Always pass the input jars to TransformJarsToJar, even if there is only a single jar, we need the output
	// file of the module to be named jarName.
	var outputFile android.Path
	combinedImplementationJar := android.PathForModuleOut(ctx, "combined", jarName)
	implementationJars := completeStaticLibsImplementationJars.ToList()
	TransformJarsToJar(ctx, combinedImplementationJar, "combine prebuilt implementation jars", implementationJars, android.OptionalPath{},
		false, j.properties.Exclude_files, j.properties.Exclude_dirs)
	outputFile = combinedImplementationJar

	// If no dependencies have separate header jars then there is no need to create a separate
	// header jar for this module.
	reuseImplementationJarAsHeaderJar := slices.Equal(staticJars, staticHeaderJars)

	var resourceJarFile android.Path
	if len(staticResourceJars) > 1 {
		combinedJar := android.PathForModuleOut(ctx, "res-combined", jarName)
		TransformJarsToJar(ctx, combinedJar, "for resources", staticResourceJars, android.OptionalPath{},
			false, nil, nil)
		resourceJarFile = combinedJar
	} else if len(staticResourceJars) == 1 {
		resourceJarFile = staticResourceJars[0]
	}

	var headerJar android.Path
	if reuseImplementationJarAsHeaderJar {
		headerJar = outputFile
	} else {
		headerJars := completeStaticLibsHeaderJars.ToList()
		headerOutputFile := android.PathForModuleOut(ctx, "turbine-combined", jarName)
		TransformJarsToJar(ctx, headerOutputFile, "combine prebuilt header jars", headerJars, android.OptionalPath{},
			false, j.properties.Exclude_files, j.properties.Exclude_dirs)
		headerJar = headerOutputFile
	}

	if Bool(j.properties.Jetifier) {
		jetifierOutputFile := android.PathForModuleOut(ctx, "jetifier", jarName)
		TransformJetifier(ctx, jetifierOutputFile, outputFile)
		outputFile = jetifierOutputFile
		j.addKSnapshot(ctx, jetifierOutputFile)
		if !reuseImplementationJarAsHeaderJar {
			jetifierHeaderJar := android.PathForModuleOut(ctx, "jetifier-headers", jarName)
			TransformJetifier(ctx, jetifierHeaderJar, headerJar)
			headerJar = jetifierHeaderJar
			j.addKSnapshot(ctx, jetifierHeaderJar)
		} else {
			headerJar = outputFile
		}

		// Enabling jetifier requires modifying classes from transitive dependencies, disable transitive
		// classpath and use the combined header jar instead.
		completeStaticLibsHeaderJars = depset.New(depset.PREORDER, android.Paths{headerJar}, nil)
		completeStaticLibsImplementationJars = depset.New(depset.PREORDER, android.Paths{outputFile}, nil)
	}

	implementationJarFile := outputFile

	// merge implementation jar with resources if necessary
	if resourceJarFile != nil {
		jars := android.Paths{resourceJarFile, outputFile}
		combinedJar := android.PathForModuleOut(ctx, "withres", jarName)
		TransformJarsToJar(ctx, combinedJar, "for resources", jars, android.OptionalPath{},
			false, nil, nil)
		outputFile = combinedJar
	}

	proguardFlags := android.PathForModuleOut(ctx, "proguard_flags")
	TransformJarToR8Rules(ctx, proguardFlags, outputFile)

	transitiveProguardFlags, transitiveUnconditionalExportedFlags := collectDepProguardSpecInfo(ctx)
	android.SetProvider(ctx, ProguardSpecInfoProvider, ProguardSpecInfo{
		ProguardFlagsFiles: depset.New[android.Path](
			depset.POSTORDER,
			android.Paths{proguardFlags},
			transitiveProguardFlags,
		),
		UnconditionallyExportedProguardFlags: depset.New[android.Path](
			depset.POSTORDER,
			nil,
			transitiveUnconditionalExportedFlags,
		),
	})

	// Save the output file with no relative path so that it doesn't end up in a subdirectory when used as a resource.
	// Also strip the relative path from the header output file so that the reuseImplementationJarAsHeaderJar check
	// in a module that depends on this module considers them equal.
	j.combinedHeaderFile = headerJar.WithoutRel()
	j.combinedImplementationFile = outputFile.WithoutRel()

	installFile := j.maybeInstall(ctx, jarName, outputFile)

	j.exportAidlIncludeDirs = android.PathsForModuleSrc(ctx, j.properties.Aidl.Export_include_dirs)

	ctx.CheckbuildFile(localJars...)

	if ctx.Device() {
		// Shared libraries deapexed from prebuilt apexes are no longer supported.
		// Set the dexJarBuildPath to a fake path.
		// This allows soong analysis pass, but will be an error during ninja execution if there are
		// any rdeps.
		ai, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)
		if ai.ForPrebuiltApex {
			j.dexJarFile = makeDexJarPathFromPath(android.PathForModuleInstall(ctx, "intentionally_no_longer_supported"))
			j.initHiddenAPI(ctx, j.dexJarFile, outputFile, j.dexProperties.Uncompress_dex)
		} else if Bool(j.dexProperties.Compile_dex) {
			sdkDep := decodeSdkDep(ctx, android.SdkContext(j))
			if sdkDep.invalidVersion {
				ctx.AddMissingDependencies(sdkDep.bootclasspath)
				ctx.AddMissingDependencies(sdkDep.java9Classpath)
			} else if sdkDep.useFiles {
				// sdkDep.jar is actually equivalent to turbine header.jar.
				flags.classpath = append(flags.classpath, sdkDep.jars...)
			}

			// Dex compilation

			j.dexpreopter.installPath = j.dexpreopter.getInstallPath(
				ctx, android.RemoveOptionalPrebuiltPrefix(ctx.ModuleName()), android.PathForModuleInstall(ctx, "framework", jarName))
			setUncompressDex(ctx, &j.dexpreopter, &j.dexer)
			j.dexpreopter.uncompressedDex = *j.dexProperties.Uncompress_dex

			var dexOutputFile android.Path
			dexParams := &compileDexParams{
				flags:         flags,
				sdkVersion:    j.SdkVersion(ctx),
				minSdkVersion: j.MinSdkVersion(ctx),
				classesJar:    outputFile,
				jarName:       jarName,
			}

			dexOutputFile, _ = j.dexer.compileDex(ctx, dexParams)
			if ctx.Failed() {
				return
			}
			ctx.CheckbuildFile(dexOutputFile)

			// Initialize the hiddenapi structure.
			j.initHiddenAPI(ctx, makeDexJarPathFromPath(dexOutputFile), outputFile, j.dexProperties.Uncompress_dex)

			// Encode hidden API flags in dex file.
			dexOutputFile = j.hiddenAPIEncodeDex(ctx, dexOutputFile)

			j.dexJarFile = makeDexJarPathFromPath(dexOutputFile)
			j.dexJarInstallFile = android.PathForModuleInstall(ctx, "framework", jarName)
		}
	}

	javaInfo := &JavaInfo{
		InstallFile:                            installFile,
		HeaderJars:                             android.PathsIfNonNil(j.combinedHeaderFile),
		LocalHeaderJars:                        android.PathsIfNonNil(j.combinedHeaderFile),
		TransitiveLibsHeaderJarsForR8:          j.transitiveLibsHeaderJarsForR8,
		TransitiveStaticLibsHeaderJarsForR8:    j.transitiveStaticLibsHeaderJarsForR8,
		TransitiveStaticLibsHeaderJars:         completeStaticLibsHeaderJars,
		TransitiveStaticLibsImplementationJars: completeStaticLibsImplementationJars,
		TransitiveStaticLibsResourceJars:       completeStaticLibsResourceJars,
		ImplementationAndResourcesJars:         android.PathsIfNonNil(j.combinedImplementationFile),
		ImplementationJars:                     android.PathsIfNonNil(implementationJarFile.WithoutRel()),
		ResourceJars:                           android.PathsIfNonNil(resourceJarFile),
		AidlIncludeDirs:                        j.exportAidlIncludeDirs,
		StubsLinkType:                          j.stubsLinkType,
		KSnapshotFiles:                         j.kSnapshotFiles,
		// TODO(b/289117800): LOCAL_ACONFIG_FILES for prebuilts
	}
	setExtraJavaInfo(ctx, j, javaInfo)
	android.SetProvider(ctx, JavaInfoProvider, javaInfo)

	android.SetProvider(ctx, JavaLibraryInfoProvider, JavaLibraryInfo{
		Prebuilt:          true,
		PermittedPackages: j.properties.Permitted_packages,
	})

	ctx.SetOutputFiles(android.Paths{j.combinedImplementationFile}, "")
	ctx.SetOutputFiles(android.Paths{j.combinedImplementationFile}, ".jar")

	buildComplianceMetadata(ctx)

	moduleInfoJSON := ctx.ModuleInfoJSON()
	moduleInfoJSON.Class = []string{"JAVA_LIBRARIES"}
	if j.combinedImplementationFile != nil {
		moduleInfoJSON.ClassesJar = []string{j.combinedImplementationFile.String()}
	}
	moduleInfoJSON.SystemSharedLibs = []string{"none"}
}

func (j *Import) addKSnapshot(ctx android.ModuleContext, jarFile android.Path) {
	if jarFile == nil {
		return
	}
	if _, exists := j.kSnapshotFiles[jarFile.String()]; !exists {
		snapshot := SnapshotJarForKotlin(ctx, jarFile.(android.WritablePath))
		j.kSnapshotFiles[jarFile.String()] = snapshot
	}
}

func (j *Import) maybeInstall(ctx android.ModuleContext, jarName string, outputFile android.Path) android.Path {
	if !Bool(j.properties.Installable) {
		return nil
	}

	var installDir android.InstallPath
	if ctx.InstallInTestcases() {
		var archDir string
		if !ctx.Host() {
			archDir = ctx.DeviceConfig().DeviceArch()
		}
		installDir = android.PathForModuleInstall(ctx, ctx.ModuleName(), archDir)
	} else {
		installDir = android.PathForModuleInstall(ctx, "framework")
	}
	return ctx.InstallFile(installDir, jarName, outputFile)
}

func (j *Import) HeaderJars() android.Paths {
	return android.PathsIfNonNil(j.combinedHeaderFile)
}

func (j *Import) ImplementationAndResourcesJars() android.Paths {
	return android.PathsIfNonNil(j.combinedImplementationFile)
}

func (j *Import) DexJarBuildPath(ctx android.ModuleErrorfContext) OptionalDexJarPath {
	if j.dexJarFileErr != nil {
		ctx.ModuleErrorf(j.dexJarFileErr.Error())
	}
	return j.dexJarFile
}

func (j *Import) DexJarInstallPath() android.Path {
	return j.dexJarInstallFile
}

func (j *Import) ClassLoaderContexts() dexpreopt.ClassLoaderContextMap {
	return j.classLoaderContexts
}

var _ android.ApexModule = (*Import)(nil)

// Implements android.ApexModule
func (m *Import) GetDepInSameApexChecker() android.DepInSameApexChecker {
	return JavaImportDepInSameApexChecker{}
}

type JavaImportDepInSameApexChecker struct {
	android.BaseDepInSameApexChecker
}

func (m JavaImportDepInSameApexChecker) OutgoingDepIsInSameApex(tag blueprint.DependencyTag) bool {
	return depIsInSameApex(tag)
}

// Implements android.ApexModule
func (j *Import) MinSdkVersionSupported(ctx android.BaseModuleContext) android.ApiLevel {
	sdkVersionSpec := j.SdkVersion(ctx)
	minSdkVersion := j.MinSdkVersion(ctx)

	// If the module is compiling against core (via sdk_version), skip comparison check.
	if sdkVersionSpec.Kind == android.SdkCore {
		return android.MinApiLevel
	}

	return minSdkVersion
}

// requiredFilesFromPrebuiltApexForImport returns information about the files that a java_import or
// java_sdk_library_import with the specified base module name requires to be exported from a
// prebuilt_apex/apex_set.
func requiredFilesFromPrebuiltApexForImport(name string, d *dexpreopter) []string {
	dexJarFileApexRootRelative := ApexRootRelativePathToJavaLib(name)
	// Add the dex implementation jar to the set of exported files.
	files := []string{
		dexJarFileApexRootRelative,
	}
	if BoolDefault(d.importDexpreoptProperties.Dex_preopt.Profile_guided, false) {
		files = append(files, dexJarFileApexRootRelative+".prof")
	}
	return files
}

// ApexRootRelativePathToJavaLib returns the path, relative to the root of the apex's contents, for
// the java library with the specified name.
func ApexRootRelativePathToJavaLib(name string) string {
	return filepath.Join("javalib", name+".jar")
}

var _ android.RequiredFilesFromPrebuiltApex = (*Import)(nil)

func (j *Import) RequiredFilesFromPrebuiltApex(_ android.BaseModuleContext) []string {
	name := j.BaseModuleName()
	return requiredFilesFromPrebuiltApexForImport(name, &j.dexpreopter)
}

func (j *Import) UseProfileGuidedDexpreopt() bool {
	return proptools.Bool(j.importDexpreoptProperties.Dex_preopt.Profile_guided)
}

// Add compile time check for interface implementation
var _ android.IDEInfo = (*Import)(nil)

// Collect information for opening IDE project files in java/jdeps.go.
func (j *Import) IDEInfo(ctx android.BaseModuleContext, dpInfo *android.IdeInfo) {
	dpInfo.Jars = append(dpInfo.Jars, j.combinedImplementationFile.String())
}

var _ android.PrebuiltInterface = (*Import)(nil)

func (j *Import) IsInstallable() bool {
	return Bool(j.properties.Installable)
}

var _ DexpreopterInterface = (*Import)(nil)

// java_import imports one or more `.jar` files into the build graph as if they were built by a java_library module.
//
// By default, a java_import has a single variant that expects a `.jar` file containing `.class` files that were
// compiled against an Android classpath.
//
// Specifying `host_supported: true` will produce two variants, one for use as a dependency of device modules and one
// for host modules.
func ImportFactory() android.Module {
	module := &Import{}

	module.AddProperties(
		&module.properties,
		&module.dexer.dexProperties,
		&module.importDexpreoptProperties,
	)

	module.initModuleAndImport(module)

	module.dexProperties.Optimize.EnabledByDefault = false

	android.InitPrebuiltModule(module, &module.properties.Jars)
	android.InitApexModule(module)
	InitJavaModule(module, android.HostAndDeviceSupported)
	return module
}

// java_import imports one or more `.jar` files into the build graph as if they were built by a java_library_host
// module.
//
// A java_import_host has a single variant that expects a `.jar` file containing `.class` files that were
// compiled against a host bootclasspath.
func ImportFactoryHost() android.Module {
	module := &Import{}

	module.AddProperties(&module.properties)

	android.InitPrebuiltModule(module, &module.properties.Jars)
	android.InitApexModule(module)
	InitJavaModule(module, android.HostSupported)
	return module
}

// dex_import module

type DexImportProperties struct {
	Jars []string `android:"path"`

	// set the name of the output
	Stem *string
}

type DexImport struct {
	android.ModuleBase
	android.DefaultableModuleBase
	android.ApexModuleBase
	prebuilt android.Prebuilt

	properties DexImportProperties

	dexJarFile OptionalDexJarPath

	dexpreopter
}

func (j *DexImport) Prebuilt() *android.Prebuilt {
	return &j.prebuilt
}

func (j *DexImport) PrebuiltSrcs() []string {
	return j.properties.Jars
}

func (j *DexImport) Name() string {
	return j.prebuilt.Name(j.ModuleBase.Name())
}

func (j *DexImport) Stem() string {
	return proptools.StringDefault(j.properties.Stem, j.ModuleBase.Name())
}

func (j *DexImport) IsInstallable() bool {
	return true
}

func (j *DexImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	if len(j.properties.Jars) != 1 {
		ctx.PropertyErrorf("jars", "exactly one jar must be provided")
	}

	apexInfo, _ := android.ModuleProvider(ctx, android.ApexInfoProvider)
	if !apexInfo.IsForPlatform() {
		j.HideFromMake()
	}

	j.dexpreopter.installPath = j.dexpreopter.getInstallPath(
		ctx, android.RemoveOptionalPrebuiltPrefix(ctx.ModuleName()), android.PathForModuleInstall(ctx, "framework", j.Stem()+".jar"))
	j.dexpreopter.uncompressedDex = shouldUncompressDex(ctx, android.RemoveOptionalPrebuiltPrefix(ctx.ModuleName()), &j.dexpreopter)

	inputJar := android.PathForModuleSrc(ctx, j.properties.Jars[0])
	dexOutputFile := android.PathForModuleOut(ctx, ctx.ModuleName()+".jar")

	if j.dexpreopter.uncompressedDex {
		rule := android.NewRuleBuilder(pctx, ctx)

		temporary := android.PathForModuleOut(ctx, ctx.ModuleName()+".jar.unaligned")
		rule.Temporary(temporary)

		// use zip2zip to uncompress classes*.dex files
		rule.Command().
			BuiltTool("zip2zip").
			FlagWithInput("-i ", inputJar).
			FlagWithOutput("-o ", temporary).
			FlagWithArg("-0 ", "'classes*.dex'")

		// use zipalign to align uncompressed classes*.dex files
		rule.Command().
			BuiltTool("zipalign").
			Flag("-f").
			Text("4").
			Input(temporary).
			Output(dexOutputFile)

		rule.DeleteTemporaryFiles()

		rule.Build("uncompress_dex", "uncompress dex")
	} else {
		ctx.Build(pctx, android.BuildParams{
			Rule:   android.Cp,
			Input:  inputJar,
			Output: dexOutputFile,
		})
	}

	j.dexJarFile = makeDexJarPathFromPath(dexOutputFile)

	j.dexpreopt(ctx, android.RemoveOptionalPrebuiltPrefix(ctx.ModuleName()), dexOutputFile)

	if apexInfo.IsForPlatform() {
		ctx.InstallFile(android.PathForModuleInstall(ctx, "framework"),
			j.Stem()+".jar", dexOutputFile)
	}

	javaInfo := &JavaInfo{}
	setExtraJavaInfo(ctx, j, javaInfo)
	javaInfo.BootDexJarPath = j.dexJarFile

	android.SetProvider(ctx, JavaInfoProvider, javaInfo)

	android.SetProvider(ctx, JavaDexImportInfoProvider, JavaDexImportInfo{})
}

func (j *DexImport) DexJarBuildPath(ctx android.ModuleErrorfContext) OptionalDexJarPath {
	return j.dexJarFile
}

var _ android.ApexModule = (*DexImport)(nil)

// Implements android.ApexModule
func (m *DexImport) MinSdkVersionSupported(ctx android.BaseModuleContext) android.ApiLevel {
	return android.MinApiLevel
}

// dex_import imports a `.jar` file containing classes.dex files.
//
// A dex_import module cannot be used as a dependency of a java_* or android_* module, it can only be installed
// to the device.
func DexImportFactory() android.Module {
	module := &DexImport{}

	module.AddProperties(&module.properties)

	android.InitPrebuiltModule(module, &module.properties.Jars)
	android.InitApexModule(module)
	InitJavaModule(module, android.DeviceSupported)
	return module
}

// Defaults
type Defaults struct {
	android.ModuleBase
	android.DefaultsModuleBase
}

// java_defaults provides a set of properties that can be inherited by other java or android modules.
//
// A module can use the properties from a java_defaults module using `defaults: ["defaults_module_name"]`.  Each
// property in the defaults module that exists in the depending module will be prepended to the depending module's
// value for that property.
//
// Example:
//
//	java_defaults {
//	    name: "example_defaults",
//	    srcs: ["common/**/*.java"],
//	    javacflags: ["-Xlint:all"],
//	    aaptflags: ["--auto-add-overlay"],
//	}
//
//	java_library {
//	    name: "example",
//	    defaults: ["example_defaults"],
//	    srcs: ["example/**/*.java"],
//	}
//
// is functionally identical to:
//
//	java_library {
//	    name: "example",
//	    srcs: [
//	        "common/**/*.java",
//	        "example/**/*.java",
//	    ],
//	    javacflags: ["-Xlint:all"],
//	}
func DefaultsFactory() android.Module {
	module := &Defaults{}

	module.AddProperties(
		&CommonProperties{},
		&DeviceProperties{},
		&OverridableProperties{},
		&DexProperties{},
		&DexpreoptProperties{},
		&android.ProtoProperties{},
		&aaptProperties{},
		&androidLibraryProperties{},
		&appProperties{},
		&appTestProperties{},
		&overridableAppProperties{},
		&hostTestProperties{},
		&testProperties{},
		&ImportProperties{},
		&AARImportProperties{},
		&sdkLibraryProperties{},
		&commonToSdkLibraryAndImportProperties{},
		&DexImportProperties{},
		&android.ApexProperties{},
		&RuntimeResourceOverlayProperties{},
		&LintProperties{},
		&appTestHelperAppProperties{},
		&JavaApiLibraryProperties{},
		&bootclasspathFragmentProperties{},
		&SourceOnlyBootclasspathProperties{},
		&ravenwoodTestProperties{},
		&AndroidAppImportProperties{},
		&UsesLibraryProperties{},
	)

	android.InitDefaultsModule(module)
	return module
}

func kytheExtractJavaFactory() android.Singleton {
	return &kytheExtractJavaSingleton{}
}

type kytheExtractJavaSingleton struct {
}

func (ks *kytheExtractJavaSingleton) GenerateBuildActions(ctx android.SingletonContext) {
	var xrefTargets android.Paths
	var xrefKotlinTargets android.Paths
	ctx.VisitAllModuleProxies(func(module android.ModuleProxy) {
		if javaInfo, ok := android.OtherModuleProvider(ctx, module, JavaInfoProvider); ok {
			xrefTargets = append(xrefTargets, javaInfo.XrefJavaFiles...)
			xrefKotlinTargets = append(xrefKotlinTargets, javaInfo.XrefKotlinFiles...)
		}
	})
	// TODO(asmundak): perhaps emit a rule to output a warning if there were no xrefTargets
	if len(xrefTargets) > 0 {
		ctx.Phony("xref_java", xrefTargets...)
	}
	if len(xrefKotlinTargets) > 0 {
		ctx.Phony("xref_kotlin", xrefKotlinTargets...)
	}
}

var Bool = proptools.Bool
var BoolDefault = proptools.BoolDefault
var String = proptools.String
var inList = android.InList[string]

// Add class loader context (CLC) of a given dependency to the current CLC.
func addCLCFromDep(ctx android.ModuleContext, depModule android.ModuleProxy,
	clcMap dexpreopt.ClassLoaderContextMap) {

	dep, ok := android.OtherModuleProvider(ctx, depModule, JavaInfoProvider)
	if !ok || dep.UsesLibraryDependencyInfo == nil {
		return
	}

	depName := android.RemoveOptionalPrebuiltPrefix(ctx.OtherModuleName(depModule))

	var sdkLib *string
	if lib, ok := android.OtherModuleProvider(ctx, depModule, SdkLibraryInfoProvider); ok && lib.SharedLibrary {
		// A shared SDK library. This should be added as a top-level CLC element.
		sdkLib = &depName
	} else if lib, ok := android.OtherModuleProvider(ctx, depModule, SdkLibraryComponentDependencyInfoProvider); ok && lib.OptionalSdkLibraryImplementation != nil {
		if depModule.Name() == proptools.String(lib.OptionalSdkLibraryImplementation)+".impl" {
			sdkLib = lib.OptionalSdkLibraryImplementation
		}
	} else if ulib := dep.ProvidesUsesLibInfo; ulib != nil {
		// A non-SDK library disguised as an SDK library by the means of `provides_uses_lib`
		// property. This should be handled in the same way as a shared SDK library.
		sdkLib = ulib.ProvidesUsesLib
	}

	depTag := ctx.OtherModuleDependencyTag(depModule)
	if IsLibDepTag(depTag) {
		// Ok, propagate <uses-library> through non-static library dependencies.
	} else if tag, ok := depTag.(usesLibraryDependencyTag); ok && tag.sdkVersion == dexpreopt.AnySdkVersion {
		// Ok, propagate <uses-library> through non-compatibility <uses-library> dependencies.
	} else if depTag == staticLibTag {
		// Propagate <uses-library> through static library dependencies, unless it is a component
		// library (such as stubs). Component libraries have a dependency on their SDK library,
		// which should not be pulled just because of a static component library.
		if sdkLib != nil {
			return
		}
	} else {
		// Don't propagate <uses-library> for other dependency tags.
		return
	}

	// If this is an SDK (or SDK-like) library, then it should be added as a node in the CLC tree,
	// and its CLC should be added as subtree of that node. Otherwise the library is not a
	// <uses_library> and should not be added to CLC, but the transitive <uses-library> dependencies
	// from its CLC should be added to the current CLC.
	if sdkLib != nil {
		optional := false
		if module, ok := ctx.Module().(ModuleWithUsesLibrary); ok {
			if android.InList(*sdkLib, module.UsesLibrary().usesLibraryProperties.Optional_uses_libs.GetOrDefault(ctx, nil)) {
				optional = true
			}
		}
		clcMap.AddContext(ctx, dexpreopt.AnySdkVersion, *sdkLib, optional,
			dep.DexJarBuildPath.PathOrNil(),
			dep.UsesLibraryDependencyInfo.DexJarInstallPath, dep.UsesLibraryDependencyInfo.GetClassLoaderContexts())
	} else {
		clcMap.AddContextMap(dep.UsesLibraryDependencyInfo.GetClassLoaderContexts(), depName)
	}
}

func addMissingOptionalUsesLibsFromDep(ctx android.ModuleContext, depModule android.ModuleProxy,
	usesLibrary *usesLibrary) {

	dep, ok := android.OtherModuleProvider(ctx, depModule, JavaInfoProvider)
	if !ok {
		return
	}

	for _, lib := range dep.MissingOptionalUsesLibs {
		if !android.InList(lib, usesLibrary.usesLibraryProperties.Missing_optional_uses_libs) {
			usesLibrary.usesLibraryProperties.Missing_optional_uses_libs =
				append(usesLibrary.usesLibraryProperties.Missing_optional_uses_libs, lib)
		}
	}
}

type JavaApiContributionImport struct {
	JavaApiContribution

	prebuilt           android.Prebuilt
	prebuiltProperties javaApiContributionImportProperties
}

type javaApiContributionImportProperties struct {
	// Name of the source soong module that gets shadowed by this prebuilt
	// If unspecified, follows the naming convention that the source module of
	// the prebuilt is Name() without "prebuilt_" prefix
	Source_module_name *string

	// Non-nil if this java_import module was dynamically created by a java_sdk_library_import
	// The name is the undecorated name of the java_sdk_library as it appears in the blueprint file
	// (without any prebuilt_ prefix)
	Created_by_java_sdk_library_name *string `blueprint:"mutated"`
}

func ApiContributionImportFactory() android.Module {
	module := &JavaApiContributionImport{}
	android.InitAndroidModule(module)
	android.InitDefaultableModule(module)
	android.InitPrebuiltModule(module, &[]string{""})
	module.AddProperties(&module.properties, &module.prebuiltProperties)
	module.AddProperties(&module.sdkLibraryComponentProperties)
	return module
}

func (module *JavaApiContributionImport) Prebuilt() *android.Prebuilt {
	return &module.prebuilt
}

func (module *JavaApiContributionImport) Name() string {
	return module.prebuilt.Name(module.ModuleBase.Name())
}

func (j *JavaApiContributionImport) BaseModuleName() string {
	return proptools.StringDefault(j.prebuiltProperties.Source_module_name, j.ModuleBase.Name())
}

func (j *JavaApiContributionImport) CreatedByJavaSdkLibraryName() *string {
	return j.prebuiltProperties.Created_by_java_sdk_library_name
}

func (ap *JavaApiContributionImport) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	ap.JavaApiContribution.GenerateAndroidBuildActions(ctx)
}

func setExtraJavaInfo(ctx android.ModuleContext, module android.Module, javaInfo *JavaInfo) {
	if alDep, ok := module.(AndroidLibraryDependency); ok {
		javaInfo.AndroidLibraryDependencyInfo = &AndroidLibraryDependencyInfo{
			ExportPackage:       alDep.ExportPackage(),
			ResourcesNodeDepSet: alDep.ResourcesNodeDepSet(),
			RRODirsDepSet:       alDep.RRODirsDepSet(),
			ManifestsDepSet:     alDep.ManifestsDepSet(),
		}
	}

	if ulDep, ok := module.(UsesLibraryDependency); ok {
		javaInfo.UsesLibraryDependencyInfo = &UsesLibraryDependencyInfo{
			DexJarInstallPath:   ulDep.DexJarInstallPath(),
			ClassLoaderContexts: ulDep.ClassLoaderContexts(),
		}
	}

	if pul, ok := module.(ProvidesUsesLib); ok {
		javaInfo.ProvidesUsesLibInfo = &ProvidesUsesLibInfo{
			ProvidesUsesLib: pul.ProvidesUsesLib(),
		}
	}

	if mwul, ok := module.(ModuleWithUsesLibrary); ok {
		javaInfo.MissingOptionalUsesLibs = mwul.UsesLibrary().usesLibraryProperties.Missing_optional_uses_libs
	}

	if mwsd, ok := module.(moduleWithSdkDep); ok {
		linkType, stubs := mwsd.getSdkLinkType(ctx, ctx.ModuleName())
		javaInfo.ModuleWithSdkDepInfo = &ModuleWithSdkDepInfo{
			SdkLinkType: linkType,
			Stubs:       stubs,
		}
	}

	if st, ok := module.(ModuleWithStem); ok {
		javaInfo.Stem = st.Stem()
	}

	if mm, ok := module.(interface {
		DexJarBuildPath(ctx android.ModuleErrorfContext) OptionalDexJarPath
	}); ok {
		javaInfo.DexJarBuildPath = mm.DexJarBuildPath(ctx)
	}

	if ham, ok := module.(hiddenAPIModule); ok {
		javaInfo.BootDexJarPath = ham.bootDexJar()
		javaInfo.HiddenapiClassesJarPaths = ham.classesJars()
		javaInfo.UncompressDexState = ham.uncompressDex()
	}

	if mm, ok := module.(interface {
		MaxSdkVersion(ctx android.EarlyModuleContext) android.ApiLevel
	}); ok {
		javaInfo.MaxSdkVersion = mm.MaxSdkVersion(ctx)
	}

	if di, ok := module.(DexpreopterInterface); ok {
		javaInfo.DexpreopterInfo = &DexpreopterInfo{
			OutputProfilePathOnHost:           di.OutputProfilePathOnHost(),
			ApexSystemServerDexpreoptInstalls: di.ApexSystemServerDexpreoptInstalls(),
			ApexSystemServerDexJars:           di.ApexSystemServerDexJars(),
		}
	}

	if xr, ok := module.(xref); ok {
		javaInfo.XrefJavaFiles = xr.XrefJavaFiles()
		javaInfo.XrefKotlinFiles = xr.XrefKotlinFiles()
	}

	if sdk, ok := module.(android.SdkContext); ok {
		javaInfo.SystemModules = sdk.SystemModules()
		javaInfo.SdkVersion = sdk.SdkVersion(ctx)
	}

	if ap, ok := module.(ApexDependency); ok {
		javaInfo.ApexDependencyInfo = &ApexDependencyInfo{
			HeaderJars:                     ap.HeaderJars(),
			ImplementationAndResourcesJars: ap.ImplementationAndResourcesJars(),
		}
	}

	if ksc, ok := module.(KSnapshotContainer); ok {
		if javaInfo.KSnapshotFiles == nil {
			javaInfo.KSnapshotFiles = make(map[string]android.Path)
		}
		maps.Copy(javaInfo.KSnapshotFiles, ksc.JarToSnapshotMap())
	}
}
