/* * Copyright 2014 Goldman Sachs. * * 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.gs.collections.codegenerator; import java.io.File; import java.net.URL; import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; import java.util.zip.CRC32; import com.gs.collections.codegenerator.model.Primitive; import com.gs.collections.codegenerator.tools.FileUtils; import com.gs.collections.codegenerator.tools.IntegerOrStringRenderer; import org.stringtemplate.v4.ST; import org.stringtemplate.v4.STErrorListener; import org.stringtemplate.v4.STGroupFile; import org.stringtemplate.v4.misc.STMessage; public class GsCollectionsCodeGenerator { public static final String GENERATED_TEST_SOURCES_LOCATION = "target/generated-test-sources/java/"; public static final String GENERATED_SOURCES_LOCATION = "target/generated-sources/java/"; private final String templateDirectory; private final File moduleBaseDir; private final List<URL> classPathURLs; private boolean isTest; private STGroupFile templateFile; private final STErrorListener stErrorListener; private URL url; private int numFileWritten = 0; public GsCollectionsCodeGenerator(String templateDirectory, File moduleBaseDir, List<URL> classPathURLs, ErrorListener errorListener) { this.templateDirectory = templateDirectory; this.moduleBaseDir = moduleBaseDir; this.classPathURLs = classPathURLs; this.stErrorListener = new LoggingErrorListener(errorListener); } /** * Generates code and only write contents to disk which differ from the current file contents. * * @return The number of files written. */ public int generateFiles() { List<URL> allTemplateFilesFromClassPath = FileUtils.getAllTemplateFilesFromClasspath(this.templateDirectory, this.classPathURLs); for (URL url : allTemplateFilesFromClassPath) { this.url = url; this.templateFile = new STGroupFile(this.url, "UTF-8", '<', '>'); this.templateFile.setListener(this.stErrorListener); this.templateFile.registerRenderer(String.class, new IntegerOrStringRenderer()); if (this.templateFile.isDefined("fileName")) { this.setTest(); File targetPath = this.constructTargetPath(); FileUtils.createDirectory(targetPath); boolean hasTwoPrimitives = this.templateFile.isDefined("hasTwoPrimitives") && Boolean.valueOf(this.templateFile.getInstanceOf("hasTwoPrimitives").render()); boolean skipBoolean = this.templateFile.isDefined("skipBoolean") && Boolean.valueOf(this.templateFile.getInstanceOf("skipBoolean").render()); boolean skipBooleanKeys = this.templateFile.isDefined("skipBooleanKeys") && Boolean.valueOf(this.templateFile.getInstanceOf("skipBooleanKeys").render()); if (hasTwoPrimitives) { for (Primitive primitive1 : Primitive.values()) { if (primitive1 == Primitive.BOOLEAN && (skipBoolean || skipBooleanKeys)) { continue; } for (Primitive primitive2 : Primitive.values()) { if (primitive2 == Primitive.BOOLEAN && skipBoolean) { continue; } String sourceFileName = this.executeTemplate(primitive1, primitive2, "fileName"); File outputFile = new File(targetPath, sourceFileName + ".java"); if (!GsCollectionsCodeGenerator.sourceFileExists(outputFile)) { String classContents = this.executeTemplate(primitive1, primitive2, "class"); this.checkSumClassContentsAndWrite(classContents, targetPath, sourceFileName); } } } } else { for (Primitive primitive : Primitive.values()) { if (primitive == Primitive.BOOLEAN && skipBoolean) { continue; } String sourceFileName = this.executeTemplate(primitive, "fileName"); File outputFile = new File(targetPath, sourceFileName + ".java"); if (!GsCollectionsCodeGenerator.sourceFileExists(outputFile)) { String classContents = this.executeTemplate(primitive, "class"); this.checkSumClassContentsAndWrite(classContents, targetPath, sourceFileName); } } } } } return this.numFileWritten; } private void checkSumClassContentsAndWrite(String classContents, File targetPath, String sourceFileName) { long checksumValue = GsCollectionsCodeGenerator.calculateChecksum(classContents); File outputFile = new File(targetPath, sourceFileName + ".java"); Path outputChecksumPath = Paths.get(targetPath.getAbsolutePath(), sourceFileName + ".java.crc"); if (!outputChecksumPath.toFile().exists()) { this.writeFileAndChecksum(outputFile, classContents, checksumValue, outputChecksumPath, false); return; } String existingChecksum = FileUtils.readFile(outputChecksumPath); if (existingChecksum.equals(String.valueOf(checksumValue))) { return; } this.writeFileAndChecksum(outputFile, classContents, checksumValue, outputChecksumPath, true); } private static long calculateChecksum(String string) { CRC32 checksum = new CRC32(); checksum.update(string.getBytes(StandardCharsets.UTF_8)); return checksum.getValue(); } private void writeFileAndChecksum(File outputFile, String output, long checksumValue, Path outputChecksumPath, boolean outputFileMustExist) { this.numFileWritten++; FileUtils.writeToFile(output, outputFile, outputFileMustExist); FileUtils.writeToFile(String.valueOf(checksumValue), outputChecksumPath.toFile(), outputFileMustExist); } private String executeTemplate(Primitive primitive, String templateName) { ST template = this.findTemplate(templateName); template.add("primitive", primitive); return template.render(); } private String executeTemplate(Primitive primitive1, Primitive primitive2, String templateName) { ST template = this.findTemplate(templateName); template.add("primitive1", primitive1); template.add("primitive2", primitive2); template.add("sameTwoPrimitives", primitive1 == primitive2); return template.render(); } private ST findTemplate(String templateName) { ST template = this.templateFile.getInstanceOf(templateName); if (template == null) { throw new RuntimeException("Could not find template " + templateName + " in " + this.templateFile.getFileName()); } return template; } private void setTest() { this.isTest = this.templateFile.getInstanceOf("isTest") == null ? false : Boolean.valueOf(this.templateFile.getInstanceOf("isTest").render()); } private File constructTargetPath() { ST targetPath = this.findTemplate("targetPath"); return this.isTest ? new File(this.moduleBaseDir, GENERATED_TEST_SOURCES_LOCATION + targetPath.render()) : new File(this.moduleBaseDir, GENERATED_SOURCES_LOCATION + targetPath.render()); } private static boolean sourceFileExists(File outputFile) { File newPath = new File(outputFile.getAbsolutePath() .replace("target", "src") .replace("generated-sources", "main") .replace("generated-test-sources", "test")); return newPath.exists(); } public boolean isTest() { return this.isTest; } public interface ErrorListener { void error(String string); } private final class LoggingErrorListener implements STErrorListener { private final ErrorListener errorListener; private LoggingErrorListener(ErrorListener errorListener) { this.errorListener = errorListener; } private void logError(STMessage stMessage, String errorType) { String error = String.format("String template %s error while processing [%s]: %s", errorType, GsCollectionsCodeGenerator.this.url.getPath(), stMessage.toString()); this.errorListener.error(error); throw new RuntimeException(); } public void compileTimeError(STMessage stMessage) { this.logError(stMessage, "compile time"); } public void runTimeError(STMessage stMessage) { this.logError(stMessage, "run time"); } public void IOError(STMessage stMessage) { this.logError(stMessage, "IO"); } public void internalError(STMessage stMessage) { this.logError(stMessage, "internal"); } } }