Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions CodenameOne/src/com/codename1/annotations/grpc/GrpcClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*/
package com.codename1.annotations.grpc;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/// Marks an interface as a gRPC client that the build-time annotation
/// processor wires up to a generated gRPC-Web implementation. Each
/// abstract method must carry an [Rpc] annotation naming the gRPC
/// method, and at most one non-callback parameter -- the request
/// message, a `@ProtoMessage`-annotated POJO. The final parameter is
/// an `OnComplete<Response<ResponseMessage>>` callback.
///
/// The fully qualified service path defaults to
/// `<value()>/<methodName>` -- for example
/// `helloworld.Greeter/SayHello`. Override per-method via
/// [Rpc#service()] when the service path needs to differ from the
/// interface-level default.
///
/// The processor emits a `<SimpleName>Impl` class in generated-sources
/// and registers it with [com.codename1.io.grpc.GrpcClients] so the
/// interface's `static T of(String baseUrl)` factory can return an
/// instance without the project source referencing the impl directly.
/// Mirrors [com.codename1.annotations.rest.RestClient].
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface GrpcClient {
/// Fully qualified gRPC service path (without leading slash), e.g.
/// `helloworld.Greeter`. Combined with each method's [Rpc#value()]
/// to form the request URI segment `/<service>/<method>` appended
/// to the `baseUrl`. Empty string means each method must specify
/// the full service path via [Rpc#service()].
String value() default "";
}
31 changes: 31 additions & 0 deletions CodenameOne/src/com/codename1/annotations/grpc/ProtoEnum.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*/
package com.codename1.annotations.grpc;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/// Marks a Java `enum` as a Protocol Buffers enum. The generator
/// emits the enum with a `public final int number` field and a
/// `static Xxx forNumber(int n)` lookup. On the wire enums are
/// encoded as varint -- the field's tag from [ProtoField] is read /
/// written like an `int32`, and the integer value is mapped back to
/// the enum constant via `forNumber`.
///
/// Unknown numbers map to `null` so callers can distinguish "no
/// such constant" from "constant with number 0". For proto3 the
/// zero constant is the default and is what the wire produces when
/// the field is absent.
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface ProtoEnum {
}
51 changes: 51 additions & 0 deletions CodenameOne/src/com/codename1/annotations/grpc/ProtoField.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*/
package com.codename1.annotations.grpc;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/// Binds a [ProtoMessage] field to a protobuf field tag. Tags must
/// be positive, unique within the message, and correspond to the
/// tag declared in the upstream `.proto` file.
///
/// Optional [#wireType()] forces a non-default scalar encoding for
/// integer fields. Defaults to [WireKind#DEFAULT], which selects
/// varint for `int32` / `int64` / `bool`, fixed32 / fixed64 for
/// `float` / `double`, and length-delimited for strings, byte arrays,
/// nested messages, and `repeated` packed scalars. Specify
/// [WireKind#SINT] for ZigZag-encoded signed integers, or
/// [WireKind#FIXED] for fixed-width unsigned integers (matches
/// `fixed32` / `fixed64` in proto3).
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface ProtoField {
/// Protobuf field tag (positive integer, unique per message).
int tag();

/// Optional override of the Java field name when the generator
/// had to rename it to a valid identifier. Carries the original
/// `.proto` name so introspection tooling can recover it.
String name() default "";

/// Non-default integer encoding selector. Has no effect for
/// non-integer fields.
WireKind wireType() default WireKind.DEFAULT;

/// Integer encoding selectors. `DEFAULT` matches `int32` /
/// `int64` / `uint32` / `uint64` (varint). `SINT` matches
/// `sint32` / `sint64` (ZigZag-encoded varint). `FIXED` matches
/// `fixed32` / `fixed64` / `sfixed32` / `sfixed64` (fixed-width).
enum WireKind {
DEFAULT, SINT, FIXED
}
}
38 changes: 38 additions & 0 deletions CodenameOne/src/com/codename1/annotations/grpc/ProtoMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*/
package com.codename1.annotations.grpc;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/// Marks a POJO (or Java 17+ record) as a Protocol Buffers message.
/// The Codename One Maven plugin scans every `@ProtoMessage` class at
/// build time and emits a reflection-free `ProtoCodec` next to it
/// that serializes / deserializes the class to / from the binary
/// protobuf wire format. The generated `cn1app.ProtoBootstrap`
/// registers every codec with [com.codename1.io.grpc.ProtoCodecs] so
/// generated gRPC clients can resolve nested message types by class
/// without reflection.
///
/// Each persistable field on the class must carry [ProtoField] with a
/// unique tag. Field types may be: scalar (int / long / float /
/// double / boolean / String / byte[]), other `@ProtoMessage` types,
/// `@ProtoEnum`-marked enums, or `java.util.List` of any of the
/// above (interpreted as `repeated` in proto3).
///
/// Mirrors the design of [com.codename1.annotations.Mapped] for JSON
/// projection; you can carry both annotations on the same class to
/// support JSON and protobuf wire formats off the same POJO.
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface ProtoMessage {
}
36 changes: 36 additions & 0 deletions CodenameOne/src/com/codename1/annotations/grpc/Rpc.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*/
package com.codename1.annotations.grpc;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/// Binds a [GrpcClient] interface method to a gRPC unary RPC. The
/// `value` is the gRPC method name (e.g. `SayHello`) and is combined
/// with the interface-level [GrpcClient#value()] to form
/// `/<service>/<method>`. The optional [#service()] overrides the
/// interface-level service path for a single method (useful when an
/// interface aggregates calls into multiple services).
///
/// Streaming RPCs are not supported in this release -- only unary
/// (single request, single response).
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.METHOD)
public @interface Rpc {
/// The gRPC method name as declared in the `.proto` `rpc` line,
/// e.g. `SayHello`. Joined to the service path via `/`.
String value();

/// Optional override of the interface-level service path. Empty
/// string means inherit from [GrpcClient#value()].
String service() default "";
}
54 changes: 54 additions & 0 deletions CodenameOne/src/com/codename1/annotations/grpc/package-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*/
/// gRPC-Web client annotations. [GrpcClient] marks an interface as a
/// gRPC client; [Rpc] binds each method to a unary RPC.
/// [ProtoMessage] marks a POJO as a protobuf message;
/// [ProtoField] tags each field; [ProtoEnum] marks an enum as
/// protobuf-encoded.
///
/// The Codename One Maven plugin's `cn1:generate-grpc` mojo emits
/// these annotations from a user-supplied `.proto` file; the
/// `RestClient` / `ProtoMessage` annotation processors then emit
/// the implementation code at build time. End-to-end usage:
///
/// ```java
/// // generated by cn1:generate-grpc
/// @ProtoMessage
/// public final class HelloRequest {
/// @ProtoField(tag = 1) public String name;
/// }
///
/// @ProtoMessage
/// public final class HelloReply {
/// @ProtoField(tag = 1) public String message;
/// }
///
/// @GrpcClient("helloworld.Greeter")
/// public interface GreeterGrpc {
/// @Rpc("SayHello")
/// void sayHello(HelloRequest req,
/// OnComplete<Response<HelloReply>> callback);
///
/// static GreeterGrpc of(String baseUrl) {
/// return GrpcClients.create(GreeterGrpc.class, baseUrl);
/// }
/// }
///
/// // call site
/// GreeterGrpc g = GreeterGrpc.of("https://api.example.com");
/// HelloRequest req = new HelloRequest();
/// req.name = "world";
/// g.sayHello(req, resp -> {
/// if (resp.getResponseCode() == 200) {
/// System.out.println(resp.getResponseData().message);
/// }
/// });
/// ```
package com.codename1.annotations.grpc;
57 changes: 57 additions & 0 deletions CodenameOne/src/com/codename1/io/grpc/GrpcClients.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*/
package com.codename1.io.grpc;

