com.machak.idea.plugins.actions.CopyHippoSharedFiles.java Source code

Java tutorial

Introduction

Here is the source code for com.machak.idea.plugins.actions.CopyHippoSharedFiles.java

Source

/*
 * Copyright 2013 m.milicevic (http://www.machak.com)
 * http://www.apache.org/licenses/LICENSE-2.0
 */

package com.machak.idea.plugins.actions;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;

import com.google.common.base.CharMatcher;
import com.google.common.base.Charsets;
import com.google.common.base.Strings;
import com.google.common.io.ByteSource;
import com.google.common.io.Resources;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationDisplayType;
import com.intellij.notification.NotificationGroup;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.LibraryOrderEntry;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.OrderEntry;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.ui.DialogBuilder;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.BooleanTableCellRenderer;
import com.intellij.ui.table.JBTable;
import com.machak.idea.plugins.config.ApplicationComponent;
import com.machak.idea.plugins.config.ProjectComponent;
import com.machak.idea.plugins.model.Assembly;
import com.machak.idea.plugins.model.DependencySet;
import com.machak.idea.plugins.util.VersionUtils;

public class CopyHippoSharedFiles extends AnAction {
    /**
     * Name of the project files
     */
    public static final CharMatcher VERSION_CHARS = CharMatcher.anyOf("0123456789-._");
    public static final String PROJECT_FILE = "hippo_project_directory.txt";
    public static final String DEFAULT_DIST_FILE_PATH = "src" + File.separator + "main" + File.separator
            + "assembly" + File.separator + "distribution.xml";
    public static final String DEFAULT_LOG4j_FILE_PATH = "conf" + File.separator + "log4j-dev.xml";
    public static final NotificationGroup log = NotificationGroup.logOnlyGroup("Hippo shared jars");
    public static final NotificationGroup ERROR_GROUP = new NotificationGroup("Hippo shared lib error messages",
            NotificationDisplayType.BALLOON, true);
    public static final NotificationGroup INFO_GROUP = new NotificationGroup("Hippo shared lib info messages",
            NotificationDisplayType.NONE, false);
    private static final int CHECKED_COLUMN = 0;
    private static final int FILE_COLUMN = 1;
    private static final Pattern ARTIFACT_SPLITTER = Pattern.compile(":");
    private static final Pattern LIBRARY_MATCHER = Pattern.compile("(?:Maven:\\s*)(.*):(.*):(?:.*)");
    private static final Object[] COLUMN_NAMES = { "Files", "Delete?" };
    private static final Pattern PATTERN_JAR_EXTENSION = Pattern.compile("(?:(.*))(.jar)$");
    private Project project;
    private String[] myFiles;
    private boolean[] myCheckedMarks;

