de.blizzy.documentr.markdown.macro.GroovyMacroScanner.java Source code

Java tutorial

Introduction

Here is the source code for de.blizzy.documentr.markdown.macro.GroovyMacroScanner.java

Source

/*
documentr - Edit, maintain, and present software documentation on the web.
Copyright (C) 2012-2013 Maik Schreiber
    
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
    
You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package de.blizzy.documentr.markdown.macro;

import groovy.lang.GroovyClassLoader;
import groovy.lang.Script;

import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;

import javax.annotation.PostConstruct;

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.MultipleCompilationErrorsException;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.codehaus.groovy.control.messages.Message;
import org.codehaus.groovy.control.messages.SyntaxErrorMessage;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.google.common.base.Charsets;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import de.blizzy.documentr.DocumentrConstants;
import de.blizzy.documentr.Settings;

@Component
@Slf4j
class GroovyMacroScanner {
    private static final class GroovyFileFilterImplementation implements FileFilter {
        @Override
        public boolean accept(File file) {
            return file.isFile() && file.getName().endsWith(".groovy"); //$NON-NLS-1$
        }
    }

    static final String MACROS_DIR_NAME = "macros"; //$NON-NLS-1$

    @Autowired
    private Settings settings;
    @Autowired
    private BeanFactory beanFactory;
    private File macrosDir;

    @PostConstruct
    public void init() throws IOException {
        macrosDir = new File(settings.getDocumentrDataDir(), MACROS_DIR_NAME);
        if (!macrosDir.exists()) {
            FileUtils.forceMkdir(macrosDir);
        }
    }

    Set<IMacro> findGroovyMacros() {
        log.info("registering macros from Groovy scripts in folder {}", macrosDir.getAbsolutePath()); //$NON-NLS-1$
        GroovyClassLoader classLoader = getGroovyClassLoader();
        Set<IMacro> macros = Sets.newHashSet();
        for (File file : findGroovyMacroFiles()) {
            IMacro macro = getMacro(file, classLoader);
            if (macro != null) {
                macros.add(macro);
            }
        }
        return macros;
    }

    private Set<File> findGroovyMacroFiles() {
        Set<File> result = Sets.newHashSet();
        for (File file : macrosDir.listFiles(new GroovyFileFilterImplementation())) {
            result.add(file);
        }
        return result;
    }

    private GroovyClassLoader getGroovyClassLoader() {
        ImportCustomizer importCustomizer = new ImportCustomizer();
        importCustomizer
                .addStarImports(DocumentrConstants.GROOVY_DEFAULT_IMPORTS.toArray(ArrayUtils.EMPTY_STRING_ARRAY));
        CompilerConfiguration compilerConfiguration = new CompilerConfiguration();
        compilerConfiguration.addCompilationCustomizers(importCustomizer);
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        GroovyClassLoader classLoader = new GroovyClassLoader(contextClassLoader, compilerConfiguration);
        return classLoader;
    }

    private IMacro getMacro(File file, GroovyClassLoader classLoader) {
        IMacro macro = null;
        try {
            Class<?> clazz = classLoader.parseClass(file);
            if (IMacro.class.isAssignableFrom(clazz)) {
                macro = (IMacro) clazz.newInstance();
            } else {
                Macro annotation = clazz.getAnnotation(Macro.class);
                if (annotation != null) {
                    if (ISimpleMacro.class.isAssignableFrom(clazz)) {
                        ISimpleMacro simpleMacro = (ISimpleMacro) clazz.newInstance();
                        macro = new SimpleMacroMacro(simpleMacro, annotation, beanFactory);
                    } else if (IMacroRunnable.class.isAssignableFrom(clazz)) {
                        @SuppressWarnings("unchecked")
                        Class<? extends IMacroRunnable> c = (Class<? extends IMacroRunnable>) clazz;
                        macro = new MacroRunnableMacro(c, annotation, beanFactory);
                    } else {
                        log.warn("class {} not supported: {}", clazz.getName(), file.getName()); //$NON-NLS-1$
                    }
                } else {
                    log.warn("class {} not supported, no @Macro annotation found: {}", clazz.getName(), //$NON-NLS-1$
                            file.getName());
                }
            }
        } catch (IOException e) {
            log.warn("error loading Groovy macro: " + file.getName(), e); //$NON-NLS-1$
        } catch (InstantiationException e) {
            log.warn("error loading Groovy macro: " + file.getName(), e); //$NON-NLS-1$
        } catch (IllegalAccessException e) {
            log.warn("error loading Groovy macro: " + file.getName(), e); //$NON-NLS-1$
        } catch (RuntimeException e) {
            log.warn("error loading Groovy macro: " + file.getName(), e); //$NON-NLS-1$
        }
        return macro;
    }

    List<String> listMacros() {
        List<File> files = Lists.newArrayList(findGroovyMacroFiles());
        Function<File, String> function = new Function<File, String>() {
            @Override
            public String apply(File file) {
                return StringUtils.substringBeforeLast(file.getName(), ".groovy"); //$NON-NLS-1$
            }
        };
        List<String> result = Lists.newArrayList(Lists.transform(files, function));
        Collections.sort(result, new Comparator<String>() {
            @Override
            public int compare(String name1, String name2) {
                return name1.compareToIgnoreCase(name2);
            }
        });
        return result;
    }

    String getMacroCode(String name) throws IOException {
        File file = new File(macrosDir, name + ".groovy"); //$NON-NLS-1$
        return FileUtils.readFileToString(file, Charsets.UTF_8);
    }

    List<CompilationMessage> verifyMacro(String code) {
        List<CompilationMessage> result = Lists.newArrayList();
        if (StringUtils.isNotBlank(code)) {
            GroovyClassLoader groovyClassLoader = getGroovyClassLoader();
            try {
                Class<?> clazz = groovyClassLoader.parseClass(code);
                if (Script.class.isAssignableFrom(clazz)) {
                    result.add(new CompilationMessage(CompilationMessage.Type.ERROR, 1, 1, 1, 1,
                            "Script must represent a class implementing IMacro, ISimpleMacro, or " + //$NON-NLS-1$
                                    "IMacroRunnable.")); //$NON-NLS-1$
                } else if (!IMacro.class.isAssignableFrom(clazz)) {
                    Macro annotation = clazz.getAnnotation(Macro.class);
                    if (annotation != null) {
                        if (!ISimpleMacro.class.isAssignableFrom(clazz)
                                && !IMacroRunnable.class.isAssignableFrom(clazz)) {

                            result.add(new CompilationMessage(CompilationMessage.Type.ERROR, 1, 1, 1, 1,
                                    "Class " + clazz.getSimpleName() + " must implement IMacro or ISimpleMacro.")); //$NON-NLS-1$ //$NON-NLS-2$
                        }
                    } else {
                        result.add(new CompilationMessage(CompilationMessage.Type.ERROR, 1, 1, 1, 1,
                                "Class " + clazz.getSimpleName() + " not supported for macros: " + //$NON-NLS-1$ //$NON-NLS-2$
                                        "No @Macro annotation found.")); //$NON-NLS-1$
                    }
                }
            } catch (MultipleCompilationErrorsException e) {
                @SuppressWarnings("unchecked")
                List<Message> errors = e.getErrorCollector().getErrors();
                result.addAll(toCompilationMessages(errors));
            } catch (RuntimeException e) {
                // ignore
            }
        }
        return result;
    }

    private List<CompilationMessage> toCompilationMessages(List<Message> errors) {
        List<CompilationMessage> messages = Lists.newArrayList();
        if (errors != null) {
            for (Message error : errors) {
                if (error instanceof SyntaxErrorMessage) {
                    SyntaxErrorMessage syntaxError = (SyntaxErrorMessage) error;
                    int startLine = syntaxError.getCause().getStartLine();
                    int startColumn = syntaxError.getCause().getStartColumn();
                    int endLine = syntaxError.getCause().getEndLine();
                    int endColumn = syntaxError.getCause().getEndColumn();
                    String message = syntaxError.getCause().getMessage();
                    messages.add(new CompilationMessage(CompilationMessage.Type.ERROR, startLine, startColumn,
                            endLine, endColumn, message));
                }
            }
        }
        return messages;
    }

    public void saveMacro(String name, String code) throws IOException {
        File file = new File(macrosDir, name + ".groovy"); //$NON-NLS-1$
        FileUtils.writeStringToFile(file, code, Charsets.UTF_8);
    }

    public void deleteMacro(String name) throws IOException {
        File file = new File(macrosDir, name + ".groovy"); //$NON-NLS-1$
        FileUtils.forceDelete(file);
    }
}