From 7a06ffd7ce5af3eec339d4ae932ddf8857d055bf Mon Sep 17 00:00:00 2001 From: "eric.neumann" Date: Tue, 3 Jun 2025 17:39:25 -0700 Subject: [PATCH 1/2] EN #4386 fix simple-name collisions --- .../generation/PolyObjectResolverService.java | 16 +++++- .../service/template/PolyHandlebars.java | 45 +++++++++++++++ .../resources/templates/ResolvedContext.hbs | 57 +++++++++++-------- 3 files changed, 92 insertions(+), 26 deletions(-) diff --git a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/generation/PolyObjectResolverService.java b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/generation/PolyObjectResolverService.java index fc5190ad..7fb74f16 100644 --- a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/generation/PolyObjectResolverService.java +++ b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/generation/PolyObjectResolverService.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Function; @@ -43,6 +44,8 @@ @Slf4j public class PolyObjectResolverService { private final JsonSchemaParser jsonSchemaParser; + private static final Pattern VALID_IMPORT = + Pattern.compile("^[a-zA-Z_][a-zA-Z0-9_]*(\\.[a-zA-Z_][a-zA-Z0-9_]*)+$"); public PolyObjectResolverService(JsonSchemaParser jsonSchemaParser) { this.jsonSchemaParser = jsonSchemaParser; @@ -92,12 +95,21 @@ public ResolvedServerVariableSpecification resolve(ServerVariableSpecification s public ResolvedContext resolve(Context context) { Set imports = new HashSet<>(); - context.getSubcontexts().stream().map(subcontext -> format("%s.%s", subcontext.getPackageName(), subcontext.getClassName())).forEach(imports::add); + context.getSubcontexts().stream() + .map(subcontext -> format("%s.%s", subcontext.getPackageName(), subcontext.getClassName())) + .filter(s -> !s.isBlank()) + .forEach(imports::add); context.getSpecifications().forEach(specification -> { ImportsCollectorVisitor importsCollectorVisitor = new ImportsCollectorVisitor(specification.getPackageName(), specification.getClassName(), jsonSchemaParser); importsCollectorVisitor.doVisit(specification); - imports.addAll(importsCollectorVisitor.getImports()); + importsCollectorVisitor.getImports().stream() + .filter(Objects::nonNull) + .filter(s -> !s.isBlank()) + .filter(s -> VALID_IMPORT.matcher(s).matches()) + .filter(s -> !s.substring(s.lastIndexOf('.') + 1).equals(context.getClassName())) + .forEach(imports::add); }); + return new ResolvedContext(context.getName(), context.getPackageName(), imports, context.getClassName(), context.getSubcontexts().stream().map(this::resolve).toList(), context.getSpecifications().stream().map(specification -> { PolyObjectResolverVisitor visitor = new PolyObjectResolverVisitor(this); visitor.doVisit(specification); diff --git a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/template/PolyHandlebars.java b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/template/PolyHandlebars.java index 5d812e8a..5781e0ba 100644 --- a/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/template/PolyHandlebars.java +++ b/polyapi-maven-plugin/src/main/java/io/polyapi/plugin/service/template/PolyHandlebars.java @@ -7,6 +7,7 @@ import java.util.function.BiPredicate; import java.util.function.Function; +import java.lang.reflect.Method; public class PolyHandlebars extends Handlebars { @@ -15,6 +16,50 @@ public PolyHandlebars() { registerSimpleHelper("toCamelCase", StringUtils::toCamelCase); registerSimpleHelper("toPascalCase", StringUtils::toCamelCase); registerConditionalHelper("ifIsType", (object, options) -> object.getClass().getSimpleName().equals(options.param(0))); + registerSimpleHelper("lastSegment", (Object fqn) -> { + if (fqn == null) { + return ""; + } + String s = fqn.toString(); + int idx = s.lastIndexOf('.'); + return idx == -1 ? s : s.substring(idx + 1); + }); + registerConditionalHelper("eq", + (obj, opts) -> { + Object other = opts.param(0, ""); + return obj != null && obj.toString().equals(other == null ? "" : other.toString()); + }); + registerHelper("typeRef", (Object ctx, Options opts) -> { + if (ctx == null) { + return ""; + } + String fqn = ctx.toString(); + String parentSimple = opts.param(0, "").toString(); + String simple = fqn.substring(fqn.lastIndexOf('.') + 1); + return simple.equals(parentSimple) ? fqn : simple; + }); + registerHelper("classFqn", (Object ctx, Options o) -> { + if (ctx == null) return ""; + + for (String m : new String[]{"getFullClassName", "getFullName"}) { + try { + Method mm = ctx.getClass().getMethod(m); + Object val = mm.invoke(ctx); + if (val != null) return val.toString(); + } catch (ReflectiveOperationException ignored) {} + } + + try { + Method pm = ctx.getClass().getMethod("getPackageName"); + Method cm = ctx.getClass().getMethod("getClassName"); + Object pkg = pm.invoke(ctx); + Object cls = cm.invoke(ctx); + if (pkg != null && cls != null) return pkg + "." + cls; + if (cls != null) return cls.toString(); + } catch (ReflectiveOperationException ignored) {} + + return ""; + }); } private void registerSimpleHelper(String name, Function helper) { diff --git a/polyapi-maven-plugin/src/main/resources/templates/ResolvedContext.hbs b/polyapi-maven-plugin/src/main/resources/templates/ResolvedContext.hbs index 9765b8fd..92eff32b 100644 --- a/polyapi-maven-plugin/src/main/resources/templates/ResolvedContext.hbs +++ b/polyapi-maven-plugin/src/main/resources/templates/ResolvedContext.hbs @@ -8,68 +8,74 @@ import io.polyapi.client.api.model.PolyEntity; import io.polyapi.client.api.AuthTokenOptions; import io.polyapi.commons.api.model.PolyGeneratedClass; {{~#each this.imports}} +{{~#unless (eq (lastSegment this) ../className)}} import {{{this}}}; +{{~/unless}} {{~/each}} @PolyGeneratedClass public class {{className}} extends PolyContext { {{~#each functionSpecifications}} - private final {{this.className}} {{this.name}}; + private final {{typeRef (classFqn this) ../className}} {{this.name}}; {{~/each}} {{~#each standardAuthFunctionSpecifications}} - private final {{this.className}} {{this.name}}; + private final {{typeRef (classFqn this) ../className}} {{this.name}}; {{~/each}} {{~#each subresourceAuthFunctionSpecifications}} - private final {{this.className}} {{this.name}}; + private final {{typeRef (classFqn this) ../className}} {{this.name}}; {{~/each}} {{~#each serverVariableSpecifications}} - public final {{this.className}} {{this.name}}; + private final {{typeRef (classFqn this) ../className}} {{this.name}}; {{~/each}} {{~#each webhookHandlerSpecifications}} - private final {{this.className}} {{this.name}}; + private final {{typeRef (classFqn this) ../className}} {{this.name}}; {{~/each}} {{#each subcontexts}} - public final {{this.className}} {{this.name}}; + private final {{typeRef (classFqn this) ../className}} {{this.name}}; {{~/each}} - public {{className}}(PolyProxyFactory proxyFactory, WebSocketClient webSocketClient) { - super(proxyFactory, webSocketClient); +public {{className}}(PolyProxyFactory proxyFactory, WebSocketClient webSocketClient) { +super(proxyFactory, webSocketClient); {{~#each serverFunctionSpecifications}} - this.{{this.name}} = createServerFunctionProxy({{this.className}}.class); + this.{{this.name}} = + createServerFunctionProxy({{classFqn this}}.class); {{~/each}} {{~#each customFunctionSpecifications}} - this.{{this.name}} = createCustomFunctionProxy({{this.className}}.class); + this.{{this.name}} = + createCustomFunctionProxy({{classFqn this}}.class); {{~/each}} {{~#each apiFunctionSpecifications}} - this.{{this.name}} = createApiFunctionProxy({{this.className}}.class); + this.{{this.name}} = + createApiFunctionProxy({{classFqn this}}.class); {{~/each}} {{~#each subresourceAuthFunctionSpecifications}} - this.{{this.name}} = createSubresourceAuthFunction({{this.className}}.class); + this.{{this.name}} = + createSubresourceAuthFunction({{classFqn this}}.class); {{~/each}} {{~#each standardAuthFunctionSpecifications}} - this.{{this.name}} = create{{#if audienceRequired}}Audience{{/if}}TokenAuthFunction({{this.className}}.class); + this.{{this.name}} = + create{{#if audienceRequired}}Audience{{/if}}TokenAuthFunction({{classFqn this}}.class); {{~/each}} {{~#each serverVariableSpecifications}} - this.{{this.name}} = createServerVariableHandler({{this.className}}.class); + this.{{this.name}} = + createServerVariableHandler({{classFqn this}}.class); {{~/each}} {{~#each webhookHandlerSpecifications}} - this.{{this.name}} = createPolyTriggerProxy({{this.className}}.class); + this.{{this.name}} = + createPolyTriggerProxy({{classFqn this}}.class); {{~/each}} {{#each subcontexts}} - this.{{this.name}} = new {{this.className}}(proxyFactory, webSocketClient); + this.{{this.name}} = new {{typeRef (classFqn this) ../className}}(proxyFactory, webSocketClient); {{~/each}} } {{~#each functionSpecifications}} public {{{this.returnType}}} {{{this.methodSignature}}} { - {{~#if this.returnsValue}} - return - {{~else}} - {{~/if}} this.{{this.name}}.{{this.name}}({{this.paramVariableNames}}); + {{#if this.returnsValue}}return {{/if}}this.{{this.name}}.{{this.name}}({{this.paramVariableNames}}); } - public {{{this.className}}} get{{{this.className}}}Function() { - return this.{{{this.name}}}; + public {{typeRef (classFqn this) ../className}} get{{this.className}}Function() { + return this.{{this.name}}; } {{~/each}} @@ -110,6 +116,9 @@ public class {{className}} extends PolyContext { {{~#each specifications}} {{~#ifIsType this "AuthFunctionSpecification"}} + public {{typeRef (classFqn this) ../className}} get{{this.className}}AuthFunction() { + return this.{{this.name}}; + } {{~#if subResource}} public void {{name}}(String token) { this.{{name}}.{{name}}(token); @@ -127,8 +136,8 @@ public class {{className}} extends PolyContext { } {{~/if}} - public {{{this.className}}} get{{{this.className}}}AuthFunction() { - return this.{{{this.name}}}; + public {{typeRef (classFqn this) ../className}} get{{this.className}}AuthFunction() { + return this.{{this.name}}; } {{~/ifIsType}} {{~/each}} From 0657473e32b917555addcd62f63e6a9de7562f65 Mon Sep 17 00:00:00 2001 From: "eric.neumann" Date: Wed, 4 Jun 2025 08:14:28 -0700 Subject: [PATCH 2/2] EN #4386 update changelog --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b903e2ba..64ee4ccc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## +## [0.15.5] - 2025-06-04 + +### Added + +- `classFqn` helper to compute fully-qualified names +- import-filter to drop any illegal import strings +- `typeRef` helper to pick simple name or FQN and avoid duplicates +- `lastSegment` helper to filter out a context’s own class from imports + +### Changed + +- Updated `ResolvedContext.hbs` so that all generated-type references go through `typeRef` (simple names only when safe) +- Updated constructor loops in `ResolvedContext.hbs` to call proxy methods with `classFqn` (true FQNs) + +### Fixed + +- “already defined in this compilation unit” compile errors—SDK now builds without name collisions + +## ## [0.15.4] - 2024-11-15 ### Added