/** * * Copyright (c) 2006-2017, Speedment, Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); You may not * use this file except in compliance with the License. You may obtain a copy of * the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.speedment.generator.core.internal.translator; import com.speedment.common.codegen.Generator; import com.speedment.common.codegen.Meta; import com.speedment.common.codegen.internal.java.JavaGenerator; import com.speedment.common.codegen.model.File; import com.speedment.common.codegen.util.Formatting; import com.speedment.common.injector.annotation.Inject; import com.speedment.common.logger.Logger; import com.speedment.common.logger.LoggerManager; import com.speedment.generator.core.component.EventComponent; import com.speedment.generator.core.component.PathComponent; import com.speedment.generator.core.event.AfterGenerate; import com.speedment.generator.core.event.BeforeGenerate; import com.speedment.generator.core.event.FileGenerated; import com.speedment.generator.core.internal.util.HashUtil; import com.speedment.generator.translator.Translator; import com.speedment.generator.translator.TranslatorManager; import com.speedment.generator.translator.component.CodeGenerationComponent; import com.speedment.runtime.config.Project; import com.speedment.runtime.config.Table; import com.speedment.runtime.config.trait.HasEnabled; import com.speedment.runtime.core.component.InfoComponent; import com.speedment.runtime.core.component.ProjectComponent; import com.speedment.runtime.core.exception.SpeedmentException; import com.speedment.runtime.core.internal.util.Statistics; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import java.util.stream.Stream; import static com.speedment.common.codegen.internal.util.NullUtil.requireNonNulls; import static com.speedment.runtime.config.util.DocumentDbUtil.traverseOver; import static com.speedment.runtime.core.internal.util.Statistics.Event.GENERATE; import static java.util.Objects.requireNonNull; /** * * @author Emil Forslund * @since 3.0.2 */ public final class TranslatorManagerHelper { private static final Logger LOGGER = LoggerManager.getLogger(DefaultTranslatorManager.class); private static final String HASH_PREFIX = "."; private static final String HASH_SUFFIX = ".md5"; private static final boolean PRINT_CODE = false; private final AtomicInteger fileCounter = new AtomicInteger(0); @Inject private InfoComponent info; @Inject private PathComponent paths; @Inject private EventComponent events; @Inject private ProjectComponent projects; @Inject private CodeGenerationComponent codeGenerationComponent; public void accept(TranslatorManager delegator, Project project) { requireNonNull(project); Statistics.report(info, projects, GENERATE); final List<Translator<?, ?>> writeOnceTranslators = new ArrayList<>(); final List<Translator<?, ?>> writeAlwaysTranslators = new ArrayList<>(); final Generator gen = new JavaGenerator(); fileCounter.set(0); Formatting.tab(" "); events.notify(new BeforeGenerate(project, gen, delegator)); codeGenerationComponent.translators(project) .forEachOrdered(t -> { if (t.isInGeneratedPackage()) { writeAlwaysTranslators.add(t); } else { writeOnceTranslators.add(t); } }); traverseOver(project, Table.class) .filter(HasEnabled::test) .forEach(table -> codeGenerationComponent.translators(table).forEachOrdered(t -> { if (t.isInGeneratedPackage()) { writeAlwaysTranslators.add(t); } else { writeOnceTranslators.add(t); } }) ); // Erase any previous unmodified files. delegator.clearExistingFiles(project); // Write generated code to file. gen.metaOn(writeOnceTranslators.stream() .map(Translator::get) .collect(Collectors.toList()) ).forEach(meta -> delegator.writeToFile(project, meta, false)); gen.metaOn(writeAlwaysTranslators.stream() .map(Translator::get) .collect(Collectors.toList()) ).forEach(meta -> delegator.writeToFile(project, meta, true)); events.notify(new AfterGenerate(project, gen, delegator)); } public void clearExistingFiles(Project project) { final Path dir = paths.packageLocation(); try { clearExistingFilesIn(dir); } catch (final IOException ex) { throw new SpeedmentException( "Error! Could not delete files in '" + dir.toString() + "'.", ex ); } } private void clearExistingFilesIn(Path directory) throws IOException { if (Files.exists(directory)) { try (final DirectoryStream<Path> stream = Files.newDirectoryStream(directory)) { for (final Path entry : stream) { if (Files.isDirectory(entry)) { clearExistingFilesIn(entry); if (isDirectoryEmpty(entry)) { Files.delete(entry); } } else { final String filename = entry.toFile().getName(); if (filename.startsWith(HASH_PREFIX) && filename.endsWith(HASH_SUFFIX)) { final Path original = entry .getParent() // The hidden folder .getParent() // The parent folder .resolve(filename.substring( // Lookup original .java file HASH_PREFIX.length(), filename.length() - HASH_SUFFIX.length() )); if (original.toFile().exists() && HashUtil.compare(original, entry)) { delete(original); delete(entry); } } } } } } } private static void delete(Path path) throws IOException { LOGGER.info("Deleting '" + path.toString() + "'."); Files.delete(path); } private static boolean isDirectoryEmpty(Path directory) throws IOException { try (final DirectoryStream<Path> dirStream = Files.newDirectoryStream(directory)) { return !dirStream.iterator().hasNext(); } } public void writeToFile(TranslatorManager delegator, Project project, Meta<File, String> meta, boolean overwriteExisting) { events.notify(new FileGenerated(project, meta)); delegator.writeToFile(project, meta.getModel().getName(), meta.getResult(), overwriteExisting); } public void writeToFile(TranslatorManager delegator, Project project, String filename, String content, boolean overwriteExisting) { final Path codePath = paths.packageLocation().resolve(filename); delegator.writeToFile(codePath, content, overwriteExisting); } public void writeToFile(TranslatorManager delegator, Path codePath, String content, boolean overwriteExisting) { requireNonNulls(codePath, content); try { if (overwriteExisting || !codePath.toFile().exists()) { final Path hashPath = codePath.getParent() .resolve(secretFolderName()) .resolve(HASH_PREFIX + codePath.getFileName().toString() + HASH_SUFFIX); write(hashPath, HashUtil.md5(content), true); write(codePath, content, false); fileCounter.incrementAndGet(); } } catch (final IOException ex) { LOGGER.error(ex, "Failed to write file " + codePath); } if (PRINT_CODE) { LOGGER.info("*** BEGIN File:" + codePath); Stream.of(content.split(Formatting.nl())).forEachOrdered(LOGGER::info); LOGGER.info("*** END File:" + codePath); } } public int getFilesCreated() { return fileCounter.get(); } private static void write(Path path, String content, boolean hidden) throws IOException { LOGGER.info("Creating '" + path.toString() + "'."); final Path parent = path.getParent(); try { if (parent != null) { Files.createDirectories(parent); if (hidden) { setAttributeHidden(parent); } } } catch (final SecurityException se) { throw new SpeedmentException("Unable to create directory " + parent.toString(), se); } Files.write(path, content.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING ); if (hidden) { setAttributeHidden(path); } } private static void setAttributeHidden(Path path) { try { Files.setAttribute(path, "dos:hidden", true); } catch (final IOException | UnsupportedOperationException e) { // Ignore. Maybe this is Linux or MacOS } } private String secretFolderName() { return "." + info.getTitle() .replace(" ", "") .replace(".", "") .replace("/", "") .toLowerCase(); } }