Skip to content

Commit

Permalink
Generator: Fix #230 Make it possible to clear generated files with a …
Browse files Browse the repository at this point in the history
…maven goal.

You can now use speedment:clear to remove all generated files. This
solution uses a hash key for each file to determine if they have been
modified or not. If the hash matches the content of the file, it is
considered unchanged and can safely be regenerated.
  • Loading branch information
Emil Forslund committed Jul 26, 2016
1 parent 484155a commit 13a6e9f
Show file tree
Hide file tree
Showing 4 changed files with 331 additions and 24 deletions.
Expand Up @@ -34,11 +34,13 @@ public interface TranslatorManager {


void accept(Project project); void accept(Project project);


int getFilesCreated(); void clearExistingFiles(Project project);


void writeToFile(Project project, Meta<File, String> meta, boolean overwriteExisting); void writeToFile(Project project, Meta<File, String> meta, boolean overwriteExisting);


void writeToFile(Project project, String filename, String content, boolean overwriteExisting); void writeToFile(Project project, String filename, String content, boolean overwriteExisting);


void writeToFile(Path location, String content, boolean overwriteExisting); void writeToFile(Path location, String content, boolean overwriteExisting);

int getFilesCreated();
} }
Expand Up @@ -45,13 +45,16 @@
import java.nio.file.StandardOpenOption; import java.nio.file.StandardOpenOption;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors; import java.util.stream.Collectors;


import static com.speedment.common.codegen.internal.util.NullUtil.requireNonNulls; import static com.speedment.common.codegen.internal.util.NullUtil.requireNonNulls;
import com.speedment.generator.internal.util.HashUtil;
import com.speedment.runtime.component.InfoComponent;
import static com.speedment.runtime.internal.util.document.DocumentDbUtil.traverseOver; import static com.speedment.runtime.internal.util.document.DocumentDbUtil.traverseOver;
import java.nio.file.DirectoryStream;
import static java.util.Objects.requireNonNull; import static java.util.Objects.requireNonNull;
import java.util.stream.Stream;


/** /**
* *
Expand All @@ -60,11 +63,14 @@
*/ */
public class TranslatorManagerImpl implements TranslatorManager { public class TranslatorManagerImpl implements TranslatorManager {


private final static Logger LOGGER = LoggerManager.getLogger(TranslatorManagerImpl.class); private final static Logger LOGGER = LoggerManager.getLogger(TranslatorManagerImpl.class);
private final static String HASH_PREFIX = ".";
private final static String HASH_SUFFIX = ".sha1";
private final static boolean PRINT_CODE = false; private final static boolean PRINT_CODE = false;


private final AtomicInteger fileCounter = new AtomicInteger(0); private final AtomicInteger fileCounter = new AtomicInteger(0);


private @Inject InfoComponent info;
private @Inject EventComponent eventComponent; private @Inject EventComponent eventComponent;
private @Inject CodeGenerationComponent codeGenerationComponent; private @Inject CodeGenerationComponent codeGenerationComponent;


Expand Down Expand Up @@ -102,7 +108,11 @@ public void accept(Project project) {
} }
}); });
}); });


// Erase any previous unmodified files.
clearExistingFiles(project);

// Write generated code to file.
gen.metaOn(writeOnceTranslators.stream() gen.metaOn(writeOnceTranslators.stream()
.map(Translator::get) .map(Translator::get)
.collect(Collectors.toList()) .collect(Collectors.toList())
Expand All @@ -117,8 +127,60 @@ public void accept(Project project) {
} }


@Override @Override
public int getFilesCreated() { public void clearExistingFiles(Project project) {
return fileCounter.get(); final Path dir = Paths.get(project.getPackageLocation());

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 {
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();
}
} }


@Override @Override
Expand All @@ -134,32 +196,67 @@ public void writeToFile(Project project, String filename, String content, boolea
} }