    @Override
    public void actionPerformed(AnActionEvent event) {

        project = CommonDataKeys.PROJECT.getData(event.getDataContext());
        if (project != null) {

            // project settings have higher priority (override):
            ApplicationComponent component = project.getComponent(ProjectComponent.class);
            if (notValid(component)) {
                // try to fetch application (global) settings:
                component = ApplicationManager.getApplication().getComponent(ApplicationComponent.class);
                if (notValid(component)) {
                    if (component == null) {
                        error("Couldn't read settings (project nor application wide)");
                        return;
                    }
                }
            }

            String tomcatSharedDirectory = component.getTomcatDirectory();
            // check if we have correct settings:
            if (Strings.isNullOrEmpty(tomcatSharedDirectory)) {
                error("Tomcat shared library not defined");
                return;
            }

            if (!tomcatSharedDirectory.endsWith(File.separator)) {
                tomcatSharedDirectory = tomcatSharedDirectory + File.separator;
            }
            final File sharedDirectory = new File(tomcatSharedDirectory);
            if (!sharedDirectory.exists()) {
                error("Tomcat shared library does not exists: " + tomcatSharedDirectory);
                return;
            }

            final String distributionFilePath = extractDistributionFilePath(project, component);

            final File distFile = new File(distributionFilePath);
            if (!distFile.exists()) {
                error("Missing dist file: " + distributionFilePath);
                return;
            }

            //############################################
            // CHECK FOR LOG4J FILE
            //############################################

            if (component.isCopyLog4J()) {
                final String logFilePath = extractLog4jFilePath(project, component);
                final File logFile = new File(logFilePath);
                if (!logFile.exists()) {
                    error("Missing log4j file : " + logFilePath);
                }
                final String tomcatRootDir = getDirectory(component.getTomcatRootDirectory());

                if (Strings.isNullOrEmpty(tomcatRootDir)) {
                    error("Cannot copy log4j file");

                } else {

                    final String logText = readText(logFile);
                    if (logText != null) {
                        final String filePath = tomcatRootDir.endsWith(File.separator)
                                ? tomcatRootDir + "conf" + File.separator + "log4j.xml"
                                : tomcatRootDir + File.separator + "conf" + File.separator + "log4j.xml";

                        createAndWriteFile(filePath, logText);
                    }

                }

            }

            final Map<String, String> depMap = extractDependencies(component, distFile);
            if (depMap.isEmpty()) {
                info("No files to copy");
                return;
            }
            // cleanup old stuff
            processJars(component, tomcatSharedDirectory, sharedDirectory, depMap);
            // check if we need to create a project root file:
            if (component.isCreateProjectFile()) {
                // first check if we have an override:
                final String projectRootDir = getDirectory(component.getProjectRootDirectory());
                if (!Strings.isNullOrEmpty(projectRootDir)) {
                    final File projectDirectory = new File(component.getProjectRootDirectory());
                    if (!projectDirectory.exists()) {
                        error("Cannot create project root file for tomcat: project root directory is defined but does not exists: "
                                + projectRootDir);
                    }
                }
                // check if tomcat root is defined:

                final String tomcatRootDir = getDirectory(component.getTomcatRootDirectory());

                if (Strings.isNullOrEmpty(tomcatRootDir)) {
                    error("Cannot create project root file for tomcat: Tomcat root directory not defined (or not defined)");
                    return;
                }
                final File tomcatDirectory = new File(tomcatRootDir);
                if (!tomcatDirectory.exists()) {
                    error("Cannot create project root file for tomcat: tomcat root directory does not exists: "
                            + tomcatDirectory);
                }

                // check override:
                String basePath = component.getProjectRootDirectory();
                if (Strings.isNullOrEmpty(basePath)) {
                    basePath = project.getBasePath();
                }
                if (Strings.isNullOrEmpty(basePath)) {
                    error("Cannot create project root file, project path was empty or null");
                    return;
                }
                // create a popup:                
                showProjectRootPopup(basePath, tomcatDirectory);

            }

        }

    }

    private void showProjectRootPopup(final String basePath, final File tomcatDirectory) {

        final DialogBuilder dialogBuilder = new DialogBuilder(project);
        dialogBuilder.setTitle("Create project file:");
        final JPanel simplePanel = new JPanel();
        simplePanel.add(new JLabel("Following path will be used as project base:\n" + basePath));
        dialogBuilder.setCenterPanel(simplePanel);

        final Action acceptAction = new AbstractAction() {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(final ActionEvent e) {
                // check if file exists and overwrite:
                final String filePath = tomcatDirectory.getAbsolutePath() + File.separator + "bin" + File.separator
                        + PROJECT_FILE;
                createAndWriteFile(filePath, basePath);

                dialogBuilder.getDialogWrapper().close(DialogWrapper.OK_EXIT_CODE);
            }
        };
        acceptAction.putValue(Action.NAME, "OK");
        dialogBuilder.addAction(acceptAction);

        dialogBuilder.addCancelAction();
        dialogBuilder.showModal(true);
    }

    public static String readText(final File file) {
        try {
            final ByteSource source = Resources.asByteSource(file.toURI().toURL());
            return source.asCharSource(Charsets.UTF_8).read();
        } catch (IOException e) {

        }
        return null;
    }

    private void createAndWriteFile(final String filePath, final String content) {
        info("Creating file: " + filePath);
        final File file = new File(filePath);
        FileWriter writer = null;
        try {
            if (!file.exists()) {
                final boolean created = file.createNewFile();
                if (created) {
                    info("Created file:" + filePath);
                }
            }
            writer = new FileWriter(file, false);
            writer.write(content);

        } catch (IOException error) {
            error(error.getMessage());
        } finally {
            try {
                if (writer != null) {
                    writer.close();
                }
            } catch (IOException e1) {
                // ignore
            }
        }
    }

    private String getDirectory(String input) {

        // check if we have correct settings:
        if (Strings.isNullOrEmpty(input)) {
            //error("Directory not defined");
            return null;
        }

        if (!input.endsWith(File.separator)) {
            input = input + File.separator;
        }
        return input;
    }

