Skip to content

Commit 29e1356

Browse files
committed
Codegen- option to generate a schema doc
1 parent 7ae0d27 commit 29e1356

File tree

7 files changed

+140
-13
lines changed

7 files changed

+140
-13
lines changed

hollow/src/main/java/com/netflix/hollow/api/codegen/AbstractHollowAPIGeneratorBuilder.java

+14-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ public abstract class AbstractHollowAPIGeneratorBuilder<B extends AbstractHollow
3636
protected boolean parameterizeAllClassNames = false;
3737
protected boolean useErgonomicShortcuts = false;
3838
protected Path destinationPath;
39-
4039
protected CodeGeneratorConfig config = new CodeGeneratorConfig();
4140

4241
protected abstract G instantiateGenerator();
@@ -141,6 +140,20 @@ public B withDestination(Path destinationPath) {
141140
return getBuilder();
142141
}
143142

143+
/**
144+
* Enable meta info (e.g. schema doc) and specify the path where it is to be generated.
145+
* @param metaInfoPath location for meta info
146+
* @return this builder
147+
*/
148+
public B withMetaInfo(String metaInfoPath) {
149+
return withMetaInfo(Paths.get(metaInfoPath));
150+
}
151+
152+
public B withMetaInfo(Path metaInfoPath) {
153+
config.setMetaInfoPath(metaInfoPath);
154+
return getBuilder();
155+
}
156+
144157
public G build() {
145158
if (apiClassname == null)
146159
throw new IllegalStateException("Please specify an API classname (.withAPIClassname()) before calling .build()");

hollow/src/main/java/com/netflix/hollow/api/codegen/CodeGeneratorConfig.java

+31
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package com.netflix.hollow.api.codegen;
1717

18+
import java.nio.file.Path;
19+
1820
public class CodeGeneratorConfig {
1921
private String classPostfix = "";
2022
private String getterPrefix = "";
@@ -28,6 +30,22 @@ public class CodeGeneratorConfig {
2830
private boolean restrictApiToFieldType = false;
2931
private boolean useVerboseToString = false;
3032

33+
private boolean useMetaInfo = false;
34+
private Path metaInfoPath;
35+
36+
public void setMetaInfoPath(Path metaInfoPath) {
37+
this.useMetaInfo = true;
38+
this.metaInfoPath = metaInfoPath;
39+
}
40+
41+
public boolean isUseMetaInfo() {
42+
return useMetaInfo;
43+
}
44+
45+
public Path getMetaInfoPath() {
46+
return metaInfoPath;
47+
}
48+
3149
public CodeGeneratorConfig() {}
3250

3351
public CodeGeneratorConfig(String classPostfix, String getterPrefix) {
@@ -134,6 +152,8 @@ public int hashCode() {
134152
result = prime * result + (useHollowPrimitiveTypes ? 1231 : 1237);
135153
result = prime * result + (usePackageGrouping ? 1231 : 1237);
136154
result = prime * result + (useVerboseToString ? 1231 : 1237);
155+
result = prime * result + (useMetaInfo ? 1231 : 1237);
156+
result = prime * result + ((metaInfoPath == null) ? 0 : metaInfoPath.hashCode());
137157
return result;
138158
}
139159

@@ -170,6 +190,13 @@ public boolean equals(Object obj) {
170190
return false;
171191
if (useVerboseToString != other.useVerboseToString)
172192
return false;
193+
if (useMetaInfo != other.useMetaInfo)
194+
return false;
195+
if (metaInfoPath == null) {
196+
if (other.metaInfoPath != null)
197+
return false;
198+
} else if (!metaInfoPath.equals(other.metaInfoPath))
199+
return false;
173200
return true;
174201
}
175202

@@ -194,6 +221,10 @@ public String toString() {
194221
builder.append(restrictApiToFieldType);
195222
builder.append(", useVerboseToString=");
196223
builder.append(useVerboseToString);
224+
builder.append(", useMetaInfo=");
225+
builder.append(useMetaInfo);
226+
builder.append(", metaInfoPath=");
227+
builder.append(metaInfoPath);
197228
builder.append("]");
198229
return builder.toString();
199230
}

hollow/src/main/java/com/netflix/hollow/api/codegen/HollowAPIClassJavaGenerator.java

+5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
package com.netflix.hollow.api.codegen;
1818

19+
import static com.netflix.hollow.api.codegen.HollowAPIGenerator.SCHEMA_DOC_SUFFIX;
1920
import static com.netflix.hollow.api.codegen.HollowCodeGenerationUtils.hollowFactoryClassname;
2021
import static com.netflix.hollow.api.codegen.HollowCodeGenerationUtils.hollowObjectProviderName;
2122
import static com.netflix.hollow.api.codegen.HollowCodeGenerationUtils.lowercase;
@@ -113,6 +114,10 @@ public String generate() {
113114
}
114115
builder.append(" {\n\n");
115116

117+
if (config.isUseMetaInfo()) {
118+
builder.append(" public static final String SCHEMA_DOC = \"" + packageName + "." + className + SCHEMA_DOC_SUFFIX + "\";\n\n");
119+
}
120+
116121
builder.append(" private final HollowObjectCreationSampler objectCreationSampler;\n\n");
117122

118123
for (HollowSchema schema : schemaList) {

hollow/src/main/java/com/netflix/hollow/api/codegen/HollowAPIGenerator.java

+32
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import com.netflix.hollow.core.schema.HollowObjectSchema;
4141
import com.netflix.hollow.core.schema.HollowSchema;
4242
import com.netflix.hollow.core.schema.HollowSchema.SchemaType;
43+
import com.netflix.hollow.core.schema.HollowSchemaSorter;
4344
import com.netflix.hollow.core.schema.HollowSetSchema;
4445
import com.netflix.hollow.core.util.HollowWriteStateCreator;
4546
import com.netflix.hollow.core.write.HollowWriteStateEngine;
@@ -50,6 +51,7 @@
5051
import java.nio.file.Path;
5152
import java.nio.file.Paths;
5253
import java.util.Collections;
54+
import java.util.List;
5355
import java.util.Set;
5456

5557
/**
@@ -118,6 +120,8 @@ public enum GeneratorArguments {
118120

119121
protected CodeGeneratorConfig config = new CodeGeneratorConfig("Hollow", "_"); // NOTE: to be backwards compatible
120122

123+
protected static final String SCHEMA_DOC_SUFFIX = ".schema";
124+
121125
/**
122126
* @param apiClassname the class name of the generated implementation of {@link HollowAPI}
123127
* @param packageName the package name under which all generated classes will be placed
@@ -434,6 +438,10 @@ public void generateFiles(File directory) throws IOException {
434438
generateFile(directory, hashIndexGenerator);
435439

436440
generateFilesForHollowSchemas(directory);
441+
442+
if (config.isUseMetaInfo()) {
443+
generateMetaInfo(apiClassname);
444+
}
437445
}
438446

439447
/**
@@ -509,6 +517,30 @@ protected void generateFile(File directory, HollowJavaFileGenerator generator) t
509517
writer.close();
510518
}
511519

520+
private void generateMetaInfo(String apiClassname) throws IOException {
521+
if (config.getMetaInfoPath() == null) {
522+
throw new IllegalStateException("Meta info generation is enabled but path for generating meta info not set");
523+
}
524+
525+
File metaInfoDir = config.getMetaInfoPath().toFile();
526+
if (!metaInfoDir.exists()) metaInfoDir.mkdirs();
527+
528+
// top-level types first
529+
List<HollowSchema> schemas = HollowSchemaSorter.dependencyOrderedSchemaList(dataset);
530+
Collections.reverse(schemas);
531+
532+
// line-separated {@code HollowSchema}s for readability
533+
StringBuilder builder = new StringBuilder();
534+
for(HollowSchema schema : schemas) {
535+
builder.append(schema.toString()).append("\n");
536+
}
537+
String schemaDoc = builder.toString();
538+
539+
FileWriter writer = new FileWriter(new File(metaInfoDir, packageName + "." + apiClassname + SCHEMA_DOC_SUFFIX));
540+
writer.write(schemaDoc);
541+
writer.close();
542+
}
543+
512544
protected HollowJavaFileGenerator getStaticAPIGenerator(HollowSchema schema) {
513545
if(schema instanceof HollowObjectSchema) {
514546
return new TypeAPIObjectJavaGenerator(apiClassname, packageName, (HollowObjectSchema) schema, dataset, config);

hollow/src/test/java/com/netflix/hollow/api/codegen/AbstractHollowAPIGeneratorTest.java

+12-7
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,16 @@
2626
import java.lang.annotation.Annotation;
2727
import java.net.URL;
2828
import java.net.URLClassLoader;
29+
import java.nio.file.Path;
30+
import java.nio.file.Paths;
2931
import java.util.function.UnaryOperator;
3032
import org.junit.After;
3133

3234
public class AbstractHollowAPIGeneratorTest {
33-
private String tmpFolder = System.getProperty("java.io.tmpdir");
35+
protected String tmpFolder = System.getProperty("java.io.tmpdir");
3436
protected String sourceFolder = String.format("%s/src", tmpFolder);
3537
protected String clazzFolder = String.format("%s/classes", tmpFolder);
38+
private Path metaInfoPath = null;
3639

3740
void runGenerator(String apiClassName, String packageName, Class<?> clazz,
3841
UnaryOperator<HollowAPIGenerator.Builder> generatorCustomizer) throws Exception {
@@ -49,16 +52,15 @@ void runGenerator(String apiClassName, String packageName, Class<?> clazz,
4952
.withDestination(sourceFolder).build();
5053
generator.generateSourceFiles();
5154

55+
if(generator.config.isUseMetaInfo()) {
56+
metaInfoPath = generator.config.getMetaInfoPath();
57+
}
5258
// Compile to validate generated files
5359
HollowCodeGenerationCompileUtil.compileSrcFiles(sourceFolder, clazzFolder);
5460
}
5561

56-
protected void assertNonEmptyFileExists(String relativePath) throws IOException {
57-
if (relativePath.startsWith("/")) {
58-
throw new IllegalArgumentException("Relative paths should not start with /");
59-
}
60-
File f = new File(sourceFolder + "/" + relativePath);
61-
assertTrue("File at " + relativePath + " should exist", f.exists() && f.length() > 0L);
62+
protected void assertNonEmptyFileExists(Path absolutePath) {
63+
assertTrue("File at " + absolutePath + " should exist", absolutePath.toFile().exists() && absolutePath.toFile().length() > 0L);
6264
}
6365

6466
void assertClassHasHollowTypeName(String clazz, String typeName) throws IOException, ClassNotFoundException {
@@ -81,5 +83,8 @@ void assertFileDoesNotExist(String relativePath) {
8183
public void cleanup() {
8284
HollowCodeGenerationCompileUtil.cleanupFolder(new File(sourceFolder), null);
8385
HollowCodeGenerationCompileUtil.cleanupFolder(new File(clazzFolder), null);
86+
if (metaInfoPath != null) {
87+
HollowCodeGenerationCompileUtil.cleanupFolder(metaInfoPath.toFile(), null);
88+
}
8489
}
8590
}

hollow/src/test/java/com/netflix/hollow/api/codegen/HollowAPIGeneratorTest.java

+42-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,48 @@
11
package com.netflix.hollow.api.codegen;
22

3+
import static org.junit.Assert.assertEquals;
4+
5+
import com.netflix.hollow.core.schema.HollowSchema;
6+
import com.netflix.hollow.core.schema.HollowSchemaParser;
7+
import com.netflix.hollow.core.schema.SimpleHollowDataset;
8+
import com.netflix.hollow.core.write.objectmapper.HollowPrimaryKey;
9+
import java.io.BufferedReader;
10+
import java.io.FileInputStream;
11+
import java.io.InputStream;
12+
import java.io.InputStreamReader;
13+
import java.nio.file.Paths;
14+
import java.util.HashSet;
15+
import java.util.List;
316
import org.junit.Test;
417

518
public class HollowAPIGeneratorTest extends AbstractHollowAPIGeneratorTest {
619

20+
@Test
21+
public void assertMetaInfoAtCustomLocation() throws Exception {
22+
runGenerator("API", "codegen.api", MyClass.class, b -> b.withMetaInfo("meta-info"));
23+
assertNonEmptyFileExists(Paths.get("meta-info"));
24+
}
25+
26+
@Test
27+
public void testSchemaDocAtCustomLocation() throws Exception {
28+
runGenerator("MyClassTestAPI", "codegen.api", MyClass.class,
29+
builder -> builder.withMetaInfo(tmpFolder + "/" + "resources/META-INF/hollow"));
30+
assertNonEmptyFileExists(Paths.get(tmpFolder, "resources/META-INF/hollow/codegen.api.MyClassTestAPI.schema"));
31+
}
32+
33+
@Test
34+
public void testSchemaDocContents() throws Exception {
35+
runGenerator("MyClassTestAPI", "codegen.api", MyClass.class, b -> b.withMetaInfo(Paths.get(sourceFolder)));
36+
assertNonEmptyFileExists(Paths.get(sourceFolder, "codegen.api.MyClassTestAPI.schema"));
37+
38+
List<HollowSchema> expected = SimpleHollowDataset.fromClassDefinitions(MyClass.class).getSchemas();
39+
try (InputStream input = new FileInputStream(sourceFolder + "/" + "codegen.api.MyClassTestAPI.schema")) {
40+
List<HollowSchema> actual = HollowSchemaParser.parseCollectionOfSchemas(new BufferedReader(new InputStreamReader(input)));
41+
assertEquals(expected.size(), actual.size());
42+
assertEquals(new HashSet(expected), new HashSet(actual));
43+
}
44+
}
45+
746
@Test
847
public void generatesFileUsingDestinationPath() throws Exception {
948
runGenerator("API", "com.netflix.hollow.example.api.generated", MyClass.class, b -> b);
@@ -13,15 +52,15 @@ public void generatesFileUsingDestinationPath() throws Exception {
1352
public void testGenerateWithPostfix() throws Exception {
1453
runGenerator("MyClassTestAPI", "codegen.api", MyClass.class,
1554
builder -> builder.withClassPostfix("Generated"));
16-
assertNonEmptyFileExists("codegen/api/StringGenerated.java");
55+
assertNonEmptyFileExists(Paths.get(sourceFolder, "codegen/api/StringGenerated.java"));
1756
assertClassHasHollowTypeName("codegen.api.MyClassGenerated", "MyClass");
1857
}
1958

2059
@Test
2160
public void testGenerateWithPostfixAndPackageGrouping() throws Exception {
2261
runGenerator("MyClassTestAPI", "codegen.api", MyClass.class,
2362
builder -> builder.withClassPostfix("Generated").withPackageGrouping());
24-
assertNonEmptyFileExists("codegen/api/core/StringGenerated.java");
63+
assertNonEmptyFileExists(Paths.get(sourceFolder, "codegen/api/core/StringGenerated.java"));
2564
}
2665

2766
@Test
@@ -43,6 +82,7 @@ public void testGenerateWithPostfixAndAggressiveSubstitutions() throws Exception
4382
}
4483

4584
@SuppressWarnings("unused")
85+
@HollowPrimaryKey(fields = "id")
4686
private static class MyClass {
4787
int id;
4888
String foo;

hollow/src/test/java/com/netflix/hollow/api/codegen/perfapi/HollowPerformanceAPIGeneratorTest.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.netflix.hollow.api.codegen.HollowCodeGenerationCompileUtil;
55
import com.netflix.hollow.core.schema.SimpleHollowDataset;
66
import java.io.File;
7+
import java.nio.file.Paths;
78
import org.junit.Test;
89

910
public class HollowPerformanceAPIGeneratorTest extends AbstractHollowAPIGeneratorTest {
@@ -16,9 +17,9 @@ public void testGeneratePerformanceApi() throws Exception {
1617
@Test
1718
public void testGeneratedFilesArePlacedInPackageDirectory() throws Exception {
1819
runGenerator("API", "codegen.api", MyClass.class);
19-
assertNonEmptyFileExists("codegen/api/MyClassPerfAPI.java");
20-
assertNonEmptyFileExists("codegen/api/StringPerfAPI.java");
21-
assertNonEmptyFileExists("codegen/api/API.java");
20+
assertNonEmptyFileExists(Paths.get(sourceFolder, "codegen/api/MyClassPerfAPI.java"));
21+
assertNonEmptyFileExists(Paths.get(sourceFolder, "codegen/api/StringPerfAPI.java"));
22+
assertNonEmptyFileExists(Paths.get(sourceFolder, "codegen/api/API.java"));
2223
}
2324

2425
private void runGenerator(String apiClassName, String packageName, Class<?> clazz) throws Exception {

0 commit comments

Comments
 (0)