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 diff --git a/README.md b/README.md index 6e77fed3..88cbc32a 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Java Client Library -* Latest released version 0.15.4 -* Latest snapshot version 0.15.5-SNAPSHOT +* Latest released version 0.15.5 +* Latest snapshot version 0.15.6-SNAPSHOT ## Introduction This is the PolyAPI Java client GitHub page. If you are here, then it means you're familiar with what we do at Poly. If you aren't, you can always check [here](https://github.com/polyapi/poly-alpha). 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}}