package codegen.codetemplates; import static CIAPI.Java.logging.Log.debug; import static CIAPI.Java.logging.Log.trace; import static CIAPI.Java.logging.Log.error; import static CIAPI.Java.logging.Log.warn; import java.io.File; import java.io.FileNotFoundException; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import java.util.Scanner; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * <p> * Represents a Code Template. This can contain simple or complex template entries. * </p> * <p> * The general idea is, that once you have a code template, you can write replacement files describe * how the templates should be filled. The code templates and replacement files can be combined with * model objects to fill the templates and create actual code. * </p> * * @author Justin Nelson */ public class CodeTemplate implements TemplateEntry { private static String simplePatternS = "<@([^@]*)@>"; private static String compoundPatternStartS = "<@@([^@]*)@@>"; private static String compoundPatternEndS = "<@@@@>"; private Map<String, TemplateEntry> templateReplacement; private String fullTemplateString; private String resultingTemplate; /** * Creates a new template from the given string. * * @param template */ public CodeTemplate(String template) { debug("Parsing template starting with: " + template.substring(0, Math.min(30, template.length()))); templateReplacement = new HashMap<String, TemplateEntry>(); fullTemplateString = template; // While there are still compound patterns while (template.contains(compoundPatternEndS)) { String nextTagString = findNextTagString(template, 0); // find the beginning of the compound template int compoundStart = template.indexOf(nextTagString) + nextTagString.length(); // find the end of the template int compoundEnd = findMatchingPatternClose(template, compoundStart); // the compound template is the string starting at the open tag, and // going to the close tag String matchedTemplate = template.substring(compoundStart, compoundEnd); // Use this to find the name of the parameter in the compound tag String compountPropDescriptor = nextTagString.substring(3, nextTagString.length() - 3); // compoundProperties may have a delimiter specified String[] compoundPropNameParts = compountPropDescriptor.split(":"); String delim = null; if (compoundPropNameParts.length == 2) delim = compoundPropNameParts[1]; templateReplacement.put(compoundPropNameParts[0], new CompoundCodeTemplate(matchedTemplate, delim)); // here we replace the text of the combined template with a simple template entry text. template = template.replace(nextTagString + matchedTemplate + compoundPatternEndS, "<@" + compoundPropNameParts[0] + "@>"); } // we have replaced all of the complex template entries, with simple entries. Now, we simply // find all of the simple entries and put in empty template replacements Pattern simplePattern = Pattern.compile(simplePatternS); Matcher m = simplePattern.matcher(template); while (m.find()) { String matchedTerm = m.group(1); if (!templateReplacement.containsKey(matchedTerm)) { templateReplacement.put(matchedTerm, new EmptyCodeTemplate()); } } resultingTemplate = template; } /** * From the template, find the next compound tag. * * @param template * the template string * @return */ private static String findNextTagString(String template, int patternStart) { Pattern compoundOpen = Pattern.compile(compoundPatternStartS); Matcher m = compoundOpen.matcher(template); if (!m.find(patternStart)) { // there was no matching pattern return null; } else { if (!m.group().equals(compoundPatternEndS)) { return m.group(0); } else { return null; } } } /** * This number represents the max depth we allow the following method to traverse before we * assume there is a bug in the code, or the Template file was poorly structured. */ private static final int MAX_DEPTH_BEFORE_ERROR = 100000; /** * Parses a string to find matching tag pairs * * @param template * the template to match * @param patternOpenLocation * the place in the string the compound pattern begins * @return the location of the tag that closes the compound pattern */ private static int findMatchingPatternClose(String template, int patternOpenLocation) { // Start looking right after the start index int currentIndex = patternOpenLocation + 1; // keep track of how deep the nesting goes int depthCount = 0; while (true) { String nextOpenTag = findNextTagString(template, currentIndex); // find the next index of an open tag int nextOpenIdx = nextOpenTag == null ? -1 : template.indexOf(nextOpenTag, currentIndex) + nextOpenTag.length(); // find the next index of a closing tag int nextCloseIdx = template.indexOf(compoundPatternEndS, currentIndex); // if we find a close tag before an open tag if (nextOpenIdx == -1 || nextCloseIdx < nextOpenIdx) { // if we are a 0 depth if (depthCount == 0) { // then we have found our match return nextCloseIdx; } else { // otherwise, we have gone up a depth, and will start // looking after that tag depthCount--; currentIndex = nextCloseIdx + 1; } } else { // we have found an open tag before a close tag, so we go one // deeper, and begin looking at that point depthCount++; currentIndex = nextOpenIdx + 1; // Check to make sure we aren't stuck in the loop if (depthCount > MAX_DEPTH_BEFORE_ERROR) { error( new IllegalStateException( "The template parser has encountered an error. " + "Please make sure your template file is correctly built. " + "If there is no error with hte template file, please file a bug with the developers of this API.")); } } // if at any point the depth has gone negative, there is a serious // error if (depthCount < 0) { error(new AssertionError("The code is broken, or the template is. Check both.")); } } } /** * Adds or replaces a template entry definition * * @param templateKey * the name of the template to replace * @param entry * the new entry to place at the given template name * @return the old entry for the key. All templates will be initially filled with * {@link EmptyCodeTemplate}s. */ public TemplateEntry putNewTemplateDefinition(String templateKey, TemplateEntry entry) { trace("Addign new template definition: " + templateKey + " =>" + entry.toString()); TemplateEntry oldEntry = templateReplacement.put(templateKey, entry); // You can't replace a template definition for a key that doesn't exist if (oldEntry == null) { error(new IllegalArgumentException("This template does not contain a definition for the given term: " + templateKey)); } return oldEntry; } /** * Convenience method for binding a simple template entry to a key. * * @param templateKey * the template name to replace * @param entry * the value to place at the template name * @return an entry that was previously bound to the given key. */ public TemplateEntry putNewTemplateDefinition(String templateKey, String entry) { if (entry == null) { entry = ""; } return putNewTemplateDefinition(templateKey, new SimpleTemplateEntry(entry)); } /** * @param templateKey * the key of the desired template * @return the template entry corresponding to the given key */ public TemplateEntry getTemplateEntry(String templateKey) { if (templateKey == null) { throw new NullPointerException("The key must not be null"); } return templateReplacement.get(templateKey); } /** * Builds a new Code Template out of a template file. * * @param location * the location of the template file * @return a new code template based off of the file * @throws FileNotFoundException * if the file did not exist */ public static CodeTemplate loadTemplate(File location) throws FileNotFoundException { Scanner fin = new Scanner(location); StringBuilder templateString = new StringBuilder(); while (fin.hasNextLine()) { templateString.append(fin.nextLine()).append("\n"); } return new CodeTemplate(templateString.toString()); } @Override public String codeReplacement() { String code = resultingTemplate; for (Entry<String, TemplateEntry> entry : templateReplacement.entrySet()) { trace("Template: "+entry.getKey() + " => " + entry.getValue()); code = code.replaceAll("<@" + entry.getKey() + "@>", entry.getValue().codeReplacement()); } return code; } /** * Creates an empty template object. Used in copying an item */ private CodeTemplate() { } /** * Creates a copy of this template with empty mappings. * * @return an empty copy of this object. */ @Override public CodeTemplate copyEmptyTemplate() { CodeTemplate copy = new CodeTemplate(); copy.fullTemplateString = this.fullTemplateString; copy.resultingTemplate = this.resultingTemplate; copy.templateReplacement = new HashMap<String, TemplateEntry>(); for (String key : templateReplacement.keySet()) { copy.templateReplacement.put(key, templateReplacement.get(key).copyEmptyTemplate()); } return copy; } }