From 3e56ddee2478d9bbab8aba83a5665a647454c0b9 Mon Sep 17 00:00:00 2001 From: advitiya0201 Date: Sat, 3 May 2025 17:43:31 +0530 Subject: [PATCH 1/7] Add Java generator Signed-off-by: advitiya0201 --- .gitignore | 4 +- cmd/generate.go | 56 ++++++++++++++++++- cmd/generate_test.go | 27 ++++++++-- cmd/testdata/success_java.golden | 87 ++++++++++++++++++++++++++++++ internal/config/flags.go | 13 +++++ internal/generators/java/java.go | 74 +++++++++++++++++++++++++ internal/generators/java/java.tmpl | 42 +++++++++++++++ 7 files changed, 298 insertions(+), 5 deletions(-) create mode 100644 cmd/testdata/success_java.golden create mode 100644 internal/generators/java/java.go create mode 100644 internal/generators/java/java.tmpl diff --git a/.gitignore b/.gitignore index c3f0380..cef756f 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,6 @@ go.work.sum dist # openfeature cli config -.openfeature.yaml \ No newline at end of file +.openfeature.yaml + +.idea/ \ No newline at end of file diff --git a/cmd/generate.go b/cmd/generate.go index bca65d8..52cdf68 100644 --- a/cmd/generate.go +++ b/cmd/generate.go @@ -8,6 +8,7 @@ import ( "github.com/open-feature/cli/internal/generators" "github.com/open-feature/cli/internal/generators/csharp" "github.com/open-feature/cli/internal/generators/golang" + "github.com/open-feature/cli/internal/generators/java" "github.com/open-feature/cli/internal/generators/nestjs" "github.com/open-feature/cli/internal/generators/nodejs" "github.com/open-feature/cli/internal/generators/python" @@ -257,6 +258,58 @@ func getGenerateCSharpCmd() *cobra.Command { return csharpCmd } +func getGenerateJavaCmd() *cobra.Command { + javaCmd := &cobra.Command{ + Use: "java", + Short: "Generate typesafe Java decorators", + Long: `Generate typesafe Java decorator compatible with the OpenFeature Java SDK.`, + Annotations: map[string]string{ + "stability": string(generators.Alpha), + }, + PreRunE: func(cmd *cobra.Command, args []string) error { + return initializeConfig(cmd, "generate.java") + }, + RunE: func(cmd *cobra.Command, args []string) error { + manifestPath := config.GetManifestPath(cmd) + javaPackageName := config.GetJavaPackageName(cmd) + outputPath := config.GetOutputPath(cmd) + + logger.Default.GenerationStarted("Java") + + params := generators.Params[java.Params]{ + OutputPath: outputPath, + Custom: java.Params{ + JavaPackage: javaPackageName, + }, + } + + flagset, err := flagset.Load(manifestPath) + if err != nil { + return err + } + + generator := java.NewGenerator(flagset) + logger.Default.Debug("Executing Java generator") + err = generator.Generate(¶ms) + if err != nil { + return err + } + + logger.Default.GenerationComplete("Java") + + return nil + + }, + } + + // Add Java specific flags + config.AddJavaGenerateFlags(javaCmd) + + addStabilityInfo(javaCmd) + + return javaCmd +} + func getGenerateGoCmd() *cobra.Command { goCmd := &cobra.Command{ Use: "go", @@ -355,6 +408,7 @@ func init() { generators.DefaultManager.Register(getGenerateGoCmd) generators.DefaultManager.Register(getGenerateNodeJSCmd) generators.DefaultManager.Register(getGeneratePythonCmd) - generators.DefaultManager.Register(getGenerateCSharpCmd) + generators.DefaultManager.Register(getGenerateCSharpCmd) generators.DefaultManager.Register(GetGenerateNestJsCmd) + generators.DefaultManager.Register(getGenerateJavaCmd) } diff --git a/cmd/generate_test.go b/cmd/generate_test.go index b2534d5..f713981 100644 --- a/cmd/generate_test.go +++ b/cmd/generate_test.go @@ -21,7 +21,7 @@ type generateTestCase struct { outputGolden string // path to the golden output file outputPath string // output directory (optional, defaults to "output") outputFile string // output file name - packageName string // optional, used for Go (package-name) and C# (namespace) + packageName string // optional, used for Go (package-name), Java (package-name) and C# (namespace) } func TestGenerate(t *testing.T) { @@ -70,6 +70,14 @@ func TestGenerate(t *testing.T) { outputFile: "OpenFeature.g.cs", packageName: "TestNamespace", // Using packageName field for namespace }, + { + name: "Java generation success", + command: "java", + manifestGolden: "testdata/success_manifest.golden", + outputGolden: "testdata/success_java.golden", + outputFile: "OpenFeature.java", + packageName: "com.openfeature.generated", + }, // Add more test cases here as needed } @@ -107,6 +115,8 @@ func TestGenerate(t *testing.T) { args = append(args, "--namespace", tc.packageName) } else if tc.command == "go" { args = append(args, "--package-name", tc.packageName) + } else if tc.command == "java" { + args = append(args, "--package-name", tc.packageName) } } @@ -146,6 +156,17 @@ func readOsFileAndWriteToMemMap(t *testing.T, inputPath string, memPath string, } } +// normalizeLines trims trailing whitespace and carriage returns from each line. +// This helps ensure consistent comparison by ignoring formatting differences like indentation or line endings. +func normalizeLines(input []string) []string { + normalized := make([]string, len(input)) + for i, line := range input { + // Trim right whitespace and convert \r\n or \r to \n + normalized[i] = strings.TrimRight(line, " \t\r") + } + return normalized +} + func compareOutput(t *testing.T, testFile, memoryOutputPath string, fs afero.Fs) { want, err := os.ReadFile(testFile) if err != nil { @@ -158,8 +179,8 @@ func compareOutput(t *testing.T, testFile, memoryOutputPath string, fs afero.Fs) } // Convert to string arrays by splitting on newlines - wantLines := strings.Split(string(want), "\n") - gotLines := strings.Split(string(got), "\n") + wantLines := normalizeLines(strings.Split(string(want), "\n")) + gotLines := normalizeLines(strings.Split(string(got), "\n")) if diff := cmp.Diff(wantLines, gotLines); diff != "" { t.Errorf("output mismatch (-want +got):\n%s", diff) diff --git a/cmd/testdata/success_java.golden b/cmd/testdata/success_java.golden new file mode 100644 index 0000000..af090e2 --- /dev/null +++ b/cmd/testdata/success_java.golden @@ -0,0 +1,87 @@ +// AUTOMATICALLY GENERATED BY OPENFEATURE CLI, DO NOT EDIT. +package com.openfeature.generated; + +import dev.openfeature.sdk.Client; +import dev.openfeature.sdk.EvaluationContext; +import dev.openfeature.sdk.EvaluationDetails; +import dev.openfeature.sdk.OpenFeature; +import dev.openfeature.sdk.OpenFeatureAPI; + +public class GeneratedClient { + + private final Client client; + + public GeneratedClient(Client client) { + this.client = client; + } + + public static GeneratedClient createClient() { + return new GeneratedClient(OpenFeatureAPI.getInstance().getClient()); + } + + public static GeneratedClient createClient(String domain) { + return new GeneratedClient(OpenFeatureAPI.getInstance().getClient(domain)); + } + + + /** + * Discount percentage applied to purchases. + * Details: + * - Flag key: discountPercentage + * - Type: float + * - Default value: 0.15 + */ + public float discountPercentage(EvaluationContext ctx) { + return client.getFloatValue("discountPercentage", 0.15, ctx); + } + + public EvaluationDetails discountPercentageDetails(EvaluationContext ctx) { + return client.getFloatDetails("discountPercentage", 0.15, ctx); + } + + /** + * Controls whether Feature A is enabled. + * Details: + * - Flag key: enableFeatureA + * - Type: boolean + * - Default value: false + */ + public boolean enableFeatureA(EvaluationContext ctx) { + return client.getBooleanValue("enableFeatureA", false, ctx); + } + + public EvaluationDetails enableFeatureADetails(EvaluationContext ctx) { + return client.getBooleanDetails("enableFeatureA", false, ctx); + } + + /** + * The message to use for greeting users. + * Details: + * - Flag key: greetingMessage + * - Type: String + * - Default value: Hello there! + */ + public String greetingMessage(EvaluationContext ctx) { + return client.getStringValue("greetingMessage", "Hello there!", ctx); + } + + public EvaluationDetails greetingMessageDetails(EvaluationContext ctx) { + return client.getStringDetails("greetingMessage", "Hello there!", ctx); + } + + /** + * Maximum allowed length for usernames. + * Details: + * - Flag key: usernameMaxLength + * - Type: int + * - Default value: 50 + */ + public int usernameMaxLength(EvaluationContext ctx) { + return client.getIntValue("usernameMaxLength", 50, ctx); + } + + public EvaluationDetails usernameMaxLengthDetails(EvaluationContext ctx) { + return client.getIntDetails("usernameMaxLength", 50, ctx); + } + +} diff --git a/internal/config/flags.go b/internal/config/flags.go index 2bb5526..66b02a8 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -13,6 +13,7 @@ const ( GoPackageFlagName = "package-name" CSharpNamespaceName = "namespace" OverrideFlagName = "override" + JavaPackageFlagName = "package-name" ) // Default values for flags @@ -21,6 +22,7 @@ const ( DefaultOutputPath = "" DefaultGoPackageName = "openfeature" DefaultCSharpNamespace = "OpenFeature" + DefaultJavaPackageName = "com.openfeature.generated" ) // AddRootFlags adds the common flags to the given command @@ -45,6 +47,11 @@ func AddCSharpGenerateFlags(cmd *cobra.Command) { cmd.Flags().String(CSharpNamespaceName, DefaultCSharpNamespace, "Namespace for the generated C# code") } +// AddJavaGenerateFlags adds the Java generator specific flags to the given command +func AddJavaGenerateFlags(cmd *cobra.Command) { + cmd.Flags().String(JavaPackageFlagName, DefaultJavaPackageName, "Name of the generated Java package") +} + // AddInitFlags adds the init command specific flags func AddInitFlags(cmd *cobra.Command) { cmd.Flags().Bool(OverrideFlagName, false, "Override an existing configuration") @@ -74,6 +81,12 @@ func GetCSharpNamespace(cmd *cobra.Command) string { return namespace } +// GetJavaPackageName gets the Java package name from the given command +func GetJavaPackageName(cmd *cobra.Command) string { + javaPackageName, _ := cmd.Flags().GetString(JavaPackageFlagName) + return javaPackageName +} + // GetNoInput gets the no-input flag from the given command func GetNoInput(cmd *cobra.Command) bool { noInput, _ := cmd.Flags().GetBool(NoInputFlagName) diff --git a/internal/generators/java/java.go b/internal/generators/java/java.go new file mode 100644 index 0000000..e2692dc --- /dev/null +++ b/internal/generators/java/java.go @@ -0,0 +1,74 @@ +package java + +import ( + _ "embed" + "fmt" + "text/template" + + "github.com/open-feature/cli/internal/flagset" + "github.com/open-feature/cli/internal/generators" +) + +type JavaGenerator struct { + generators.CommonGenerator +} + +type Params struct { + // Add Java parameters here if needed + JavaPackage string +} + +//go:embed java.tmpl +var javaTmpl string + +func openFeatureType(t flagset.FlagType) string { + switch t { + case flagset.IntType: + return "int" + case flagset.FloatType: + return "float" + case flagset.BoolType: + return "boolean" + case flagset.StringType: + return "String" + default: + return "" + } +} + +func formatDefaultValueForJava(flag flagset.Flag) string { + switch flag.Type { + case flagset.StringType: + return fmt.Sprintf("\"%s\"", flag.DefaultValue) + case flagset.BoolType: + if flag.DefaultValue == true { + return "true" + } + return "false" + default: + return fmt.Sprintf("%v", flag.DefaultValue) + } +} + +func (g *JavaGenerator) Generate(params *generators.Params[Params]) error { + funcs := template.FuncMap{ + "OpenFeatureType": openFeatureType, + "FormatDefaultValue": formatDefaultValueForJava, + } + + newParams := &generators.Params[any]{ + OutputPath: params.OutputPath, + Custom: params.Custom, + } + + return g.GenerateFile(funcs, javaTmpl, newParams, "OpenFeature.java") +} + +// NewGenerator creates a generator for Java. +func NewGenerator(fs *flagset.Flagset) *JavaGenerator { + return &JavaGenerator{ + CommonGenerator: *generators.NewGenerator(fs, map[flagset.FlagType]bool{ + flagset.ObjectType: true, + }), + } +} diff --git a/internal/generators/java/java.tmpl b/internal/generators/java/java.tmpl new file mode 100644 index 0000000..1fbd4ff --- /dev/null +++ b/internal/generators/java/java.tmpl @@ -0,0 +1,42 @@ +// AUTOMATICALLY GENERATED BY OPENFEATURE CLI, DO NOT EDIT. +package {{ .Params.Custom.JavaPackage }}; + +import dev.openfeature.sdk.Client; +import dev.openfeature.sdk.EvaluationContext; +import dev.openfeature.sdk.EvaluationDetails; +import dev.openfeature.sdk.OpenFeature; +import dev.openfeature.sdk.OpenFeatureAPI; + +public class GeneratedClient { + + private final Client client; + + public GeneratedClient(Client client) { + this.client = client; + } + + public static GeneratedClient createClient() { + return new GeneratedClient(OpenFeatureAPI.getInstance().getClient()); + } + + public static GeneratedClient createClient(String domain) { + return new GeneratedClient(OpenFeatureAPI.getInstance().getClient(domain)); + } + + {{ range .Flagset.Flags }} + /** + * {{ .Description }} + * Details: + * - Flag key: {{ .Key }} + * - Type: {{ .Type | OpenFeatureType }} + * - Default value: {{ .DefaultValue }} + */ + public {{ .Type | OpenFeatureType }} {{ .Key | ToCamel }}(EvaluationContext ctx) { + return client.get{{ .Type | OpenFeatureType | ToPascal }}Value("{{ .Key }}", {{ . | FormatDefaultValue }}, ctx); + } + + public EvaluationDetails<{{ .Type | OpenFeatureType }}> {{ .Key | ToCamel }}Details(EvaluationContext ctx) { + return client.get{{ .Type | OpenFeatureType | ToPascal }}Details("{{ .Key }}", {{ . | FormatDefaultValue }}, ctx); + } + {{ end }} +} From 5c61918b4655382305101e4b503cc4c098b9ed67 Mon Sep 17 00:00:00 2001 From: advitiya0201 Date: Sat, 3 May 2025 18:01:51 +0530 Subject: [PATCH 2/7] Add Readme file for java Signed-off-by: advitiya0201 --- docs/commands/openfeature_generate.md | 3 +- docs/commands/openfeature_generate_java.md | 37 ++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 docs/commands/openfeature_generate_java.md diff --git a/docs/commands/openfeature_generate.md b/docs/commands/openfeature_generate.md index 7402c7e..ddef81b 100644 --- a/docs/commands/openfeature_generate.md +++ b/docs/commands/openfeature_generate.md @@ -25,11 +25,12 @@ openfeature generate [flags] ### SEE ALSO -* [openfeature](openfeature.md) - CLI for OpenFeature. +* [openfeature](openfeature.md) - CLI for OpenFeature. * [openfeature generate csharp](openfeature_generate_csharp.md) - Generate typesafe C# client. * [openfeature generate go](openfeature_generate_go.md) - Generate typesafe accessors for OpenFeature. * [openfeature generate nestjs](openfeature_generate_nestjs.md) - Generate typesafe NestJS decorators. * [openfeature generate nodejs](openfeature_generate_nodejs.md) - Generate typesafe Node.js client. * [openfeature generate python](openfeature_generate_python.md) - Generate typesafe Python client. * [openfeature generate react](openfeature_generate_react.md) - Generate typesafe React Hooks. +* [openfeature generate java](openfeature_generate_java.md) - Generate typesafe Java client. diff --git a/docs/commands/openfeature_generate_java.md b/docs/commands/openfeature_generate_java.md new file mode 100644 index 0000000..8e665c1 --- /dev/null +++ b/docs/commands/openfeature_generate_java.md @@ -0,0 +1,37 @@ + + +## openfeature generate java + +Generate typesafe Java client. + + +> **Stability**: alpha + +### Synopsis + +Generate typesafe Java client compatible with the OpenFeature Java SDK. + +``` +openfeature generate java [flags] +``` + +### Options + +``` + -h, --help help for java + --package-name string Name of the generated Java package (default "com.openfeature.generated") +``` + +### Options inherited from parent commands + +``` + --debug Enable debug logging + -m, --manifest string Path to the flag manifest (default "flags.json") + --no-input Disable interactive prompts + -o, --output string Path to where the generated files should be saved +``` + +### SEE ALSO + +* [openfeature generate](openfeature_generate.md) - Generate typesafe OpenFeature accessors. + From 7abb0fb4e2d2025516e879beea4da40e0315cd0a Mon Sep 17 00:00:00 2001 From: advitiya0201 Date: Sun, 4 May 2025 17:50:52 +0530 Subject: [PATCH 3/7] Fix command description to show same value as openfeature_generate_java.md Signed-off-by: advitiya0201 --- cmd/generate.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/generate.go b/cmd/generate.go index 52cdf68..e327ce5 100644 --- a/cmd/generate.go +++ b/cmd/generate.go @@ -261,8 +261,8 @@ func getGenerateCSharpCmd() *cobra.Command { func getGenerateJavaCmd() *cobra.Command { javaCmd := &cobra.Command{ Use: "java", - Short: "Generate typesafe Java decorators", - Long: `Generate typesafe Java decorator compatible with the OpenFeature Java SDK.`, + Short: "Generate typesafe Java client.", + Long: `Generate typesafe Java client compatible with the OpenFeature Java SDK.`, Annotations: map[string]string{ "stability": string(generators.Alpha), }, From e98d3eeb96fd8d1f19f198de62e85bd9b8077151 Mon Sep 17 00:00:00 2001 From: advitiya0201 Date: Wed, 7 May 2025 19:12:35 +0530 Subject: [PATCH 4/7] Change package name to com.example.openfeature Signed-off-by: advitiya0201 --- cmd/generate_test.go | 2 +- cmd/testdata/success_java.golden | 2 +- docs/commands/openfeature_generate_java.md | 2 +- internal/config/flags.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/generate_test.go b/cmd/generate_test.go index f713981..559bb88 100644 --- a/cmd/generate_test.go +++ b/cmd/generate_test.go @@ -76,7 +76,7 @@ func TestGenerate(t *testing.T) { manifestGolden: "testdata/success_manifest.golden", outputGolden: "testdata/success_java.golden", outputFile: "OpenFeature.java", - packageName: "com.openfeature.generated", + packageName: "com.example.openfeature", }, // Add more test cases here as needed } diff --git a/cmd/testdata/success_java.golden b/cmd/testdata/success_java.golden index af090e2..efb10ca 100644 --- a/cmd/testdata/success_java.golden +++ b/cmd/testdata/success_java.golden @@ -1,5 +1,5 @@ // AUTOMATICALLY GENERATED BY OPENFEATURE CLI, DO NOT EDIT. -package com.openfeature.generated; +package com.example.openfeature; import dev.openfeature.sdk.Client; import dev.openfeature.sdk.EvaluationContext; diff --git a/docs/commands/openfeature_generate_java.md b/docs/commands/openfeature_generate_java.md index 8e665c1..a4c99a8 100644 --- a/docs/commands/openfeature_generate_java.md +++ b/docs/commands/openfeature_generate_java.md @@ -19,7 +19,7 @@ openfeature generate java [flags] ``` -h, --help help for java - --package-name string Name of the generated Java package (default "com.openfeature.generated") + --package-name string Name of the generated Java package (default "com.example.openfeature") ``` ### Options inherited from parent commands diff --git a/internal/config/flags.go b/internal/config/flags.go index 66b02a8..b4fd833 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -22,7 +22,7 @@ const ( DefaultOutputPath = "" DefaultGoPackageName = "openfeature" DefaultCSharpNamespace = "OpenFeature" - DefaultJavaPackageName = "com.openfeature.generated" + DefaultJavaPackageName = "com.example.openfeature" ) // AddRootFlags adds the common flags to the given command From b6b17bd68dc97e8a1b864f0912bbf25e148c7490 Mon Sep 17 00:00:00 2001 From: advitiya0201 Date: Wed, 7 May 2025 21:59:28 +0530 Subject: [PATCH 5/7] Fix variable type and imports according to Openfeature java-SDK Signed-off-by: advitiya0201 --- cmd/testdata/success_java.golden | 31 +++++++++++++++--------------- internal/generators/java/java.go | 6 +++--- internal/generators/java/java.tmpl | 5 ++--- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/cmd/testdata/success_java.golden b/cmd/testdata/success_java.golden index efb10ca..bda7976 100644 --- a/cmd/testdata/success_java.golden +++ b/cmd/testdata/success_java.golden @@ -3,8 +3,7 @@ package com.example.openfeature; import dev.openfeature.sdk.Client; import dev.openfeature.sdk.EvaluationContext; -import dev.openfeature.sdk.EvaluationDetails; -import dev.openfeature.sdk.OpenFeature; +import dev.openfeature.sdk.FlagEvaluationDetails; import dev.openfeature.sdk.OpenFeatureAPI; public class GeneratedClient { @@ -28,29 +27,29 @@ public class GeneratedClient { * Discount percentage applied to purchases. * Details: * - Flag key: discountPercentage - * - Type: float + * - Type: Double * - Default value: 0.15 */ - public float discountPercentage(EvaluationContext ctx) { - return client.getFloatValue("discountPercentage", 0.15, ctx); + public Double discountPercentage(EvaluationContext ctx) { + return client.getDoubleValue("discountPercentage", 0.15, ctx); } - public EvaluationDetails discountPercentageDetails(EvaluationContext ctx) { - return client.getFloatDetails("discountPercentage", 0.15, ctx); + public FlagEvaluationDetails discountPercentageDetails(EvaluationContext ctx) { + return client.getDoubleDetails("discountPercentage", 0.15, ctx); } /** * Controls whether Feature A is enabled. * Details: * - Flag key: enableFeatureA - * - Type: boolean + * - Type: Boolean * - Default value: false */ - public boolean enableFeatureA(EvaluationContext ctx) { + public Boolean enableFeatureA(EvaluationContext ctx) { return client.getBooleanValue("enableFeatureA", false, ctx); } - public EvaluationDetails enableFeatureADetails(EvaluationContext ctx) { + public FlagEvaluationDetails enableFeatureADetails(EvaluationContext ctx) { return client.getBooleanDetails("enableFeatureA", false, ctx); } @@ -65,7 +64,7 @@ public class GeneratedClient { return client.getStringValue("greetingMessage", "Hello there!", ctx); } - public EvaluationDetails greetingMessageDetails(EvaluationContext ctx) { + public FlagEvaluationDetails greetingMessageDetails(EvaluationContext ctx) { return client.getStringDetails("greetingMessage", "Hello there!", ctx); } @@ -73,15 +72,15 @@ public class GeneratedClient { * Maximum allowed length for usernames. * Details: * - Flag key: usernameMaxLength - * - Type: int + * - Type: Integer * - Default value: 50 */ - public int usernameMaxLength(EvaluationContext ctx) { - return client.getIntValue("usernameMaxLength", 50, ctx); + public Integer usernameMaxLength(EvaluationContext ctx) { + return client.getIntegerValue("usernameMaxLength", 50, ctx); } - public EvaluationDetails usernameMaxLengthDetails(EvaluationContext ctx) { - return client.getIntDetails("usernameMaxLength", 50, ctx); + public FlagEvaluationDetails usernameMaxLengthDetails(EvaluationContext ctx) { + return client.getIntegerDetails("usernameMaxLength", 50, ctx); } } diff --git a/internal/generators/java/java.go b/internal/generators/java/java.go index e2692dc..23ee8a5 100644 --- a/internal/generators/java/java.go +++ b/internal/generators/java/java.go @@ -24,11 +24,11 @@ var javaTmpl string func openFeatureType(t flagset.FlagType) string { switch t { case flagset.IntType: - return "int" + return "Integer" case flagset.FloatType: - return "float" + return "Double" //using Double as per openfeature Java-SDK case flagset.BoolType: - return "boolean" + return "Boolean" case flagset.StringType: return "String" default: diff --git a/internal/generators/java/java.tmpl b/internal/generators/java/java.tmpl index 1fbd4ff..3a1019e 100644 --- a/internal/generators/java/java.tmpl +++ b/internal/generators/java/java.tmpl @@ -3,8 +3,7 @@ package {{ .Params.Custom.JavaPackage }}; import dev.openfeature.sdk.Client; import dev.openfeature.sdk.EvaluationContext; -import dev.openfeature.sdk.EvaluationDetails; -import dev.openfeature.sdk.OpenFeature; +import dev.openfeature.sdk.FlagEvaluationDetails; import dev.openfeature.sdk.OpenFeatureAPI; public class GeneratedClient { @@ -35,7 +34,7 @@ public class GeneratedClient { return client.get{{ .Type | OpenFeatureType | ToPascal }}Value("{{ .Key }}", {{ . | FormatDefaultValue }}, ctx); } - public EvaluationDetails<{{ .Type | OpenFeatureType }}> {{ .Key | ToCamel }}Details(EvaluationContext ctx) { + public FlagEvaluationDetails<{{ .Type | OpenFeatureType }}> {{ .Key | ToCamel }}Details(EvaluationContext ctx) { return client.get{{ .Type | OpenFeatureType | ToPascal }}Details("{{ .Key }}", {{ . | FormatDefaultValue }}, ctx); } {{ end }} From 3da1786d1626d20b1086ce2cf81741d361b9fd3a Mon Sep 17 00:00:00 2001 From: advitiya0201 Date: Wed, 7 May 2025 23:30:44 +0530 Subject: [PATCH 6/7] Refactor: expose only interface via factory method and hide client implementation Signed-off-by: advitiya0201 --- cmd/testdata/success_java.golden | 190 ++++++++++++++++++++--------- internal/generators/java/java.tmpl | 67 ++++++---- 2 files changed, 174 insertions(+), 83 deletions(-) diff --git a/cmd/testdata/success_java.golden b/cmd/testdata/success_java.golden index bda7976..8de4f45 100644 --- a/cmd/testdata/success_java.golden +++ b/cmd/testdata/success_java.golden @@ -6,81 +6,149 @@ import dev.openfeature.sdk.EvaluationContext; import dev.openfeature.sdk.FlagEvaluationDetails; import dev.openfeature.sdk.OpenFeatureAPI; -public class GeneratedClient { +public final class Generated { + + private Generated() {} // prevent instantiation + + public interface GeneratedClient { + + /** + * Discount percentage applied to purchases. + * Details: + * - Flag key: discountPercentage + * - Type: Double + * - Default value: 0.15 + * Returns the flag value + */ + Double discountPercentage(EvaluationContext ctx); + + /** + * Discount percentage applied to purchases. + * Details: + * - Flag key: discountPercentage + * - Type: Double + * - Default value: 0.15 + * Returns the evaluation details containing the flag value and metadata + */ + FlagEvaluationDetails discountPercentageDetails(EvaluationContext ctx); + + /** + * Controls whether Feature A is enabled. + * Details: + * - Flag key: enableFeatureA + * - Type: Boolean + * - Default value: false + * Returns the flag value + */ + Boolean enableFeatureA(EvaluationContext ctx); + + /** + * Controls whether Feature A is enabled. + * Details: + * - Flag key: enableFeatureA + * - Type: Boolean + * - Default value: false + * Returns the evaluation details containing the flag value and metadata + */ + FlagEvaluationDetails enableFeatureADetails(EvaluationContext ctx); + + /** + * The message to use for greeting users. + * Details: + * - Flag key: greetingMessage + * - Type: String + * - Default value: Hello there! + * Returns the flag value + */ + String greetingMessage(EvaluationContext ctx); + + /** + * The message to use for greeting users. + * Details: + * - Flag key: greetingMessage + * - Type: String + * - Default value: Hello there! + * Returns the evaluation details containing the flag value and metadata + */ + FlagEvaluationDetails greetingMessageDetails(EvaluationContext ctx); + + /** + * Maximum allowed length for usernames. + * Details: + * - Flag key: usernameMaxLength + * - Type: Integer + * - Default value: 50 + * Returns the flag value + */ + Integer usernameMaxLength(EvaluationContext ctx); + + /** + * Maximum allowed length for usernames. + * Details: + * - Flag key: usernameMaxLength + * - Type: Integer + * - Default value: 50 + * Returns the evaluation details containing the flag value and metadata + */ + FlagEvaluationDetails usernameMaxLengthDetails(EvaluationContext ctx); - private final Client client; - - public GeneratedClient(Client client) { - this.client = client; } - public static GeneratedClient createClient() { - return new GeneratedClient(OpenFeatureAPI.getInstance().getClient()); - } + private static final class OpenFeatureGeneratedClient implements GeneratedClient { + private final Client client; - public static GeneratedClient createClient(String domain) { - return new GeneratedClient(OpenFeatureAPI.getInstance().getClient(domain)); - } + private OpenFeatureGeneratedClient(Client client) { + this.client = client; + } - /** - * Discount percentage applied to purchases. - * Details: - * - Flag key: discountPercentage - * - Type: Double - * - Default value: 0.15 - */ - public Double discountPercentage(EvaluationContext ctx) { - return client.getDoubleValue("discountPercentage", 0.15, ctx); - } + @Override + public Double discountPercentage(EvaluationContext ctx) { + return client.getDoubleValue("discountPercentage", 0.15, ctx); + } - public FlagEvaluationDetails discountPercentageDetails(EvaluationContext ctx) { - return client.getDoubleDetails("discountPercentage", 0.15, ctx); - } + @Override + public FlagEvaluationDetails discountPercentageDetails(EvaluationContext ctx) { + return client.getDoubleDetails("discountPercentage", 0.15, ctx); + } - /** - * Controls whether Feature A is enabled. - * Details: - * - Flag key: enableFeatureA - * - Type: Boolean - * - Default value: false - */ - public Boolean enableFeatureA(EvaluationContext ctx) { - return client.getBooleanValue("enableFeatureA", false, ctx); - } + @Override + public Boolean enableFeatureA(EvaluationContext ctx) { + return client.getBooleanValue("enableFeatureA", false, ctx); + } - public FlagEvaluationDetails enableFeatureADetails(EvaluationContext ctx) { - return client.getBooleanDetails("enableFeatureA", false, ctx); - } + @Override + public FlagEvaluationDetails enableFeatureADetails(EvaluationContext ctx) { + return client.getBooleanDetails("enableFeatureA", false, ctx); + } - /** - * The message to use for greeting users. - * Details: - * - Flag key: greetingMessage - * - Type: String - * - Default value: Hello there! - */ - public String greetingMessage(EvaluationContext ctx) { - return client.getStringValue("greetingMessage", "Hello there!", ctx); - } + @Override + public String greetingMessage(EvaluationContext ctx) { + return client.getStringValue("greetingMessage", "Hello there!", ctx); + } - public FlagEvaluationDetails greetingMessageDetails(EvaluationContext ctx) { - return client.getStringDetails("greetingMessage", "Hello there!", ctx); - } + @Override + public FlagEvaluationDetails greetingMessageDetails(EvaluationContext ctx) { + return client.getStringDetails("greetingMessage", "Hello there!", ctx); + } + + @Override + public Integer usernameMaxLength(EvaluationContext ctx) { + return client.getIntegerValue("usernameMaxLength", 50, ctx); + } + + @Override + public FlagEvaluationDetails usernameMaxLengthDetails(EvaluationContext ctx) { + return client.getIntegerDetails("usernameMaxLength", 50, ctx); + } - /** - * Maximum allowed length for usernames. - * Details: - * - Flag key: usernameMaxLength - * - Type: Integer - * - Default value: 50 - */ - public Integer usernameMaxLength(EvaluationContext ctx) { - return client.getIntegerValue("usernameMaxLength", 50, ctx); } - public FlagEvaluationDetails usernameMaxLengthDetails(EvaluationContext ctx) { - return client.getIntegerDetails("usernameMaxLength", 50, ctx); + public static GeneratedClient getClient() { + return new OpenFeatureGeneratedClient(OpenFeatureAPI.getInstance().getClient()); } + public static GeneratedClient getClient(String domain) { + return new OpenFeatureGeneratedClient(OpenFeatureAPI.getInstance().getClient(domain)); + } } diff --git a/internal/generators/java/java.tmpl b/internal/generators/java/java.tmpl index 3a1019e..f15a601 100644 --- a/internal/generators/java/java.tmpl +++ b/internal/generators/java/java.tmpl @@ -6,36 +6,59 @@ import dev.openfeature.sdk.EvaluationContext; import dev.openfeature.sdk.FlagEvaluationDetails; import dev.openfeature.sdk.OpenFeatureAPI; -public class GeneratedClient { +public final class Generated { - private final Client client; + private Generated() {} // prevent instantiation - public GeneratedClient(Client client) { - this.client = client; - } + public interface GeneratedClient { + {{ range .Flagset.Flags }} + /** + * {{ .Description }} + * Details: + * - Flag key: {{ .Key }} + * - Type: {{ .Type | OpenFeatureType }} + * - Default value: {{ .DefaultValue }} + * Returns the flag value + */ + {{ .Type | OpenFeatureType }} {{ .Key | ToCamel }}(EvaluationContext ctx); - public static GeneratedClient createClient() { - return new GeneratedClient(OpenFeatureAPI.getInstance().getClient()); + /** + * {{ .Description }} + * Details: + * - Flag key: {{ .Key }} + * - Type: {{ .Type | OpenFeatureType }} + * - Default value: {{ .DefaultValue }} + * Returns the evaluation details containing the flag value and metadata + */ + FlagEvaluationDetails<{{ .Type | OpenFeatureType }}> {{ .Key | ToCamel }}Details(EvaluationContext ctx); + {{ end }} } - public static GeneratedClient createClient(String domain) { - return new GeneratedClient(OpenFeatureAPI.getInstance().getClient(domain)); + private static final class OpenFeatureGeneratedClient implements GeneratedClient { + private final Client client; + + private OpenFeatureGeneratedClient(Client client) { + this.client = client; + } + + {{ range .Flagset.Flags }} + @Override + public {{ .Type | OpenFeatureType }} {{ .Key | ToCamel }}(EvaluationContext ctx) { + return client.get{{ .Type | OpenFeatureType | ToPascal }}Value("{{ .Key }}", {{ . | FormatDefaultValue }}, ctx); + } + + @Override + public FlagEvaluationDetails<{{ .Type | OpenFeatureType }}> {{ .Key | ToCamel }}Details(EvaluationContext ctx) { + return client.get{{ .Type | OpenFeatureType | ToPascal }}Details("{{ .Key }}", {{ . | FormatDefaultValue }}, ctx); + } + {{ end }} } - {{ range .Flagset.Flags }} - /** - * {{ .Description }} - * Details: - * - Flag key: {{ .Key }} - * - Type: {{ .Type | OpenFeatureType }} - * - Default value: {{ .DefaultValue }} - */ - public {{ .Type | OpenFeatureType }} {{ .Key | ToCamel }}(EvaluationContext ctx) { - return client.get{{ .Type | OpenFeatureType | ToPascal }}Value("{{ .Key }}", {{ . | FormatDefaultValue }}, ctx); + public static GeneratedClient getClient() { + return new OpenFeatureGeneratedClient(OpenFeatureAPI.getInstance().getClient()); } - public FlagEvaluationDetails<{{ .Type | OpenFeatureType }}> {{ .Key | ToCamel }}Details(EvaluationContext ctx) { - return client.get{{ .Type | OpenFeatureType | ToPascal }}Details("{{ .Key }}", {{ . | FormatDefaultValue }}, ctx); + public static GeneratedClient getClient(String domain) { + return new OpenFeatureGeneratedClient(OpenFeatureAPI.getInstance().getClient(domain)); } - {{ end }} } From 4671abf3dbc0762dca52485676e78db432fdd9c3 Mon Sep 17 00:00:00 2001 From: advitiya0201 Date: Thu, 8 May 2025 01:46:36 +0530 Subject: [PATCH 7/7] Update generated docs after java generator extension Signed-off-by: advitiya0201 --- docs/commands/openfeature_generate.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/commands/openfeature_generate.md b/docs/commands/openfeature_generate.md index ddef81b..ce9b24d 100644 --- a/docs/commands/openfeature_generate.md +++ b/docs/commands/openfeature_generate.md @@ -25,12 +25,12 @@ openfeature generate [flags] ### SEE ALSO -* [openfeature](openfeature.md) - CLI for OpenFeature. +* [openfeature](openfeature.md) - CLI for OpenFeature. * [openfeature generate csharp](openfeature_generate_csharp.md) - Generate typesafe C# client. * [openfeature generate go](openfeature_generate_go.md) - Generate typesafe accessors for OpenFeature. +* [openfeature generate java](openfeature_generate_java.md) - Generate typesafe Java client. * [openfeature generate nestjs](openfeature_generate_nestjs.md) - Generate typesafe NestJS decorators. * [openfeature generate nodejs](openfeature_generate_nodejs.md) - Generate typesafe Node.js client. * [openfeature generate python](openfeature_generate_python.md) - Generate typesafe Python client. * [openfeature generate react](openfeature_generate_react.md) - Generate typesafe React Hooks. -* [openfeature generate java](openfeature_generate_java.md) - Generate typesafe Java client.