Speiger d5a47ce568 Added Iteration support.
Said support allows to use the #iterate & #argument #enditerate
parameters.
Which will duplicate the text block inside of it x amount of times with
arguments being replaced.

The idea being that if code can be replicated the iterate option would
simplify the process.

Multiple arguments can be defined (they will be not part of the output)
and it will simply insert them.
Note that all argument parameters have to be equal size.
Otherwise the iterator processor will fail.

Iterators can be stacked too to create layers upon layers.

On top of that the iterator is part of the Template parser and not of
the template processor meaning its a Pre processor step.
And all mappers still apply themselves on to the output of the
iterators!
2023-06-27 13:29:31 +02:00

166 lines
6.1 KiB
Java

package speiger.src.builder.processor;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import speiger.src.builder.base.Template;
import speiger.src.builder.mappers.IMapper;
import speiger.src.builder.misc.FileUtils;
public abstract class TemplateProcessor
{
Path sourceFolder;
Path outputFolder;
Path dataFolder;
boolean silencedSuccess;
boolean init = false;
public TemplateProcessor(Path sourceFolder, Path outputFolder, Path dataFolder)
{
this(false, sourceFolder, outputFolder, dataFolder);
}
public TemplateProcessor(boolean silencedSuccess, Path sourceFolder, Path outputFolder, Path dataFolder)
{
this.silencedSuccess = silencedSuccess;
this.sourceFolder = sourceFolder;
this.outputFolder = outputFolder;
this.dataFolder = dataFolder;
}
/**
* Function that is called before the initial run of the template processor.
* It is only called once to basically initialize the program and then run as needed.
*/
protected abstract void init();
/**
* Function that decides which file is going to be used and which not. Basically black/white list as needed
* @param fileName FileName without folder structure. Since this is designed to have 0 duplicated file names.
* @return true if it is valid to be processed or false if it isn't
*/
protected abstract boolean isFileValid(Path fileName);
/**
* Main function that processes a filename to its target
* @param fileName name of the file that should be processed without the type of file. (.template or .json etc etc)
* @param process all modifications to the file that should be executed
*/
public abstract void createProcesses(String fileName, Consumer<TemplateProcess> process);
/**
* Function that asks if the folder structure on the source folder should be kept or not
* @return true if the source-structure should be kept or flattened.
*/
protected abstract boolean relativePackages();
/**
* Function that tests if a Mapper was used or not. Useful for small scale projects that barely have any mappers
* but sadly more like to cause not needed warnings for large scale projects.
* @return true if it should be enabled
*/
protected abstract boolean debugUnusedMappers();
/**
* Function called before the Template Processor runs.
* But after init is called and All files are loaded in and the task queue is already ready.
*/
protected void beforeStart() {}
/**
* Function that is called while the template generation is running and all after the File hashes for the memory are being generated.
* If you have anything to do while the processor is running this would be the ideal time to do it.
*/
protected void whileProcessing() {}
/**
* Function that is being called after the template processor is called and after the file cache has been updated
* Basically just before "Saved Changes" is called.
*/
protected void afterFinish() {}
protected Path getSourceFolder() { return sourceFolder; }
protected Path getOutputFolder() { return outputFolder; }
protected Path getDataFolder() { return dataFolder; }
protected boolean isSilencedSuccess() { return silencedSuccess; }
@SuppressWarnings("unchecked")
public final boolean process(boolean force) throws IOException, InterruptedException
{
if(!init)
{
init = true;
init();
}
Map<String, String> existing = FileUtils.loadMappings(dataFolder);
List<Path> 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;
}
AtomicLong[] counters = new AtomicLong[]{new AtomicLong(), new AtomicLong(), new AtomicLong()};
final boolean relative = relativePackages();
Set<IMapper>[] mappers = debugUnusedMappers() ? new Set[]{Collections.synchronizedSet(new HashSet<>()), Collections.synchronizedSet(new HashSet<>())} : null;
ThreadPoolExecutor service = (ThreadPoolExecutor)Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
service.setKeepAliveTime(10, TimeUnit.MILLISECONDS);
service.allowCoreThreadTimeOut(true);
beforeStart();
service.submit(() -> {
for(int i = 0,m=pathsLeft.size();i<m;i++)
{
Path path = pathsLeft.get(i);
try
{
long startTime = System.currentTimeMillis();
Template template = Template.parse(path);
counters[2].addAndGet(System.currentTimeMillis() - startTime);
counters[1].addAndGet(1);
createProcesses(FileUtils.getFileName(path), T -> {service.execute(new BuildTask(relative ? outputFolder.resolve(sourceFolder.relativize(path).getParent()) : outputFolder, template, T, silencedSuccess, mappers));counters[0].addAndGet(1);});
}
catch(Exception e)
{
e.printStackTrace();
}
}
});
long start = System.currentTimeMillis();
System.out.println("Started Tasks");
for(int i = 0,m=pathsLeft.size();i<m;i++)
{
Path path = pathsLeft.get(i);
existing.put(FileUtils.getFileName(path), FileUtils.getMD5String(path));
}
whileProcessing();
while(service.getActiveCount() > 0)
{
Thread.sleep(10);
}
if(mappers != null && mappers[0].size() != mappers[1].size())
{
mappers[0].removeAll(mappers[1]);
for(IMapper mapper : mappers[0])
{
System.out.println("Mapper ["+mapper.getSearchValue()+"] is not used in the Entire Build Process");
}
}
System.out.println("Finished Building ["+counters[0].get()+"] Files from ["+counters[1].get()+"] in "+(System.currentTimeMillis() - start)+"ms (Template Parsing: "+counters[2].get()+"ms (avg: "+((counters[2].get() / Math.max(1D, counters[1].get())))+"ms)");
FileUtils.saveMappings(existing, dataFolder);
afterFinish();
System.out.println("Saved Changes");
return true;
}
}