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}