@Override @Override
public void writeToFile(Path path, String content, boolean overwriteExisting) { public void writeToFile(Path codePath, String content, boolean overwriteExisting) {
requireNonNulls(path, content); requireNonNulls(codePath, content);


try { try {
Optional.ofNullable(path.getParent()) if (overwriteExisting || !codePath.toFile().exists()) {
.ifPresent(p -> p.toFile().mkdirs()); final Path hashPath = codePath.getParent()
} catch (SecurityException se) { .resolve(secretFolderName())
throw new SpeedmentException("Unable to create directory " + path.toString(), se); .resolve(HASH_PREFIX + codePath.getFileName().toString() + HASH_SUFFIX);
}

write(hashPath, HashUtil.sha1(content), true);
try { write(codePath, content, false);
if (overwriteExisting || !path.toFile().exists()) {
Files.write(path, content.getBytes(StandardCharsets.UTF_8),
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING
);
fileCounter.incrementAndGet(); fileCounter.incrementAndGet();
} }
} catch (final IOException ex) { } catch (final IOException ex) {
LOGGER.error(ex, "Failed to write file " + path); LOGGER.error(ex, "Failed to write file " + codePath);
} }


if (PRINT_CODE) { if (PRINT_CODE) {
System.out.println("*** BEGIN File:" + path); LOGGER.info("*** BEGIN File:" + codePath);
System.out.println(content); Stream.of(content.split(Formatting.nl())).forEachOrdered(LOGGER::info);
System.out.println("*** END File:" + path); LOGGER.info("*** END File:" + codePath);
} }
} }

@Override
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) {
Files.setAttribute(parent, "dos:hidden", true);
}
}
} 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) {
Files.setAttribute(path, "dos:hidden", true);
}
}

private String secretFolderName() {
return "." + info.title()
.replace(" ", "")
.replace(".", "")
.replace("/", "")
.toLowerCase();
}
} }
@@ -0,0 +1,100 @@
package com.speedment.generator.internal.util;

import com.speedment.runtime.exception.SpeedmentException;
import static com.speedment.runtime.util.StaticClassUtil.instanceNotAllowed;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.List;
import static java.util.stream.Collectors.joining;

/**
* A utility method for creating and comparing hashes of files.
*
* @author Emil Forslund
* @since 3.0.0
*/
public final class HashUtil {

private final static String ALGORITHM = "SHA-1";
private final static Charset CHARSET = StandardCharsets.UTF_8;

public static boolean compare(Path path, Path sha1) {
final String expected = sha1(path);
final String actual = load(sha1).get(0);
return expected.equals(actual);
}

public static boolean compare(Path path, String sha1) {
final String expected = sha1(path);
return expected.equals(sha1);
}

public static boolean compare(String content, String sha1) {
final String expected = sha1(content);
return expected.equals(sha1);
}

public static String sha1(Path path) {
return sha1(load(path));
}

public static String sha1(String content) {
return sha1(Arrays.asList(content.split("\\s+")));
}

private static String sha1(List<String> rows) {
return sha1(rows.stream()
.map(String::trim)
.flatMap(s -> Arrays.asList(s.split("\\s+")).stream())
.collect(joining())
.getBytes(CHARSET)
);
}

private static String sha1(byte[] bytes) {
final MessageDigest md;

try {
md = MessageDigest.getInstance(ALGORITHM);
} catch(final NoSuchAlgorithmException ex) {
throw new SpeedmentException(
"Could not find hashing algorithm '" + ALGORITHM + "'.", ex
);
}

return bytesToHex(md.digest(bytes));
}

private static String bytesToHex(byte[] bytes) {
final StringBuilder result = new StringBuilder();

for (int i = 0; i < bytes.length; i++) {
result.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1));
}

return result.toString();
}

private static List<String> load(Path path) {
try {
return Files.readAllLines(path, CHARSET);
} catch (final IOException ex) {
throw new SpeedmentException(
"Error reading file '" + path + "' for hashing.", ex
);
}
}

/**
* Utility classes should never be instantiated.
*/
private HashUtil() {
instanceNotAllowed(getClass());
}
}

0 comments on commit 13a6e9f

Please sign in to comment.