Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[client] Fully support package private #551

Merged
merged 8 commits into from
Jan 26, 2025
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
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package io.avaje.http.generator.client;

import static io.avaje.http.generator.core.ProcessingContext.createMetaInfWriter;
import static io.avaje.http.generator.core.ProcessingContext.logError;
import static io.avaje.http.generator.core.ProcessingContext.platform;
import static io.avaje.http.generator.core.ProcessingContext.setPlatform;
import static io.avaje.http.generator.core.ProcessingContext.typeElement;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

Expand All @@ -15,10 +18,13 @@
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.tools.FileObject;

import io.avaje.http.generator.core.APContext;
import io.avaje.http.generator.core.ClientPrism;
import io.avaje.http.generator.core.Constants;
import io.avaje.http.generator.core.ControllerReader;
import io.avaje.http.generator.core.ImportPrism;
import io.avaje.http.generator.core.ProcessingContext;
Expand All @@ -29,9 +35,9 @@
public class ClientProcessor extends AbstractProcessor {

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

private SimpleComponentWriter componentWriter;

private boolean readModuleInfo;

@Override
Expand Down Expand Up @@ -76,7 +82,7 @@ private void readModule() {
return;
}
readModuleInfo = true;
new ComponentReader(metaData).read();
new ComponentReader(metaData, privateMetaData).read();
}

private void writeForImported(Element importedElement) {
Expand All @@ -91,39 +97,65 @@ private void writeClient(Element controller) {
final ControllerReader reader = new ControllerReader((TypeElement) controller);
reader.read(false);
try {
metaData.add(writeClientAdapter(reader));
var packagePrivate =
!controller.getModifiers().contains(Modifier.PUBLIC)
&& ClientPrism.isPresent(controller);
if (packagePrivate) {
var packageName = APContext.elements().getPackageOf(controller).getQualifiedName().toString();
var meta = privateMetaData.computeIfAbsent(packageName, k -> new ComponentMetaData());
meta.add(writeClientAdapter(reader, true));
} else {
metaData.add(writeClientAdapter(reader, false));
}

} catch (final Exception e) {
logError(reader.beanType(), "Failed to write client class " + e);
}
}
}

protected String writeClientAdapter(ControllerReader reader) throws IOException {
protected String writeClientAdapter(ControllerReader reader, boolean packagePrivate) throws IOException {
var suffix = ClientSuffix.fromInterface(reader.beanType().getQualifiedName().toString());
return new ClientWriter(reader, suffix).write();
}

private void initialiseComponent() {
metaData.initialiseFullName();
if (!metaData.all().isEmpty()) {
ProcessingContext.addClientComponent(metaData.fullName());
ProcessingContext.validateModule();
}
try {
componentWriter.init();
} catch (final IOException e) {
logError("Error creating writer for JsonbComponent", e);
}
return new ClientWriter(reader, suffix, packagePrivate).write();
}

private void writeComponent(boolean processingOver) {
initialiseComponent();
if (processingOver) {
try {
componentWriter.write();
if (!metaData.all().isEmpty()) {
ProcessingContext.addClientComponent(metaData.fullName());
componentWriter.init();
componentWriter.write();
}

for (var meta : privateMetaData.values()) {
ProcessingContext.addClientComponent(meta.fullName());
var writer = new SimpleComponentWriter(meta);
writer.init();
writer.write();
}
writeMetaInf();
ProcessingContext.validateModule();
} catch (final IOException e) {
logError("Error writing component", e);
}
}
}

void writeMetaInf() throws IOException {
final FileObject fileObject = createMetaInfWriter(Constants.META_INF_COMPONENT);
if (fileObject != null) {
try (var fileWriter = fileObject.openWriter()) {
if (!metaData.all().isEmpty()) {
fileWriter.write(metaData.fullName());
fileWriter.write("\n");
}

for (var meta : privateMetaData.values()) {
fileWriter.write(meta.fullName());
fileWriter.write("\n");
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.avaje.http.generator.client;

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

ClientWriter(ControllerReader reader, String suffix) throws IOException {
private final boolean packagePrivate;

ClientWriter(ControllerReader reader, String suffix, boolean packagePrivate) throws IOException {
super(reader, suffix);
this.suffix = suffix;
this.packagePrivate = packagePrivate;
reader.addImportType(HTTP_CLIENT);
readMethods();
}
Expand Down Expand Up @@ -76,12 +78,12 @@ private void writeMethods() {
private void writeClassStart() {
writer.append(AT_GENERATED).eol();
AnnotationUtil.writeAnnotations(writer, reader.beanType());

writer.append("public final class %s%s implements %s, AutoCloseable {", shortName, suffix, shortName).eol().eol();
var access = packagePrivate ? "" : "public ";
writer.append("%sfinal class %s%s implements %s, AutoCloseable {", access, shortName, suffix, shortName).eol().eol();

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

writer.append(" public %s%s(HttpClient client) {", shortName, suffix).eol();
writer.append(" %s%s%s(HttpClient client) {", access, shortName, suffix).eol();
writer.append(" this.client = client;").eol();
writer.append(" }").eol().eol();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,14 @@

final class ComponentMetaData {

private final List<String> generatedClients = new ArrayList<>();
private final Set<String> generatedClients = new HashSet<>();
private String fullName;

@Override
public String toString() {
return generatedClients.toString();
}

/** Ensure the component name has been initialised. */
void initialiseFullName() {
fullName();
}

void add(String type) {
generatedClients.add(type);
}
Expand All @@ -28,13 +23,13 @@ void setFullName(String fullName) {
String fullName() {
if (fullName == null) {
String topPackage = TopPackage.of(generatedClients);
fullName = topPackage + ".GeneratedHttpComponent";
fullName = topPackage + "." + name(topPackage) + "HttpComponent";
}
return fullName;
}

List<String> all() {
return generatedClients;
return new ArrayList<>(generatedClients);
}

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

return packageImports;
}


static String name(String name) {
if (name == null) {
return null;
}
final int pos = name.lastIndexOf('.');
if (pos > -1) {
name = name.substring(pos + 1);
}
return camelCase(name).replaceFirst("Httpclient", "Generated");
}

private static String camelCase(String name) {
StringBuilder sb = new StringBuilder(name.length());
boolean upper = true;
for (char aChar : name.toCharArray()) {
if (Character.isLetterOrDigit(aChar)) {
if (upper) {
aChar = Character.toUpperCase(aChar);
upper = false;
}
sb.append(aChar);
} else if (toUpperOn(aChar)) {
upper = true;
}
}
return sb.toString();
}

private static boolean toUpperOn(char aChar) {
return aChar == ' ' || aChar == '-' || aChar == '_';
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package io.avaje.http.generator.client;

import static io.avaje.http.generator.core.ProcessingContext.filer;
import static io.avaje.http.generator.core.ProcessingContext.logDebug;
import static io.avaje.http.generator.core.ProcessingContext.logWarn;
import static io.avaje.http.generator.core.ProcessingContext.typeElement;
import static java.util.stream.Collectors.toList;

import java.io.FileNotFoundException;
import java.io.LineNumberReader;
Expand All @@ -11,57 +13,60 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.annotation.processing.FilerException;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.FileObject;
import javax.tools.StandardLocation;

import io.avaje.http.generator.core.APContext;
import io.avaje.http.generator.core.Constants;
import io.avaje.prism.GeneratePrism;

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

private final ComponentMetaData componentMetaData;
private final Map<String, ComponentMetaData> privateMetaData;

ComponentReader(ComponentMetaData metaData) {
ComponentReader(ComponentMetaData metaData, Map<String, ComponentMetaData> privateMetaData) {
this.componentMetaData = metaData;
this.privateMetaData = privateMetaData;
}

void read() {
final String componentFullName = loadMetaInfServices();
if (componentFullName != null) {
final TypeElement moduleType = typeElement(componentFullName);
for (String fqn : loadMetaInf()) {
final TypeElement moduleType = typeElement(fqn);
if (moduleType != null) {
componentMetaData.setFullName(componentFullName);
readMetaData(moduleType);
}
}
}
var adapters =
MetaDataPrism.getInstanceOn(moduleType).value().stream()
.map(APContext::asTypeElement)
.collect(toList());

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

private String loadMetaInfServices() {
final List<String> lines = loadMetaInf();
return lines.isEmpty() ? null : lines.get(0);
} else {
var packageName = APContext.elements().getPackageOf(moduleType).getQualifiedName().toString();
var meta = privateMetaData.computeIfAbsent(packageName, k -> new ComponentMetaData());
adapters.stream()
.map(TypeElement::getQualifiedName)
.map(Object::toString)
.forEach(meta::add);
}
}
}
}

private List<String> loadMetaInf() {
try {
final FileObject fileObject = filer()
.getResource(StandardLocation.CLASS_OUTPUT, "", Constants.META_INF_COMPONENT);

final FileObject fileObject = filer().getResource(StandardLocation.CLASS_OUTPUT, "", Constants.META_INF_COMPONENT);
if (fileObject != null) {
final List<String> lines = new ArrayList<>();
final Reader reader = fileObject.openReader(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,16 +46,6 @@ void write() throws IOException {
writeRegister();
writeClassEnd();
writer.close();
writeMetaInf();
}

void writeMetaInf() throws IOException {
final FileObject fileObject = createMetaInfWriter(Constants.META_INF_COMPONENT);
if (fileObject != null) {
try (var fileWriter = fileObject.openWriter()) {
fileWriter.write(fullName);
}
}
}

private void writeRegister() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.avaje.http.generator.client;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class ComponentMetaDataTest {

@Test
void name() {
assertThat(ComponentMetaData.name(null)).isNull();
assertThat(ComponentMetaData.name("org.foo")).isEqualTo("Foo");
assertThat(ComponentMetaData.name("org.fooBar")).isEqualTo("FooBar");
assertThat(ComponentMetaData.name("org.FooBar")).isEqualTo("FooBar");
assertThat(ComponentMetaData.name("org.FooBarHttpclient")).isEqualTo("FooBarGenerated");
assertThat(ComponentMetaData.name("org.FooBarHttpclientAgainHttpclient")).isEqualTo("FooBarGeneratedAgainHttpclient");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package io.avaje.http.generator.client.clients;

import io.avaje.http.api.Client;
import io.avaje.http.api.Get;
import io.avaje.http.api.Header;

@Client
interface PrivateClient {

@Get("/private")
String apiCall(@Header("Accept") String accept);

}
Loading
Loading