MavenResourceCompiler.java :  » IDE » IntelliJ » org » jetbrains » idea » maven » compiler » Java Open Source

Java Open Source » IDE » IntelliJ 
IntelliJ » org » jetbrains » idea » maven » compiler » MavenResourceCompiler.java
/*
 * Copyright 2000-2009 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jetbrains.idea.maven.compiler;

import com.intellij.compiler.CompilerConfigurationImpl;
import com.intellij.compiler.CompilerIOUtil;
import com.intellij.compiler.impl.CompilerUtil;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.application.Result;
import com.intellij.openapi.compiler.*;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.testFramework.LightVirtualFile;
import com.intellij.util.text.CaseInsensitiveStringHashingStrategy;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.idea.maven.dom.MavenPropertyResolver;
import org.jetbrains.idea.maven.project.MavenProject;
import org.jetbrains.idea.maven.project.MavenProjectsManager;
import org.jetbrains.idea.maven.project.MavenResource;
import org.jetbrains.idea.maven.utils.MavenJDOMUtil;
import org.jetbrains.idea.maven.utils.MavenLog;
import org.jetbrains.idea.maven.utils.MavenUtil;

import java.io.*;
import java.util.*;
import java.util.regex.Pattern;

public class MavenResourceCompiler implements ClassPostProcessingCompiler {
  private static final Key<List<String>> FILES_TO_DELETE_KEY = Key.create(MavenResourceCompiler.class.getSimpleName() + ".FILES_TO_DELETE");

  private Map<String, Set<String>> myOutputItemsCache = new THashMap<String, Set<String>>();

  public MavenResourceCompiler(Project project) {
    loadCache(project);
  }

  private void loadCache(final Project project) {
    File file = getCacheFile(project);
    if (!file.exists()) return;

    try {
      DataInputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
      try {
        if (in.readInt() != CompilerConfigurationImpl.DEPENDENCY_FORMAT_VERSION) return;
        int modulesSize = in.readInt();
        Map<String, Set<String>> temp = new THashMap<String, Set<String>>();
        while (modulesSize-- > 0) {
          String module = CompilerIOUtil.readString(in);
          int pathsSize = in.readInt();
          Set<String> paths = createPathsSet(pathsSize);
          while (pathsSize-- > 0) {
            paths.add(CompilerIOUtil.readString(in));
          }
          temp.put(module, paths);
        }
        myOutputItemsCache = temp;
      }
      finally {
        in.close();
      }
    }
    catch (IOException e) {
      MavenLog.LOG.warn(e);
    }
  }

  private static Set<String> createPathsSet(int size) {
    return SystemInfo.isFileSystemCaseSensitive
           ? new THashSet<String>(size)
           : new THashSet<String>(size, CaseInsensitiveStringHashingStrategy.INSTANCE);
  }

  private void saveCache(final Project project) {
    File file = getCacheFile(project);
    file.getParentFile().mkdirs();
    try {
      DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
      try {
        out.writeInt(CompilerConfigurationImpl.DEPENDENCY_FORMAT_VERSION);
        out.writeInt(myOutputItemsCache.size());
        for (Map.Entry<String, Set<String>> eachEntry : myOutputItemsCache.entrySet()) {
          String module = eachEntry.getKey();
          Set<String> paths = eachEntry.getValue();

          CompilerIOUtil.writeString(module, out);
          out.writeInt(paths.size());
          for (String eachPath : paths) {
            CompilerIOUtil.writeString(eachPath, out);
          }
        }
      }
      finally {
        out.close();
      }
    }
    catch (IOException e) {
      MavenLog.LOG.error(e);
    }
  }

  private static File getCacheFile(final Project project) {
    return new File(CompilerPaths.getCompilerSystemDirectory(project), "maven_compiler_caches.dat");
  }

  public boolean validateConfiguration(CompileScope scope) {
    return true;
  }

  @NotNull
  public ProcessingItem[] getProcessingItems(final CompileContext context) {
    final Project project = context.getProject();
    final MavenProjectsManager mavenProjectManager = MavenProjectsManager.getInstance(project);
    if (!mavenProjectManager.isMavenizedProject()) return ProcessingItem.EMPTY_ARRAY;
    return new ReadAction<ProcessingItem[]>() {
      protected void run(Result<ProcessingItem[]> resultObject) throws Throwable {
        List<ProcessingItem> allItemsToProcess = new ArrayList<ProcessingItem>();
        List<String> filesToDelete = new ArrayList<String>();

        for (Module eachModule : context.getCompileScope().getAffectedModules()) {
          MavenProject mavenProject = mavenProjectManager.findProject(eachModule);
          if (mavenProject == null) continue;

          Properties properties = loadPropertiesAndFilters(context, mavenProject);

          List<String> nonFilteredExtensions = collectNonFilteredExtensions(mavenProject);
          String escapeString = MavenJDOMUtil.findChildValueByPath(mavenProject.getPluginConfiguration("org.apache.maven.plugins",
                                                                                                       "maven-resources-plugin"),
                                                                   "escapeString", "\\");

          long propertiesHashCode = calculateHashCode(properties);

          List<ProcessingItem> moduleItemsToProcess = new ArrayList<ProcessingItem>();
          collectProcessingItems(eachModule, mavenProject, context, properties, propertiesHashCode,
                                 nonFilteredExtensions, escapeString, false, moduleItemsToProcess);
          collectProcessingItems(eachModule, mavenProject, context, properties, propertiesHashCode,
                                 nonFilteredExtensions, escapeString, true, moduleItemsToProcess);
          collectItemsToDelete(eachModule, moduleItemsToProcess, filesToDelete);
          allItemsToProcess.addAll(moduleItemsToProcess);
        }

        if (!filesToDelete.isEmpty()) {
          allItemsToProcess.add(new FakeProcessingItem());
        }
        context.putUserData(FILES_TO_DELETE_KEY, filesToDelete);
        resultObject.setResult(allItemsToProcess.toArray(new ProcessingItem[allItemsToProcess.size()]));
        removeObsoleteModulesFromCache(project);
        saveCache(project);
      }
    }.execute().getResultObject();
  }

  private static List<String> collectNonFilteredExtensions(MavenProject mavenProject) {
    List<String> result = new ArrayList<String>(Arrays.asList("jpg", "jpeg", "gif", "bmp", "png"));
    Element config = mavenProject.getPluginConfiguration("org.apache.maven.plugins", "maven-resources-plugin");
    if (config == null) return result;

    for (String each : MavenJDOMUtil.findChildrenValuesByPath(config, "nonFilteredFileExtensions", "nonFilteredFileExtension")) {
      result.add(each);
    }
    return result;
  }

  private static long calculateHashCode(Properties properties) {
    Set<String> sorted = new TreeSet<String>();
    for (Map.Entry<Object, Object> each : properties.entrySet()) {
      sorted.add(each.getKey().toString() + "->" + each.getValue().toString());
    }
    return sorted.hashCode();
  }

  private static Properties loadPropertiesAndFilters(CompileContext context, MavenProject mavenProject) {
    Properties properties = new Properties();
    properties.putAll(mavenProject.getProperties());
    
    for (String each : mavenProject.getFilters()) {
      try {
        FileInputStream in = new FileInputStream(each);
        try {
          properties.load(in);
        }
        finally {
          in.close();
        }
      }
      catch (IOException e) {
        String url = VfsUtil.pathToUrl(mavenProject.getFile().getPath());
        context.addMessage(CompilerMessageCategory.WARNING, "Maven: Cannot read the filter. " + e.getMessage(), url, -1, -1);
      }
    }
    return properties;
  }

  private void collectProcessingItems(Module module,
                                      MavenProject mavenProject,
                                      CompileContext context,
                                      Properties properties,
                                      long propertiesHashCode,
                                      List<String> nonFilteredExtensions,
                                      String escapeString,
                                      boolean tests,
                                      List<ProcessingItem> result) {
    String outputDir = CompilerPaths.getModuleOutputPath(module, tests);
    if (outputDir == null) {
      context.addMessage(CompilerMessageCategory.ERROR, "Maven: Module '" + module.getName() + "'output is not specified", null, -1, -1);
      return;
    }

    List<MavenResource> resources = tests ? mavenProject.getTestResources() : mavenProject.getResources();

    for (MavenResource each : resources) {
      VirtualFile dir = LocalFileSystem.getInstance().findFileByPath(each.getDirectory());
      if (dir == null) continue;

      List<Pattern> includes = collectPatterns(each.getIncludes(), "**/*");
      List<Pattern> excludes = collectPatterns(each.getExcludes(), null);
      String targetPath = each.getTargetPath();
      String resourceOutputDir = StringUtil.isEmptyOrSpaces(targetPath) ? outputDir : (outputDir + "/" + targetPath);

      collectProcessingItems(module,
                             dir,
                             dir,
                             resourceOutputDir,
                             includes,
                             excludes,
                             each.isFiltered(),
                             properties,
                             propertiesHashCode,
                             nonFilteredExtensions,
                             escapeString,
                             result,
                             context.getProgressIndicator());
    }
  }

  private List<Pattern> collectPatterns(List<String> values, String defaultValue) {
    List<Pattern> result = new ArrayList<Pattern>();
    if (values == null || values.isEmpty()) {
      if (defaultValue == null) return Collections.emptyList();
      return MavenUtil.collectPattern(defaultValue, result);
    }
    for (String each : values) {
      MavenUtil.collectPattern(each, result);
    }
    return result;
  }

  private void collectProcessingItems(Module module,
                                      VirtualFile sourceRoot,
                                      VirtualFile currentDir,
                                      String outputDir,
                                      List<Pattern> includes,
                                      List<Pattern> excludes,
                                      boolean isSourceRootFiltered,
                                      Properties properties,
                                      long propertiesHashCode,
                                      List<String> nonFilteredExtensions,
                                      String escapeString,
                                      List<ProcessingItem> result,
                                      ProgressIndicator indicator) {
    indicator.checkCanceled();
    final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(module.getProject()).getFileIndex();
    for (VirtualFile eachSourceFile : currentDir.getChildren()) {
      if (eachSourceFile.isDirectory()) {
        collectProcessingItems(module,
                               sourceRoot,
                               eachSourceFile,
                               outputDir,
                               includes,
                               excludes,
                               isSourceRootFiltered,
                               properties,
                               propertiesHashCode,
                               nonFilteredExtensions,
                               escapeString,
                               result,
                               indicator);
      }
      else {
        String relPath = VfsUtil.getRelativePath(eachSourceFile, sourceRoot, '/');
        if (fileIndex.isIgnored(eachSourceFile)) continue;
        if (!MavenUtil.isIncluded(relPath, includes, excludes)) continue;

        String outputPath = outputDir + "/" + relPath;
        long outputFileTimestamp = -1;
        File outputFile = new File(outputPath);
        if (outputFile.exists()) {
          outputFileTimestamp = outputFile.lastModified();
        }
        boolean isFileterd = isSourceRootFiltered && !nonFilteredExtensions.contains(eachSourceFile.getExtension());
        result.add(new MyProcessingItem(module,
                                        eachSourceFile,
                                        outputPath,
                                        outputFileTimestamp,
                                        isFileterd,
                                        properties,
                                        propertiesHashCode,
                                        escapeString));
      }
    }
  }

  private void collectItemsToDelete(Module module, List<ProcessingItem> processingItems, List<String> result) {
    Set<String> currentPaths = createPathsSet(processingItems.size());
    for (ProcessingItem each : processingItems) {
      if (!(each instanceof MyProcessingItem)) continue;
      currentPaths.add(((MyProcessingItem)each).getOutputPath());
    }

    Set<String> cachedPaths = null;
    Set<String> otherModulesCachedPaths = new THashSet<String>();
    for (Map.Entry<String, Set<String>> eachEntry : myOutputItemsCache.entrySet()) {
      if (eachEntry.getKey().equals(module.getName())) {
        cachedPaths = eachEntry.getValue();
      }
      else {
        otherModulesCachedPaths.addAll(eachEntry.getValue());
      }
    }
    myOutputItemsCache.put(module.getName(), currentPaths);
    if (cachedPaths == null) return;

    cachedPaths.removeAll(currentPaths);
    cachedPaths.removeAll(otherModulesCachedPaths);
    result.addAll(cachedPaths);
  }

  private void removeObsoleteModulesFromCache(final Project project) {
    Set<String> existingModules = new THashSet<String>();
    for (Module each : ModuleManager.getInstance(project).getModules()) {
      existingModules.add(each.getName());
    }

    for (String each : new THashSet<String>(myOutputItemsCache.keySet())) {
      if (!existingModules.contains(each)) {
        myOutputItemsCache.remove(each);
      }
    }
  }

  public ProcessingItem[] process(final CompileContext context, ProcessingItem[] items) {
    context.getProgressIndicator().setText("Processing Maven resources...");

    List<ProcessingItem> result = new ArrayList<ProcessingItem>(items.length);
    List<File> filesToRefresh = new ArrayList<File>(items.length);

    deleteOutdatedFile(context.getUserData(FILES_TO_DELETE_KEY), filesToRefresh);

    int count = 0;
    for (final ProcessingItem each : items) {
      if (!(each instanceof MyProcessingItem)) continue;

      context.getProgressIndicator().setFraction(((double)count) / items.length);
      context.getProgressIndicator().checkCanceled();

      MyProcessingItem eachItem = (MyProcessingItem)each;
      VirtualFile sourceVirtualFile = eachItem.getFile();
      File sourceFile = new File(sourceVirtualFile.getPath());
      File outputFile = new File(eachItem.getOutputPath());

      try {
        outputFile.getParentFile().mkdirs();

        boolean shouldFilter = eachItem.isFiltered();
        if (sourceFile.length() > 10 * 1024 * 1024) {
          context.addMessage(CompilerMessageCategory.WARNING,
                             "Maven: File is too big to be filtered. Most likely it is a binary file and should be excluded from filtering.",
                             sourceVirtualFile.getUrl(), -1, -1);
          shouldFilter = false;
        }

        if (shouldFilter) {
          String charset = sourceVirtualFile.getCharset().name();
          String text = new String(FileUtil.loadFileBytes(sourceFile), charset);
          String escapedCharacters = "properties".equals(sourceVirtualFile.getExtension()) ? "\\" : null;
          text = MavenPropertyResolver.resolve(eachItem.getModule(),
                                               text,
                                               eachItem.getProperties(),
                                               eachItem.getEscapeString(),
                                               escapedCharacters);
          FileUtil.writeToFile(outputFile, text.getBytes(charset));
        }
        else {
          FileUtil.copy(sourceFile, outputFile);
        }

        ((MyValididtyState)each.getValidityState()).setOutputFileTimestamp(outputFile.lastModified());
        result.add(each);
        filesToRefresh.add(outputFile);
      }
      catch (IOException e) {
        context.addMessage(CompilerMessageCategory.ERROR,
                           "Maven: Cannot process resource file: " + e.getMessage(),
                           sourceVirtualFile.getUrl(),
                           -1,
                           -1);
      }
    }
    CompilerUtil.refreshIOFiles(filesToRefresh);
    return result.toArray(new ProcessingItem[result.size()]);
  }

  private void deleteOutdatedFile(List<String> filesToDelete, List<File> filesToRefresh) {
    for (String each : filesToDelete) {
      File file = new File(each);
      if (FileUtil.delete(file)) {
        filesToRefresh.add(file);
      }
    }
  }

  @NotNull
  public String getDescription() {
    return "Maven Resource Compiler";
  }

  public ValidityState createValidityState(DataInput in) throws IOException {
    return MyValididtyState.load(in);
  }

  private static class MyProcessingItem implements ProcessingItem {
    private final Module myModule;
    private final VirtualFile mySourceFile;
    private final String myOutputPath;
    private final boolean myFiltered;
    private final Properties myProperties;
    private final String myEscapeString;
    private final MyValididtyState myState;

    public MyProcessingItem(Module module,
                            VirtualFile sourceFile,
                            String outputPath,
                            long outputFileTimestamp,
                            boolean isFiltered,
                            Properties properties,
                            long propertiesHashCode,
                            String escapeString) {
      myModule = module;
      mySourceFile = sourceFile;
      myOutputPath = outputPath;
      myFiltered = isFiltered;
      myProperties = properties;
      myEscapeString = escapeString;
      myState = new MyValididtyState(sourceFile.getTimeStamp(), outputFileTimestamp, isFiltered, propertiesHashCode, escapeString);
    }

    @NotNull
    public VirtualFile getFile() {
      return mySourceFile;
    }

    public String getOutputPath() {
      return myOutputPath;
    }

    public Module getModule() {
      return myModule;
    }

    public boolean isFiltered() {
      return myFiltered;
    }

    public Properties getProperties() {
      return myProperties;
    }

    public String getEscapeString() {
      return myEscapeString;
    }

    public ValidityState getValidityState() {
      return myState;
    }
  }

  private static class FakeProcessingItem implements ProcessingItem {
    private LightVirtualFile myFile;

    private FakeProcessingItem() {
      myFile = new LightVirtualFile(this.getClass().getName());
    }

    @NotNull
    public VirtualFile getFile() {
      return myFile;
    }

    public ValidityState getValidityState() {
      return null;
    }
  }

  private static class MyValididtyState implements ValidityState {
    private final long mySourceFileTimestamp;
    private volatile long myOutputFileTimestamp;
    private final boolean myFiltered;
    private final long myPropertiesHashCode;
    private final String myEscapeString;

    public static MyValididtyState load(DataInput in) throws IOException {
      return new MyValididtyState(in.readLong(), in.readLong(), in.readBoolean(), in.readLong(), in.readUTF());
    }

    public void setOutputFileTimestamp(long outputFileTimestamp) {
      myOutputFileTimestamp = outputFileTimestamp;
    }

    private MyValididtyState(long sourceFileTimestamp,
                             long outputFileTimestamp,
                             boolean isFiltered,
                             long propertiesHashCode,
                             String escapeString) {
      mySourceFileTimestamp = sourceFileTimestamp;
      myOutputFileTimestamp = outputFileTimestamp;
      myFiltered = isFiltered;
      myPropertiesHashCode = propertiesHashCode;
      myEscapeString = escapeString;
    }

    @Override
    public String toString() {
      return mySourceFileTimestamp + " " + myOutputFileTimestamp + " " + myFiltered + " " + myPropertiesHashCode + " " + myEscapeString;
    }

    public boolean equalsTo(ValidityState otherState) {
      if (!(otherState instanceof MyValididtyState)) return false;
      MyValididtyState that = (MyValididtyState)otherState;

      return mySourceFileTimestamp == that.mySourceFileTimestamp
             && myOutputFileTimestamp == that.myOutputFileTimestamp
             && myFiltered == that.myFiltered
             && myPropertiesHashCode == that.myPropertiesHashCode
             && Comparing.strEqual(myEscapeString, that.myEscapeString);
    }

    public void save(DataOutput out) throws IOException {
      out.writeLong(mySourceFileTimestamp);
      out.writeLong(myOutputFileTimestamp);
      out.writeBoolean(myFiltered);
      out.writeLong(myPropertiesHashCode);
      out.writeUTF(myEscapeString);
    }
  }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.