Skip to content

Commit 19acb3c

Browse files
authored
[client] Fully support package private (#551)
* fully support package private will now generate separate HttpComponents for package-private client interfaces * Create PrivateClient.java
1 parent 3025c93 commit 19acb3c

File tree

15 files changed

+257
-75
lines changed

15 files changed

+257
-75
lines changed

http-generator-client/src/main/java/io/avaje/http/generator/client/ClientProcessor.java

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package io.avaje.http.generator.client;
22

3+
import static io.avaje.http.generator.core.ProcessingContext.createMetaInfWriter;
34
import static io.avaje.http.generator.core.ProcessingContext.logError;
45
import static io.avaje.http.generator.core.ProcessingContext.platform;
56
import static io.avaje.http.generator.core.ProcessingContext.setPlatform;
67
import static io.avaje.http.generator.core.ProcessingContext.typeElement;
78

89
import java.io.IOException;
10+
import java.util.HashMap;
11+
import java.util.Map;
912
import java.util.Objects;
1013
import java.util.Set;
1114

@@ -15,10 +18,13 @@
1518
import javax.annotation.processing.SupportedAnnotationTypes;
1619
import javax.lang.model.SourceVersion;
1720
import javax.lang.model.element.Element;
21+
import javax.lang.model.element.Modifier;
1822
import javax.lang.model.element.TypeElement;
23+
import javax.tools.FileObject;
1924

2025
import io.avaje.http.generator.core.APContext;
2126
import io.avaje.http.generator.core.ClientPrism;
27+
import io.avaje.http.generator.core.Constants;
2228
import io.avaje.http.generator.core.ControllerReader;
2329
import io.avaje.http.generator.core.ImportPrism;
2430
import io.avaje.http.generator.core.ProcessingContext;
@@ -29,9 +35,9 @@
2935
public class ClientProcessor extends AbstractProcessor {
3036

3137
private final ComponentMetaData metaData = new ComponentMetaData();
38+
private final Map<String, ComponentMetaData> privateMetaData = new HashMap<>();
3239

3340
private SimpleComponentWriter componentWriter;
34-
3541
private boolean readModuleInfo;
3642

3743
@Override
@@ -76,7 +82,7 @@ private void readModule() {
7682
return;
7783
}
7884
readModuleInfo = true;
79-
new ComponentReader(metaData).read();
85+
new ComponentReader(metaData, privateMetaData).read();
8086
}
8187

8288
private void writeForImported(Element importedElement) {
@@ -91,39 +97,65 @@ private void writeClient(Element controller) {
9197
final ControllerReader reader = new ControllerReader((TypeElement) controller);
9298
reader.read(false);
9399
try {
94-
metaData.add(writeClientAdapter(reader));
100+
var packagePrivate =
101+
!controller.getModifiers().contains(Modifier.PUBLIC)
102+
&& ClientPrism.isPresent(controller);
103+
if (packagePrivate) {
104+
var packageName = APContext.elements().getPackageOf(controller).getQualifiedName().toString();
105+
var meta = privateMetaData.computeIfAbsent(packageName, k -> new ComponentMetaData());
106+
meta.add(writeClientAdapter(reader, true));
107+
} else {
108+
metaData.add(writeClientAdapter(reader, false));
109+
}
110+
95111
} catch (final Exception e) {
96112
logError(reader.beanType(), "Failed to write client class " + e);
97113
}
98114
}
99115
}
100116

101-
protected String writeClientAdapter(ControllerReader reader) throws IOException {
117+
protected String writeClientAdapter(ControllerReader reader, boolean packagePrivate) throws IOException {
102118
var suffix = ClientSuffix.fromInterface(reader.beanType().getQualifiedName().toString());
103-
return new ClientWriter(reader, suffix).write();
104-
}
105-
106-
private void initialiseComponent() {
107-
metaData.initialiseFullName();
108-
if (!metaData.all().isEmpty()) {
109-
ProcessingContext.addClientComponent(metaData.fullName());
110-
ProcessingContext.validateModule();
111-
}
112-
try {
113-
componentWriter.init();
114-
} catch (final IOException e) {
115-
logError("Error creating writer for JsonbComponent", e);
116-
}
119+
return new ClientWriter(reader, suffix, packagePrivate).write();
117120
}
118121

119122
private void writeComponent(boolean processingOver) {
120-
initialiseComponent();
121123
if (processingOver) {
122124
try {
123-
componentWriter.write();
125+
if (!metaData.all().isEmpty()) {
126+
ProcessingContext.addClientComponent(metaData.fullName());
127+
componentWriter.init();
128+
componentWriter.write();
129+
}
130+
131+
for (var meta : privateMetaData.values()) {
132+
ProcessingContext.addClientComponent(meta.fullName());
133+
var writer = new SimpleComponentWriter(meta);
134+
writer.init();
135+
writer.write();
136+
}
137+
writeMetaInf();
138+
ProcessingContext.validateModule();
124139
} catch (final IOException e) {
125140
logError("Error writing component", e);
126141
}
127142
}
128143
}
144+
145+
void writeMetaInf() throws IOException {
146+
final FileObject fileObject = createMetaInfWriter(Constants.META_INF_COMPONENT);
147+
if (fileObject != null) {
148+
try (var fileWriter = fileObject.openWriter()) {
149+
if (!metaData.all().isEmpty()) {
150+
fileWriter.write(metaData.fullName());
151+
fileWriter.write("\n");
152+
}
153+
154+
for (var meta : privateMetaData.values()) {
155+
fileWriter.write(meta.fullName());
156+
fileWriter.write("\n");
157+
}
158+
}
159+
}
160+
}
129161
}

http-generator-client/src/main/java/io/avaje/http/generator/client/ClientWriter.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.avaje.http.generator.client;
22

3-
import io.avaje.http.generator.core.APContext;
43
import io.avaje.http.generator.core.BaseControllerWriter;
54
import io.avaje.http.generator.core.ClientPrism;
65
import io.avaje.http.generator.core.ControllerReader;
@@ -27,9 +26,12 @@ final class ClientWriter extends BaseControllerWriter {
2726
private final Set<String> propertyConstants = new HashSet<>();
2827
private final String suffix;
2928

30-
ClientWriter(ControllerReader reader, String suffix) throws IOException {
29+
private final boolean packagePrivate;
30+
31+
ClientWriter(ControllerReader reader, String suffix, boolean packagePrivate) throws IOException {
3132
super(reader, suffix);
3233
this.suffix = suffix;
34+
this.packagePrivate = packagePrivate;
3335
reader.addImportType(HTTP_CLIENT);
3436
readMethods();
3537
}
@@ -76,12 +78,12 @@ private void writeMethods() {
7678
private void writeClassStart() {
7779
writer.append(AT_GENERATED).eol();
7880
AnnotationUtil.writeAnnotations(writer, reader.beanType());
79-
80-
writer.append("public final class %s%s implements %s, AutoCloseable {", shortName, suffix, shortName).eol().eol();
81+
var access = packagePrivate ? "" : "public ";
82+
writer.append("%sfinal class %s%s implements %s, AutoCloseable {", access, shortName, suffix, shortName).eol().eol();
8183

8284
writer.append(" private final HttpClient client;").eol().eol();
8385

84-
writer.append(" public %s%s(HttpClient client) {", shortName, suffix).eol();
86+
writer.append(" %s%s%s(HttpClient client) {", access, shortName, suffix).eol();
8587
writer.append(" this.client = client;").eol();
8688
writer.append(" }").eol().eol();
8789
}

http-generator-client/src/main/java/io/avaje/http/generator/client/ComponentMetaData.java

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,14 @@
44

55
final class ComponentMetaData {
66

7-
private final List<String> generatedClients = new ArrayList<>();
7+
private final Set<String> generatedClients = new HashSet<>();
88
private String fullName;
99

1010
@Override
1111
public String toString() {
1212
return generatedClients.toString();
1313
}
1414

15-
/** Ensure the component name has been initialised. */
16-
void initialiseFullName() {
17-
fullName();
18-
}
19-
2015
void add(String type) {
2116
generatedClients.add(type);
2217
}
@@ -28,13 +23,13 @@ void setFullName(String fullName) {
2823
String fullName() {
2924
if (fullName == null) {
3025
String topPackage = TopPackage.of(generatedClients);
31-
fullName = topPackage + ".GeneratedHttpComponent";
26+
fullName = topPackage + "." + name(topPackage) + "HttpComponent";
3227
}
3328
return fullName;
3429
}
3530

3631
List<String> all() {
37-
return generatedClients;
32+
return new ArrayList<>(generatedClients);
3833
}
3934

4035
/** Return the package imports for the JsonAdapters and related types. */
@@ -46,4 +41,37 @@ Collection<String> allImports() {
4641

4742
return packageImports;
4843
}
44+
45+
46+
static String name(String name) {
47+
if (name == null) {
48+
return null;
49+
}
50+
final int pos = name.lastIndexOf('.');
51+
if (pos > -1) {
52+
name = name.substring(pos + 1);
53+
}
54+
return camelCase(name).replaceFirst("Httpclient", "Generated");
55+
}
56+
57+
private static String camelCase(String name) {
58+
StringBuilder sb = new StringBuilder(name.length());
59+
boolean upper = true;
60+
for (char aChar : name.toCharArray()) {
61+
if (Character.isLetterOrDigit(aChar)) {
62+
if (upper) {
63+
aChar = Character.toUpperCase(aChar);
64+
upper = false;
65+
}
66+
sb.append(aChar);
67+
} else if (toUpperOn(aChar)) {
68+
upper = true;
69+
}
70+
}
71+
return sb.toString();
72+
}
73+
74+
private static boolean toUpperOn(char aChar) {
75+
return aChar == ' ' || aChar == '-' || aChar == '_';
76+
}
4977
}

http-generator-client/src/main/java/io/avaje/http/generator/client/ComponentReader.java

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package io.avaje.http.generator.client;
2+
23
import static io.avaje.http.generator.core.ProcessingContext.filer;
34
import static io.avaje.http.generator.core.ProcessingContext.logDebug;
45
import static io.avaje.http.generator.core.ProcessingContext.logWarn;
56
import static io.avaje.http.generator.core.ProcessingContext.typeElement;
7+
import static java.util.stream.Collectors.toList;
68

79
import java.io.FileNotFoundException;
810
import java.io.LineNumberReader;
@@ -11,57 +13,60 @@
1113
import java.util.ArrayList;
1214
import java.util.Collections;
1315
import java.util.List;
16+
import java.util.Map;
1417

1518
import javax.annotation.processing.FilerException;
16-
import javax.lang.model.element.AnnotationMirror;
19+
import javax.lang.model.element.Modifier;
1720
import javax.lang.model.element.TypeElement;
18-
import javax.lang.model.type.TypeMirror;
1921
import javax.tools.FileObject;
2022
import javax.tools.StandardLocation;
2123

24+
import io.avaje.http.generator.core.APContext;
2225
import io.avaje.http.generator.core.Constants;
2326
import io.avaje.prism.GeneratePrism;
2427

2528
@GeneratePrism(io.avaje.http.api.spi.MetaData.class)
2629
final class ComponentReader {
2730

2831
private final ComponentMetaData componentMetaData;
32+
private final Map<String, ComponentMetaData> privateMetaData;
2933

30-
ComponentReader(ComponentMetaData metaData) {
34+
ComponentReader(ComponentMetaData metaData, Map<String, ComponentMetaData> privateMetaData) {
3135
this.componentMetaData = metaData;
36+
this.privateMetaData = privateMetaData;
3237
}
3338

3439
void read() {
35-
final String componentFullName = loadMetaInfServices();
36-
if (componentFullName != null) {
37-
final TypeElement moduleType = typeElement(componentFullName);
40+
for (String fqn : loadMetaInf()) {
41+
final TypeElement moduleType = typeElement(fqn);
3842
if (moduleType != null) {
39-
componentMetaData.setFullName(componentFullName);
40-
readMetaData(moduleType);
41-
}
42-
}
43-
}
43+
var adapters =
44+
MetaDataPrism.getInstanceOn(moduleType).value().stream()
45+
.map(APContext::asTypeElement)
46+
.collect(toList());
4447

45-
/** Read the existing JsonAdapters from the MetaData annotation of the generated component. */
46-
private void readMetaData(TypeElement moduleType) {
47-
for (final AnnotationMirror annotationMirror : moduleType.getAnnotationMirrors()) {
48-
MetaDataPrism.getOptional(annotationMirror).map(MetaDataPrism::value).stream()
49-
.flatMap(List::stream)
50-
.map(TypeMirror::toString)
51-
.forEach(componentMetaData::add);
52-
}
53-
}
48+
if (adapters.get(0).getModifiers().contains(Modifier.PUBLIC)) {
49+
componentMetaData.setFullName(fqn);
50+
adapters.stream()
51+
.map(TypeElement::getQualifiedName)
52+
.map(Object::toString)
53+
.forEach(componentMetaData::add);
5454

55-
private String loadMetaInfServices() {
56-
final List<String> lines = loadMetaInf();
57-
return lines.isEmpty() ? null : lines.get(0);
55+
} else {
56+
var packageName = APContext.elements().getPackageOf(moduleType).getQualifiedName().toString();
57+
var meta = privateMetaData.computeIfAbsent(packageName, k -> new ComponentMetaData());
58+
adapters.stream()
59+
.map(TypeElement::getQualifiedName)
60+
.map(Object::toString)
61+
.forEach(meta::add);
62+
}
63+
}
64+
}
5865
}
5966

6067
private List<String> loadMetaInf() {
6168
try {
62-
final FileObject fileObject = filer()
63-
.getResource(StandardLocation.CLASS_OUTPUT, "", Constants.META_INF_COMPONENT);
64-
69+
final FileObject fileObject = filer().getResource(StandardLocation.CLASS_OUTPUT, "", Constants.META_INF_COMPONENT);
6570
if (fileObject != null) {
6671
final List<String> lines = new ArrayList<>();
6772
final Reader reader = fileObject.openReader(true);

http-generator-client/src/main/java/io/avaje/http/generator/client/SimpleComponentWriter.java

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,6 @@ void write() throws IOException {
4646
writeRegister();
4747
writeClassEnd();
4848
writer.close();
49-
writeMetaInf();
50-
}
51-
52-
void writeMetaInf() throws IOException {
53-
final FileObject fileObject = createMetaInfWriter(Constants.META_INF_COMPONENT);
54-
if (fileObject != null) {
55-
try (var fileWriter = fileObject.openWriter()) {
56-
fileWriter.write(fullName);
57-
}
58-
}
5949
}
6050

6151
private void writeRegister() {
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package io.avaje.http.generator.client;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import static org.assertj.core.api.Assertions.assertThat;
6+
7+
class ComponentMetaDataTest {
8+
9+
@Test
10+
void name() {
11+
assertThat(ComponentMetaData.name(null)).isNull();
12+
assertThat(ComponentMetaData.name("org.foo")).isEqualTo("Foo");
13+
assertThat(ComponentMetaData.name("org.fooBar")).isEqualTo("FooBar");
14+
assertThat(ComponentMetaData.name("org.FooBar")).isEqualTo("FooBar");
15+
assertThat(ComponentMetaData.name("org.FooBarHttpclient")).isEqualTo("FooBarGenerated");
16+
assertThat(ComponentMetaData.name("org.FooBarHttpclientAgainHttpclient")).isEqualTo("FooBarGeneratedAgainHttpclient");
17+
}
18+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package io.avaje.http.generator.client.clients;
2+
3+
import io.avaje.http.api.Client;
4+
import io.avaje.http.api.Get;
5+
import io.avaje.http.api.Header;
6+
7+
@Client
8+
interface PrivateClient {
9+
10+
@Get("/private")
11+
String apiCall(@Header("Accept") String accept);
12+
13+
}

0 commit comments

Comments
 (0)