/*
* ******************************************************************************
* MontiCore Language Workbench
* Copyright (c) 2015, MontiCore, All rights reserved.
*
* This project is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this project. If not, see <http://www.gnu.org/licenses/>.
* ******************************************************************************
*/
package de.monticore.generating.templateengine.reporting.reporter;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import de.monticore.ast.ASTNode;
import com.google.common.collect.Lists;
import com.google.common.hash.Hashing;
import com.google.common.io.CharStreams;
import com.google.common.io.Files;
import de.monticore.generating.templateengine.reporting.commons.AReporter;
import de.monticore.generating.templateengine.reporting.commons.Layouter;
import de.monticore.generating.templateengine.reporting.commons.ReportCreator;
import de.monticore.generating.templateengine.reporting.commons.ReportingConstants;
import de.monticore.incremental.IncrementalChecker;
import de.se_rwth.commons.logging.Log;
/**
* This report is used to enable incremental generation. It reports the name of
* all input and output files.
*
* @author (last commit) $Author$
* @version $Revision$, $Date$
*/
public class InputOutputFilesReporter extends AReporter {
public static final String SIMPLE_FILE_NAME = "17_InputOutputFiles";
final static String INDENT = Layouter.getSpaceString(40);
private List<String> inputFiles = Lists.newArrayList();
private List<String> hwcFiles = Lists.newArrayList();
private List<String> outputFiles = Lists.newArrayList();
/**
* Constructor for de.monticore.generating.templateengine.reporting.reporter.
* InputOutputFilesReporter
*
* @param outputDir
*/
public InputOutputFilesReporter(String outputDir) {
super(outputDir + File.separator + ReportingConstants.REPORTING_DIR + File.separator,
SIMPLE_FILE_NAME, ReportingConstants.REPORT_FILE_EXTENSION);
this.outputDirectory = outputDir;
}
final String outputDirectory;
public static final String INPUT_FILE_HEADING = "========================================================== Input files";
protected void writeInputFileHeading() {
writeLine(INPUT_FILE_HEADING);
}
public static final String HWC_FILE_HEADING = "====================================================== Handwritten files";
protected void writeHWCFileHeading() {
writeLine(HWC_FILE_HEADING);
}
public static final String OUTPUT_FILE_HEADING = "========================================================== Output files";
protected void writeOutputFileHeading() {
writeLine(OUTPUT_FILE_HEADING);
}
public static final String FOOTER_HEADING = "========================================================== Explanation";
private void writeFooter() {
writeLine(FOOTER_HEADING);
writeLine("This report is used to enable incremental generation");
writeLine("Input files: the list of input files ordered by their paths.");
writeLine("Types of input files are:");
writeLine("- Model files");
writeLine("- Handwritten sourcecode files");
writeLine("- User specific script files");
writeLine("- User specific template files");
writeLine("Output files: the list of generated output files ordered by their paths.");
writeLine("(EOF)");
}
/**
* @see mc.codegen.reporting.commons.IReportEventHandler#reportFileCreation(java.lang.String,
* java.lang.String, java.lang.String, de.monticore.ast.ASTNode)
*/
@Override
public void reportFileCreation(String templatename,
String qualifiedfilename, String fileextension, ASTNode ast) {
String filePath = qualifiedfilename.replaceAll("\\.", Matcher.quoteReplacement(File.separator));
outputFiles.add(filePath + "." + fileextension);
}
public static final String PARENT_FILE_SEPARATOR = " sub ";
public static final String INPUT_STATE_SEPARATOR = " state ";
/**
* @see de.monticore.generating.templateengine.reporting.commons.DefaultReportEventHandler#reportOpenInputFile(java.nio.file.Path)
*/
@Override
public void reportOpenInputFile(Path parentPath, Path file) {
if (file.compareTo(qualifiedInputFile) == 0) {
return;
}
String toAdd = "";
if (parentPath != null) {
toAdd = parentPath.toString() + PARENT_FILE_SEPARATOR + file.toString();
modelToArtifactMap.put(file, parentPath);
}
else {
if (modelToArtifactMap.keySet().contains(file)) {
toAdd = modelToArtifactMap.get(file).toString() + PARENT_FILE_SEPARATOR
+ file.toString();
}
else {
filesThatMatterButAreNotThereInTime.add(file);
}
}
if (!toAdd.isEmpty() && !inputFiles.contains(toAdd)) {
inputFiles.add(toAdd);
}
}
private Set<Path> filesThatMatterButAreNotThereInTime = new LinkedHashSet<>();
// TODO: think about when to clean this up
private static Map<Path, Path> modelToArtifactMap = new HashMap<>();
// TODO: see todo above :-)
public static void resetModelToArtifactMap() {
modelToArtifactMap = new HashMap<>();
}
private String inputFile;
private Path qualifiedInputFile;
/**
* @see de.monticore.generating.templateengine.reporting.commons.DefaultReportEventHandler#reportParseInputFile(java.nio.file.Path,
* java.lang.String)
*/
@Override
public void reportParseInputFile(Path inputFilePath, String modelName) {
// IMPORTANT
// this entirely resets the gathered information, hence the corresponding
// event reportParseInputFile must only be called once for each actual input
// file, i.e., the things that are parsed
this.reportingHelper = new ReportCreator(outputDirectory + File.separator
+ ReportingConstants.REPORTING_DIR + File.separator + modelName);
inputFiles.clear();
hwcFiles.clear();
outputFiles.clear();
filesThatMatterButAreNotThereInTime.clear();
inputFile = inputFilePath.toString();
qualifiedInputFile = Paths.get(modelName.replaceAll("\\.", "/") + "."
+ Files.getFileExtension(inputFilePath.getFileName().toString()));
Path parent = inputFilePath.subpath(0,
inputFilePath.getNameCount() - qualifiedInputFile.getNameCount());
parent = inputFilePath.getRoot().resolve(parent);
modelToArtifactMap.put(qualifiedInputFile, parent);
}
/**
* @see de.monticore.generating.templateengine.reporting.commons.DefaultReportEventHandler#reportUseHandwrittenCodeFile(java.nio.file.Path,
* java.nio.file.Path)
*/
@Override
public void reportUseHandwrittenCodeFile(Path parentDir, Path fileName) {
Optional<Path> parent = Optional.ofNullable(parentDir);
String hwcLine = "";
if (parent.isPresent()) {
Path parentPath = parent.get().subpath(0,
parent.get().getNameCount() - fileName.getNameCount());
parentPath = parent.get().getRoot().resolve(parentPath);
hwcLine = parentPath.toString();
}
hwcLine = hwcLine.concat(PARENT_FILE_SEPARATOR).concat(fileName.toString());
hwcFiles.add(hwcLine);
}
public static final String MISSING = "not found";
public static final String GEN_ERROR = "error during generation";
private void writeContent(ASTNode ast) {
// the magic TODO AHo: document
for (Path lateOne : filesThatMatterButAreNotThereInTime) {
if (modelToArtifactMap.keySet().contains(lateOne)) {
String toAdd = modelToArtifactMap.get(lateOne).toString() + PARENT_FILE_SEPARATOR
+ lateOne.toString();
if (!inputFiles.contains(toAdd)) {
inputFiles.add(toAdd);
}
}
}
Collections.sort(inputFiles);
Collections.sort(outputFiles);
if (inputFile != null && !inputFile.isEmpty()) {
String checkSum;
if (ast != null) {
checkSum = IncrementalChecker.getChecksum(inputFile);
writeLine(inputFile + INPUT_STATE_SEPARATOR + checkSum);
} else {
writeLine(inputFile + INPUT_STATE_SEPARATOR + GEN_ERROR);
}
for (String s : inputFiles) {
if (s.contains(PARENT_FILE_SEPARATOR)) {
String[] elements = s.split(PARENT_FILE_SEPARATOR);
if (elements[0].endsWith(".jar")) {
String inputFile = elements[0].concat("!" + File.separator).concat(elements[1]);
try {
String url = "jar:file:" + inputFile;
url = url.replaceAll("\\" + File.separator, "/");
URL input = new URL(url);
String inputModel = CharStreams.toString(new InputStreamReader(input.openStream()));
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(inputModel.getBytes());
String digest = Hashing.md5().hashString(inputModel, Charset.forName("UTF-8"))
.toString();
writeLine(s + INPUT_STATE_SEPARATOR + digest);
}
catch (IOException | NoSuchAlgorithmException e) {
Log.warn("0xA0134 Cannot write to log file", e);
}
}
else {
File inputFile = new File(elements[0].concat(File.separator).concat(elements[1]));
if (inputFile.exists()) {
checkSum = IncrementalChecker.getChecksum(inputFile.toString());
writeLine(s + INPUT_STATE_SEPARATOR + checkSum);
}
else {
writeLine(s + INPUT_STATE_SEPARATOR + MISSING);
}
}
}
else {
checkSum = IncrementalChecker.getChecksum(s);
writeLine(s + INPUT_STATE_SEPARATOR + checkSum);
}
}
writeHWCFileHeading();
for (String hwc : hwcFiles) {
writeLine(hwc);
}
writeOutputFileHeading();
for (String s : outputFiles) {
writeLine(s);
}
}
}
@Override
public void flush(ASTNode ast) {
writeContent(ast);
writeFooter();
super.flush(ast);
}
/**
* @see de.monticore.generating.templateengine.reporting.commons.AReporter#writeHeader()
*/
@Override
protected void writeHeader() {
writeInputFileHeading();
}
}