
software.amazon.smithy.traitcodegen.writer.TraitCodegenWriter Maven / Gradle / Ivy
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package software.amazon.smithy.traitcodegen.writer;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Optional;
import java.util.Scanner;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import software.amazon.smithy.codegen.core.Symbol;
import software.amazon.smithy.codegen.core.SymbolReference;
import software.amazon.smithy.codegen.core.SymbolWriter;
import software.amazon.smithy.traitcodegen.SymbolProperties;
import software.amazon.smithy.traitcodegen.TraitCodegenSettings;
import software.amazon.smithy.traitcodegen.TraitCodegenUtils;
import software.amazon.smithy.utils.ListUtils;
import software.amazon.smithy.utils.StringUtils;
/**
* Writes Java code for trait definitions.
*
* This writer supports two custom formatters, a Java type formatter '$T' and
* a Base type formatter '$B'.
*
* - {@link JavaTypeFormatter}|{@code 'T'}
* - This formatter handles the formatting of
* Java types and also ensures that parameterized types (such as {@code List
} are
* written correctly.
* - {@link BaseTypeFormatter}|{@code 'B'}
* - This formatter allows you to use the base type
* for a trait. For example a String Trait may have a base type of {@code ShapeId}. To write
* this base type, use the {@code $B} formatter and provide the trait symbol. Note that
* if no base type is found (i.e. type is not a trait) then this formatter behaves exactly the
* same as the {@link JavaTypeFormatter}.
* - {@link CapitalizingFormatter}|{@code 'U'}
* - This formatter will capitalize the first letter of any string literal it is used to format.
*
*/
public class TraitCodegenWriter extends SymbolWriter {
private static final int MAX_LINE_LENGTH = 120;
private static final Pattern PATTERN = Pattern.compile("<([a-z]+)*>.*?\\1>", Pattern.DOTALL);
private final String namespace;
private final String fileName;
private final TraitCodegenSettings settings;
private final Map> symbolNames = new HashMap<>();
public TraitCodegenWriter(String fileName,
String namespace,
TraitCodegenSettings settings
) {
super(new TraitCodegenImportContainer(namespace));
this.namespace = namespace;
this.fileName = fileName;
this.settings = settings;
// Ensure extraneous white space is trimmed
trimBlankLines();
trimTrailingSpaces();
putFormatter('T', new JavaTypeFormatter());
putFormatter('B', new BaseTypeFormatter());
putFormatter('U', new CapitalizingFormatter());
}
private void addImport(Symbol symbol) {
addImport(symbol, symbol.getName());
}
/**
* Writes the provided text in the format of a Java doc string.
*
* @param contents text to format as a doc string.
*/
public void writeDocString(String contents) {
writeWithNoFormatting("/**");
// Split out any HTML-tag wrapped sections as we do not want to wrap
// any customer documentation with tags
Matcher matcher = PATTERN.matcher(contents);
int lastMatchPos = 0;
writeInlineWithNoFormatting(" * ");
while (matcher.find()) {
// write all contents up to the match.
writeDocStringLine(contents.substring(lastMatchPos, matcher.start()));
// write match contents
writeInlineWithNoFormatting(contents.substring(matcher.start(), matcher.end()).replace("\n", "\n * "));
lastMatchPos = matcher.end();
}
// Write out all remaining contents
writeDocStringLine(contents.substring(lastMatchPos));
writeWithNoFormatting("\n */");
}
private void writeDocStringLine(String string) {
for (Scanner it = new Scanner(string); it.hasNextLine();) {
String s = it.nextLine();
writeInlineWithNoFormatting(StringUtils.wrap(s, MAX_LINE_LENGTH, getNewline() + " * ", false));
if (it.hasNextLine()) {
writeInlineWithNoFormatting(getNewline() + " * ");
}
}
}
@Override
public String toString() {
// Do not add code headers to META-INF files
if (fileName.startsWith("META-INF")) {
return super.toString();
}
StringBuilder builder = new StringBuilder();
builder.append(getHeader()).append(getNewline());
builder.append(getPackageHeader()).append(getNewline());
builder.append(getImportContainer().toString()).append(getNewline());
// Handle duplicates that may need to use full name
resolveNameContext();
builder.append(format(super.toString()));
return builder.toString();
}
private void resolveNameContext() {
for (Map.Entry> entry : symbolNames.entrySet()) {
Set duplicates = entry.getValue();
// If the duplicates list has more than one entry
// then duplicates are present, and we need to de-dupe
if (duplicates.size() > 1) {
duplicates.forEach(dupe -> {
// If we are in the namespace of a Symbol, use its
// short name, otherwise use the full name
if (dupe.getNamespace().equals(namespace)) {
putContext(dupe.getFullName(), dupe.getName());
} else {
putContext(dupe.getFullName(), dupe.getFullName());
}
});
} else {
Symbol symbol = duplicates.iterator().next();
putContext(symbol.getFullName(), symbol.getName());
}
}
}
public String getPackageHeader() {
return String.format("package %s;%n", namespace);
}
public String getHeader() {
StringBuilder builder = new StringBuilder().append("/**").append(getNewline());
for (String line : settings.headerLines()) {
builder.append(" * ").append(line).append(getNewline());
}
builder.append(" */").append(getNewline());
return builder.toString();
}
public void newLine() {
writeInlineWithNoFormatting(getNewline());
}
public void override() {
writeWithNoFormatting("@Override");
}
/**
* A factory class to create {@link TraitCodegenWriter}s.
*/
public static final class Factory implements SymbolWriter.Factory {
private final TraitCodegenSettings settings;
/**
* @param settings The Trait codegen plugin settings.
*/
public Factory(TraitCodegenSettings settings) {
this.settings = settings;
}
@Override
public TraitCodegenWriter apply(String filename, String namespace) {
return new TraitCodegenWriter(filename, namespace, settings);
}
}
/**
* Implements a formatter for {@code $T} that formats Java types.
*/
private final class JavaTypeFormatter implements BiFunction
© 2015 - 2025 Weber Informatics LLC | Privacy Policy