    private void copyJars(final String tomcatSharedDirectory, final Map<String, String> depMap) {
        final Module[] modules = ModuleManager.getInstance(project).getModules();
        final Set<LibWrapper> jars = new HashSet<LibWrapper>();
        for (Module module : modules) {
            final OrderEntry[] orderEntries = ModuleRootManager.getInstance(module).getOrderEntries();
            for (OrderEntry library : orderEntries) {
                if (library instanceof LibraryOrderEntry) {
                    final LibraryOrderEntry lib = (LibraryOrderEntry) library;
                    final String libraryName = lib.getLibraryName();
                    final Matcher matcher = LIBRARY_MATCHER.matcher(libraryName);
                    if (matcher.matches()) {
                        final String artifactName = matcher.group(1);
                        final String groupName = matcher.group(2);
                        final String ourName = depMap.get(groupName);
                        if (ourName == null || !ourName.equals(artifactName)) {
                            continue;
                        }
                        final VirtualFile[] jarFiles = lib.getFiles(OrderRootType.CLASSES);
                        if (jarFiles.length == 1) {
                            final VirtualFile jarFile = jarFiles[0];
                            final LibWrapper wrapper = new LibWrapper(artifactName, groupName, ourName, jarFile,
                                    module);
                            jars.add(wrapper);
                        }
                    }
                }
            }

        }
        // copy files
        final Collection<VirtualFile> filteredFiles = filterDuplicates(jars, depMap);
        for (VirtualFile jar : filteredFiles) {
            try {
                copyFile(tomcatSharedDirectory, jar);
            } catch (IOException e) {
                error(String.format("Error while copy file%s", e.getMessage()));
            }
        }

    }

    private Collection<VirtualFile> filterDuplicates(final Iterable<LibWrapper> libWrapperSet,
            final Map<String, String> depMap) {
        final Map<String, VirtualFile> filtered = new HashMap<String, VirtualFile>();
        // check if duplicate & log a warning
        for (Map.Entry<String, String> dependency : depMap.entrySet()) {
            final String name = dependency.getKey();
            LibWrapper found = null;
            String foundVersion = null;
            for (LibWrapper wrapper : libWrapperSet) {
                final String jarFile = wrapper.jarFile.getName();
                if (jarFile.startsWith(name)) {
                    if (found == null) {
                        foundVersion = cleanupVersion(name, jarFile);
                        found = wrapper;
                        filtered.put(name, found.getJarFile());
                        continue;
                    }
                    // check if version is same:
                    final String version = cleanupVersion(name, jarFile);
                    if (version.equals(foundVersion)) {
                        continue;
                    }
                    // version difference, keep highest:
                    final int highest = VersionUtils.compareVersionNumbers(foundVersion, version);
                    final String foundModuleName = found.getModule().getName();
                    final String currentModuleName = wrapper.getModule().getName();
                    final String foundJarPath = found.getJarFile().getPath();
                    // always remove:
                    final VirtualFile remove = filtered.remove(name);
                    // keep already found one::
                    if (highest == 1) {
                        filtered.put(name, found.getJarFile());
                        // keep highest:
                        error(String.format("Found duplicate jar: [%s] in module: [%s]", jarFile,
                                currentModuleName));
                        error(String.format("Keeping duplicate jar: [%s] from module: %s",
                                found.getJarFile().getName(), foundModuleName));
                    }
                    // keep *new* found one:
                    else if (highest == -1) {
                        // swap and continue
                        found = wrapper;
                        foundVersion = version;
                        filtered.put(name, found.getJarFile());
                        error(String.format("Found duplicate jar: [%s] in module: [%s]", foundJarPath,
                                foundModuleName));
                        error(String.format("Keeping duplicate jar: [%s] from module: [%s]",
                                found.getJarFile().getName(), currentModuleName));
                    } else {
                        // nothing we can do, just print error:
                        error(String.format("Check versions of: [%s] from: [%s] and: [%s] form module: [%s]",
                                foundJarPath, foundModuleName, jarFile, currentModuleName));
                        filtered.put(name, wrapper.getJarFile());
                    }
                }
            }
        }
        return filtered.values();
    }

    private String cleanupVersion(final String name, final String jarFile) {
        String version = jarFile;
        if (version.startsWith(name)) {
            version = version.substring(name.length(), jarFile.length());
            version = PATTERN_JAR_EXTENSION.matcher(version).replaceAll("$1");
            version = VERSION_CHARS.retainFrom(version);

        }
        return version;

    }

