diff --git a/README.md b/README.md index 14c9907..5428dd3 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,45 @@ # Simple-Code-Generator +This Tool is a very Simple Code Generator that can read a template file and segment it based on a Set of variables that can be defined. +Separate to that this tool can replace text via simple Regular Expression mapping. +It has a cache that keeps track of the input (if it was changed), and only cares that the file format is Text based. + +It is as bare bones as it can get but that also makes it flexible. + + +# How to create a Template Processor + +Create a class that extends TemplateProcessor. +And run the process method +SourceFolder: Is the folder that is traversed through and Files are given back. +OutputFolder: Is the folder where the Processed files get put into. If the "relativePackages" are set to true then the source folder structure is transferred. +DataFolder: Is the folder where the input cache is stored. It uses a MD5 generator to compare inputs. Right now only FileNames without extensions are stored in there. So no Duplicated FileName support for now. + +##### Methods: +init: Is called when the Processes was started for the first time. +isFileValid: The Method that checks if a File can be used for a template. +relativePackages: If the Folder Structure from the SourceFolder should be transferred to the OutputFolder. +createProcesses: The Main Function that provides the FileName (without extension) and a consumer of how the file should be processed. +A TemplateProcess is a Collection of Variables that is used for File Segmentation and a List of UnaryOperators that allow to edit the build code. +A Simple sum up: Template Process is Tuple, List> that also contains the new FileName. + +```java +package test.example; + +public class ExampleClass +{ +#if METHOD_ONE + public void methodOne() {} +#else if METHOD_TWO && !SKIP + public void methodTwo() {} +#else + public void methodThree() {} +#endif +} +``` + +If the Variable "METHOD_ONE" is present only the code in the if is parsed into the template process. +If the Variable "METHOD_TWO" is present and the "SKIP" Variable is missing then the code in the else if block is parsed into the template process +Otherwise the else block is included. +Whenever a if is started, it has to end with a #endif, Even if the last condition is a else if or a else. +Recursion is supported and necessary \ No newline at end of file diff --git a/src/main/java/speiger/src/builder/base/ConditionedSegment.java b/src/main/java/speiger/src/builder/base/ConditionedSegment.java new file mode 100644 index 0000000..ba65c72 --- /dev/null +++ b/src/main/java/speiger/src/builder/base/ConditionedSegment.java @@ -0,0 +1,83 @@ +package speiger.src.builder.base; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.StringJoiner; +import java.util.regex.Pattern; + +import speiger.src.builder.conditions.ICondition; + +public class ConditionedSegment +{ + static final Pattern AND = Pattern.compile("(&&)"); + static final Pattern OR = Pattern.compile("(||)"); + + int index; + List segments = new ArrayList<>(); + + public ConditionedSegment(int index) + { + this.index = index; + } + + public void addSegment(Segment segment) + { + segments.add(segment); + } + + public int build(Set parsePool, StringBuilder builder, int baseIndex) + { + baseIndex += index; + int length = builder.length(); + for(int i = 0,m=segments.size();i lines, int currentIndex, int startIndex, List segments) throws IllegalStateException + { + ConditionedSegment segment = new ConditionedSegment(startIndex); + ICondition condition = ICondition.parse(currentLine); + List childSegments = new ArrayList<>(); + StringJoiner segmentText = new StringJoiner("\n", "\n", ""); + for(int i = currentIndex+1;i(); + segmentText = new StringJoiner("\n", "\n", ""); + } + else if(trimmed.startsWith("#else")) + { + segment.addSegment(new Segment(segmentText.toString(), condition, childSegments)); + condition = ICondition.ALWAYS_TRUE; + childSegments = new ArrayList<>(); + segmentText = new StringJoiner("\n", "\n", ""); + } + else if(trimmed.startsWith("#endif")) + { + segment.addSegment(new Segment(segmentText.toString(), condition, childSegments)); + segments.add(segment); + return i - currentIndex; + } + else if(trimmed.startsWith("#if")) + { + i += parse(trimmed.substring(3).trim(), lines, i, segmentText.length(), childSegments); + } + continue; + } + segmentText.add(s); + } + throw new IllegalStateException("Unclosed #If found!"); + } + +} diff --git a/src/main/java/speiger/src/builder/base/Segment.java b/src/main/java/speiger/src/builder/base/Segment.java new file mode 100644 index 0000000..fb48f7a --- /dev/null +++ b/src/main/java/speiger/src/builder/base/Segment.java @@ -0,0 +1,35 @@ +package speiger.src.builder.base; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import speiger.src.builder.conditions.ICondition; + +public class Segment +{ + ICondition condition; + String text; + List segments = new ArrayList<>(); + + public Segment(String text, ICondition condition, List segments) + { + this.text = text; + this.condition = condition; + this.segments = segments; + } + + public boolean build(Set parsePool, StringBuilder builder, int index) + { + if(condition.isValid(parsePool)) + { + builder.insert(index, text); + for(int i = 0,offset=0,m=segments.size();i segments; + + public Template(String fileName, String textFile, List segments) + { + this.fileName = fileName; + this.textFile = textFile; + this.segments = segments; + } + + public String getFileName() + { + return fileName; + } + + public String build(Set parsePool, List> mappers) + { + StringBuilder builder = new StringBuilder(textFile); + for(int i = 0,offset=0,m=segments.size();i segments = new ArrayList(); + StringJoiner joiner = new StringJoiner("\n"); + List lines = Files.readAllLines(file); + for(int i = 0;i conditions = new ArrayList<>(); + + public AndCondition(ICondition base) + { + conditions.add(base); + } + + public void addCondition(ICondition e) + { + conditions.add(e); + } + + @Override + public boolean isValid(Set parsePool) + { + for(int i = 0,m=conditions.size();i parsePool) + { + return parsePool.contains(flag) != inverted; + } +} diff --git a/src/main/java/speiger/src/builder/conditions/ICondition.java b/src/main/java/speiger/src/builder/conditions/ICondition.java new file mode 100644 index 0000000..78f9877 --- /dev/null +++ b/src/main/java/speiger/src/builder/conditions/ICondition.java @@ -0,0 +1,61 @@ +package speiger.src.builder.conditions; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public interface ICondition +{ + public static final ICondition ALWAYS_TRUE = T -> true; + + public boolean isValid(Set parsePool); + + public static ICondition parse(String condition) + { + String[] elements = condition.split(" "); + List conditions = new ArrayList(); + for(int i = 0;i conditions; + + public OrCondition(List conditions) + { + this.conditions = conditions; + } + + @Override + public boolean isValid(Set parsePool) + { + for(int i = 0,m=conditions.size();i +{ + Pattern pattern; + String replacement; + String argumentBreaker; + String braces = "()"; + boolean removeBraces; + + public ArgumentMapper(String pattern, String replacement, String argumentBreaker) + { + this.pattern = Pattern.compile(pattern); + this.replacement = replacement; + this.argumentBreaker = argumentBreaker; + } + + public ArgumentMapper setBraceType(String s) + { + if(s.length() != 2) throw new IllegalStateException("Start and End char only"); + braces = s; + return this; + } + + public ArgumentMapper removeBraces() + { + removeBraces = true; + return this; + } + + @Override + public String apply(String t) + { + Matcher matcher = pattern.matcher(t); + if(matcher.find()) + { + StringBuffer buffer = new StringBuffer(); + do + { + String text = RegexUtil.searchUntil(t, matcher.end()-1, braces.charAt(0), braces.charAt(1)); + if(!text.isEmpty()) + { + RegexUtil.skip(matcher.appendReplacement(buffer, ""), text.length()); + buffer.append(String.format(replacement, (Object[])getString(text).split(argumentBreaker))); + } + } + while(matcher.find()); + matcher.appendTail(buffer); + return buffer.toString(); + } + return t; + } + + protected String getString(String s) + { + return removeBraces ? s.substring(1, s.length() - 1) : s; + } +} diff --git a/src/main/java/speiger/src/builder/mappers/InjectMapper.java b/src/main/java/speiger/src/builder/mappers/InjectMapper.java new file mode 100644 index 0000000..b80ff22 --- /dev/null +++ b/src/main/java/speiger/src/builder/mappers/InjectMapper.java @@ -0,0 +1,61 @@ +package speiger.src.builder.mappers; + +import java.util.function.UnaryOperator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import speiger.src.builder.misc.RegexUtil; + +public class InjectMapper implements UnaryOperator +{ + Pattern pattern; + String replacement; + String braces = "()"; + boolean removeBraces; + + public InjectMapper(String pattern, String replacement) + { + this.pattern = Pattern.compile(pattern); + this.replacement = replacement; + } + + public InjectMapper setBraceType(String s) + { + if(s.length() != 2) throw new IllegalStateException("Start and End char only"); + braces = s; + return this; + } + + public InjectMapper removeBraces() + { + removeBraces = true; + return this; + } + + @Override + public String apply(String t) + { + Matcher matcher = pattern.matcher(t); + if(matcher.find()) + { + StringBuffer buffer = new StringBuffer(); + do + { + String text = RegexUtil.searchUntil(t, matcher.end()-1, braces.charAt(0), braces.charAt(1)); + if(!text.isEmpty()) + { + RegexUtil.skip(matcher.appendReplacement(buffer, ""), text.length()); + buffer.append(String.format(replacement, getString(text))); + } + } while (matcher.find()); + matcher.appendTail(buffer); + return buffer.toString(); + } + return t; + } + + protected String getString(String s) + { + return removeBraces ? s.substring(1, s.length() - 1) : s; + } +} diff --git a/src/main/java/speiger/src/builder/mappers/LineMapper.java b/src/main/java/speiger/src/builder/mappers/LineMapper.java new file mode 100644 index 0000000..d908b61 --- /dev/null +++ b/src/main/java/speiger/src/builder/mappers/LineMapper.java @@ -0,0 +1,43 @@ +package speiger.src.builder.mappers; + +import java.util.function.UnaryOperator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import speiger.src.builder.misc.RegexUtil; + +public class LineMapper implements UnaryOperator +{ + Pattern pattern; + + public LineMapper(String pattern) + { + this.pattern = Pattern.compile(pattern, Pattern.LITERAL); + } + + @Override + public String apply(String t) + { + Matcher matcher = pattern.matcher(t); + if(matcher.find()) + { + StringBuffer buffer = new StringBuffer(); + do + { + int start = matcher.end() - 1; + int[] result = RegexUtil.findFullLine(t, start); + if(result != null) + { + matcher.appendReplacement(buffer, pattern.pattern()); + buffer.setLength(buffer.length() - (start - result[0])); + RegexUtil.skip(matcher, result[1] - start); + } + } while (matcher.find()); + matcher.appendTail(buffer); + return buffer.toString(); + } + return t; + } + + +} diff --git a/src/main/java/speiger/src/builder/mappers/SimpleMapper.java b/src/main/java/speiger/src/builder/mappers/SimpleMapper.java new file mode 100644 index 0000000..e63c489 --- /dev/null +++ b/src/main/java/speiger/src/builder/mappers/SimpleMapper.java @@ -0,0 +1,22 @@ +package speiger.src.builder.mappers; + +import java.util.function.UnaryOperator; +import java.util.regex.Pattern; + +public class SimpleMapper implements UnaryOperator +{ + Pattern pattern; + String replacement; + + public SimpleMapper(String pattern, String replacement) + { + this.pattern = Pattern.compile(pattern, Pattern.LITERAL); + this.replacement = replacement; + } + + @Override + public String apply(String t) + { + return pattern.matcher(t).replaceAll(replacement); + } +} diff --git a/src/main/java/speiger/src/builder/misc/FileUtils.java b/src/main/java/speiger/src/builder/misc/FileUtils.java new file mode 100644 index 0000000..ab0daf0 --- /dev/null +++ b/src/main/java/speiger/src/builder/misc/FileUtils.java @@ -0,0 +1,88 @@ +package speiger.src.builder.misc; + +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +public class FileUtils +{ + public static boolean isValid(Path path, Map existing) + { + String s = existing.get(getFileName(path)); + return s == null || !s.equals(FileUtils.getMD5String(path)); + } + + public static Map loadMappings(Path dataFolder) throws IOException + { + Map result = new LinkedHashMap<>(); + dataFolder = dataFolder.resolve("cache.bin"); + if(Files.exists(dataFolder)) + { + for(String s : Files.readAllLines(dataFolder)) + { + String[] array = s.split("="); + if(array.length == 2) + { + result.put(array[0], array[1]); + } + } + } + return result; + } + + public static void saveMappings(Map mappings, Path dataFolder) + { + try(BufferedWriter writer = Files.newBufferedWriter(dataFolder.resolve("cache.bin"))) + { + for(Entry entry : mappings.entrySet()) + { + writer.write(entry.getKey()+"="+entry.getValue()); + writer.newLine(); + } + writer.flush(); + } + catch(Exception e) + { + e.printStackTrace(); + } + } + + + public static String getFileName(Path path) + { + String name = path.getFileName().toString(); + int index = name.indexOf("."); + return index == -1 ? name : name.substring(0, index); + } + + public static String getMD5String(Path path) + { + try(InputStream stream = Files.newInputStream(path)) + { + MessageDigest digest = MessageDigest.getInstance("MD5"); + byte[] byteArray = new byte[2048]; + int bytesCount = 0; + while((bytesCount = stream.read(byteArray)) != -1) + { + digest.update(byteArray, 0, bytesCount); + } + byte[] bytes = digest.digest(); + StringBuilder sb = new StringBuilder(); + for(int i = 0;i < bytes.length;i++) + { + sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16).substring(1)); + } + return sb.toString(); + } + catch(Exception e) + { + return null; + } + } +} diff --git a/src/main/java/speiger/src/builder/misc/RegexUtil.java b/src/main/java/speiger/src/builder/misc/RegexUtil.java new file mode 100644 index 0000000..75e096f --- /dev/null +++ b/src/main/java/speiger/src/builder/misc/RegexUtil.java @@ -0,0 +1,77 @@ +package speiger.src.builder.misc; + +import java.lang.reflect.Field; +import java.util.regex.Matcher; + +public class RegexUtil +{ + static Field LAST_POS; + + public static Matcher skip(Matcher matcher, int amount) + { + try + { + LAST_POS.setInt(matcher, LAST_POS.getInt(matcher) + amount); + } + catch(Exception e) + { + e.printStackTrace(); + } + return matcher; + } + + public static String searchUntil(String text, int startIndex, char increase, char decrease) + { + if(text.charAt(startIndex + 1) != increase) + { + return ""; + } + int inc = 0; + StringBuilder builder = new StringBuilder(); + for(int i = startIndex + 1;i pathBuilder; + String fileName; + Set parsePool = new HashSet<>(); + List> mappers = new ArrayList<>(); + + public TemplateProcess(String fileName) + { + this.fileName = fileName; + } + + public void setPathBuilder(UnaryOperator pathBuilder) + { + this.pathBuilder = pathBuilder; + } + + public void addFlags(String...flags) + { + parsePool.addAll(Arrays.asList(flags)); + } + + public void addFlags(Collection flags) + { + parsePool.addAll(flags); + } + + public void addMapper(UnaryOperator mapper) + { + mappers.add(mapper); + } + + public void addMappers(Collection> mappers) + { + this.mappers.addAll(mappers); + } +} diff --git a/src/main/java/speiger/src/builder/processor/TemplateProcessor.java b/src/main/java/speiger/src/builder/processor/TemplateProcessor.java new file mode 100644 index 0000000..4c67276 --- /dev/null +++ b/src/main/java/speiger/src/builder/processor/TemplateProcessor.java @@ -0,0 +1,88 @@ +package speiger.src.builder.processor; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import speiger.src.builder.base.Template; +import speiger.src.builder.misc.FileUtils; + +public abstract class TemplateProcessor +{ + Path sourceFolder; + Path outputFolder; + Path dataFolder; + boolean init = false; + + public TemplateProcessor(Path sourceFolder, Path outputFolder, Path dataFolder) + { + this.sourceFolder = sourceFolder; + this.outputFolder = outputFolder; + this.dataFolder = dataFolder; + } + + protected abstract void init(); + + protected abstract boolean isFileValid(Path fileName); + + public abstract void createProcesses(String fileName, Consumer process); + + protected abstract boolean relativePackages(); + + public final boolean process(boolean force) throws IOException, InterruptedException + { + if(!init) + { + init = true; + init(); + } + Map existing = FileUtils.loadMappings(dataFolder); + List pathsLeft = Files.walk(sourceFolder).filter(Files::isRegularFile).filter(T -> isFileValid(T.getFileName())).filter(T -> force || FileUtils.isValid(T, existing)).collect(Collectors.toList()); + if(pathsLeft.isEmpty() && !force) + { + System.out.println("Nothing has changed"); + return false; + } + final boolean relative = relativePackages(); + ThreadPoolExecutor service = (ThreadPoolExecutor)Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + service.setKeepAliveTime(10, TimeUnit.MILLISECONDS); + service.allowCoreThreadTimeOut(true); + service.submit(() -> { + for(int i = 0,m=pathsLeft.size();i service.execute(new BuildTask(relative ? outputFolder.resolve(sourceFolder.relativize(path).getParent()) : outputFolder, template, T))); + } + catch(Exception e) + { + e.printStackTrace(); + } + } + }); + long start = System.currentTimeMillis(); + System.out.println("Started Tasks"); + for(int i = 0,m=pathsLeft.size();i 0) + { + Thread.sleep(10); + } + System.out.println("Finished Tasks: "+(System.currentTimeMillis() - start)+"ms"); + FileUtils.saveMappings(existing, dataFolder); + System.out.print("Saved Changes"); + return true; + } +}