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

Smithy Generator Experiment #318

Closed
wants to merge 2 commits into from
Closed
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
31 changes: 31 additions & 0 deletions java-codegen/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
plugins {
java
id("com.github.johnrengelman.shadow") version "7.1.2"
}

group = "org.opensearch.client"
version = "2.1.1-SNAPSHOT"

repositories {
mavenCentral()
}

dependencies {
val smithyVersion = "1.26.1"
val junitVersion = "5.8.2"

implementation("org.jetbrains:annotations:24.0.0")

implementation("software.amazon.smithy:smithy-codegen-core:$smithyVersion")
implementation("software.amazon.smithy:smithy-waiters:$smithyVersion")
implementation("software.amazon.smithy:smithy-rules-engine:$smithyVersion")
implementation("software.amazon.smithy:smithy-aws-traits:$smithyVersion")
implementation("software.amazon.smithy:smithy-protocol-test-traits:$smithyVersion")

// Test dependencies
testImplementation("org.junit.jupiter:junit-jupiter:$junitVersion")
}

tasks.getByName<Test>("test") {
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package org.opensearch.client.codegen;

import org.opensearch.client.codegen.core.GenerationContext;
import org.opensearch.client.codegen.core.JavaDelegator;
import org.opensearch.client.codegen.core.JavaSymbolProvider;
import org.opensearch.client.codegen.core.RenderingContext;
import org.opensearch.client.codegen.model.OperationNormalizer;
import org.opensearch.client.codegen.render.ServiceGenerator;
import org.opensearch.client.codegen.render.StructureGenerator;
import software.amazon.smithy.build.FileManifest;
import software.amazon.smithy.build.PluginContext;
import software.amazon.smithy.codegen.core.Symbol;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.knowledge.HttpBindingIndex;
import software.amazon.smithy.model.neighbor.Walker;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeVisitor;
import software.amazon.smithy.model.shapes.StructureShape;
import software.amazon.smithy.model.transform.ModelTransformer;

import java.util.logging.Logger;

public class CodegenVisitor extends ShapeVisitor.Default<Void> {
private static final Logger LOGGER = Logger.getLogger(CodegenVisitor.class.getName());
private final OpenSearchJavaSettings settings;
private final Model model;
private final ServiceShape service;
private final JavaSymbolProvider symbolProvider;
private final JavaDelegator writers;
private final GenerationContext generationContext;


public CodegenVisitor(PluginContext context) {
settings = OpenSearchJavaSettings.from(context.getModel(), context.getSettings());
model = baselineTransform(context.getModel());
service = settings.getService(model);
FileManifest fileManifest = context.getFileManifest();
symbolProvider = new JavaSymbolProvider(model);
HttpBindingIndex httpBindingIndex = HttpBindingIndex.of(model);
writers = new JavaDelegator(fileManifest, symbolProvider);
generationContext = new GenerationContext(model, symbolProvider, httpBindingIndex, settings);
}

private Model baselineTransform(Model model) {
model = ModelTransformer.create().flattenAndRemoveMixins(model);
model = ModelTransformer.create().copyServiceErrorsToOperations(model, settings.getService(model));
model = OperationNormalizer.transform(model);
return model;
}

public void execute() {
new Walker(model)
.walkShapes(service)
.forEach(s -> s.accept(this));
writers.flushWriters();
}

@Override
protected Void getDefault(Shape shape) {
return null;
}

@Override
public Void serviceShape(ServiceShape shape) {
LOGGER.info("Generating structure " + shape.getId().getName());

writers.useShapeWriter(shape, w -> {
RenderingContext<ServiceShape> ctx = new RenderingContext<>(generationContext, w, shape);
new ServiceGenerator(ctx).render();
});

Symbol asyncSymbol = symbolProvider.serviceAsyncSymbol(shape);
writers.useSymbolWriter(asyncSymbol, w -> {
RenderingContext<ServiceShape> ctx = new RenderingContext<>(generationContext, w, shape, asyncSymbol);
new ServiceGenerator(ctx).render();
});

return null;
}

@Override
public Void structureShape(StructureShape shape) {
writers.useShapeWriter(shape, w -> {
RenderingContext<StructureShape> ctx = new RenderingContext<>(generationContext, w, shape);
new StructureGenerator(ctx).render();
});

return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.opensearch.client.codegen;

import software.amazon.smithy.build.PluginContext;
import software.amazon.smithy.build.SmithyBuildPlugin;

public class OpenSearchJavaCodegenPlugin implements SmithyBuildPlugin {
@Override
public String getName() {
return "opensearch-java";
}

@Override
public void execute(PluginContext context) {
new CodegenVisitor(context).execute();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.opensearch.client.codegen;

import software.amazon.smithy.codegen.core.CodegenException;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.node.ObjectNode;
import software.amazon.smithy.model.node.StringNode;
import software.amazon.smithy.model.shapes.ServiceShape;
import software.amazon.smithy.model.shapes.Shape;
import software.amazon.smithy.model.shapes.ShapeId;

import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import java.util.stream.Collectors;

public class OpenSearchJavaSettings {
private static final Logger LOGGER = Logger.getLogger(OpenSearchJavaSettings.class.getName());
private static final String SERVICE = "service";
private final ShapeId service;

private OpenSearchJavaSettings(ShapeId service) {
this.service = service;
}

public ServiceShape getService(Model model) {
return model.expectShape(service, ServiceShape.class);
}

public static OpenSearchJavaSettings from(Model model, ObjectNode config) {
config.warnIfAdditionalProperties(Collections.singletonList(SERVICE));

ShapeId service = config.getStringMember(SERVICE).map(StringNode::expectShapeId).orElseGet(() -> inferService(model));

return new OpenSearchJavaSettings(service);
}

private static ShapeId inferService(Model model) {
List<ShapeId> services = model.shapes(ServiceShape.class).map(Shape::getId).sorted().collect(Collectors.toList());

if (services.isEmpty()) {
throw new CodegenException("Cannot infer a service to generate because the model does not contain any service shapes");
}
if (services.size() > 1) {
throw new CodegenException("Cannot infer a service to generate because the model contains multiple service shapes: " + services);
}

ShapeId service = services.get(0);
LOGGER.info("Inferring service to generate as: " + service);
return service;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.opensearch.client.codegen.core;

import org.opensearch.client.codegen.OpenSearchJavaSettings;
import software.amazon.smithy.codegen.core.SymbolProvider;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.knowledge.HttpBindingIndex;

public interface CodegenContext {
Model getModel();

SymbolProvider getSymbolProvider();

HttpBindingIndex getHttpBindingIndex();

OpenSearchJavaSettings getSettings();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package org.opensearch.client.codegen.core;

import org.opensearch.client.codegen.OpenSearchJavaSettings;
import software.amazon.smithy.codegen.core.SymbolProvider;
import software.amazon.smithy.model.Model;
import software.amazon.smithy.model.knowledge.HttpBindingIndex;

public class GenerationContext implements CodegenContext {
private final Model model;
private final SymbolProvider symbolProvider;
private final HttpBindingIndex httpBindingIndex;
private final OpenSearchJavaSettings settings;

public GenerationContext(Model model, SymbolProvider symbolProvider, HttpBindingIndex httpBindingIndex, OpenSearchJavaSettings settings) {
this.model = model;
this.symbolProvider = symbolProvider;
this.httpBindingIndex = httpBindingIndex;
this.settings = settings;
}

@Override
public Model getModel() {
return model;
}

@Override
public SymbolProvider getSymbolProvider() {
return symbolProvider;
}

@Override
public HttpBindingIndex getHttpBindingIndex() {
return httpBindingIndex;
}

@Override
public OpenSearchJavaSettings getSettings() {
return settings;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.opensearch.client.codegen.core;

import software.amazon.smithy.codegen.core.CodegenException;
import software.amazon.smithy.codegen.core.ImportContainer;
import software.amazon.smithy.codegen.core.Symbol;

import java.util.HashSet;
import java.util.Objects;
import java.util.stream.Collectors;

public class ImportStatements implements ImportContainer {
private final String packageName;
private final HashSet<ImportStatement> imports = new HashSet<>();

public ImportStatements(String packageName) {
this.packageName = packageName;
}

@Override
public void importSymbol(Symbol symbol, String alias) {
String name = symbol.getName();
String namespace = symbol.getNamespace();

if (!name.equals(alias)) {
throw new CodegenException("Java does not allow import aliasing");
}

if (!namespace.isEmpty() && !namespace.equals(packageName)) {
imports.add(new ImportStatement(namespace, name));
}
}

@Override
public String toString() {
return imports
.stream()
.map(ImportStatement::toString)
.sorted()
.collect(Collectors.joining("\n"));
}

private static class ImportStatement {
private final String packageName;
private final String symbolName;

public ImportStatement(String packageName, String symbolName) {
this.packageName = packageName;
this.symbolName = symbolName;
}

@Override
public String toString() {
return "import " + packageName + "." + symbolName + ";";
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ImportStatement that = (ImportStatement) o;
return Objects.equals(packageName, that.packageName) && Objects.equals(symbolName, that.symbolName);
}

@Override
public int hashCode() {
return Objects.hash(packageName, symbolName);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.opensearch.client.codegen.core;

import software.amazon.smithy.build.FileManifest;
import software.amazon.smithy.codegen.core.Symbol;
import software.amazon.smithy.codegen.core.SymbolProvider;
import software.amazon.smithy.codegen.core.WriterDelegator;
import software.amazon.smithy.model.shapes.Shape;

import java.util.function.Consumer;

public class JavaDelegator {
private final WriterDelegator<JavaWriter> inner;

public JavaDelegator(FileManifest fileManifest, SymbolProvider symbolProvider) {
inner = new WriterDelegator<>(fileManifest, symbolProvider, JavaWriter.factory());
}

public void useShapeWriter(Shape shape, Consumer<JavaWriter> writerConsumer) {
inner.useShapeWriter(shape, writerConsumer);
}

public void useSymbolWriter(Symbol symbol, Consumer<JavaWriter> writerConsumer) {
inner.useFileWriter(symbol.getDefinitionFile(), symbol.getNamespace(), writerConsumer);
}

public void flushWriters() {
inner.flushWriters();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.opensearch.client.codegen.core;

import software.amazon.smithy.codegen.core.ReservedWords;
import software.amazon.smithy.codegen.core.ReservedWordsBuilder;

public class JavaReservedWords {
private static final String[] HARD_RESERVED_WORDS = {
"null"
};
private static final ReservedWords INSTANCE;

static {
ReservedWordsBuilder builder = new ReservedWordsBuilder();
for (String word : HARD_RESERVED_WORDS) {
builder.put(word, "_" + word);
}
INSTANCE = builder.build();
}

public static ReservedWords instance() {
return INSTANCE;
}
}
Loading