    private String extractLog4jFilePath(final Project project, final ApplicationComponent component) {
        final String logFilePath;
        if (Strings.isNullOrEmpty(component.getLog4JDirectory())) {
            final String basePath = project.getBasePath();
            if (basePath.endsWith(File.separator)) {
                logFilePath = project.getBasePath() + DEFAULT_LOG4j_FILE_PATH;
            } else {
                logFilePath = project.getBasePath() + File.separator + DEFAULT_LOG4j_FILE_PATH;
            }
        } else {
            logFilePath = component.getLog4JDirectory();
        }
        return logFilePath;
    }

    private String extractDistributionFilePath(final Project project, final ApplicationComponent component) {
        final String distributionFilePath;
        if (Strings.isNullOrEmpty(component.getDistFile())) {
            final String basePath = project.getBasePath();
            if (basePath.endsWith(File.separator)) {
                distributionFilePath = project.getBasePath() + DEFAULT_DIST_FILE_PATH;
            } else {
                distributionFilePath = project.getBasePath() + File.separator + DEFAULT_DIST_FILE_PATH;
            }
        } else {
            distributionFilePath = component.getDistFile();
        }
        return distributionFilePath;
    }

    private void copyFile(final String tomcatSharedDirectory, final VirtualFile jarFile) throws IOException {
        final boolean exists = jarFile.exists();
        if (exists) {
            String canonicalPath = jarFile.getCanonicalPath();
            if (canonicalPath != null) {
                if (canonicalPath.lastIndexOf("!/") != -1) {
                    canonicalPath = canonicalPath.substring(0, canonicalPath.length() - 2);
                }
                final File jar = new File(canonicalPath);
                if (jar.exists() && !jar.isDirectory()) {
                    final String destination = String.format("%s%s", tomcatSharedDirectory, jarFile.getName());
                    final File toFile = new File(destination);
                    if (!toFile.exists()) {
                        info(String.format("Copy file: %s as: %s", canonicalPath, toFile.getAbsolutePath()));
                        FileUtil.copy(jar, toFile);
                    }
                }
            }
        }
    }

    private Map<String, String> extractDependencies(final ApplicationComponent component, final File distFile) {
        final Map<String, String> depMap = new HashMap<String, String>();

        try {
            final JAXBContext context = JAXBContext.newInstance(Assembly.class);
            final Unmarshaller unmarshaller = context.createUnmarshaller();
            @SuppressWarnings("unchecked")
            final JAXBElement<Assembly> jaxbElement = (JAXBElement<Assembly>) unmarshaller.unmarshal(distFile);
            final Assembly assembly = jaxbElement.getValue();
            final Assembly.DependencySets dependencySets = assembly.getDependencySets();
            final List<DependencySet> dependencySet = dependencySets.getDependencySet();

            for (DependencySet set : dependencySet) {
                final String outputDirectory = set.getOutputDirectory();
                boolean accept = outputDirectory.equals("/shared/lib");
                if (!accept && component.isCopyOtherJars()) {
                    accept = outputDirectory.equals("/common/lib");
                }
                if (accept) {
                    final DependencySet.Includes includes = set.getIncludes();
                    final List<String> include = includes.getInclude();
                    for (String inc : include) {
                        final String[] dep = ARTIFACT_SPLITTER.split(inc);
                        final String groupId = dep[0];
                        final String artifactId = dep[1];
                        depMap.put(artifactId, groupId);
                    }
                }
            }
        } catch (JAXBException e) {
            error("Error creating marshaller" + e.getMessage());
        }
        return depMap;
    }

