
com.peterebeles.autocode.AutocodeConcurrent Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of autoconcurrent Show documentation
Show all versions of autoconcurrent Show documentation
A minimalist library for converting double (64-bit) numerical Java code into float (32-bit)
The newest version!
/*
* Auto64to32F is released to Public Domain or MIT License. Either maybe used.
*/
package com.peterebeles.autocode;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* Generates concurrent implementations of a class using hints provided in comments throughout the code.
*
*
* - //CONCURRENT_CLASS_NAME TEXT override the default class name. Can be anywhere.
* - //CONCURRENT_INLINE TEXT will remove the comment in insert the text
* - //CONCURRENT_ABOVE TEXT will replace the line above with the text
* - //CONCURRENT_BELOW TEXT will replace the line below with the text
* - //CONCURRENT_REMOVE_BELOW will remove the line below
* - //CONCURRENT_REMOVE_ABOVE will remove the line above
* - //CONCURRENT_REMOVE_LINE will remove the line it is placed on
* - //CONCURRENT_MACRO NAME TEXT creates a macro that can be used instead of text
* - //CONCURRENT_OMIT_BEGIN It will omit everything until it finds an OMIT_END
* - //CONCURRENT_OMIT_END It will stop omitting when this is encountered.
*
*
* A macro is identified by enclosing its name with brackets, e.g. {NAME}.
*
* @author Peter Abeles
*/
@SuppressWarnings("NullAway")
public class AutocodeConcurrent {
public static String IMPORT_GENERATED = "import javax.annotation.Generated;";
public static String prefix = "//CONCURRENT_";
public static String tab = "\t";
public static String sourceRootName = "java";
public static String pathRootToTest = "../../test/java";
public static ConvertString originalToMT = name -> name + "_MT.java";
/**
* Converts the file from single thread into concurrent implementation
*/
public static void convertFile(File original) throws IOException {
File outputFile = determineClassName(original);
String classNameOld = className(original);
String classNameNew = className(outputFile);
// Read the file and split it up into lines
List inputLines = readLines(original, UTF_8);
List outputLines = new ArrayList<>();
List macros = new ArrayList<>();
// If an import statement has been found
boolean foundImport = false;
// parse each line by line looking for instructions
boolean foundClassDef = false;
// If true it will not copy lines over
boolean omit = false;
for (int i = 0; i < inputLines.size(); i++) {
String line = inputLines.get(i);
int where = line.indexOf(prefix);
if (where < 0) {
if (!foundImport && !foundClassDef && line.startsWith("import")) {
foundImport = true;
// Don't add the import twice
if (!containsStartsWith(IMPORT_GENERATED, inputLines))
outputLines.add(IMPORT_GENERATED);
} else if (!foundClassDef && line.contains("class " + classNameOld)) {
foundClassDef = true;
if (foundImport)
outputLines.add("@Generated(\"" + derivePackagePath(outputFile) + "." + classNameOld + "\")");
line = line.replaceFirst("class " + classNameOld, "class " + classNameNew);
} else if (foundImport && line.startsWith("@Generated")) {
// If the file already has a generated statement and we are going to add our own remove the old one
continue;
} else {
line = line.replace(classNameOld + "(", classNameNew + "(");
}
if (omit)
continue;
for (Macro m : macros)
line = line.replace(m.name, m.text);
outputLines.add(line);
continue;
}
String type = readType(line, where + prefix.length());
String whitespaces = line.substring(0, where);
int frontLength = where + prefix.length() + type.length();
String message = line.length() > frontLength ? line.substring(frontLength + 1) : "";
switch (type) {
case "CLASS_NAME":
continue; // ignore. already processed
case "INLINE":
outputLines.add(whitespaces + message);
break;
case "ABOVE":
// remove the previous line
outputLines.remove(outputLines.size() - 1);
outputLines.add(whitespaces + message);
break;
case "BELOW":
outputLines.add(whitespaces + message);
i += 1; // skip next line
break;
case "REMOVE_ABOVE":
outputLines.remove(outputLines.size() - 1);
break;
case "REMOVE_BELOW":
i += 1; // skip next line
break;
case "REMOVE_LINE":
break;
case "OMIT_BEGIN":
omit = true;
break;
case "OMIT_END":
omit = false;
break;
case "MACRO": {
String[] words = message.split(" ");
if (words.length != 2)
throw new RuntimeException("Expected only two words for the macro. " + message);
Macro m = new Macro();
m.name = words[0];
m.text = words[1];
macros.add(m);
break;
}
default:
throw new RuntimeException("Unknown: " + type);
}
}
PrintStream out = new PrintStream(outputFile);
for (int i = 0; i < outputLines.size(); i++) {
out.println(outputLines.get(i));
}
out.close();
createTestIfNotThere(outputFile, sourceRootName, pathRootToTest);
}
/**
* If a test class doesn't exist it will create one. This is to remind the user to do it
*/
private static void createTestIfNotThere(File file, String sourceRootName, String pathRootToTest) {
String fileName = "Test" + file.getName();
List packagePath = new ArrayList<>();
while (true) {
if (file.getParentFile() == null) {
throw new IllegalArgumentException("Problem! Can't find '" + sourceRootName + "' directory");
}
String parentName = file.getParentFile().getName();
file = file.getParentFile();
if (parentName.equals(sourceRootName)) {
break;
} else {
packagePath.add(parentName);
}
}
file = new File(file, pathRootToTest);
for (int i = packagePath.size() - 1; i >= 0; i--) {
file = new File(file, packagePath.get(i));
}
file = new File(file, fileName);
// only create it if it doesn't exist
if (file.exists()) {
return;
}
// Simplify the path before passing it in
createTestFile(file.toPath().toAbsolutePath().normalize().toFile());
}
private static void createTestFile(File path) {
if (!path.getParentFile().exists()) {
if (!path.getParentFile().mkdirs())
throw new RuntimeException("Failed to create directories. " + path.getAbsolutePath());
}
System.out.println("Creating " + path);
try {
String className = className(path);
String packagePath = derivePackagePath(path);
PrintStream out = new PrintStream(path);
out.println("package " + packagePath + ";\n" +
"\n" +
"import org.junit.jupiter.api.Test;\n" +
"\n" +
"import static org.junit.jupiter.api.Assertions.fail;\n" +
"\n" +
"class " + className + " {\n" +
tab + "@Test\n" +
tab + "void compareToSingle() {\n" +
tab + tab + "fail(\"implement\");\n" +
tab + "}\n" +
"}\n");
out.close();
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
private static String derivePackagePath(File file) {
List packagePath = new ArrayList<>();
while (true) {
if (file.getParentFile() == null)
throw new IllegalArgumentException("Problem! Can't find java directory");
String parent = file.getParentFile().getName();
if (parent.equals("test") || parent.equals("src") || parent.equals("java")) {
break;
} else {
packagePath.add(parent);
file = file.getParentFile();
}
}
String output = "";
for (int i = packagePath.size() - 1; i >= 0; i--) {
output += packagePath.get(i) + ".";
}
return output.substring(0, output.length() - 1);
}
/**
* Searches the input file for an override. If none is found then _MT is added to the class name.
*
* @param original Input file
* @return Output file
*/
private static File determineClassName(File original) throws IOException {
String text = readFileToString(original, Charset.forName("UTF-8"));
if (!text.contains("//CONCURRENT"))
throw new IOException("Not a concurrent file");
String pattern = "//CONCURRENT_CLASS_NAME ";
int where = text.indexOf(pattern);
if (where < 0) {
String name = originalToMT.convert(className(original));
return new File(original.getParent(), name);
}
String name = readUntilEndOfLine(text, where + pattern.length());
return new File(original.getParent(), name + ".java");
}
private static String readType(String line, int location) {
int index0 = location;
while (location < line.length()) {
char c = line.charAt(location);
if (Character.isWhitespace(c)) {
return line.substring(index0, location);
}
location += 1;
}
// something went wrong
return line.substring(index0, location);
}
private static String readUntilEndOfLine(String text, int location) {
int index0 = location;
while (location < text.length()) {
char c = text.charAt(location);
if (c == '\r' || c == '\n') {
return text.substring(index0, location);
}
location += 1;
}
return text.substring(index0, location);
}
static String readFileToString(File file, Charset encoding) throws IOException {
byte[] encoded = Files.readAllBytes(file.toPath());
return new String(encoded, encoding);
}
private static String className(File file) {
String n = file.getName();
return n.substring(0, n.length() - 5);
}
private static class Macro {
String name;
String text;
}
public static void convertDir( File directory, String include, String exclude ) {
if (!directory.isDirectory())
throw new IllegalArgumentException("Must be a directory: '" + directory.getPath() + "'");
File[] files = directory.listFiles();
if (files == null)
throw new IllegalArgumentException("No files");
for (File f : files) {
String name = f.getName();
if (!name.matches(include) || name.matches(exclude))
continue;
try {
convertFile(f);
} catch (IOException ignore) {
// System.out.println(name+" "+e.getMessage());
}
}
}
public static List readLines(final File file, final Charset encoding) throws IOException {
List lines = new ArrayList<>();
try (Stream stream = Files.lines(file.toPath(), encoding)) {
stream.forEach(lines::add);
}
return lines;
}
/**
* Checks to see if any of the lines start with the specified text
*/
public static boolean containsStartsWith(String text, List lines) {
for (int i = 0; i < lines.size(); i++) {
if (lines.get(i).startsWith(text))
return true;
}
return false;
}
@FunctionalInterface public interface ConvertString {
String convert(String input);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy