package com.github.setial.intellijjavadocs.template.impl;
import com.github.setial.intellijjavadocs.exception.SetupTemplateException;
import com.github.setial.intellijjavadocs.exception.TemplateNotFoundException;
import com.github.setial.intellijjavadocs.template.DocTemplateManager;
import com.github.setial.intellijjavadocs.template.DocTemplateProcessor;
import com.github.setial.intellijjavadocs.utils.XmlUtils;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiClassType;
import com.intellij.psi.PsiCodeBlock;
import com.intellij.psi.PsiField;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiModifierList;
import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.apache.commons.lang3.StringUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;
/**
* The type Doc template manager impl.
*
* @author Sergey Timofiychuk
*/
public class DocTemplateManagerImpl implements DocTemplateManager {
private static final Logger LOGGER = Logger.getInstance(DocTemplateManagerImpl.class);
private static final String TEMPLATES_PATH = "/templates.xml";
private static final String TEMPLATE = "template";
private static final String REGEXP = "regexp";
private static final String CLASS = "class";
private static final String FIELD = "field";
private static final String METHOD = "method";
private static final String CONSTRUCTOR = "constructor";
private Map<String, Template> classTemplates = new LinkedHashMap<String, Template>();
private Map<String, Template> fieldTemplates = new LinkedHashMap<String, Template>();
private Map<String, Template> methodTemplates = new LinkedHashMap<String, Template>();
private Map<String, Template> constructorTemplates = new LinkedHashMap<String, Template>();
private Configuration config;
private StringTemplateLoader templateLoader;
/**
* Instantiates a new Doc template manager object.
*/
public DocTemplateManagerImpl() {
templateLoader = new StringTemplateLoader();
config = new Configuration();
config.setDefaultEncoding("UTF-8");
config.setLocalizedLookup(false);
config.setTemplateLoader(templateLoader);
}
@Override
public void initComponent() {
try {
Document document = new SAXBuilder().build(DocTemplateProcessor.class.getResourceAsStream
(TEMPLATES_PATH));
Element root = document.getRootElement();
if (root != null) {
readTemplates(root, CLASS, classTemplates);
readTemplates(root, FIELD, fieldTemplates);
readTemplates(root, METHOD, methodTemplates);
readTemplates(root, CONSTRUCTOR, constructorTemplates);
}
} catch (Exception e) {
LOGGER.error(e);
}
}
@Override
public void disposeComponent() {
}
@NotNull
@Override
public String getComponentName() {
return COMPONENT_NAME;
}
@Nullable
@Override
@SuppressWarnings("ConstantConditions")
public Template getClassTemplate(@NotNull PsiClass classElement) {
StringBuilder builder = getClassSignature(classElement);
return getMatchingTemplate(builder.toString(), classTemplates);
}
@Nullable
@Override
public Template getMethodTemplate(@NotNull PsiMethod methodElement) {
Map<String, Template> templates;
if (methodElement.isConstructor()) {
templates = constructorTemplates;
} else {
templates = methodTemplates;
}
String signature = methodElement.getText();
PsiCodeBlock methodBody = methodElement.getBody();
if (methodBody != null) {
signature = signature.replace(methodBody.getText(), "");
}
return getMatchingTemplate(signature, templates);
}
@Nullable
@Override
public Template getFieldTemplate(@NotNull PsiField psiField) {
return getMatchingTemplate(psiField.getText(), fieldTemplates);
}
@NotNull
@Override
public Map<String, String> getClassTemplates() {
Map<String, String> templates = new LinkedHashMap<String, String>();
for (Entry<String, Template> entry : classTemplates.entrySet()) {
String template = extractTemplate(entry.getValue());
templates.put(entry.getKey(), template);
}
return templates;
}
@NotNull
@Override
public Map<String, String> getConstructorTemplates() {
Map<String, String> templates = new LinkedHashMap<String, String>();
for (Entry<String, Template> entry : constructorTemplates.entrySet()) {
String template = extractTemplate(entry.getValue());
templates.put(entry.getKey(), template);
}
return templates;
}
@NotNull
@Override
public Map<String, String> getMethodTemplates() {
Map<String, String> templates = new LinkedHashMap<String, String>();
for (Entry<String, Template> entry : methodTemplates.entrySet()) {
String template = extractTemplate(entry.getValue());
templates.put(entry.getKey(), template);
}
return templates;
}
@NotNull
@Override
public Map<String, String> getFieldTemplates() {
Map<String, String> templates = new LinkedHashMap<String, String>();
for (Entry<String, Template> entry : fieldTemplates.entrySet()) {
String template = extractTemplate(entry.getValue());
templates.put(entry.getKey(), template);
}
return templates;
}
@Override
public void setClassTemplates(@NotNull Map<String, String> templates) {
setupTemplates(templates, classTemplates, CLASS);
}
@Override
public void setConstructorTemplates(@NotNull Map<String, String> templates) {
setupTemplates(templates, constructorTemplates, CONSTRUCTOR);
}
@Override
public void setMethodTemplates(@NotNull Map<String, String> templates) {
setupTemplates(templates, methodTemplates, METHOD);
}
@Override
public void setFieldTemplates(@NotNull Map<String, String> templates) {
setupTemplates(templates, fieldTemplates, FIELD);
}
private void readTemplates(Element document, String elementName, Map<String, Template> templates)
throws IOException {
Element root = document.getChild(elementName);
@SuppressWarnings("unchecked")
List<Element> elements = root.getChildren(TEMPLATE);
for (Element element : elements) {
String name = element.getAttribute(REGEXP).getValue();
templates.put(name, createTemplate(name, elementName, XmlUtils.trimElementContent(element)));
}
}
@Nullable
private Template getMatchingTemplate(@NotNull String elementText, @NotNull Map<String, Template> templates) {
Template result = null;
for (Entry<String, Template> entry : templates.entrySet()) {
if (Pattern.compile(entry.getKey(), Pattern.DOTALL | Pattern.MULTILINE).matcher(elementText).matches()) {
result = entry.getValue();
break;
}
}
if (result == null) {
throw new TemplateNotFoundException(elementText);
}
return result;
}
private void setupTemplates(Map<String, String> from, Map<String, Template> to, String elementName) {
Map<String, Template> result = new LinkedHashMap<String, Template>();
for (Entry<String, String> entry : from.entrySet()) {
try {
result.put(entry.getKey(), createTemplate(entry.getKey(), elementName, entry.getValue()));
} catch (Exception e) {
throw new SetupTemplateException(e);
}
}
to.clear();
to.putAll(result);
}
private StringBuilder getClassSignature(PsiClass classElement) {
StringBuilder builder = new StringBuilder();
PsiModifierList modifierList = classElement.getModifierList();
if (modifierList != null) {
builder.append(modifierList.getText());
}
builder.append(" ");
if (classElement.isInterface()) {
builder.append("interface ");
} else if (classElement.isEnum()) {
builder.append("enum ");
} else {
builder.append("class ");
}
builder.append(classElement.getName());
builder.append(" ");
PsiClassType[] extendsTypes = classElement.getExtendsListTypes();
if (extendsTypes.length > 0) {
builder.append("extends ");
for (int i = 0; i < extendsTypes.length; i++) {
PsiClassType extendsType = extendsTypes[i];
builder.append(extendsType.getClassName());
if (i < extendsTypes.length - 1) {
builder.append(",");
}
builder.append(" ");
}
}
PsiClassType[] implementTypes = classElement.getImplementsListTypes();
if (implementTypes.length > 0) {
builder.append("implements");
for (int i = 0; i < implementTypes.length; i++) {
PsiClassType implementType = implementTypes[i];
builder.append(implementType.getClassName());
if (i < implementTypes.length - 1) {
builder.append(",");
}
builder.append(" ");
}
}
return builder;
}
private Template createTemplate(String templateRegexp, String elementName, String templateContent) throws IOException {
String templateName = normalizeName(elementName + templateRegexp);
if (templateLoader.findTemplateSource(templateName) != null) {
config.clearTemplateCache();
}
templateLoader.putTemplate(templateName, templateContent);
return config.getTemplate(templateName);
}
private String normalizeName(String templateName) {
String result = templateName.replaceAll("\\*", "_");
result = result.replaceAll("\\.", "_");
return result;
}
private String extractTemplate(Template templateData) {
Writer writer = new StringWriter();
try {
templateData.dump(writer);
} catch (IOException e) {
return StringUtils.EMPTY;
}
return writer.toString();
}
}