    private void processJars(final ApplicationComponent component, final String tomcatSharedDirectory,
            final File sharedDirectory, final Map<String, String> depMap) {

        final FilenameFilter fileFilter = createJarsFilter(component, depMap);
        final File[] jars = sharedDirectory.listFiles(fileFilter);
        if (!component.isShowDialog()) {
            for (File jar : jars) {
                deleteFile(jar);
            }
            copyJars(tomcatSharedDirectory, depMap);
            return;
        }
        final int length = jars.length;
        myFiles = new String[length];
        myCheckedMarks = new boolean[length];
        for (int i = 0; i < length; i++) {
            final File file = jars[i];
            myFiles[i] = file.getAbsolutePath();
            myCheckedMarks[i] = true;
        }

        final TableModel model = new MyTableModel();
        final JBTable myTable = new JBTable();
        myTable.setPreferredSize(new Dimension(700, 400));
        myTable.setModel(model);
        final TableColumnModel columnModel = myTable.getColumnModel();
        columnModel.getColumn(CHECKED_COLUMN).setCellRenderer(new BooleanTableCellRenderer());
        columnModel.getColumn(CHECKED_COLUMN).setPreferredWidth(40);
        columnModel.getColumn(FILE_COLUMN).setPreferredWidth(660);

        final DialogBuilder dialogBuilder = new DialogBuilder(project);
        dialogBuilder.setTitle("Files to delete");
        dialogBuilder.setCenterPanel(myTable);

        final Action deleteAction = new AbstractAction() {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(final ActionEvent e) {
                final int length = model.getRowCount();
                for (int i = 0; i < length; i++) {
                    final Boolean checked = (Boolean) model.getValueAt(i, CHECKED_COLUMN);
                    if (checked) {
                        final File jar = jars[i];
                        deleteFile(jar);
                    }
                }
                // do copy:
                copyJars(tomcatSharedDirectory, depMap);
                dialogBuilder.getDialogWrapper().close(DialogWrapper.OK_EXIT_CODE);
            }
        };
        deleteAction.putValue(Action.NAME, "OK");
        dialogBuilder.addAction(deleteAction);

        dialogBuilder.addCancelAction();
        dialogBuilder.showModal(true);

    }

    private void deleteFile(final File jar) {
        final String filePath = jar.getPath();
        final boolean deleted = jar.delete();
        if (deleted) {
            info(String.format("Deleted: %s", filePath));
        } else {
            warn(String.format("Failed to delete: %s", filePath));
        }
    }

    private FilenameFilter createJarsFilter(final ApplicationComponent component,
            final Map<String, String> depMap) {
        final FilenameFilter fileFilter;
        if (component.isDeleteAllJars()) {
            final FilenameFilter allJarsFilter = new FilenameFilter() {
                @Override
                public boolean accept(final File file, final String name) {
                    return name.endsWith(".jar");
                }
            };
            fileFilter = allJarsFilter;
        } else {
            fileFilter = new FilenameFilter() {
                @Override
                public boolean accept(final File file, final String name) {
                    final boolean jar = name.endsWith(".jar");
                    if (!jar) {
                        return false;
                    }
                    for (String prefix : depMap.keySet()) {
                        if (name.startsWith(prefix)) {
                            return true;
                        }
                    }
                    return false;
                }
            };
        }
        return fileFilter;
    }

    private boolean notValid(final ApplicationComponent component) {
        return component == null || Strings.isNullOrEmpty(component.getTomcatDirectory());
    }

    private void error(final String message) {
        final Notification notification = ERROR_GROUP.createNotification(message, NotificationType.ERROR);
        notification.notify(project);
    }

    private void info(final String message) {
        final Notification notification = INFO_GROUP.createNotification(message, NotificationType.INFORMATION);
        notification.notify(project);
    }

    private void warn(final String message) {
        final Notification notification = INFO_GROUP.createNotification(message, NotificationType.WARNING);
        notification.notify(project);
    }

    private class MyTableModel extends AbstractTableModel {
        private static final long serialVersionUID = 1L;

        public int getRowCount() {
            return myFiles.length;
        }

        public int getColumnCount() {
            return COLUMN_NAMES.length;
        }

        public Object getValueAt(int rowIndex, int columnIndex) {
            switch (columnIndex) {
            case CHECKED_COLUMN:
                return myCheckedMarks[rowIndex] ? Boolean.TRUE : Boolean.FALSE;
            case FILE_COLUMN:
                return myFiles[rowIndex];

            }
            throw new RuntimeException("Incorrect column index");
        }

        public String getColumnName(int column) {
            switch (column) {
            case CHECKED_COLUMN:
                return "Delete";
            case FILE_COLUMN:
                return "Files";
            default:
                throw new RuntimeException("Incorrect column index");
            }
        }

        public Class<?> getColumnClass(int columnIndex) {
            if (columnIndex == CHECKED_COLUMN) {
                return Boolean.class;
            }
            return super.getColumnClass(columnIndex);
        }

        public boolean isCellEditable(int rowIndex, int columnIndex) {
            return columnIndex == CHECKED_COLUMN;
        }

        public void setValueAt(final Object aValue, final int rowIndex, final int columnIndex) {
            if (columnIndex == CHECKED_COLUMN) {
                myCheckedMarks[rowIndex] = (Boolean) aValue;
                fireTableRowsUpdated(rowIndex, rowIndex);
            } else {
                String name = (String) aValue;
                myFiles[rowIndex] = name;
            }
        }
    }
}