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

javax0.jamal.plantuml.PlantUml Maven / Gradle / Ivy

There is a newer version: 2.5.0
Show newest version
package javax0.jamal.plantuml;

import javax0.jamal.api.BadSyntax;
import javax0.jamal.api.InnerScopeDependent;
import javax0.jamal.api.Input;
import javax0.jamal.api.Macro;
import javax0.jamal.api.Processor;
import javax0.jamal.tools.Cache;
import javax0.jamal.tools.FileTools;
import javax0.jamal.tools.IndexedPlaceHolders;
import javax0.jamal.tools.InputHandler;
import javax0.jamal.tools.Params;
import net.sourceforge.plantuml.FileFormat;
import net.sourceforge.plantuml.FileFormatOption;
import net.sourceforge.plantuml.SourceStringReader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Map;

public class PlantUml implements Macro, InnerScopeDependent {
    private static class Trie {
        static final IndexedPlaceHolders formatter = IndexedPlaceHolders.with(
                "$file"
        );
    }

    @Override
    public String evaluate(Input in, Processor processor) throws BadSyntax {
        final var root = Params.holder("pu$folder", "folder").orElse("./");
        final var format = Params.holder("pu$format", "format").orElse("SVG");
        final var template = Params.holder("pu$template", "template").orElse("$file");
        Params.using(processor).from(this).keys(root, format, template).between("()").parse(in);
        final var fileName = InputHandler.fetch2EOL(in).trim();
        final var imageDir = root.get().endsWith("/") ? root.get() : root.get() + "/";
        final var absoluteFileName = FileTools.absolute(in.getReference(), imageDir + fileName);
        InputHandler.skipWhiteSpaces(in);
        final var umlText = getUmlText(in);
        final var output = new File(absoluteFileName).getAbsoluteFile();
        final var entry = getCacheEntry(output);
        try {
            final boolean erred;
            if (needPlantUmlRun(umlText, output, entry)) {
                erred = convertToFile(umlText, output, format.get());
                if (entry != null) {
                    //noinspection unchecked
                    entry.save(umlText, Map.of("error", "" + erred));
                }
            } else {
                erred = "true".equals(entry.getProperty("error"));
            }
            BadSyntax.when(erred,  "There was an error processing diagram for '%s' in PlantUml.", fileName);
            return Trie.formatter.format(template.get(), fileName);
        } catch (Exception e) {
            throw new BadSyntax("PlantUml diagram '" + fileName + "'cannot be created.", e);
        }
    }

    /**
     * Convert the text to diagram and save the diagram to the file.
     *
     * @param text   the string that contains the textual description of the diagram
     * @param file   the file where the output has to be written
     * @param format the string representation of the format. like {@code SVG}, {@code PNG}, ...
     * @return {@code true} if the conversion was erroneous, {@code false} otherwise
     * @throws BadSyntax   if the format is not supported
     * @throws IOException if the file cannot be written
     */
    private boolean convertToFile(String text, File file, String format) throws BadSyntax, IOException {
        final boolean erred;
        final var reader = new SourceStringReader(text);
        FileFormat fileFormat = convertFileFormat(format);
        final var os = new ByteArrayOutputStream();
        try (os) {
            erred = "(Error)".equals(reader.outputImage(os, new FileFormatOption(fileFormat)).getDescription());
        }
        //noinspection ResultOfMethodCallIgnored
        file.getParentFile().mkdirs();
        try (final var fos = new FileOutputStream(file)) {
            fos.write(os.toByteArray());
        }
        return erred;
    }

    private boolean needPlantUmlRun(String umlText, File output, Cache.Entry entry) {
        return entry == null || entry.isMiss() || !entry.getContent().toString().equals(umlText) || !output.exists();
    }

    private Cache.Entry getCacheEntry(File output) {
        try {
            return Cache.getEntry(output.toURI().toURL());
        } catch (MalformedURLException e) {
            return null;
        }
    }

    /**
     * Convert the string to the enum value. In case the string does not represent any of the enum values then throw
     * BadSyntax.
     *
     * @param format the string that describes the output format of plantUML, like SVG, PNG...
     * @return the enum value
     * @throws BadSyntax if the string does not represent any of the enumerated values
     */
    private FileFormat convertFileFormat(String format) throws BadSyntax {
        for (final var fileFormat : FileFormat.values()) {
            if (format.equals(fileFormat.toString())) {
                return fileFormat;
            }
        }
        throw new BadSyntax("The output format '" + format + "' is not supported by PlantUml.");
    }

    /**
     * Get the diagram describing string from the input. This method does not consume the input. If the input contains
     * the
     * 
{@code
     * @startuml
     * }
*

* and * *

{@code
     * @enduml
     * }
*

* lines, then leave it there. If they are not there, then the method prepends and appends the lines. * * @param in the input * @return the diagram describing string */ private String getUmlText(Input in) { var umlText = in.toString(); if (umlText.length() > 0 && umlText.charAt(0) != '@') { umlText = "@startuml\n" + umlText + "@enduml\n"; } return umlText; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy