All Downloads are FREE. Search and download functionalities are using the official Maven repository.

it.auties.protobuf.tool.schema.ProtobufSchemaCreator Maven / Gradle / Ivy

There is a newer version: 2.0.5
Show newest version
package it.auties.protobuf.tool.schema;

import it.auties.protobuf.parser.statement.ProtobufDocument;
import it.auties.protobuf.parser.statement.ProtobufEnumStatement;
import it.auties.protobuf.parser.statement.ProtobufMessageStatement;
import it.auties.protobuf.parser.statement.ProtobufObject;
import lombok.SneakyThrows;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtEnum;
import spoon.reflect.declaration.CtEnumValue;
import spoon.reflect.declaration.CtType;
import spoon.reflect.factory.Factory;
import spoon.support.reflect.declaration.CtClassImpl;
import spoon.support.reflect.declaration.CtEnumImpl;
import spoon.support.reflect.declaration.CtFieldImpl;
import spoon.support.reflect.declaration.CtMethodImpl;

import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.*;
import java.util.regex.MatchResult;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public record ProtobufSchemaCreator(ProtobufDocument document, File directory) {
    private static final List> ORDER = List.of(
            CtFieldImpl.class,
            CtEnumValue.class,
            CtMethodImpl.class,
            CtEnumImpl.class,
            CtClassImpl.class
    );

    public ProtobufSchemaCreator(ProtobufDocument document){
        this(document, null);
    }

    public List generate(Factory factory, boolean accessors) {
        return document.statements()
                .stream()
                .map(statement -> generate(statement, accessors, factory))
                .toList();
    }

    public Path generate(ProtobufObject object,  boolean accessors, Factory factory) {
        Objects.requireNonNull(directory, "Cannot generate files without a target directory");
        var path = Path.of(directory.getPath(), "/%s.java".formatted(object.name()));
        return generate(object, accessors, factory, path);
    }

    public Path generate(ProtobufObject object, boolean accessors, Factory factory, Path path) {
        var schemaCreator = findGenerator(object, accessors, factory);
        var schema = schemaCreator.createSchema();
        sortMembers(schema);
        return writeFile(path, schema.toStringWithImports());
    }

    private SchemaCreator findGenerator(ProtobufObject object,  boolean accessors, Factory factory) {
        if (object instanceof ProtobufMessageStatement msg) {
            return new MessageSchemaCreator(msg, accessors, factory);
        }

        if (object instanceof ProtobufEnumStatement enm) {
            return new EnumSchemaCreator(enm, accessors, factory);
        }

        throw new IllegalArgumentException("Cannot find a schema generator for statement %s(%s)".formatted(object.name(), object.getClass().getName()));
    }

    @SneakyThrows
    public void update(CtType element, ProtobufObject statement, Path path, boolean accessors, boolean forceUpdate) {
        var originalType = element.clone();
        originalType.setAnnotations(new ArrayList<>(originalType.getAnnotations()));
        var schemaCreator = findSchemaUpdater(element, statement, accessors);
        var schema = schemaCreator.update();
        if(Objects.equals(originalType, schema) && !forceUpdate){
            return;
        }

        sortMembers(schema);
        var result = schema.toString();
        writeFile(
                path,
                withOldImports(Files.readString(path), result)
        );
    }

    private String withOldImports(String oldMeta, String newMeta){
        var packageMatcher = Pattern.compile("(?<=package )(.*)(?=;)")
                .matcher(oldMeta)
                .results()
                .findFirst()
                .map(MatchResult::group)
                .map("package %s;\n"::formatted)
                .orElse("");
        var importMatcher = Pattern.compile("(?<=import )(.*)(?=;)")
                .matcher(oldMeta)
                .results()
                .map(MatchResult::group)
                .map("import %s;"::formatted)
                .collect(Collectors.toSet());
        importMatcher.add("import it.auties.protobuf.base.ProtobufName;");
        importMatcher.add("import it.auties.protobuf.base.ProtobufType;");
        importMatcher.add("import lombok.*;");
        importMatcher.add("import lombok.experimental.*;");
        importMatcher.add("import static it.auties.protobuf.base.ProtobufType.*;");
        importMatcher.add("import java.util.*;");
        var imports = String.join("\n", importMatcher);
        return "%s%s%s".formatted(
                packageMatcher,
                imports.isBlank() ? imports : "%s\n".formatted(imports),
                newMeta
        );
    }

    private SchemaCreator findSchemaUpdater(CtType element, ProtobufObject statement, boolean accessors) {
        if (statement instanceof ProtobufMessageStatement msg) {
            return new MessageSchemaCreator((CtClass) element, msg, accessors, element.getFactory());
        }

        if (statement instanceof ProtobufEnumStatement enm) {
            return new EnumSchemaCreator((CtEnum) element, enm, accessors, element.getFactory());
        }

        throw new IllegalArgumentException("Cannot find a schema updater for statement");
    }

    private Path writeFile(Path path, String formattedSchema) {
        try {
            Files.createDirectories(path.getParent());
            Files.writeString(path, formattedSchema,
                    StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
            return path;
        }catch (IOException exception){
            throw new UncheckedIOException("Cannot write schema to file", exception);
        }
    }

    private void sortMembers(CtType schema) {
        var parsed = schema.getTypeMembers()
                .stream()
                .sorted(Comparator.comparingInt((entry) -> ORDER.indexOf(entry.getClass())))
                .toList();
        schema.setTypeMembers(parsed);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy