1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  package org.mal_lang.langspec.io;
18  
19  import static java.util.Objects.requireNonNull;
20  
21  import jakarta.json.Json;
22  import java.io.Closeable;
23  import java.io.FilterOutputStream;
24  import java.io.IOException;
25  import java.io.OutputStream;
26  import java.nio.charset.StandardCharsets;
27  import java.util.Map;
28  import java.util.zip.ZipEntry;
29  import java.util.zip.ZipOutputStream;
30  import org.leadpony.joy.api.JsonGenerator;
31  import org.mal_lang.langspec.Lang;
32  
33  
34  
35  
36  
37  
38  public final class LangWriter implements Closeable {
39    private final OutputStream out;
40    private boolean isWritten = false;
41    private boolean isClosed = false;
42  
43    
44  
45  
46  
47  
48  
49  
50    public LangWriter(OutputStream out) {
51      this.out = requireNonNull(out);
52    }
53  
54    private static void writeLangSpec(ZipOutputStream zipOut, Lang lang) throws IOException {
55      var jsonWriterFactory =
56          Json.createWriterFactory(
57              Map.ofEntries(
58                  Map.entry(JsonGenerator.PRETTY_PRINTING, true),
59                  Map.entry(JsonGenerator.INDENTATION_SIZE, 2)));
60      var wrappedOut =
61          new FilterOutputStream(zipOut) {
62            @Override
63            public void close() throws IOException {}
64          };
65  
66      zipOut.putNextEntry(new ZipEntry("langspec.json"));
67      try (var jsonWriter = jsonWriterFactory.createWriter(wrappedOut, StandardCharsets.UTF_8)) {
68        jsonWriter.writeObject(lang.toJson());
69      }
70      zipOut.closeEntry();
71    }
72  
73    
74  
75  
76  
77  
78  
79  
80  
81  
82  
83  
84  
85    public void write(Lang lang) throws IOException {
86      if (this.isWritten) {
87        throw new IllegalStateException("write method is already called");
88      }
89      if (this.isClosed) {
90        throw new IllegalStateException("close method is already called");
91      }
92      requireNonNull(lang);
93      try (var zipOut = new ZipOutputStream(this.out, StandardCharsets.UTF_8)) {
94        LangWriter.writeLangSpec(zipOut, lang);
95        zipOut.putNextEntry(new ZipEntry("icons/"));
96        zipOut.closeEntry();
97        for (var asset : lang.getAssets()) {
98          if (asset.hasLocalSvgIcon()) {
99            zipOut.putNextEntry(new ZipEntry(String.format("icons/%s.svg", asset.getName())));
100           zipOut.write(asset.getLocalSvgIcon());
101           zipOut.closeEntry();
102         }
103         if (asset.hasLocalPngIcon()) {
104           zipOut.putNextEntry(new ZipEntry(String.format("icons/%s.png", asset.getName())));
105           zipOut.write(asset.getLocalPngIcon());
106           zipOut.closeEntry();
107         }
108       }
109       if (lang.hasLicense()) {
110         zipOut.putNextEntry(new ZipEntry("LICENSE"));
111         zipOut.write(lang.getLicense().getBytes(StandardCharsets.UTF_8));
112         zipOut.closeEntry();
113       }
114       if (lang.hasNotice()) {
115         zipOut.putNextEntry(new ZipEntry("NOTICE"));
116         zipOut.write(lang.getNotice().getBytes(StandardCharsets.UTF_8));
117         zipOut.closeEntry();
118       }
119     } finally {
120       this.isWritten = true;
121     }
122   }
123 
124   
125 
126 
127 
128 
129 
130 
131   @Override
132   public void close() throws IOException {
133     if (this.isClosed) {
134       return;
135     }
136     try {
137       this.out.close();
138     } finally {
139       this.isClosed = true;
140     }
141   }
142 }