import java.util.HashMap;
import java.util.Map;

/// Runtime registry that wires `@GrpcClient`-annotated interfaces
/// to the build-time-generated implementations. The generated
/// `cn1app.GrpcClientBootstrap` calls [#register(Class, Factory)]
/// for every gRPC interface in the project; user code reaches them
/// via the `static of(String baseUrl)` factory that
/// `cn1:generate-grpc` puts on each interface, and that factory in
/// turn calls [#create(Class, String)] here.
///
/// Mirrors [com.codename1.io.rest.RestClients].
public final class GrpcClients {

private static final Map<Class<?>, Factory<?>> REGISTRY = new HashMap<Class<?>, Factory<?>>();

private GrpcClients() {
}

/// Registers a factory for a `@GrpcClient`-annotated interface.
public static <T> void register(Class<T> apiType, Factory<T> factory) {
if (apiType == null || factory == null) {
return;
}
REGISTRY.put(apiType, factory);
}

/// Returns a freshly-built client for the requested API.
@SuppressWarnings("unchecked")
public static <T> T create(Class<T> apiType, String baseUrl) {
Factory<T> factory = (Factory<T>) REGISTRY.get(apiType);
if (factory == null) {
throw new IllegalStateException(
"No GrpcClient impl registered for " + apiType.getName()
+ " -- did cn1:process-annotations run?");
}
return factory.create(baseUrl);
}

/// Factory the generated bootstrap registers per API interface.
/// Single-method interface -- not `java.util.function.Function`
/// -- so CLDC-targeted builds remain happy.
public interface Factory<T> {
T create(String baseUrl);
}
}
34 changes: 34 additions & 0 deletions CodenameOne/src/com/codename1/io/grpc/GrpcException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*/
package com.codename1.io.grpc;

/// Thrown when a synchronous helper observes a non-OK gRPC status
/// or a transport-level failure. The async callback path uses
/// [GrpcResponse] instead -- this is only for code paths that
/// prefer exceptions over inspecting `responseCode`.
public class GrpcException extends RuntimeException {

private final int status;
private final int httpCode;

public GrpcException(int status, int httpCode, String message) {
super(message == null ? ("gRPC status " + status) : message);
this.status = status;
this.httpCode = httpCode;
}

public int getStatus() {
return status;
}

public int getHttpCode() {
return httpCode;
}
}
Loading
Loading