001/*
002 * Copyright (c) 2012-2021 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others
003 *
004 * This program and the accompanying materials are made available under the
005 * terms of the Eclipse Public License 2.0 which is available at
006 * http://www.eclipse.org/legal/epl-2.0.
007 *
008 * SPDX-License-Identifier: EPL-2.0
009 */
010
011package org.eclipse.golo.cli;
012
013import java.io.IOException;
014import java.io.File;
015import java.io.FileOutputStream;
016import java.util.Arrays;
017import java.util.List;
018import java.util.LinkedList;
019import java.util.Deque;
020import java.util.Iterator;
021import java.util.NoSuchElementException;
022import java.util.jar.Attributes;
023import java.util.jar.JarOutputStream;
024import java.util.jar.Manifest;
025import java.util.zip.ZipEntry;
026import java.util.function.Consumer;
027import java.util.stream.StreamSupport;
028import java.util.stream.Stream;
029
030import org.eclipse.golo.compiler.CodeGenerationResult;
031import org.eclipse.golo.cli.command.Metadata;
032import static gololang.Messages.*;
033
034/**
035 * Helper class to deal with Golo files.
036 *
037 * <p>Ease the finding, loading and saving of golo source files and compilation result.
038 */
039public final class GoloFilesManager implements AutoCloseable, Consumer<CodeGenerationResult> {
040
041  private File outputDir;
042  private JarOutputStream jar;
043  private boolean recurse = false;
044  private boolean compilingToJar = false;
045
046  private GoloFilesManager() { }
047
048  private void saveToJar(CodeGenerationResult result) throws IOException {
049    this.jar.putNextEntry(new ZipEntry(result.getOutputFilename()));
050    this.jar.write(result.getBytecode());
051    this.jar.closeEntry();
052  }
053
054  private void saveToClass(CodeGenerationResult result) throws IOException {
055    File outputFile = new File(this.outputDir, result.getOutputFilename());
056    File outputFolder = outputFile.getParentFile();
057    if (!outputFolder.exists() && !outputFolder.mkdirs()) {
058      throw new IOException(message("directory_not_created", outputFolder));
059    }
060    try (FileOutputStream out = new FileOutputStream(outputFile)) {
061      out.write(result.getBytecode());
062    }
063  }
064
065  public void accept(CodeGenerationResult result) {
066    save(result);
067  }
068
069  public void save(CodeGenerationResult result) {
070    try {
071      if (this.compilingToJar) {
072        saveToJar(result);
073      } else {
074        saveToClass(result);
075      }
076    } catch (IOException e) {
077      error(e.getLocalizedMessage());
078    }
079  }
080
081  public void saveAll(List<CodeGenerationResult> results) {
082    for (CodeGenerationResult result : results) {
083      this.save(result);
084    }
085  }
086
087  public void close() throws IOException {
088    if (this.jar != null) {
089      this.jar.close();
090    }
091  }
092
093  private static Manifest manifest() {
094    Manifest manifest = new Manifest();
095    Attributes attributes = manifest.getMainAttributes();
096    attributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
097    attributes.put(new Attributes.Name("Created-By"), "Eclipse Golo " + Metadata.VERSION);
098    return manifest;
099  }
100
101  public static GoloFilesManager of(String output) throws IOException {
102    if (output.endsWith(".jar")) {
103      return withOutputJar(new File(output));
104    }
105    return withOutputDir(new File(output));
106  }
107
108  public static GoloFilesManager withOutputJar(File output) throws IOException {
109    GoloFilesManager fm = new GoloFilesManager();
110    fm.jar = new JarOutputStream(new FileOutputStream(output), manifest());
111    fm.compilingToJar = true;
112    return fm;
113
114  }
115
116  public static GoloFilesManager withOutputDir(File outputDir) throws IOException {
117    if (outputDir != null && outputDir.isFile()) {
118      throw new IOException(message("file_exists", outputDir));
119    }
120    GoloFilesManager fm = new GoloFilesManager();
121    fm.outputDir = outputDir;
122    return fm;
123  }
124
125  public static Iterable<File> findGoloFiles(Iterable<File> candidates) {
126    return findGoloFiles(candidates, true);
127  }
128
129  public static Iterable<File> findGoloFiles(Iterable<File> candidates, boolean recurse) {
130    return () -> new GolofilesIterator(candidates.iterator(), recurse);
131  }
132
133  public static Stream<File> goloFiles(Iterable<File> candidates, boolean recurse) {
134    return StreamSupport.stream(GoloFilesManager.findGoloFiles(candidates, recurse).spliterator(), false);
135  }
136
137  public static Stream<File> goloFiles(Iterable<File> candidates) {
138    return goloFiles(candidates, true);
139  }
140
141  private static final class GolofilesIterator implements Iterator<File> {
142
143    private File next;
144    private Deque<Iterator<File>> bases = new LinkedList<>();
145    private boolean recurse;
146
147    GolofilesIterator(Iterator<File> bases, boolean recurse) {
148      this.bases.addFirst(bases);
149      this.recurse = recurse;
150      this.advance();
151    }
152
153    @Override
154    public boolean hasNext() {
155      return next != null;
156    }
157
158    @Override
159    public File next() {
160      if (this.next == null) {
161        throw new NoSuchElementException();
162      }
163      File result = this.next;
164      this.advance();
165      return result;
166    }
167
168    private boolean isValidGoloFile(File file) {
169      return file.isFile() && file.getName().endsWith(".golo");
170    }
171
172    private void advance() {
173      if (this.bases.isEmpty()) {
174        this.next = null;
175        return;
176      }
177      if (this.bases.getFirst() == null || !this.bases.getFirst().hasNext()) {
178        this.bases.removeFirst();
179        this.advance();
180        return;
181      }
182      File file = this.bases.getFirst().next();
183      if (isValidGoloFile(file)) {
184        this.next = file;
185        return;
186      }
187      if (file.isDirectory()) {
188        File[] content = file.listFiles();
189        if (content != null) {
190          this.bases.push(Arrays.asList(content).iterator());
191          this.advance();
192        }
193      } else {
194        this.advance();
195      }
196    }
197  }
198
199}