Snapshot of commit d5ec1d5018ed24f1b4f32b1d09df6dbd7e2fc425
from branch master of git://git.jetbrains.org/idea/community.git
diff --git a/java/compiler/impl/compiler-impl.iml b/java/compiler/impl/compiler-impl.iml
new file mode 100644
index 0000000..9bb8c9a
--- /dev/null
+++ b/java/compiler/impl/compiler-impl.iml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module relativePaths="true" type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
+ <sourceFolder url="file://$MODULE_DIR$/testSrc" isTestSource="true" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ <orderEntry type="module" module-name="compiler-openapi" exported="" />
+ <orderEntry type="library" name="OroMatcher" level="project" />
+ <orderEntry type="library" name="JDOM" level="project" />
+ <orderEntry type="module" module-name="util" />
+ <orderEntry type="module" module-name="openapi" />
+ <orderEntry type="library" name="Trove4j" level="project" />
+ <orderEntry type="module" module-name="java-runtime" />
+ <orderEntry type="module" module-name="instrumentation-util" />
+ <orderEntry type="library" name="asm4" level="project" />
+ <orderEntry type="library" name="Eclipse" level="project" />
+ <orderEntry type="module" module-name="platform-api" />
+ <orderEntry type="module" module-name="lang-impl" />
+ <orderEntry type="module" module-name="jsp-openapi" />
+ <orderEntry type="module" module-name="java-impl" />
+ <orderEntry type="module" module-name="jps-builders" />
+ <orderEntry type="library" name="Netty" level="project" />
+ <orderEntry type="module" module-name="testFramework-java" scope="TEST" />
+ <orderEntry type="module" module-name="jps-model-impl" />
+ </component>
+ <component name="copyright">
+ <Base>
+ <setting name="state" value="1" />
+ </Base>
+ </component>
+</module>
+
diff --git a/java/compiler/impl/src/com/intellij/compiler/CompilerConfigurationImpl.java b/java/compiler/impl/src/com/intellij/compiler/CompilerConfigurationImpl.java
new file mode 100644
index 0000000..cbf1b68
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/CompilerConfigurationImpl.java
@@ -0,0 +1,1000 @@
+
+/*
+ * Copyright 2000-2012 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.
+ */
+
+/**
+ * created at Jan 3, 2002
+ * @author Jeka
+ */
+package com.intellij.compiler;
+
+import com.intellij.CommonBundle;
+import com.intellij.ProjectTopics;
+import com.intellij.compiler.impl.TranslatingCompilerFilesMonitor;
+import com.intellij.compiler.impl.javaCompiler.BackendCompiler;
+import com.intellij.compiler.impl.javaCompiler.api.CompilerAPICompiler;
+import com.intellij.compiler.impl.javaCompiler.eclipse.EclipseCompiler;
+import com.intellij.compiler.impl.javaCompiler.eclipse.EclipseEmbeddedCompiler;
+import com.intellij.compiler.impl.javaCompiler.javac.JavacCompiler;
+import com.intellij.compiler.options.ExternalBuildOptionListener;
+import com.intellij.compiler.server.BuildManager;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ApplicationNamesInfo;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.compiler.CompilerManager;
+import com.intellij.openapi.compiler.options.ExcludedEntriesConfiguration;
+import com.intellij.openapi.components.*;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.extensions.Extensions;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.ModuleAdapter;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.ui.InputValidator;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.*;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.messages.MessageBusConnection;
+import org.apache.oro.text.regex.*;
+import org.jdom.Element;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.jps.model.java.compiler.ProcessorConfigProfile;
+import org.jetbrains.jps.model.java.impl.compiler.ProcessorConfigProfileImpl;
+import org.jetbrains.jps.model.serialization.java.compiler.AnnotationProcessorProfileSerializer;
+import org.jetbrains.jps.model.serialization.java.compiler.JpsJavaCompilerConfigurationSerializer;
+
+import java.io.File;
+import java.util.*;
+
+@State(
+ name = "CompilerConfiguration",
+ storages = {
+ @Storage( file = StoragePathMacros.PROJECT_FILE),
+ @Storage( file = StoragePathMacros.PROJECT_CONFIG_DIR + "/compiler.xml", scheme = StorageScheme.DIRECTORY_BASED)
+ }
+)
+public class CompilerConfigurationImpl extends CompilerConfiguration implements PersistentStateComponent<Element>, ProjectComponent {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.CompilerConfiguration");
+ @NonNls public static final String TESTS_EXTERNAL_COMPILER_HOME_PROPERTY_NAME = "tests.external.compiler.home";
+ public static final int DEPENDENCY_FORMAT_VERSION = 55;
+
+ @SuppressWarnings({"WeakerAccess"}) public String DEFAULT_COMPILER;
+ @NotNull private BackendCompiler myDefaultJavaCompiler;
+
+ // extensions of the files considered as resource files
+ private final List<Pattern> myRegexpResourcePatterns = new ArrayList<Pattern>();
+ // extensions of the files considered as resource files. If present, overrides patterns in old regexp format stored in myRegexpResourcePatterns
+ private final List<String> myWildcardPatterns = new ArrayList<String>();
+ private final List<CompiledPattern> myCompiledPatterns = new ArrayList<CompiledPattern>();
+ private final List<CompiledPattern> myNegatedCompiledPatterns = new ArrayList<CompiledPattern>();
+ private boolean myWildcardPatternsInitialized = false;
+ private final Project myProject;
+ private final ExcludedEntriesConfiguration myExcludedEntriesConfiguration;
+
+ private final Collection<BackendCompiler> myRegisteredCompilers = new ArrayList<BackendCompiler>();
+ private JavacCompiler JAVAC_EXTERNAL_BACKEND;
+ private final Perl5Matcher myPatternMatcher = new Perl5Matcher();
+
+ {
+ loadDefaultWildcardPatterns();
+ }
+ private boolean myAddNotNullAssertions = true;
+
+ private final ProcessorConfigProfile myDefaultProcessorsProfile = new ProcessorConfigProfileImpl("Default");
+ private final List<ProcessorConfigProfile> myModuleProcessorProfiles = new ArrayList<ProcessorConfigProfile>();
+
+ // the map is calculated by module processor profiles list for faster access to module settings
+ private Map<Module, ProcessorConfigProfile> myProcessorsProfilesMap = null;
+
+ @Nullable
+ private String myBytecodeTargetLevel = null; // null means compiler default
+ private final Map<String, String> myModuleBytecodeTarget = new java.util.HashMap<String, String>();
+
+ public CompilerConfigurationImpl(Project project) {
+ myProject = project;
+ myExcludedEntriesConfiguration = new ExcludedEntriesConfiguration();
+ Disposer.register(project, myExcludedEntriesConfiguration);
+ MessageBusConnection connection = project.getMessageBus().connect(project);
+ connection.subscribe(ProjectTopics.MODULES, new ModuleAdapter() {
+ public void beforeModuleRemoved(Project project, Module module) {
+ getAnnotationProcessingConfiguration(module).removeModuleName(module.getName());
+ }
+
+ public void moduleAdded(Project project, Module module) {
+ myProcessorsProfilesMap = null; // clear cache
+ }
+ });
+
+ connection.subscribe(ExternalBuildOptionListener.TOPIC, new ExternalBuildOptionListener() {
+ @Override
+ public void externalBuildOptionChanged(boolean externalBuildEnabled) {
+ // this will schedule for compilation all files that might become compilable after resource patterns' changing
+ final TranslatingCompilerFilesMonitor monitor = TranslatingCompilerFilesMonitor.getInstance();
+ if (externalBuildEnabled) {
+ monitor.suspendProject(myProject);
+ }
+ else {
+ monitor.watchProject(myProject);
+ monitor.scanSourcesForCompilableFiles(myProject);
+ if (!myProject.isDefault()) {
+ final File buildSystem = BuildManager.getInstance().getBuildSystemDirectory();
+ final File[] subdirs = buildSystem.listFiles();
+ if (subdirs != null) {
+ final String prefix = myProject.getName().toLowerCase(Locale.US) + "_";
+ for (File subdir : subdirs) {
+ if (subdir.getName().startsWith(prefix)) {
+ FileUtil.asyncDelete(subdir);
+ }
+ }
+ }
+ }
+ }
+ }
+ });
+ }
+
+ public Element getState() {
+ try {
+ @NonNls final Element e = new Element("state");
+ writeExternal(e);
+ return e;
+ }
+ catch (WriteExternalException e1) {
+ LOG.error(e1);
+ return null;
+ }
+ }
+
+ public void loadState(Element state) {
+ try {
+ readExternal(state);
+ }
+ catch (InvalidDataException e) {
+ LOG.error(e);
+ }
+ }
+
+ public void setProjectBytecodeTarget(@Nullable String level) {
+ myBytecodeTargetLevel = level;
+ }
+
+ @Override
+ @Nullable
+ public String getProjectBytecodeTarget() {
+ return myBytecodeTargetLevel;
+ }
+
+ public void setModulesBytecodeTargetMap(@NotNull Map<String, String> mapping) {
+ myModuleBytecodeTarget.clear();
+ myModuleBytecodeTarget.putAll(mapping);
+ }
+
+ public Map<String, String> getModulesBytecodeTargetMap() {
+ return myModuleBytecodeTarget;
+ }
+
+ public void setBytecodeTargetLevel(Module module, String level) {
+ final String previous;
+ if (StringUtil.isEmpty(level)) {
+ previous = myModuleBytecodeTarget.remove(module.getName());
+ }
+ else {
+ previous = myModuleBytecodeTarget.put(module.getName(), level);
+ }
+ // todo: mark module as dirty in order to rebuild it completely with the new target level
+ //if (!Comparing.equal(previous, level)) {
+ // final Project project = module.getProject();
+ //
+ //}
+ }
+
+ @Override
+ @Nullable
+ public String getBytecodeTargetLevel(Module module) {
+ final String level = myModuleBytecodeTarget.get(module.getName());
+ if (level != null) {
+ return level.isEmpty() ? null : level;
+ }
+ return myBytecodeTargetLevel;
+ }
+
+ private void loadDefaultWildcardPatterns() {
+ if (!myWildcardPatterns.isEmpty()) {
+ removeWildcardPatterns();
+ }
+ try {
+ addWildcardResourcePattern("!?*.java");
+ addWildcardResourcePattern("!?*.form");
+ addWildcardResourcePattern("!?*.class");
+ addWildcardResourcePattern("!?*.groovy");
+ addWildcardResourcePattern("!?*.scala");
+ addWildcardResourcePattern("!?*.flex");
+ addWildcardResourcePattern("!?*.kt");
+ addWildcardResourcePattern("!?*.clj");
+ }
+ catch (MalformedPatternException e) {
+ LOG.error(e);
+ }
+ }
+
+ public static String getTestsExternalCompilerHome() {
+ String compilerHome = System.getProperty(TESTS_EXTERNAL_COMPILER_HOME_PROPERTY_NAME, null);
+ if (compilerHome == null) {
+ if (SystemInfo.isMac) {
+ compilerHome = new File(System.getProperty("java.home")).getAbsolutePath();
+ }
+ else {
+ compilerHome = new File(System.getProperty("java.home")).getParentFile().getAbsolutePath();
+ }
+ }
+ return compilerHome;
+ }
+
+ private static Pattern compilePattern(@NonNls String s) throws MalformedPatternException {
+ try {
+ final PatternCompiler compiler = new Perl5Compiler();
+ return SystemInfo.isFileSystemCaseSensitive? compiler.compile(s) : compiler.compile(s, Perl5Compiler.CASE_INSENSITIVE_MASK);
+ }
+ catch (org.apache.oro.text.regex.MalformedPatternException ex) {
+ throw new MalformedPatternException(ex);
+ }
+ }
+
+ public void disposeComponent() {
+ }
+
+ public void initComponent() { }
+
+ public void projectClosed() {
+ }
+
+ public JavacCompiler getJavacCompiler() {
+ createCompilers();
+ return JAVAC_EXTERNAL_BACKEND;
+ }
+
+ public void projectOpened() {
+ createCompilers();
+ }
+
+ private void createCompilers() {
+ if (JAVAC_EXTERNAL_BACKEND != null) {
+ return;
+ }
+
+ JAVAC_EXTERNAL_BACKEND = new JavacCompiler(myProject);
+ myRegisteredCompilers.add(JAVAC_EXTERNAL_BACKEND);
+
+ if (!ApplicationManager.getApplication().isUnitTestMode()) {
+ //final BackendCompiler JIKES_BACKEND = new JikesCompiler(myProject);
+ //myRegisteredCompilers.add(JIKES_BACKEND);
+
+ if (EclipseCompiler.isInitialized()) {
+ final EclipseCompiler eclipse = new EclipseCompiler(myProject);
+ myRegisteredCompilers.add(eclipse);
+ }
+
+ if (ApplicationManagerEx.getApplicationEx().isInternal()) {
+ try {
+ final EclipseEmbeddedCompiler eclipseEmbedded = new EclipseEmbeddedCompiler(myProject);
+ myRegisteredCompilers.add(eclipseEmbedded);
+ }
+ catch (NoClassDefFoundError e) {
+ // eclipse jar must be not in the classpath
+ }
+ try {
+ CompilerAPICompiler inProcessJavaCompiler = new CompilerAPICompiler(myProject);
+ myRegisteredCompilers.add(inProcessJavaCompiler);
+ }
+ catch (NoClassDefFoundError e) {
+ // wrong JDK
+ }
+ }
+ }
+
+ final BackendCompiler[] compilers = Extensions.getExtensions(BackendCompiler.EP_NAME, myProject);
+ final Set<FileType> types = new HashSet<FileType>();
+ for (BackendCompiler compiler : compilers) {
+ myRegisteredCompilers.add(compiler);
+ types.addAll(compiler.getCompilableFileTypes());
+ }
+
+ final CompilerManager compilerManager = CompilerManager.getInstance(myProject);
+ for (FileType type : types) {
+ compilerManager.addCompilableFileType(type);
+ }
+
+ myDefaultJavaCompiler = JAVAC_EXTERNAL_BACKEND;
+ for (BackendCompiler compiler : myRegisteredCompilers) {
+ if (compiler.getId().equals(DEFAULT_COMPILER)) {
+ myDefaultJavaCompiler = compiler;
+ break;
+ }
+ }
+ DEFAULT_COMPILER = myDefaultJavaCompiler.getId();
+ }
+
+ public Collection<BackendCompiler> getRegisteredJavaCompilers() {
+ createCompilers();
+ return myRegisteredCompilers;
+ }
+
+ public String[] getResourceFilePatterns() {
+ return getWildcardPatterns();
+ }
+
+ private String[] getRegexpPatterns() {
+ String[] patterns = ArrayUtil.newStringArray(myRegexpResourcePatterns.size());
+ int index = 0;
+ for (final Pattern myRegexpResourcePattern : myRegexpResourcePatterns) {
+ patterns[index++] = myRegexpResourcePattern.getPattern();
+ }
+ return patterns;
+ }
+
+ private String[] getWildcardPatterns() {
+ return ArrayUtil.toStringArray(myWildcardPatterns);
+ }
+
+ public void addResourceFilePattern(String namePattern) throws MalformedPatternException {
+ addWildcardResourcePattern(namePattern);
+ }
+
+ // need this method only for handling patterns in old regexp format
+ private void addRegexpPattern(String namePattern) throws MalformedPatternException {
+ Pattern pattern = compilePattern(namePattern);
+ if (pattern != null) {
+ myRegexpResourcePatterns.add(pattern);
+ }
+ }
+
+ @Override
+ public ExcludedEntriesConfiguration getExcludedEntriesConfiguration() {
+ return myExcludedEntriesConfiguration;
+ }
+
+ public boolean isExcludedFromCompilation(final VirtualFile virtualFile) {
+ return myExcludedEntriesConfiguration.isExcluded(virtualFile);
+ }
+
+ @Override
+ public boolean isResourceFile(VirtualFile virtualFile) {
+ return isResourceFile(virtualFile.getName(), virtualFile.getParent());
+ }
+
+ @Override
+ public boolean isAddNotNullAssertions() {
+ return myAddNotNullAssertions;
+ }
+
+ @Override
+ public void setAddNotNullAssertions(boolean enabled) {
+ myAddNotNullAssertions = enabled;
+ }
+
+ @NotNull
+ public ProcessorConfigProfile getDefaultProcessorProfile() {
+ return myDefaultProcessorsProfile;
+ }
+
+ public void setDefaultProcessorProfile(ProcessorConfigProfile profile) {
+ myDefaultProcessorsProfile.initFrom(profile);
+ }
+
+ @NotNull
+ public List<ProcessorConfigProfile> getModuleProcessorProfiles() {
+ return myModuleProcessorProfiles;
+ }
+
+ public void setModuleProcessorProfiles(Collection<ProcessorConfigProfile> moduleProfiles) {
+ myModuleProcessorProfiles.clear();
+ myModuleProcessorProfiles.addAll(moduleProfiles);
+ myProcessorsProfilesMap = null;
+ }
+
+ @Nullable
+ public ProcessorConfigProfile findModuleProcessorProfile(@NotNull String name) {
+ for (ProcessorConfigProfile profile : myModuleProcessorProfiles) {
+ if (name.equals(profile.getName())) {
+ return profile;
+ }
+ }
+
+ return null;
+ }
+
+ public void removeModuleProcessorProfile(ProcessorConfigProfile profile) {
+ myModuleProcessorProfiles.remove(profile);
+ myProcessorsProfilesMap = null; // clear cache
+ }
+
+ public void addModuleProcessorProfile(@NotNull ProcessorConfigProfile profile) {
+ myModuleProcessorProfiles.add(profile);
+ myProcessorsProfilesMap = null; // clear cache
+ }
+
+ @Override
+ @NotNull
+ public ProcessorConfigProfile getAnnotationProcessingConfiguration(Module module) {
+ Map<Module, ProcessorConfigProfile> map = myProcessorsProfilesMap;
+ if (map == null) {
+ map = new HashMap<Module, ProcessorConfigProfile>();
+ final Map<String, Module> namesMap = new HashMap<String, Module>();
+ for (Module m : ModuleManager.getInstance(module.getProject()).getModules()) {
+ namesMap.put(m.getName(), m);
+ }
+ if (!namesMap.isEmpty()) {
+ for (ProcessorConfigProfile profile : myModuleProcessorProfiles) {
+ for (String name : profile.getModuleNames()) {
+ final Module mod = namesMap.get(name);
+ if (mod != null) {
+ map.put(mod, profile);
+ }
+ }
+ }
+ }
+ myProcessorsProfilesMap = map;
+ }
+ final ProcessorConfigProfile profile = map.get(module);
+ return profile != null? profile : myDefaultProcessorsProfile;
+ }
+
+ @Override
+ public boolean isAnnotationProcessorsEnabled() {
+ if (myDefaultProcessorsProfile.isEnabled()) {
+ return true;
+ }
+ for (ProcessorConfigProfile profile : myModuleProcessorProfiles) {
+ if (profile.isEnabled()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void addWildcardResourcePattern(@NonNls final String wildcardPattern) throws MalformedPatternException {
+ final CompiledPattern pattern = convertToRegexp(wildcardPattern);
+ if (pattern != null) {
+ myWildcardPatterns.add(wildcardPattern);
+ if (isPatternNegated(wildcardPattern)) {
+ myNegatedCompiledPatterns.add(pattern);
+ }
+ else {
+ myCompiledPatterns.add(pattern);
+ }
+ }
+ }
+
+ public void removeResourceFilePatterns() {
+ removeWildcardPatterns();
+ }
+
+ private void removeRegexpPatterns() {
+ myRegexpResourcePatterns.clear();
+ }
+
+ private void removeWildcardPatterns() {
+ myWildcardPatterns.clear();
+ myCompiledPatterns.clear();
+ myNegatedCompiledPatterns.clear();
+ }
+
+ private static CompiledPattern convertToRegexp(String wildcardPattern) {
+ if (isPatternNegated(wildcardPattern)) {
+ wildcardPattern = wildcardPattern.substring(1);
+ }
+
+ wildcardPattern = FileUtil.toSystemIndependentName(wildcardPattern);
+
+ String srcRoot = null;
+ int colon = wildcardPattern.indexOf(":");
+ if (colon > 0) {
+ srcRoot = wildcardPattern.substring(0, colon);
+ wildcardPattern = wildcardPattern.substring(colon + 1);
+ }
+
+ String dirPattern = null;
+ int slash = wildcardPattern.lastIndexOf('/');
+ if (slash >= 0) {
+ dirPattern = wildcardPattern.substring(0, slash + 1);
+ wildcardPattern = wildcardPattern.substring(slash + 1);
+ if (!dirPattern.startsWith("/")) {
+ dirPattern = "/" + dirPattern;
+ }
+ //now dirPattern starts and ends with '/'
+
+ dirPattern = normalizeWildcards(dirPattern);
+
+ dirPattern = StringUtil.replace(dirPattern, "/.*.*/", "(/.*)?/");
+ dirPattern = StringUtil.trimEnd(dirPattern, "/");
+
+ dirPattern = optimize(dirPattern);
+ }
+
+ wildcardPattern = normalizeWildcards(wildcardPattern);
+ wildcardPattern = optimize(wildcardPattern);
+
+ final Pattern dirCompiled = dirPattern == null ? null : compilePattern(dirPattern);
+ final Pattern srcCompiled = srcRoot == null ? null : compilePattern(optimize(normalizeWildcards(srcRoot)));
+ return new CompiledPattern(compilePattern(wildcardPattern), dirCompiled, srcCompiled);
+ }
+
+ private static String optimize(String wildcardPattern) {
+ return wildcardPattern.replaceAll("(?:\\.\\*)+", ".*");
+ }
+
+ private static String normalizeWildcards(String wildcardPattern) {
+ wildcardPattern = StringUtil.replace(wildcardPattern, "\\!", "!");
+ wildcardPattern = StringUtil.replace(wildcardPattern, ".", "\\.");
+ wildcardPattern = StringUtil.replace(wildcardPattern, "*?", ".+");
+ wildcardPattern = StringUtil.replace(wildcardPattern, "?*", ".+");
+ wildcardPattern = StringUtil.replace(wildcardPattern, "*", ".*");
+ wildcardPattern = StringUtil.replace(wildcardPattern, "?", ".");
+ return wildcardPattern;
+ }
+
+ public static boolean isPatternNegated(String wildcardPattern) {
+ return wildcardPattern.length() > 1 && wildcardPattern.charAt(0) == '!';
+ }
+
+ public boolean isResourceFile(String name) {
+ return isResourceFile(name, null);
+ }
+
+ private boolean matches(String s, Pattern p) {
+ synchronized (myPatternMatcher) {
+ try {
+ return myPatternMatcher.matches(s, p);
+ }
+ catch (Exception e) {
+ LOG.error("Exception matching file name \"" + s + "\" against the pattern \"" + p + "\"", e);
+ return false;
+ }
+ }
+ }
+
+ private boolean isResourceFile(String name, @Nullable VirtualFile parent) {
+ final Ref<String> parentRef = Ref.create(null);
+ //noinspection ForLoopReplaceableByForEach
+ for (int i = 0; i < myCompiledPatterns.size(); i++) {
+ if (matches(name, parent, parentRef, myCompiledPatterns.get(i))) {
+ return true;
+ }
+ }
+
+ if (myNegatedCompiledPatterns.isEmpty()) {
+ return false;
+ }
+
+ //noinspection ForLoopReplaceableByForEach
+ for (int i = 0; i < myNegatedCompiledPatterns.size(); i++) {
+ if (matches(name, parent, parentRef, myNegatedCompiledPatterns.get(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private boolean matches(String name, VirtualFile parent, Ref<String> parentRef, CompiledPattern pair) {
+ if (!matches(name, pair.fileName)) {
+ return false;
+ }
+
+ if (parent != null && (pair.dir != null || pair.srcRoot != null)) {
+ VirtualFile srcRoot = ProjectRootManager.getInstance(myProject).getFileIndex().getSourceRootForFile(parent);
+ if (pair.dir != null) {
+ String parentPath = parentRef.get();
+ if (parentPath == null) {
+ parentRef.set(parentPath = srcRoot == null ? parent.getPath() : VfsUtilCore.getRelativePath(parent, srcRoot, '/'));
+ }
+ if (parentPath == null || !matches("/" + parentPath, pair.dir)) {
+ return false;
+ }
+ }
+
+ if (pair.srcRoot != null) {
+ String srcRootName = srcRoot == null ? null : srcRoot.getName();
+ if (srcRootName == null || !matches(srcRootName, pair.srcRoot)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+
+ public void readExternal(Element parentNode) throws InvalidDataException {
+ DefaultJDOMExternalizer.readExternal(this, parentNode);
+
+ final Element notNullAssertions = parentNode.getChild(JpsJavaCompilerConfigurationSerializer.ADD_NOTNULL_ASSERTIONS);
+ if (notNullAssertions != null) {
+ myAddNotNullAssertions = Boolean.valueOf(notNullAssertions.getAttributeValue(JpsJavaCompilerConfigurationSerializer.ENABLED, "true"));
+ }
+
+ Element node = parentNode.getChild(JpsJavaCompilerConfigurationSerializer.EXCLUDE_FROM_COMPILE);
+ if (node != null) {
+ myExcludedEntriesConfiguration.readExternal(node);
+ }
+
+ try {
+ removeRegexpPatterns();
+ node = parentNode.getChild(JpsJavaCompilerConfigurationSerializer.RESOURCE_EXTENSIONS);
+ if (node != null) {
+ for (final Object o : node.getChildren(JpsJavaCompilerConfigurationSerializer.ENTRY)) {
+ Element element = (Element)o;
+ String pattern = element.getAttributeValue(JpsJavaCompilerConfigurationSerializer.NAME);
+ if (!StringUtil.isEmpty(pattern)) {
+ addRegexpPattern(pattern);
+ }
+ }
+ }
+
+ removeWildcardPatterns();
+ node = parentNode.getChild(JpsJavaCompilerConfigurationSerializer.WILDCARD_RESOURCE_PATTERNS);
+ if (node != null) {
+ myWildcardPatternsInitialized = true;
+ for (final Object o : node.getChildren(JpsJavaCompilerConfigurationSerializer.ENTRY)) {
+ final Element element = (Element)o;
+ String pattern = element.getAttributeValue(JpsJavaCompilerConfigurationSerializer.NAME);
+ if (!StringUtil.isEmpty(pattern)) {
+ addWildcardResourcePattern(pattern);
+ }
+ }
+ }
+ }
+ catch (MalformedPatternException e) {
+ throw new InvalidDataException(e);
+ }
+
+
+ myModuleProcessorProfiles.clear();
+ myProcessorsProfilesMap = null;
+
+ final Element annotationProcessingSettings = parentNode.getChild(JpsJavaCompilerConfigurationSerializer.ANNOTATION_PROCESSING);
+ if (annotationProcessingSettings != null) {
+ final List profiles = annotationProcessingSettings.getChildren("profile");
+ if (!profiles.isEmpty()) {
+ for (Object elem : profiles) {
+ final Element profileElement = (Element)elem;
+ final boolean isDefault = "true".equals(profileElement.getAttributeValue("default"));
+ if (isDefault) {
+ AnnotationProcessorProfileSerializer.readExternal(myDefaultProcessorsProfile, profileElement);
+ }
+ else {
+ final ProcessorConfigProfile profile = new ProcessorConfigProfileImpl("");
+ AnnotationProcessorProfileSerializer.readExternal(profile, profileElement);
+ myModuleProcessorProfiles.add(profile);
+ }
+ }
+ }
+ else {
+ // assuming older format
+ loadProfilesFromOldFormat(annotationProcessingSettings);
+ }
+ }
+
+ myBytecodeTargetLevel = null;
+ myModuleBytecodeTarget.clear();
+ final Element bytecodeTargetElement = parentNode.getChild(JpsJavaCompilerConfigurationSerializer.BYTECODE_TARGET_LEVEL);
+ if (bytecodeTargetElement != null) {
+ myBytecodeTargetLevel = bytecodeTargetElement.getAttributeValue(JpsJavaCompilerConfigurationSerializer.TARGET_ATTRIBUTE);
+ for (Element elem : (Collection<Element>)bytecodeTargetElement.getChildren(JpsJavaCompilerConfigurationSerializer.MODULE)) {
+ final String name = elem.getAttributeValue(JpsJavaCompilerConfigurationSerializer.NAME);
+ if (name == null) {
+ continue;
+ }
+ final String target = elem.getAttributeValue(JpsJavaCompilerConfigurationSerializer.TARGET_ATTRIBUTE);
+ if (target == null) {
+ continue;
+ }
+ myModuleBytecodeTarget.put(name, target);
+ }
+ }
+ }
+
+ private void loadProfilesFromOldFormat(Element processing) {
+ // collect data
+ final boolean isEnabled = Boolean.parseBoolean(processing.getAttributeValue(JpsJavaCompilerConfigurationSerializer.ENABLED, "false"));
+ final boolean isUseClasspath = Boolean.parseBoolean(processing.getAttributeValue("useClasspath", "true"));
+ final StringBuilder processorPath = new StringBuilder();
+ final Set<String> optionPairs = new HashSet<String>();
+ final Set<String> processors = new HashSet<String>();
+ final List<Pair<String, String>> modulesToProcess = new ArrayList<Pair<String, String>>();
+
+ for (Object child : processing.getChildren("processorPath")) {
+ final Element pathElement = (Element)child;
+ final String path = pathElement.getAttributeValue("value", (String)null);
+ if (path != null) {
+ if (processorPath.length() > 0) {
+ processorPath.append(File.pathSeparator);
+ }
+ processorPath.append(path);
+ }
+ }
+
+ for (Object child : processing.getChildren("processor")) {
+ final Element processorElement = (Element)child;
+ final String proc = processorElement.getAttributeValue(JpsJavaCompilerConfigurationSerializer.NAME, (String)null);
+ if (proc != null) {
+ processors.add(proc);
+ }
+ final StringTokenizer tokenizer = new StringTokenizer(processorElement.getAttributeValue("options", ""), " ", false);
+ while (tokenizer.hasMoreTokens()) {
+ final String pair = tokenizer.nextToken();
+ optionPairs.add(pair);
+ }
+ }
+
+ for (Object child : processing.getChildren("processModule")) {
+ final Element moduleElement = (Element)child;
+ final String name = moduleElement.getAttributeValue(JpsJavaCompilerConfigurationSerializer.NAME, (String)null);
+ if (name == null) {
+ continue;
+ }
+ final String dir = moduleElement.getAttributeValue("generatedDirName", (String)null);
+ modulesToProcess.add(Pair.create(name, dir));
+ }
+
+ myDefaultProcessorsProfile.setEnabled(false);
+ myDefaultProcessorsProfile.setObtainProcessorsFromClasspath(isUseClasspath);
+ if (processorPath.length() > 0) {
+ myDefaultProcessorsProfile.setProcessorPath(processorPath.toString());
+ }
+ if (!optionPairs.isEmpty()) {
+ for (String pair : optionPairs) {
+ final int index = pair.indexOf("=");
+ if (index > 0) {
+ myDefaultProcessorsProfile.setOption(pair.substring(0, index), pair.substring(index + 1));
+ }
+ }
+ }
+ for (String processor : processors) {
+ myDefaultProcessorsProfile.addProcessor(processor);
+ }
+
+ final Map<String, Set<String>> dirNameToModulesMap = new HashMap<String, Set<String>>();
+ for (Pair<String, String> moduleDirPair : modulesToProcess) {
+ final String dir = moduleDirPair.getSecond();
+ Set<String> set = dirNameToModulesMap.get(dir);
+ if (set == null) {
+ set = new HashSet<String>();
+ dirNameToModulesMap.put(dir, set);
+ }
+ set.add(moduleDirPair.getFirst());
+ }
+
+ int profileIndex = 0;
+ for (Map.Entry<String, Set<String>> entry : dirNameToModulesMap.entrySet()) {
+ final String dirName = entry.getKey();
+ final ProcessorConfigProfile profile = new ProcessorConfigProfileImpl(myDefaultProcessorsProfile);
+ profile.setName("Profile" + (++profileIndex));
+ profile.setEnabled(isEnabled);
+ profile.setGeneratedSourcesDirectoryName(dirName, false);
+ for (String moduleName : entry.getValue()) {
+ profile.addModuleName(moduleName);
+ }
+ myModuleProcessorProfiles.add(profile);
+ }
+ }
+
+ public void writeExternal(Element parentNode) throws WriteExternalException {
+ DefaultJDOMExternalizer.writeExternal(this, parentNode);
+
+ if (myAddNotNullAssertions != true) {
+ addChild(parentNode, JpsJavaCompilerConfigurationSerializer.ADD_NOTNULL_ASSERTIONS).setAttribute(
+ JpsJavaCompilerConfigurationSerializer.ENABLED, String.valueOf(myAddNotNullAssertions));
+ }
+
+ if(myExcludedEntriesConfiguration.getExcludeEntryDescriptions().length > 0) {
+ myExcludedEntriesConfiguration.writeExternal(addChild(parentNode, JpsJavaCompilerConfigurationSerializer.EXCLUDE_FROM_COMPILE));
+ }
+
+ final Element newChild = addChild(parentNode, JpsJavaCompilerConfigurationSerializer.RESOURCE_EXTENSIONS);
+ for (final String pattern : getRegexpPatterns()) {
+ addChild(newChild, JpsJavaCompilerConfigurationSerializer.ENTRY).setAttribute(JpsJavaCompilerConfigurationSerializer.NAME, pattern);
+ }
+
+ if (myWildcardPatternsInitialized || !myWildcardPatterns.isEmpty()) {
+ final Element wildcardPatterns = addChild(parentNode, JpsJavaCompilerConfigurationSerializer.WILDCARD_RESOURCE_PATTERNS);
+ for (final String wildcardPattern : myWildcardPatterns) {
+ addChild(wildcardPatterns, JpsJavaCompilerConfigurationSerializer.ENTRY).setAttribute(JpsJavaCompilerConfigurationSerializer.NAME, wildcardPattern);
+ }
+ }
+
+ final Element annotationProcessingSettings = addChild(parentNode, JpsJavaCompilerConfigurationSerializer.ANNOTATION_PROCESSING);
+ final Element defaultProfileElem = addChild(annotationProcessingSettings, "profile").setAttribute("default", "true");
+ AnnotationProcessorProfileSerializer.writeExternal(myDefaultProcessorsProfile, defaultProfileElem);
+ for (ProcessorConfigProfile profile : myModuleProcessorProfiles) {
+ final Element profileElem = addChild(annotationProcessingSettings, "profile").setAttribute("default", "false");
+ AnnotationProcessorProfileSerializer.writeExternal(profile, profileElem);
+ }
+
+ if (!StringUtil.isEmpty(myBytecodeTargetLevel) || !myModuleBytecodeTarget.isEmpty()) {
+ final Element bytecodeTarget = addChild(parentNode, JpsJavaCompilerConfigurationSerializer.BYTECODE_TARGET_LEVEL);
+ if (!StringUtil.isEmpty(myBytecodeTargetLevel)) {
+ bytecodeTarget.setAttribute(JpsJavaCompilerConfigurationSerializer.TARGET_ATTRIBUTE, myBytecodeTargetLevel);
+ }
+ if (!myModuleBytecodeTarget.isEmpty()) {
+ final List<String> moduleNames = new ArrayList<String>(myModuleBytecodeTarget.keySet());
+ Collections.sort(moduleNames, String.CASE_INSENSITIVE_ORDER);
+ for (String name : moduleNames) {
+ final Element moduleElement = addChild(bytecodeTarget, JpsJavaCompilerConfigurationSerializer.MODULE);
+ moduleElement.setAttribute(JpsJavaCompilerConfigurationSerializer.NAME, name);
+ final String value = myModuleBytecodeTarget.get(name);
+ moduleElement.setAttribute(JpsJavaCompilerConfigurationSerializer.TARGET_ATTRIBUTE, value != null? value : "");
+ }
+ }
+ }
+ }
+
+ @NotNull @NonNls
+ public String getComponentName() {
+ return "CompilerConfiguration";
+ }
+
+ public BackendCompiler getDefaultCompiler() {
+ createCompilers();
+ return myDefaultJavaCompiler;
+ }
+
+ /**
+ * @param defaultCompiler The compiler that is passed as a parameter to setDefaultCompiler()
+ * must be one of the registered compilers in compiler configuration.
+ * Otherwise because of lazy compiler initialization, the value of default compiler will point to some other compiler instance
+ */
+ public void setDefaultCompiler(BackendCompiler defaultCompiler) {
+ myDefaultJavaCompiler = defaultCompiler;
+ DEFAULT_COMPILER = defaultCompiler.getId();
+ }
+
+ public void convertPatterns() {
+ if (!needPatternConversion()) {
+ return;
+ }
+ try {
+ boolean ok;
+ try {
+ ok = doConvertPatterns();
+ }
+ catch (MalformedPatternException e) {
+ ok = false;
+ }
+ if (!ok) {
+ final String initialPatternString = patternsToString(getRegexpPatterns());
+ final String message = CompilerBundle.message(
+ "message.resource.patterns.format.changed",
+ ApplicationNamesInfo.getInstance().getProductName(),
+ initialPatternString,
+ CommonBundle.getOkButtonText(),
+ CommonBundle.getCancelButtonText()
+ );
+ final String wildcardPatterns = Messages.showInputDialog(
+ myProject, message, CompilerBundle.message("pattern.conversion.dialog.title"), Messages.getWarningIcon(), initialPatternString, new InputValidator() {
+ public boolean checkInput(String inputString) {
+ return true;
+ }
+ public boolean canClose(String inputString) {
+ final StringTokenizer tokenizer = new StringTokenizer(inputString, ";", false);
+ StringBuilder malformedPatterns = new StringBuilder();
+
+ while (tokenizer.hasMoreTokens()) {
+ String pattern = tokenizer.nextToken();
+ try {
+ addWildcardResourcePattern(pattern);
+ }
+ catch (MalformedPatternException e) {
+ malformedPatterns.append("\n\n");
+ malformedPatterns.append(pattern);
+ malformedPatterns.append(": ");
+ malformedPatterns.append(e.getMessage());
+ }
+ }
+
+ if (malformedPatterns.length() > 0) {
+ Messages.showErrorDialog(CompilerBundle.message("error.bad.resource.patterns", malformedPatterns.toString()),
+ CompilerBundle.message("bad.resource.patterns.dialog.title"));
+ removeWildcardPatterns();
+ return false;
+ }
+ return true;
+ }
+ });
+ if (wildcardPatterns == null) { // cancel pressed
+ loadDefaultWildcardPatterns();
+ }
+ }
+ }
+ finally {
+ myWildcardPatternsInitialized = true;
+ }
+ }
+
+ private boolean needPatternConversion() {
+ return !myWildcardPatternsInitialized && !myRegexpResourcePatterns.isEmpty();
+ }
+
+ private boolean doConvertPatterns() throws MalformedPatternException {
+ final String[] regexpPatterns = getRegexpPatterns();
+ final List<String> converted = new ArrayList<String>();
+ final Pattern multipleExtensionsPatternPattern = compilePattern("\\.\\+\\\\\\.\\((\\w+(?:\\|\\w+)*)\\)");
+ final Pattern singleExtensionPatternPattern = compilePattern("\\.\\+\\\\\\.(\\w+)");
+ final Perl5Matcher matcher = new Perl5Matcher();
+ for (final String regexpPattern : regexpPatterns) {
+ //final Matcher multipleExtensionsMatcher = multipleExtensionsPatternPattern.matcher(regexpPattern);
+ if (matcher.matches(regexpPattern, multipleExtensionsPatternPattern)) {
+ final MatchResult match = matcher.getMatch();
+ final StringTokenizer tokenizer = new StringTokenizer(match.group(1), "|", false);
+ while (tokenizer.hasMoreTokens()) {
+ converted.add("?*." + tokenizer.nextToken());
+ }
+ }
+ else {
+ //final Matcher singleExtensionMatcher = singleExtensionPatternPattern.matcher(regexpPattern);
+ if (matcher.matches(regexpPattern, singleExtensionPatternPattern)) {
+ final MatchResult match = matcher.getMatch();
+ converted.add("?*." + match.group(1));
+ }
+ else {
+ return false;
+ }
+ }
+ }
+ for (final String aConverted : converted) {
+ addWildcardResourcePattern(aConverted);
+ }
+ return true;
+ }
+
+ private static String patternsToString(final String[] patterns) {
+ final StringBuilder extensionsString = new StringBuilder();
+ for (int idx = 0; idx < patterns.length; idx++) {
+ if (idx > 0) {
+ extensionsString.append(";");
+ }
+ extensionsString.append(patterns[idx]);
+ }
+ return extensionsString.toString();
+ }
+
+ private static class CompiledPattern {
+ @NotNull final Pattern fileName;
+ @Nullable final Pattern dir;
+ @Nullable final Pattern srcRoot;
+
+ private CompiledPattern(Pattern fileName, Pattern dir, Pattern srcRoot) {
+ this.fileName = fileName;
+ this.dir = dir;
+ this.srcRoot = srcRoot;
+ }
+ }
+
+ private static Element addChild(Element parent, final String childName) {
+ final Element child = new Element(childName);
+ parent.addContent(child);
+ return child;
+ }
+
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/CompilerEncodingService.java b/java/compiler/impl/src/com/intellij/compiler/CompilerEncodingService.java
new file mode 100644
index 0000000..9c88df8
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/CompilerEncodingService.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler;
+
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.util.Chunk;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.nio.charset.Charset;
+import java.util.Collection;
+
+/**
+ * @author nik
+ */
+public abstract class CompilerEncodingService {
+ public static CompilerEncodingService getInstance(@NotNull Project project) {
+ return ServiceManager.getService(project, CompilerEncodingService.class);
+ }
+
+ @Nullable
+ public static Charset getPreferredModuleEncoding(Chunk<Module> chunk) {
+ CompilerEncodingService service = null;
+ for (Module module : chunk.getNodes()) {
+ if (service == null) {
+ service = getInstance(module.getProject());
+ }
+ final Charset charset = service.getPreferredModuleEncoding(module);
+ if (charset != null) {
+ return charset;
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ public abstract Charset getPreferredModuleEncoding(@NotNull Module module);
+
+ @NotNull
+ public abstract Collection<Charset> getAllModuleEncodings(@NotNull Module module);
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/CompilerException.java b/java/compiler/impl/src/com/intellij/compiler/CompilerException.java
new file mode 100644
index 0000000..d982f9c
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/CompilerException.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+/*
+ * @author: Eugene Zhuravlev
+ * Date: Oct 15, 2002
+ * Time: 1:55:22 PM
+ */
+package com.intellij.compiler;
+
+public class CompilerException extends Exception {
+
+ public CompilerException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/CompilerIOUtil.java b/java/compiler/impl/src/com/intellij/compiler/CompilerIOUtil.java
new file mode 100644
index 0000000..4616bc9
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/CompilerIOUtil.java
@@ -0,0 +1,80 @@
+/*
+ * 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 com.intellij.compiler;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class CompilerIOUtil {
+ private final static ThreadLocal<byte[]> myBuffer = new ThreadLocal<byte[]>() {
+ protected byte[] initialValue() {
+ return new byte[1024];
+ }
+ };
+
+ private CompilerIOUtil() {}
+
+ public static String readString(DataInput stream) throws IOException {
+ final int length = stream.readInt();
+ if (length == -1) {
+ return null;
+ }
+
+ if (length == 0) {
+ return "";
+ }
+
+ char[] chars = new char[length];
+ int charsRead = 0;
+
+ final byte[] buff = myBuffer.get();
+ while (charsRead < length) {
+ final int bytesRead = Math.min((length - charsRead) * 2, buff.length);
+ stream.readFully(buff, 0, bytesRead);
+ for (int i = 0 ; i < bytesRead; i += 2) {
+ chars[charsRead++] = (char)((buff[i] << 8) + (buff[i + 1] & 0xFF));
+ }
+ }
+
+ return new String(chars);
+ }
+
+ public static void writeString(String s, DataOutput stream) throws IOException {
+ if (s == null) {
+ stream.writeInt(-1);
+ return;
+ }
+
+ final int len = s.length();
+ stream.writeInt(len);
+ if (len == 0) {
+ return;
+ }
+
+ int charsWritten = 0;
+ final byte[] buff = myBuffer.get();
+ while (charsWritten < len) {
+ final int bytesWritten = Math.min((len - charsWritten) * 2, buff.length);
+ for (int i = 0; i < bytesWritten; i += 2) {
+ char aChar = s.charAt(charsWritten++);
+ buff[i] = (byte)((aChar >>> 8) & 0xFF);
+ buff[i + 1] = (byte)((aChar) & 0xFF);
+ }
+ stream.write(buff, 0, bytesWritten);
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/CompilerIconLayerProvider.java b/java/compiler/impl/src/com/intellij/compiler/CompilerIconLayerProvider.java
new file mode 100644
index 0000000..3f2b16d
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/CompilerIconLayerProvider.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2000-2011 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 com.intellij.compiler;
+
+import com.intellij.codeInsight.CodeInsightBundle;
+import com.intellij.ide.IconLayerProvider;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.FileIndexFacade;
+import com.intellij.openapi.util.Iconable;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiDirectory;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiModifierListOwner;
+import com.intellij.util.PlatformIcons;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+
+/**
+ * @author yole
+ */
+public class CompilerIconLayerProvider implements IconLayerProvider {
+ @Override
+ public Icon getLayerIcon(@NotNull Iconable element, boolean isLocked) {
+ VirtualFile vFile = null;
+ Project project = null;
+ if (element instanceof PsiModifierListOwner) {
+ project = ((PsiModifierListOwner) element).getProject();
+ final PsiFile containingFile = ((PsiModifierListOwner) element).getContainingFile();
+ vFile = containingFile == null ? null : containingFile.getVirtualFile();
+ }
+ else if (element instanceof PsiDirectory) {
+ project = ((PsiDirectory) element).getProject();
+ vFile = ((PsiDirectory) element).getVirtualFile();
+ }
+ if (vFile != null && isExcluded(vFile, project)) {
+ return PlatformIcons.EXCLUDED_FROM_COMPILE_ICON;
+ }
+ return null;
+ }
+
+ @Override
+ public String getLayerDescription() {
+ return CodeInsightBundle.message("node.excluded.flag.tooltip");
+ }
+
+ public static boolean isExcluded(final VirtualFile vFile, final Project project) {
+ return vFile != null
+ && ServiceManager.getService(project, FileIndexFacade.class).isInSource(vFile)
+ && CompilerConfiguration.getInstance(project).isExcludedFromCompilation(vFile);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/CompilerManagerImpl.java b/java/compiler/impl/src/com/intellij/compiler/CompilerManagerImpl.java
new file mode 100644
index 0000000..f505855
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/CompilerManagerImpl.java
@@ -0,0 +1,466 @@
+/*
+ * 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 com.intellij.compiler;
+
+import com.intellij.compiler.impl.*;
+import com.intellij.compiler.impl.javaCompiler.AnnotationProcessingCompiler;
+import com.intellij.compiler.impl.javaCompiler.JavaCompiler;
+import com.intellij.compiler.impl.resourceCompiler.ResourceCompiler;
+import com.intellij.compiler.impl.rmiCompiler.RmicCompiler;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.*;
+import com.intellij.openapi.compiler.Compiler;
+import com.intellij.openapi.extensions.Extensions;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.StdFileTypes;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleType;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.Chunk;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.graph.CachingSemiGraph;
+import com.intellij.util.graph.Graph;
+import com.intellij.util.graph.GraphGenerator;
+import com.intellij.util.messages.MessageBus;
+import com.intellij.util.messages.MessageBusConnection;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.lang.reflect.Array;
+import java.util.*;
+import java.util.concurrent.Semaphore;
+
+public class CompilerManagerImpl extends CompilerManager {
+ private final Project myProject;
+
+ private final List<Compiler> myCompilers = new ArrayList<Compiler>();
+ private final List<TranslatingCompiler> myTranslators = new ArrayList<TranslatingCompiler>();
+
+ private final List<CompileTask> myBeforeTasks = new ArrayList<CompileTask>();
+ private final List<CompileTask> myAfterTasks = new ArrayList<CompileTask>();
+ private final Set<FileType> myCompilableTypes = new HashSet<FileType>();
+ private final CompilationStatusListener myEventPublisher;
+ private final Semaphore myCompilationSemaphore = new Semaphore(1, true);
+ private final Map<Compiler, Set<FileType>> myCompilerToInputTypes = new HashMap<Compiler, Set<FileType>>();
+ private final Map<Compiler, Set<FileType>> myCompilerToOutputTypes = new HashMap<Compiler, Set<FileType>>();
+ private final Set<ModuleType> myValidationDisabledModuleTypes = new HashSet<ModuleType>();
+ private final Set<LocalFileSystem.WatchRequest> myWatchRoots;
+
+ public CompilerManagerImpl(final Project project, CompilerConfigurationImpl compilerConfiguration, MessageBus messageBus) {
+ myProject = project;
+ myEventPublisher = messageBus.syncPublisher(CompilerTopics.COMPILATION_STATUS);
+
+ // predefined compilers
+ addTranslatingCompiler(new AnnotationProcessingCompiler(project), new HashSet<FileType>(Arrays.asList(StdFileTypes.JAVA)), new HashSet<FileType>(Arrays.asList(StdFileTypes.JAVA, StdFileTypes.CLASS)));
+ addTranslatingCompiler(new JavaCompiler(project), new HashSet<FileType>(Arrays.asList(StdFileTypes.JAVA)), new HashSet<FileType>(Arrays.asList(StdFileTypes.CLASS)));
+ addCompiler(new ResourceCompiler(project, compilerConfiguration));
+ addCompiler(new RmicCompiler());
+
+ for(Compiler compiler: Extensions.getExtensions(Compiler.EP_NAME, myProject)) {
+ addCompiler(compiler);
+ }
+ for(CompilerFactory factory: Extensions.getExtensions(CompilerFactory.EP_NAME, myProject)) {
+ Compiler[] compilers = factory.createCompilers(this);
+ for (Compiler compiler : compilers) {
+ addCompiler(compiler);
+ }
+ }
+
+ addCompilableFileType(StdFileTypes.JAVA);
+
+ final File projectGeneratedSrcRoot = CompilerPaths.getGeneratedDataDirectory(project);
+ projectGeneratedSrcRoot.mkdirs();
+ final LocalFileSystem lfs = LocalFileSystem.getInstance();
+ myWatchRoots = lfs.addRootsToWatch(Collections.singletonList(FileUtil.toCanonicalPath(projectGeneratedSrcRoot.getPath())), true);
+ Disposer.register(project, new Disposable() {
+ public void dispose() {
+ lfs.removeWatchedRoots(myWatchRoots);
+ if (ApplicationManager.getApplication().isUnitTestMode()) { // force cleanup for created compiler system directory with generated sources
+ FileUtil.delete(CompilerPaths.getCompilerSystemDirectory(project));
+ }
+ }
+ });
+
+ //
+ //addCompiler(new DummyTransformingCompiler()); // this one is for testing purposes only
+ //addCompiler(new DummySourceGeneratingCompiler(myProject)); // this one is for testing purposes only
+ /*
+ // for testing only
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ FileTypeManager.getInstance().registerFileType(DummyTranslatingCompiler.INPUT_FILE_TYPE, DummyTranslatingCompiler.FILETYPE_EXTENSION);
+ addTranslatingCompiler(
+ new DummyTranslatingCompiler(),
+ new HashSet<FileType>(Arrays.asList(DummyTranslatingCompiler.INPUT_FILE_TYPE)),
+ new HashSet<FileType>(Arrays.asList( StdFileTypes.JAVA))
+ );
+ }
+ });
+
+ }
+ });
+ */
+ }
+
+ public Semaphore getCompilationSemaphore() {
+ return myCompilationSemaphore;
+ }
+
+ public boolean isCompilationActive() {
+ return myCompilationSemaphore.availablePermits() == 0;
+ }
+
+ public void addTranslatingCompiler(@NotNull final TranslatingCompiler compiler, final Set<FileType> inputTypes, final Set<FileType> outputTypes) {
+ myTranslators.add(compiler);
+ myCompilerToInputTypes.put(compiler, inputTypes);
+ myCompilerToOutputTypes.put(compiler, outputTypes);
+
+ final List<Chunk<Compiler>> chunks = ModuleCompilerUtil.getSortedChunks(createCompilerGraph((List<Compiler>)(List)myTranslators));
+
+ myTranslators.clear();
+ for (Chunk<Compiler> chunk : chunks) {
+ for (Compiler chunkCompiler : chunk.getNodes()) {
+ myTranslators.add((TranslatingCompiler)chunkCompiler);
+ }
+ }
+ }
+
+ @NotNull
+ public Set<FileType> getRegisteredInputTypes(@NotNull final TranslatingCompiler compiler) {
+ final Set<FileType> inputs = myCompilerToInputTypes.get(compiler);
+ return inputs != null? Collections.unmodifiableSet(inputs) : Collections.<FileType>emptySet();
+ }
+
+ @NotNull
+ public Set<FileType> getRegisteredOutputTypes(@NotNull final TranslatingCompiler compiler) {
+ final Set<FileType> outs = myCompilerToOutputTypes.get(compiler);
+ return outs != null? Collections.unmodifiableSet(outs) : Collections.<FileType>emptySet();
+ }
+
+ public final void addCompiler(@NotNull Compiler compiler) {
+ if (compiler instanceof TranslatingCompiler) {
+ myTranslators.add((TranslatingCompiler)compiler);
+ }
+ else {
+ myCompilers.add(compiler);
+ // supporting file instrumenting compilers and validators for external build
+ // Since these compilers are IDE-specific and use PSI, it is ok to run them before and after the build in the IDE
+ if (compiler instanceof SourceInstrumentingCompiler) {
+ addBeforeTask(new FileProcessingCompilerAdapterTask((FileProcessingCompiler)compiler));
+ }
+ else if (compiler instanceof Validator) {
+ addAfterTask(new FileProcessingCompilerAdapterTask((FileProcessingCompiler)compiler));
+ }
+ }
+ }
+
+ public final void removeCompiler(@NotNull Compiler compiler) {
+ if (compiler instanceof TranslatingCompiler) {
+ myTranslators.remove(compiler);
+ }
+ else {
+ if (myCompilers.remove(compiler)) {
+ for (List<CompileTask> tasks : Arrays.asList(myBeforeTasks, myAfterTasks)) {
+ for (Iterator<CompileTask> iterator = tasks.iterator(); iterator.hasNext(); ) {
+ CompileTask task = iterator.next();
+ if (task instanceof FileProcessingCompilerAdapterTask && ((FileProcessingCompilerAdapterTask)task).getCompiler() == compiler) {
+ iterator.remove();
+ }
+ }
+ }
+ }
+ }
+ myCompilerToInputTypes.remove(compiler);
+ myCompilerToOutputTypes.remove(compiler);
+ }
+
+ @NotNull
+ public <T extends Compiler> T[] getCompilers(@NotNull Class<T> compilerClass) {
+ return getCompilers(compilerClass, CompilerFilter.ALL);
+ }
+
+ @NotNull
+ public <T extends Compiler> T[] getCompilers(@NotNull Class<T> compilerClass, CompilerFilter filter) {
+ final List<T> compilers = new ArrayList<T>(myCompilers.size());
+ for (final Compiler item : myCompilers) {
+ if (compilerClass.isAssignableFrom(item.getClass()) && filter.acceptCompiler(item)) {
+ compilers.add((T)item);
+ }
+ }
+ for (final Compiler item : myTranslators) {
+ if (compilerClass.isAssignableFrom(item.getClass()) && filter.acceptCompiler(item)) {
+ compilers.add((T)item);
+ }
+ }
+ final T[] array = (T[])Array.newInstance(compilerClass, compilers.size());
+ return compilers.toArray(array);
+ }
+
+ public void addCompilableFileType(@NotNull FileType type) {
+ myCompilableTypes.add(type);
+ }
+
+ public void removeCompilableFileType(@NotNull FileType type) {
+ myCompilableTypes.remove(type);
+ }
+
+ public boolean isCompilableFileType(@NotNull FileType type) {
+ return myCompilableTypes.contains(type);
+ }
+
+ public final void addBeforeTask(@NotNull CompileTask task) {
+ myBeforeTasks.add(task);
+ }
+
+ public final void addAfterTask(@NotNull CompileTask task) {
+ myAfterTasks.add(task);
+ }
+
+ @NotNull
+ public CompileTask[] getBeforeTasks() {
+ return myBeforeTasks.toArray(new CompileTask[myBeforeTasks.size()]);
+ }
+
+ @NotNull
+ public CompileTask[] getAfterTasks() {
+ return myAfterTasks.toArray(new CompileTask[myAfterTasks.size()]);
+ }
+
+ public void compile(@NotNull VirtualFile[] files, CompileStatusNotification callback) {
+ compile(createFilesCompileScope(files), callback);
+ }
+
+ public void compile(@NotNull Module module, CompileStatusNotification callback) {
+ new CompileDriver(myProject).compile(createModuleCompileScope(module, false), new ListenerNotificator(callback), true);
+ }
+
+ public void compile(@NotNull CompileScope scope, CompileStatusNotification callback) {
+ new CompileDriver(myProject).compile(scope, new ListenerNotificator(callback), false);
+ }
+
+ public void make(CompileStatusNotification callback) {
+ new CompileDriver(myProject).make(createProjectCompileScope(myProject), new ListenerNotificator(callback));
+ }
+
+ public void make(@NotNull Module module, CompileStatusNotification callback) {
+ new CompileDriver(myProject).make(createModuleCompileScope(module, true), new ListenerNotificator(callback));
+ }
+
+ public void make(@NotNull Project project, @NotNull Module[] modules, CompileStatusNotification callback) {
+ new CompileDriver(myProject).make(createModuleGroupCompileScope(project, modules, true), new ListenerNotificator(callback));
+ }
+
+ public void make(@NotNull CompileScope scope, CompileStatusNotification callback) {
+ new CompileDriver(myProject).make(scope, new ListenerNotificator(callback));
+ }
+
+ public void make(@NotNull CompileScope scope, CompilerFilter filter, @Nullable CompileStatusNotification callback) {
+ final CompileDriver compileDriver = new CompileDriver(myProject);
+ compileDriver.setCompilerFilter(filter);
+ compileDriver.make(scope, new ListenerNotificator(callback));
+ }
+
+ public boolean isUpToDate(@NotNull final CompileScope scope) {
+ return new CompileDriver(myProject).isUpToDate(scope);
+ }
+
+ public void rebuild(CompileStatusNotification callback) {
+ new CompileDriver(myProject).rebuild(new ListenerNotificator(callback));
+ }
+
+ public void executeTask(@NotNull CompileTask task, @NotNull CompileScope scope, String contentName, Runnable onTaskFinished) {
+ final CompileDriver compileDriver = new CompileDriver(myProject);
+ compileDriver.executeCompileTask(task, scope, contentName, onTaskFinished);
+ }
+
+ private final Map<CompilationStatusListener, MessageBusConnection> myListenerAdapters = new HashMap<CompilationStatusListener, MessageBusConnection>();
+
+ public void addCompilationStatusListener(@NotNull final CompilationStatusListener listener) {
+ final MessageBusConnection connection = myProject.getMessageBus().connect();
+ myListenerAdapters.put(listener, connection);
+ connection.subscribe(CompilerTopics.COMPILATION_STATUS, listener);
+ }
+
+ @Override
+ public void addCompilationStatusListener(@NotNull CompilationStatusListener listener, @NotNull Disposable parentDisposable) {
+ final MessageBusConnection connection = myProject.getMessageBus().connect(parentDisposable);
+ connection.subscribe(CompilerTopics.COMPILATION_STATUS, listener);
+ }
+
+ public void removeCompilationStatusListener(@NotNull final CompilationStatusListener listener) {
+ final MessageBusConnection connection = myListenerAdapters.remove(listener);
+ if (connection != null) {
+ connection.disconnect();
+ }
+ }
+
+ // Compiler tests support
+
+ private static List<String> ourDeletedPaths;
+ private static List<String> ourRecompiledPaths;
+ private static List<String> ourCompiledPaths;
+
+ public static void testSetup() {
+ ourDeletedPaths = new ArrayList<String>();
+ ourRecompiledPaths = new ArrayList<String>();
+ ourCompiledPaths = new ArrayList<String>();
+ }
+
+ public static void addDeletedPath(String path) {
+ ourDeletedPaths.add(path);
+ }
+
+ public static void addRecompiledPath(String path) {
+ ourRecompiledPaths.add(path);
+ }
+
+ public static void addCompiledPath(String path) {
+ ourCompiledPaths.add(path);
+ }
+
+ public static String[] getPathsToDelete() {
+ return ArrayUtil.toStringArray(ourDeletedPaths);
+ }
+
+ public static String[] getPathsToRecompile() {
+ return ArrayUtil.toStringArray(ourRecompiledPaths);
+ }
+
+ public static String[] getPathsToCompile() {
+ return ArrayUtil.toStringArray(ourCompiledPaths);
+ }
+
+ public static void clearPathsToCompile() {
+ if (ourCompiledPaths != null) {
+ ourCompiledPaths.clear();
+ }
+ }
+
+ public boolean isExcludedFromCompilation(@NotNull VirtualFile file) {
+ return CompilerConfiguration.getInstance(myProject).isExcludedFromCompilation(file);
+ }
+
+ private static final OutputToSourceMapping OUTPUT_TO_SOURCE_MAPPING = new OutputToSourceMapping() {
+ public String getSourcePath(final String outputPath) {
+ final LocalFileSystem lfs = LocalFileSystem.getInstance();
+ final VirtualFile outputFile = lfs.findFileByPath(outputPath);
+
+ final VirtualFile sourceFile = outputFile != null ? TranslatingCompilerFilesMonitor.getSourceFileByOutput(outputFile) : null;
+ return sourceFile != null? sourceFile.getPath() : null;
+ }
+ };
+ @NotNull
+ public OutputToSourceMapping getJavaCompilerOutputMapping() {
+ return OUTPUT_TO_SOURCE_MAPPING;
+ }
+
+ @NotNull
+ public CompileScope createFilesCompileScope(@NotNull final VirtualFile[] files) {
+ CompileScope[] scopes = new CompileScope[files.length];
+ for(int i = 0; i < files.length; i++){
+ scopes[i] = new OneProjectItemCompileScope(myProject, files[i]);
+ }
+ return new CompositeScope(scopes);
+ }
+
+ @NotNull
+ public CompileScope createModuleCompileScope(@NotNull final Module module, final boolean includeDependentModules) {
+ return new ModuleCompileScope(module, includeDependentModules);
+ }
+
+ @NotNull
+ public CompileScope createModulesCompileScope(@NotNull final Module[] modules, final boolean includeDependentModules) {
+ return new ModuleCompileScope(myProject, modules, includeDependentModules);
+ }
+
+ @NotNull
+ public CompileScope createModuleGroupCompileScope(@NotNull final Project project, @NotNull final Module[] modules, final boolean includeDependentModules) {
+ return new ModuleCompileScope(project, modules, includeDependentModules);
+ }
+
+ @NotNull
+ public CompileScope createProjectCompileScope(@NotNull final Project project) {
+ return new ProjectCompileScope(project);
+ }
+
+ @Override
+ public void setValidationEnabled(ModuleType moduleType, boolean enabled) {
+ if (enabled) {
+ myValidationDisabledModuleTypes.remove(moduleType);
+ }
+ else {
+ myValidationDisabledModuleTypes.add(moduleType);
+ }
+ }
+
+ @Override
+ public boolean isValidationEnabled(Module module) {
+ if (myValidationDisabledModuleTypes.isEmpty()) {
+ return true; // optimization
+ }
+ return !myValidationDisabledModuleTypes.contains(ModuleType.get(module));
+ }
+
+ private Graph<Compiler> createCompilerGraph(final List<Compiler> compilers) {
+ return GraphGenerator.create(CachingSemiGraph.create(new GraphGenerator.SemiGraph<Compiler>() {
+ public Collection<Compiler> getNodes() {
+ return compilers;
+ }
+
+ public Iterator<Compiler> getIn(Compiler compiler) {
+ final Set<FileType> compilerInput = myCompilerToInputTypes.get(compiler);
+ if (compilerInput == null || compilerInput.isEmpty()) {
+ return Collections.<Compiler>emptySet().iterator();
+ }
+
+ final Set<Compiler> inCompilers = new HashSet<Compiler>();
+
+ for (Map.Entry<Compiler, Set<FileType>> entry : myCompilerToOutputTypes.entrySet()) {
+ final Set<FileType> outputs = entry.getValue();
+ Compiler comp = entry.getKey();
+ if (outputs != null && ContainerUtil.intersects(compilerInput, outputs)) {
+ inCompilers.add(comp);
+ }
+ }
+ return inCompilers.iterator();
+ }
+ }));
+ }
+
+ private class ListenerNotificator implements CompileStatusNotification {
+ private final @Nullable CompileStatusNotification myDelegate;
+
+ private ListenerNotificator(@Nullable CompileStatusNotification delegate) {
+ myDelegate = delegate;
+ }
+
+ public void finished(boolean aborted, int errors, int warnings, final CompileContext compileContext) {
+ myEventPublisher.compilationFinished(aborted, errors, warnings, compileContext);
+ if (myDelegate != null) {
+ myDelegate.finished(aborted, errors, warnings, compileContext);
+ }
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/CompilerMessageImpl.java b/java/compiler/impl/src/com/intellij/compiler/CompilerMessageImpl.java
new file mode 100644
index 0000000..bb15576
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/CompilerMessageImpl.java
@@ -0,0 +1,133 @@
+/*
+ * 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 com.intellij.compiler;
+
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.compiler.CompilerMessage;
+import com.intellij.openapi.compiler.CompilerMessageCategory;
+import com.intellij.openapi.fileEditor.OpenFileDescriptor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.pom.Navigatable;
+import org.jetbrains.annotations.Nullable;
+
+public final class CompilerMessageImpl implements CompilerMessage {
+
+ private final Project myProject;
+ private final CompilerMessageCategory myCategory;
+ @Nullable private Navigatable myNavigatable;
+ private final String myMessage;
+ private VirtualFile myFile;
+ private final int myRow;
+ private final int myColumn;
+
+ public CompilerMessageImpl(Project project, CompilerMessageCategory category, String message) {
+ this(project, category, message, null, -1, -1, null);
+ }
+
+ public CompilerMessageImpl(Project project,
+ CompilerMessageCategory category,
+ String message,
+ @Nullable final VirtualFile file,
+ int row,
+ int column,
+ @Nullable final Navigatable navigatable) {
+ myProject = project;
+ myCategory = category;
+ myNavigatable = navigatable;
+ myMessage = message == null ? "" : message;
+ myRow = row;
+ myColumn = column;
+ myFile = file;
+ }
+
+ public CompilerMessageCategory getCategory() {
+ return myCategory;
+ }
+
+ public String getMessage() {
+ return myMessage;
+ }
+
+ public Navigatable getNavigatable() {
+ if (myNavigatable != null) {
+ return myNavigatable;
+ }
+ final VirtualFile virtualFile = getVirtualFile();
+ if (virtualFile != null && virtualFile.isValid()) {
+ final int line = getLine() - 1; // editor lines are zero-based
+ if (line >= 0) {
+ return myNavigatable = new OpenFileDescriptor(myProject, virtualFile, line, Math.max(0, getColumn()-1));
+ }
+ }
+ return null;
+ }
+
+ public VirtualFile getVirtualFile() {
+ return myFile;
+ }
+
+ public String getExportTextPrefix() {
+ if (getLine() >= 0) {
+ return CompilerBundle.message("compiler.results.export.text.prefix", getLine());
+ }
+ return "";
+ }
+
+ public String getRenderTextPrefix() {
+ if (getLine() >= 0) {
+ return "(" + getLine() + ", " + getColumn() + ")";
+ }
+ return "";
+ }
+
+ public int getLine() {
+ return myRow;
+ }
+
+ public int getColumn() {
+ return myColumn;
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof CompilerMessage)) return false;
+
+ final CompilerMessageImpl compilerMessage = (CompilerMessageImpl)o;
+
+ if (myColumn != compilerMessage.myColumn) return false;
+ if (myRow != compilerMessage.myRow) return false;
+ if (!myCategory.equals(compilerMessage.myCategory)) return false;
+ if (myFile != null ? !myFile.equals(compilerMessage.myFile) : compilerMessage.myFile != null) return false;
+ if (!myMessage.equals(compilerMessage.myMessage)) return false;
+
+ return true;
+ }
+
+ public int hashCode() {
+ int result;
+ result = myCategory.hashCode();
+ result = 29 * result + myMessage.hashCode();
+ result = 29 * result + (myFile != null ? myFile.hashCode() : 0);
+ result = 29 * result + myRow;
+ result = 29 * result + myColumn;
+ return result;
+ }
+
+ public String toString() {
+ return myMessage;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/DependencyProcessor.java b/java/compiler/impl/src/com/intellij/compiler/DependencyProcessor.java
new file mode 100644
index 0000000..3907eb8
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/DependencyProcessor.java
@@ -0,0 +1,31 @@
+/*
+ * 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 com.intellij.compiler;
+
+import com.intellij.compiler.make.CacheCorruptedException;
+import com.intellij.compiler.make.CachingSearcher;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.extensions.ExtensionPointName;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Aug 19, 2008
+ */
+public interface DependencyProcessor {
+ ExtensionPointName<DependencyProcessor> EXTENSION_POINT_NAME = ExtensionPointName.create("com.intellij.compiler.makeDependencyProcessor");
+
+ void processDependencies(CompileContext context, int classQualifiedName, CachingSearcher searcher) throws CacheCorruptedException;
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/HelpID.java b/java/compiler/impl/src/com/intellij/compiler/HelpID.java
new file mode 100644
index 0000000..9b34a51
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/HelpID.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2000-2011 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 com.intellij.compiler;
+
+import org.jetbrains.annotations.NonNls;
+
+public interface HelpID {
+ @NonNls String COMPILER = "reference.toolWindows.messages";
+ @NonNls String GENERATE_ANT_BUILD = "editing.generateAntBuild";
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/OutputParser.java b/java/compiler/impl/src/com/intellij/compiler/OutputParser.java
new file mode 100644
index 0000000..37f6882
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/OutputParser.java
@@ -0,0 +1,72 @@
+/*
+ * 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 com.intellij.compiler;
+
+import com.intellij.compiler.impl.javaCompiler.FileObject;
+import com.intellij.openapi.compiler.CompilerMessageCategory;
+import com.intellij.openapi.util.text.StringUtil;
+import org.jetbrains.annotations.NonNls;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class OutputParser {
+ protected final List<ParserAction> myParserActions = new ArrayList<ParserAction>(10);
+
+ public interface Callback {
+ @NonNls String getNextLine();
+ @NonNls String getCurrentLine();
+ void pushBack(String line);
+ void setProgressText(String text);
+ void fileProcessed(@NonNls String path);
+ void fileGenerated(@NonNls FileObject path);
+ void message(CompilerMessageCategory category, String message, @NonNls String url, int lineNum, int columnNum);
+ }
+
+ public boolean processMessageLine(Callback callback) {
+ final String line = callback.getNextLine();
+ if(line == null) {
+ return false;
+ }
+ // common statistics messages (javac & jikes)
+ for (ParserAction action : myParserActions) {
+ if (action.execute(line, callback)) {
+ return true;
+ }
+ }
+ if (StringUtil.startsWithChar(line, '[') && StringUtil.endsWithChar(line, ']')) {
+ // at this point any meaningful output surrounded with '[' and ']' characters is processed, so
+ // suppress messages like "[total 4657ms]" or "[search path for source files: []]"
+ return true;
+ }
+ return false;
+ }
+
+ protected static void addMessage(Callback callback, CompilerMessageCategory type, String message) {
+ if(message == null || message.trim().length() == 0) {
+ return;
+ }
+ addMessage(callback, type, message, null, -1, -1);
+ }
+
+ protected static void addMessage(Callback callback, CompilerMessageCategory type, String text, String url, int line, int column){
+ callback.message(type, text, url, line, column);
+ }
+
+ public boolean isTrimLines() {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/ParserAction.java b/java/compiler/impl/src/com/intellij/compiler/ParserAction.java
new file mode 100644
index 0000000..d9b0c29
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ParserAction.java
@@ -0,0 +1,26 @@
+/*
+ * 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 com.intellij.compiler;
+
+import org.jetbrains.annotations.NonNls;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Sep 24, 2005
+ */
+public abstract class ParserAction {
+ public abstract boolean execute(@NonNls String line, final OutputParser.Callback callback);
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ProblemsView.java b/java/compiler/impl/src/com/intellij/compiler/ProblemsView.java
new file mode 100644
index 0000000..3948ac7
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ProblemsView.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler;
+
+import com.intellij.compiler.progress.CompilerTask;
+import com.intellij.openapi.compiler.CompileScope;
+import com.intellij.openapi.compiler.CompilerMessage;
+import com.intellij.openapi.compiler.CompilerMessageCategory;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.fileEditor.OpenFileDescriptor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.pom.Navigatable;
+import com.intellij.util.ArrayUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.UUID;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 9/18/12
+ */
+public abstract class ProblemsView {
+
+ private final Project myProject;
+
+ public static class SERVICE {
+ private SERVICE() {
+ }
+
+ public static ProblemsView getInstance(Project project) {
+ return ServiceManager.getService(project, ProblemsView.class);
+ }
+ }
+
+ protected ProblemsView(Project project) {
+ myProject = project;
+ }
+
+ public abstract void clearOldMessages(CompileScope scope, UUID currentSessionId);
+
+ public abstract void addMessage(int type, @NotNull String[] text, @Nullable String groupName, @Nullable Navigatable navigatable, @Nullable String exportTextPrefix, @Nullable String rendererTextPrefix, @NotNull UUID sessionId);
+
+ public final void addMessage(CompilerMessage message, @NotNull UUID sessionId) {
+ final VirtualFile file = message.getVirtualFile();
+ Navigatable navigatable = message.getNavigatable();
+ if (navigatable == null && file != null) {
+ navigatable = new OpenFileDescriptor(myProject, file, -1, -1);
+ }
+ final CompilerMessageCategory category = message.getCategory();
+ final int type = CompilerTask.translateCategory(category);
+ final String[] text = convertMessage(message);
+ final String groupName = file != null? file.getPresentableUrl() : category.getPresentableText();
+ addMessage(type, text, groupName, navigatable, message.getExportTextPrefix(), message.getRenderTextPrefix(), sessionId);
+ }
+
+ public abstract void setProgress(String text, float fraction);
+
+ public abstract void setProgress(String text);
+
+ public abstract void clearProgress();
+
+ private static String[] convertMessage(final CompilerMessage message) {
+ String text = message.getMessage();
+ if (!text.contains("\n")) {
+ return new String[]{text};
+ }
+ final List<String> lines = new ArrayList<String>();
+ StringTokenizer tokenizer = new StringTokenizer(text, "\n", false);
+ while (tokenizer.hasMoreTokens()) {
+ lines.add(tokenizer.nextToken());
+ }
+ return ArrayUtil.toStringArray(lines);
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/PsiClassWriter.java b/java/compiler/impl/src/com/intellij/compiler/PsiClassWriter.java
new file mode 100644
index 0000000..61d1e05
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/PsiClassWriter.java
@@ -0,0 +1,88 @@
+/*
+ * 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 com.intellij.compiler;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.JavaSdk;
+import com.intellij.openapi.projectRoots.JavaSdkVersion;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.roots.ModuleRootManager;
+import com.intellij.openapi.util.Computable;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiManager;
+import com.intellij.psi.util.ClassUtil;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.asm4.ClassWriter;
+
+/**
+ * @author yole
+ */
+public class PsiClassWriter extends ClassWriter {
+ private final Project myProject;
+
+ public PsiClassWriter(final Project project, boolean isJava6) {
+ super(isJava6 ? ClassWriter.COMPUTE_FRAMES : ClassWriter.COMPUTE_MAXS);
+ myProject = project;
+ }
+
+ public PsiClassWriter(final Module module) {
+ this(module.getProject(), isJdk6(module));
+ }
+
+ private static boolean isJdk6(final Module module) {
+ final Sdk projectJdk = ModuleRootManager.getInstance(module).getSdk();
+ if (projectJdk == null) return false;
+ return JavaSdk.getInstance().isOfVersionOrHigher(projectJdk, JavaSdkVersion.JDK_1_6);
+ }
+
+ protected String getCommonSuperClass(final String type1, final String type2) {
+ //PsiManager.getInstance(myProject).findClass(type1.replace('/', '.').replace('$', '.'), myProject.getAllScope());
+ return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
+ @NonNls
+ public String compute() {
+ final PsiManager manager = PsiManager.getInstance(myProject);
+ PsiClass c = ClassUtil.findPsiClassByJVMName(manager, type1);
+ if (c == null) {
+ return "java/lang/Object";
+ }
+ PsiClass d = ClassUtil.findPsiClassByJVMName(manager, type2);
+ if (d == null) {
+ return "java/lang/Object";
+ }
+ if (c.isInheritor(d, true)) {
+ return ClassUtil.getJVMClassName(d).replace('.', '/');
+ }
+ if (d.isInheritor(c, true)) {
+ return ClassUtil.getJVMClassName(c).replace('.', '/');
+ }
+ if (c.isInterface() || d.isInterface()) {
+ return "java/lang/Object";
+ }
+ do {
+ c = c.getSuperClass();
+ }
+ while (c != null && !d.isInheritor(c, true));
+ if (c == null) {
+ return "java/lang/Object";
+ }
+ return ClassUtil.getJVMClassName(c).replace('.', '/');
+ }
+ });
+ }
+
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/SymbolTable.java b/java/compiler/impl/src/com/intellij/compiler/SymbolTable.java
new file mode 100644
index 0000000..48603f5
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/SymbolTable.java
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+/*
+ * @author: Eugene Zhuravlev
+ * Date: Mar 3, 2003
+ * Time: 12:34:44 PM
+ */
+package com.intellij.compiler;
+
+import com.intellij.compiler.make.CacheCorruptedException;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.util.containers.SLRUCache;
+import com.intellij.util.io.PersistentEnumerator;
+import com.intellij.util.io.PersistentStringEnumerator;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+
+public class SymbolTable {
+ private final PersistentStringEnumerator myTrie;
+
+ // both caches should have equal size
+ private static final int STRING_CACHE_SIZE = 1024;
+
+ private final SLRUCache<Integer, String> myIndexStringCache = new SLRUCache<Integer, String>(STRING_CACHE_SIZE * 2, STRING_CACHE_SIZE) {
+ @NotNull
+ public String createValue(Integer key) {
+ try {
+ return myTrie.valueOf(key.intValue());
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ };
+
+ private final SLRUCache<String, Integer> myStringIndexCache = new SLRUCache<String, Integer>(STRING_CACHE_SIZE * 2, STRING_CACHE_SIZE) {
+ @NotNull
+ public Integer createValue(String key) {
+ try {
+ return myTrie.enumerate(key);
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ };
+
+
+ public SymbolTable(File file) throws CacheCorruptedException {
+ try {
+ if (!file.exists()) {
+ FileUtil.createIfDoesntExist(file);
+ }
+ myTrie = new PersistentStringEnumerator(file);
+ }
+ catch (PersistentEnumerator.CorruptedException e) {
+ throw new CacheCorruptedException(CompilerBundle.message("error.compiler.caches.corrupted"), e);
+ }
+ catch (IOException e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public synchronized int getId(@NotNull String symbol) throws CacheCorruptedException {
+ if (symbol.length() == 0) {
+ return -1;
+ }
+ try {
+ return myStringIndexCache.get(symbol);
+ }
+ catch (RuntimeException e) {
+ if (e.getCause() instanceof IOException) {
+ throw new CacheCorruptedException(e.getCause());
+ }
+ throw e;
+ }
+ }
+
+ public synchronized String getSymbol(int id) throws CacheCorruptedException {
+ if (id == -1) {
+ return "";
+ }
+ try {
+ return myIndexStringCache.get(id);
+ }
+ catch (RuntimeException e) {
+ if (e.getCause() instanceof IOException) {
+ throw new CacheCorruptedException(e.getCause());
+ }
+ throw e;
+ }
+ }
+
+ public synchronized void dispose() throws CacheCorruptedException {
+ try {
+ myIndexStringCache.clear();
+ myStringIndexCache.clear();
+ myTrie.close(); // will call "flush()" if needed
+ }
+ catch (IOException e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/actions/AntBuildForm.form b/java/compiler/impl/src/com/intellij/compiler/actions/AntBuildForm.form
new file mode 100644
index 0000000..da94634
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/actions/AntBuildForm.form
@@ -0,0 +1,113 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.compiler.actions.GenerateAntBuildDialog">
+ <grid id="3d49c" binding="myPanel" layout-manager="GridLayoutManager" row-count="6" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <xy x="52" y="58" width="727" height="229"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <grid id="770dc" layout-manager="GridLayoutManager" row-count="3" column-count="4" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="7d65" class="javax.swing.JRadioButton" binding="myRbGenerateMultipleFilesBuild">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="3" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="label.generate.ant.script.generate.multiple.files"/>
+ </properties>
+ </component>
+ <component id="f1821" class="javax.swing.JRadioButton" binding="myRbGenerateSingleFileBuild">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="3" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="label.generate.ant.script.single.file"/>
+ </properties>
+ </component>
+ <component id="81381" class="javax.swing.JRadioButton" binding="myRbBackupFiles">
+ <constraints>
+ <grid row="0" column="3" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="label.generate.ant.script.backup.files"/>
+ </properties>
+ </component>
+ <component id="a019e" class="javax.swing.JRadioButton" binding="myRbOverwriteFiles">
+ <constraints>
+ <grid row="1" column="3" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="label.generate.ant.script.overwrite.files"/>
+ </properties>
+ </component>
+ <component id="cd13a" class="javax.swing.JTextField" binding="myOutputFileNameField">
+ <constraints>
+ <grid row="2" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
+ <preferred-size width="150" height="-1"/>
+ </grid>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="3a774" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="2" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="label.generate.ant.script.filename"/>
+ </properties>
+ </component>
+ </children>
+ </grid>
+ <component id="ec5e9" class="javax.swing.JCheckBox" binding="myCbEnableUIFormsCompilation">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="9" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="label.generate.ant.script.enable.ui.forms.compilation"/>
+ </properties>
+ </component>
+ <xy id="6f3e8" binding="myChunksPanel" layout-manager="XYLayout" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="5" column="0" row-span="1" col-span="1" vsize-policy="7" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children/>
+ </xy>
+ <component id="c9ba" class="javax.swing.JCheckBox" binding="myCbForceTargetJdk">
+ <constraints>
+ <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="label.generate.ant.script.use.jdk.definitions"/>
+ </properties>
+ </component>
+ <component id="ca33b" class="javax.swing.JCheckBox" binding="myCbInlineRuntimeClasspath">
+ <constraints>
+ <grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="label.generate.ant.script.inline.runtime.classpaths"/>
+ </properties>
+ </component>
+ <component id="c612c" class="javax.swing.JCheckBox" binding="myGenerateIdeaHomeProperty" default-binding="true">
+ <constraints>
+ <grid row="4" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <selected value="false"/>
+ <text resource-bundle="messages/CompilerBundle" key="label.generate.ant.script.generate.idea.home"/>
+ </properties>
+ </component>
+ </children>
+ </grid>
+</form>
diff --git a/java/compiler/impl/src/com/intellij/compiler/actions/ArtifactAwareProjectSettingsService.java b/java/compiler/impl/src/com/intellij/compiler/actions/ArtifactAwareProjectSettingsService.java
new file mode 100644
index 0000000..f84bc59
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/actions/ArtifactAwareProjectSettingsService.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2000-2011 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 com.intellij.compiler.actions;
+
+import com.intellij.packaging.artifacts.Artifact;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author nik
+ */
+public interface ArtifactAwareProjectSettingsService {
+ void openArtifactSettings(@Nullable Artifact artifact);
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/actions/BuildArtifactAction.java b/java/compiler/impl/src/com/intellij/compiler/actions/BuildArtifactAction.java
new file mode 100644
index 0000000..6f660d1
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/actions/BuildArtifactAction.java
@@ -0,0 +1,369 @@
+/*
+ * 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 com.intellij.compiler.actions;
+
+import com.intellij.notification.NotificationGroup;
+import com.intellij.notification.NotificationType;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.CommonShortcuts;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.compiler.CompileScope;
+import com.intellij.openapi.compiler.CompilerManager;
+import com.intellij.openapi.keymap.KeymapUtil;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.Task;
+import com.intellij.openapi.project.DumbAwareAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.openapi.ui.popup.ListSeparator;
+import com.intellij.openapi.ui.popup.MultiSelectionListPopupStep;
+import com.intellij.openapi.ui.popup.PopupStep;
+import com.intellij.openapi.ui.popup.util.BaseListPopupStep;
+import com.intellij.openapi.util.Comparing;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.impl.artifacts.ArtifactUtil;
+import com.intellij.packaging.impl.compiler.ArtifactCompileScope;
+import com.intellij.packaging.impl.compiler.ArtifactsWorkspaceSettings;
+import com.intellij.ui.popup.list.ListPopupImpl;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.ui.EmptyIcon;
+import gnu.trove.TIntArrayList;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.awt.event.ActionEvent;
+import java.io.File;
+import java.util.*;
+
+/**
+ * @author nik
+ */
+public class BuildArtifactAction extends DumbAwareAction {
+ private static final NotificationGroup NOTIFICATION_GROUP = NotificationGroup.balloonGroup("Clean artifact");
+
+ public BuildArtifactAction() {
+ super("Build Artifacts...", "Select and build artifacts configured in the project", null);
+ }
+ @Override
+ public void update(AnActionEvent e) {
+ final Project project = getEventProject(e);
+ final Presentation presentation = e.getPresentation();
+ presentation.setEnabled(project != null && !ArtifactUtil.getArtifactWithOutputPaths(project).isEmpty());
+ }
+
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ final Project project = getEventProject(e);
+ if (project == null) return;
+
+ final List<Artifact> artifacts = ArtifactUtil.getArtifactWithOutputPaths(project);
+ if (artifacts.isEmpty()) return;
+
+ List<ArtifactPopupItem> items = new ArrayList<ArtifactPopupItem>();
+ if (artifacts.size() > 1) {
+ items.add(0, new ArtifactPopupItem(null, "All Artifacts", EmptyIcon.ICON_16));
+ }
+ Set<Artifact> selectedArtifacts = new HashSet<Artifact>(ArtifactsWorkspaceSettings.getInstance(project).getArtifactsToBuild());
+ TIntArrayList selectedIndices = new TIntArrayList();
+ if (Comparing.haveEqualElements(artifacts, selectedArtifacts) && selectedArtifacts.size() > 1) {
+ selectedIndices.add(0);
+ selectedArtifacts.clear();
+ }
+
+ for (Artifact artifact : artifacts) {
+ final ArtifactPopupItem item = new ArtifactPopupItem(artifact, artifact.getName(), artifact.getArtifactType().getIcon());
+ if (selectedArtifacts.contains(artifact)) {
+ selectedIndices.add(items.size());
+ }
+ items.add(item);
+ }
+
+ final ProjectSettingsService projectSettingsService = ProjectSettingsService.getInstance(project);
+ final ArtifactAwareProjectSettingsService settingsService = projectSettingsService instanceof ArtifactAwareProjectSettingsService ? (ArtifactAwareProjectSettingsService)projectSettingsService : null;
+
+ final ChooseArtifactStep step = new ChooseArtifactStep(items, artifacts.get(0), project, settingsService);
+ step.setDefaultOptionIndices(selectedIndices.toNativeArray());
+
+ final ListPopupImpl popup = (ListPopupImpl)JBPopupFactory.getInstance().createListPopup(step);
+ final KeyStroke editKeyStroke = KeymapUtil.getKeyStroke(CommonShortcuts.getEditSource());
+ if (settingsService != null && editKeyStroke != null) {
+ popup.registerAction("editArtifact", editKeyStroke, new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ Object[] values = popup.getSelectedValues();
+ popup.cancel();
+ settingsService.openArtifactSettings(values.length > 0 ? ((ArtifactPopupItem)values[0]).getArtifact() : null);
+ }
+ });
+ }
+ popup.showCenteredInCurrentWindow(project);
+ }
+
+ protected static void doBuild(@NotNull Project project, final @NotNull List<ArtifactPopupItem> items, boolean rebuild) {
+ final Set<Artifact> artifacts = getArtifacts(items, project);
+ final CompileScope scope = ArtifactCompileScope.createArtifactsScope(project, artifacts);
+
+ ArtifactsWorkspaceSettings.getInstance(project).setArtifactsToBuild(artifacts);
+ if (!rebuild) {
+ CompilerManager.getInstance(project).make(scope, null);
+ }
+ else {
+ CompilerManager.getInstance(project).compile(scope, null);
+ }
+ }
+
+ private static Set<Artifact> getArtifacts(final List<ArtifactPopupItem> items, final Project project) {
+ Set<Artifact> artifacts = new LinkedHashSet<Artifact>();
+ for (ArtifactPopupItem item : items) {
+ artifacts.addAll(item.getArtifacts(project));
+ }
+ return artifacts;
+ }
+
+ private static class BuildArtifactItem extends ArtifactActionItem {
+ private BuildArtifactItem(List<ArtifactPopupItem> item, Project project) {
+ super(item, project, "Build");
+ }
+
+ @Override
+ public void run() {
+ doBuild(myProject, myArtifactPopupItems, false);
+ }
+ }
+
+ private static class CleanArtifactItem extends ArtifactActionItem {
+ private CleanArtifactItem(@NotNull List<ArtifactPopupItem> item, @NotNull Project project) {
+ super(item, project, "Clean");
+ }
+
+ @Override
+ public void run() {
+ Set<VirtualFile> parents = new HashSet<VirtualFile>();
+ final VirtualFile[] roots = ProjectRootManager.getInstance(myProject).getContentSourceRoots();
+ for (VirtualFile root : roots) {
+ VirtualFile parent = root;
+ while (parent != null && !parents.contains(parent)) {
+ parents.add(parent);
+ parent = parent.getParent();
+ }
+ }
+
+ Map<String, String> outputPathContainingSourceRoots = new HashMap<String, String>();
+ final List<Pair<File, Artifact>> toClean = new ArrayList<Pair<File, Artifact>>();
+ Set<Artifact> artifacts = getArtifacts(myArtifactPopupItems, myProject);
+ for (Artifact artifact : artifacts) {
+ String outputPath = artifact.getOutputFilePath();
+ if (outputPath != null) {
+ toClean.add(Pair.create(new File(FileUtil.toSystemDependentName(outputPath)), artifact));
+ final VirtualFile outputFile = LocalFileSystem.getInstance().findFileByPath(outputPath);
+ if (parents.contains(outputFile)) {
+ outputPathContainingSourceRoots.put(artifact.getName(), outputPath);
+ }
+ }
+ }
+
+ if (!outputPathContainingSourceRoots.isEmpty()) {
+ final String message;
+ if (outputPathContainingSourceRoots.size() == 1 && outputPathContainingSourceRoots.values().size() == 1) {
+ final String name = ContainerUtil.getFirstItem(outputPathContainingSourceRoots.keySet());
+ final String output = outputPathContainingSourceRoots.get(name);
+ message = "The output directory '" + output + "' of '" + name + "' artifact contains source roots of the project. Do you want to continue and clear it?";
+ }
+ else {
+ StringBuilder info = new StringBuilder();
+ for (String name : outputPathContainingSourceRoots.keySet()) {
+ info.append(" '").append(name).append("' artifact ('").append(outputPathContainingSourceRoots.get(name)).append("')\n");
+ }
+ message = "The output directories of the following artifacts contains source roots:\n" +
+ info + "Do you want to continue and clear these directories?";
+ }
+ final int answer = Messages.showYesNoDialog(myProject, message, "Clean Artifacts", null);
+ if (answer != 0) {
+ return;
+ }
+ }
+
+ new Task.Backgroundable(myProject, "Cleaning artifacts...", true) {
+ @Override
+ public void run(@NotNull ProgressIndicator indicator) {
+ List<File> deleted = new ArrayList<File>();
+ for (Pair<File, Artifact> pair : toClean) {
+ indicator.checkCanceled();
+ File file = pair.getFirst();
+ if (!FileUtil.delete(file)) {
+ NOTIFICATION_GROUP.createNotification("Cannot clean '" + pair.getSecond().getName() + "' artifact", "cannot delete '" + file.getAbsolutePath() + "'", NotificationType.ERROR, null).notify(myProject);
+ }
+ else {
+ deleted.add(file);
+ }
+ }
+ LocalFileSystem.getInstance().refreshIoFiles(deleted, true, true, null);
+ }
+ }.queue();
+ }
+ }
+
+ private static class RebuildArtifactItem extends ArtifactActionItem {
+ private RebuildArtifactItem(List<ArtifactPopupItem> item, Project project) {
+ super(item, project, "Rebuild");
+ }
+
+ @Override
+ public void run() {
+ doBuild(myProject, myArtifactPopupItems, true);
+ }
+ }
+
+ private static class EditArtifactItem extends ArtifactActionItem {
+ private final ArtifactAwareProjectSettingsService mySettingsService;
+
+ private EditArtifactItem(List<ArtifactPopupItem> item, Project project, final ArtifactAwareProjectSettingsService projectSettingsService) {
+ super(item, project, "Edit...");
+ mySettingsService = projectSettingsService;
+ }
+
+ @Override
+ public void run() {
+ mySettingsService.openArtifactSettings(myArtifactPopupItems.get(0).getArtifact());
+ }
+ }
+
+ private static abstract class ArtifactActionItem implements Runnable {
+ protected final List<ArtifactPopupItem> myArtifactPopupItems;
+ protected final Project myProject;
+ private String myActionName;
+
+ protected ArtifactActionItem(@NotNull List<ArtifactPopupItem> item, @NotNull Project project, @NotNull String name) {
+ myArtifactPopupItems = item;
+ myProject = project;
+ myActionName = name;
+ }
+
+ public String getActionName() {
+ return myActionName;
+ }
+ }
+
+ private static class ArtifactPopupItem {
+ @Nullable private final Artifact myArtifact;
+ private final String myText;
+ private final Icon myIcon;
+
+ private ArtifactPopupItem(@Nullable Artifact artifact, String text, Icon icon) {
+ myArtifact = artifact;
+ myText = text;
+ myIcon = icon;
+ }
+
+ @Nullable
+ public Artifact getArtifact() {
+ return myArtifact;
+ }
+
+ public String getText() {
+ return myText;
+ }
+
+ public Icon getIcon() {
+ return myIcon;
+ }
+
+ public List<Artifact> getArtifacts(Project project) {
+ final Artifact artifact = getArtifact();
+ return artifact != null ? Collections.singletonList(artifact) : ArtifactUtil.getArtifactWithOutputPaths(project);
+ }
+ }
+
+ private static class ChooseArtifactStep extends MultiSelectionListPopupStep<ArtifactPopupItem> {
+ private final Artifact myFirst;
+ private final Project myProject;
+ private ArtifactAwareProjectSettingsService mySettingsService;
+
+ public ChooseArtifactStep(List<ArtifactPopupItem> artifacts,
+ Artifact first,
+ Project project, final ArtifactAwareProjectSettingsService settingsService) {
+ super("Build Artifact", artifacts);
+ myFirst = first;
+ myProject = project;
+ mySettingsService = settingsService;
+ }
+
+ @Override
+ public boolean isSpeedSearchEnabled() {
+ return true;
+ }
+
+ @Override
+ public Icon getIconFor(ArtifactPopupItem aValue) {
+ return aValue.getIcon();
+ }
+
+ @NotNull
+ @Override
+ public String getTextFor(ArtifactPopupItem value) {
+ return value.getText();
+ }
+
+ @Override
+ public boolean hasSubstep(List<ArtifactPopupItem> selectedValues) {
+ return true;
+ }
+
+ @Override
+ public ListSeparator getSeparatorAbove(ArtifactPopupItem value) {
+ return myFirst.equals(value.getArtifact()) ? new ListSeparator() : null;
+ }
+
+ @Override
+ public PopupStep<?> onChosen(final List<ArtifactPopupItem> selectedValues, boolean finalChoice) {
+ if (finalChoice) {
+ return doFinalStep(new Runnable() {
+ @Override
+ public void run() {
+ doBuild(myProject, selectedValues, false);
+ }
+ });
+ }
+ final List<ArtifactActionItem> actions = new ArrayList<ArtifactActionItem>();
+ actions.add(new BuildArtifactItem(selectedValues, myProject));
+ actions.add(new RebuildArtifactItem(selectedValues, myProject));
+ actions.add(new CleanArtifactItem(selectedValues, myProject));
+ if (mySettingsService != null) {
+ actions.add(new EditArtifactItem(selectedValues, myProject, mySettingsService));
+ }
+ return new BaseListPopupStep<ArtifactActionItem>(selectedValues.size() == 1 ? "Action" : "Action for " + selectedValues.size() + " artifacts", actions) {
+ @NotNull
+ @Override
+ public String getTextFor(ArtifactActionItem value) {
+ return value.getActionName();
+ }
+
+ @Override
+ public PopupStep onChosen(ArtifactActionItem selectedValue, boolean finalChoice) {
+ return doFinalStep(selectedValue);
+ }
+ };
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/actions/CompileAction.java b/java/compiler/impl/src/com/intellij/compiler/actions/CompileAction.java
new file mode 100644
index 0000000..2fe4bcd
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/actions/CompileAction.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.actions;
+
+import com.intellij.compiler.CompilerConfiguration;
+import com.intellij.idea.ActionsBundle;
+import com.intellij.openapi.actionSystem.*;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.compiler.CompilerManager;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.FileTypeManager;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ProjectFileIndex;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.impl.artifacts.ArtifactBySourceFileFinder;
+import com.intellij.psi.*;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class CompileAction extends CompileActionBase {
+ protected void doAction(DataContext dataContext, Project project) {
+ final Module module = LangDataKeys.MODULE_CONTEXT.getData(dataContext);
+ if (module != null) {
+ CompilerManager.getInstance(project).compile(module, null);
+ }
+ else {
+ VirtualFile[] files = getCompilableFiles(project, PlatformDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext));
+ if (files.length > 0) {
+ CompilerManager.getInstance(project).compile(files, null);
+ }
+ }
+
+ }
+
+ public void update(AnActionEvent event) {
+ super.update(event);
+ Presentation presentation = event.getPresentation();
+ if (!presentation.isEnabled()) {
+ return;
+ }
+ DataContext dataContext = event.getDataContext();
+
+ presentation.setText(ActionsBundle.actionText(IdeActions.ACTION_COMPILE));
+ presentation.setVisible(true);
+
+ Project project = PlatformDataKeys.PROJECT.getData(dataContext);
+ if (project == null) {
+ presentation.setEnabled(false);
+ return;
+ }
+
+ CompilerConfiguration compilerConfiguration = CompilerConfiguration.getInstance(project);
+ final Module module = LangDataKeys.MODULE_CONTEXT.getData(dataContext);
+
+ final VirtualFile[] files = getCompilableFiles(project, PlatformDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext));
+ if (module == null && files.length == 0) {
+ presentation.setEnabled(false);
+ presentation.setVisible(!ActionPlaces.isPopupPlace(event.getPlace()));
+ return;
+ }
+
+ String elementDescription = null;
+ if (module != null) {
+ elementDescription = CompilerBundle.message("action.compile.description.module", module.getName());
+ }
+ else {
+ PsiPackage aPackage = null;
+ if (files.length == 1) {
+ final PsiDirectory directory = PsiManager.getInstance(project).findDirectory(files[0]);
+ if (directory != null) {
+ aPackage = JavaDirectoryService.getInstance().getPackage(directory);
+ }
+ }
+ else {
+ PsiElement element = LangDataKeys.PSI_ELEMENT.getData(dataContext);
+ if (element instanceof PsiPackage) {
+ aPackage = (PsiPackage)element;
+ }
+ }
+
+ if (aPackage != null) {
+ String name = aPackage.getQualifiedName();
+ if (name.length() == 0) {
+ //noinspection HardCodedStringLiteral
+ name = "<default>";
+ }
+ elementDescription = "'" + name + "'";
+ }
+ else if (files.length == 1) {
+ final VirtualFile file = files[0];
+ FileType fileType = file.getFileType();
+ if (CompilerManager.getInstance(project).isCompilableFileType(fileType) || isCompilableResourceFile(project, compilerConfiguration, file)) {
+ elementDescription = "'" + file.getName() + "'";
+ }
+ else {
+ if (!ActionPlaces.MAIN_MENU.equals(event.getPlace())) {
+ // the action should be invisible in popups for non-java files
+ presentation.setEnabled(false);
+ presentation.setVisible(false);
+ return;
+ }
+ }
+ }
+ else {
+ elementDescription = CompilerBundle.message("action.compile.description.selected.files");
+ }
+ }
+
+ if (elementDescription == null) {
+ presentation.setEnabled(false);
+ return;
+ }
+
+ presentation.setText(createPresentationText(elementDescription), true);
+ presentation.setEnabled(true);
+ }
+
+ private static String createPresentationText(String elementDescription) {
+ StringBuffer buffer = new StringBuffer(40);
+ buffer.append(ActionsBundle.actionText(IdeActions.ACTION_COMPILE)).append(" ");
+ int length = elementDescription.length();
+ if (length > 23) {
+ if (StringUtil.startsWithChar(elementDescription, '\'')) {
+ buffer.append("'");
+ }
+ buffer.append("...");
+ buffer.append(elementDescription.substring(length - 20, length));
+ }
+ else {
+ buffer.append(elementDescription);
+ }
+ return buffer.toString();
+ }
+
+ private static VirtualFile[] getCompilableFiles(Project project, VirtualFile[] files) {
+ if (files == null || files.length == 0) {
+ return VirtualFile.EMPTY_ARRAY;
+ }
+ final PsiManager psiManager = PsiManager.getInstance(project);
+ final CompilerConfiguration compilerConfiguration = CompilerConfiguration.getInstance(project);
+ final FileTypeManager typeManager = FileTypeManager.getInstance();
+ final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
+ final CompilerManager compilerManager = CompilerManager.getInstance(project);
+ final List<VirtualFile> filesToCompile = new ArrayList<VirtualFile>();
+ for (final VirtualFile file : files) {
+ if (!fileIndex.isInSourceContent(file)) {
+ continue;
+ }
+ if (!file.isInLocalFileSystem()) {
+ continue;
+ }
+ if (file.isDirectory()) {
+ final PsiDirectory directory = psiManager.findDirectory(file);
+ if (directory == null || JavaDirectoryService.getInstance().getPackage(directory) == null) {
+ continue;
+ }
+ }
+ else {
+ FileType fileType = file.getFileType();
+ if (!(compilerManager.isCompilableFileType(fileType) || isCompilableResourceFile(project, compilerConfiguration, file))) {
+ continue;
+ }
+ }
+ filesToCompile.add(file);
+ }
+ return VfsUtil.toVirtualFileArray(filesToCompile);
+ }
+
+ private static boolean isCompilableResourceFile(final Project project, final CompilerConfiguration compilerConfiguration, final VirtualFile file) {
+ if (!compilerConfiguration.isResourceFile(file)) {
+ return false;
+ }
+ final Collection<? extends Artifact> artifacts = ArtifactBySourceFileFinder.getInstance(project).findArtifacts(file);
+ return artifacts.isEmpty();
+ }
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/actions/CompileActionBase.java b/java/compiler/impl/src/com/intellij/compiler/actions/CompileActionBase.java
new file mode 100644
index 0000000..905dd92
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/actions/CompileActionBase.java
@@ -0,0 +1,54 @@
+/*
+ * 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 com.intellij.compiler.actions;
+
+import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
+import com.intellij.openapi.actionSystem.*;
+import com.intellij.openapi.compiler.CompilerManager;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.DumbAware;
+import com.intellij.openapi.project.DumbService;
+import com.intellij.psi.PsiFile;
+
+public abstract class CompileActionBase extends AnAction implements DumbAware {
+ public void actionPerformed(AnActionEvent e) {
+ final DataContext dataContext = e.getDataContext();
+ final Project project = e.getData(PlatformDataKeys.PROJECT);
+ if (project == null) {
+ return;
+ }
+ Editor editor = e.getData(PlatformDataKeys.EDITOR);
+ PsiFile file = e.getData(LangDataKeys.PSI_FILE);
+ if (file != null && editor != null && !DumbService.getInstance(project).isDumb()) {
+ DaemonCodeAnalyzer.getInstance(project).autoImportReferenceAtCursor(editor, file); //let autoimport complete
+ }
+ doAction(dataContext, project);
+ }
+
+ protected abstract void doAction(final DataContext dataContext, final Project project);
+
+ public void update(final AnActionEvent e) {
+ super.update(e);
+ final Project project = e.getData(PlatformDataKeys.PROJECT);
+ if (project == null) {
+ e.getPresentation().setEnabled(false);
+ }
+ else {
+ e.getPresentation().setEnabled(!CompilerManager.getInstance(project).isCompilationActive());
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/actions/CompileDirtyAction.java b/java/compiler/impl/src/com/intellij/compiler/actions/CompileDirtyAction.java
new file mode 100644
index 0000000..8bea288
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/actions/CompileDirtyAction.java
@@ -0,0 +1,39 @@
+/*
+ * 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 com.intellij.compiler.actions;
+
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.PlatformDataKeys;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.compiler.CompilerManager;
+import com.intellij.openapi.project.Project;
+
+public class CompileDirtyAction extends CompileActionBase {
+
+ protected void doAction(DataContext dataContext, Project project) {
+ CompilerManager.getInstance(project).make(null);
+ }
+
+ public void update(AnActionEvent event){
+ super.update(event);
+ Presentation presentation = event.getPresentation();
+ if (!presentation.isEnabled()) {
+ return;
+ }
+ presentation.setEnabled(PlatformDataKeys.PROJECT.getData(event.getDataContext()) != null);
+ }
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/actions/CompileProjectAction.java b/java/compiler/impl/src/com/intellij/compiler/actions/CompileProjectAction.java
new file mode 100644
index 0000000..fde4f55
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/actions/CompileProjectAction.java
@@ -0,0 +1,52 @@
+/*
+ * 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 com.intellij.compiler.actions;
+
+import com.intellij.history.LocalHistory;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.PlatformDataKeys;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.CompileStatusNotification;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.compiler.CompilerManager;
+import com.intellij.openapi.project.Project;
+
+public class CompileProjectAction extends CompileActionBase {
+ protected void doAction(DataContext dataContext, final Project project) {
+ CompilerManager.getInstance(project).rebuild(new CompileStatusNotification() {
+ public void finished(boolean aborted, int errors, int warnings, final CompileContext compileContext) {
+ if (aborted) return;
+
+ String text = getTemplatePresentation().getText();
+ LocalHistory.getInstance().putSystemLabel(project, errors == 0
+ ? CompilerBundle.message("rebuild.lvcs.label.no.errors", text)
+ : CompilerBundle.message("rebuild.lvcs.label.with.errors", text));
+ }
+ });
+ }
+
+ public void update(AnActionEvent event) {
+ super.update(event);
+ Presentation presentation = event.getPresentation();
+ if (!presentation.isEnabled()) {
+ return;
+ }
+ Project project = PlatformDataKeys.PROJECT.getData(event.getDataContext());
+ presentation.setEnabled(project != null);
+ }
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/actions/GenerateAntBuildAction.java b/java/compiler/impl/src/com/intellij/compiler/actions/GenerateAntBuildAction.java
new file mode 100644
index 0000000..8af49a5
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/actions/GenerateAntBuildAction.java
@@ -0,0 +1,359 @@
+/*
+ * 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 com.intellij.compiler.actions;
+
+import com.intellij.compiler.CompilerConfiguration;
+import com.intellij.compiler.CompilerConfigurationImpl;
+import com.intellij.compiler.ant.*;
+import com.intellij.compiler.impl.CompilerUtil;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.actionSystem.PlatformDataKeys;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.progress.Task;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.ReadonlyStatusHandler;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.*;
+import java.util.*;
+
+public class GenerateAntBuildAction extends CompileActionBase {
+ @NonNls private static final String XML_EXTENSION = ".xml";
+
+ protected void doAction(DataContext dataContext, final Project project) {
+ ((CompilerConfigurationImpl)CompilerConfiguration.getInstance(project)).convertPatterns();
+ final GenerateAntBuildDialog dialog = new GenerateAntBuildDialog(project);
+ dialog.show();
+ if (dialog.isOK()) {
+ final String[] names = dialog.getRepresentativeModuleNames();
+ final GenerationOptionsImpl[] genOptions = {null};
+ Runnable runnable = new Runnable() {
+ public void run() {
+ genOptions[0] = new GenerationOptionsImpl(project, dialog.isGenerateSingleFileBuild(), dialog.isFormsCompilationEnabled(),
+ dialog.isBackupFiles(), dialog.isForceTargetJdk(), dialog.isRuntimeClasspathInlined(),
+ dialog.isIdeaHomeGenerated(), names, dialog.getOutputFileName());
+ }
+ };
+ if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(runnable, "Analyzing project structure...", true, project)) {
+ return;
+ }
+ if (!validateGenOptions(project, genOptions[0])) {
+ return;
+ }
+ generate(project, genOptions[0]);
+ }
+ }
+
+ /**
+ * Validate generation options and notify user about possible problems
+ *
+ * @param project a context project
+ * @param genOptions a generation optiosn
+ * @return true if the generator should proceed with current options or if there is not conflict.
+ */
+ private static boolean validateGenOptions(Project project, GenerationOptionsImpl genOptions) {
+ final Collection<String> EMPTY = Collections.emptyList();
+ Collection<String> conflicts = EMPTY;
+ for (ModuleChunk chunk : genOptions.getModuleChunks()) {
+ final ChunkCustomCompilerExtension[] customeCompilers = chunk.getCustomCompilers();
+ if (customeCompilers.length > 1) {
+ if (conflicts == EMPTY) {
+ conflicts = new LinkedList<String>();
+ }
+ conflicts.add(chunk.getName());
+ }
+ }
+ if (!conflicts.isEmpty()) {
+ StringBuilder msg = new StringBuilder();
+ for (String conflictingChunk : conflicts) {
+ msg.append(CompilerBundle.message("generate.ant.build.custom.compiler.conflict.message.row", conflictingChunk));
+ }
+ int rc = Messages
+ .showOkCancelDialog(project, CompilerBundle.message("generate.ant.build.custom.compiler.conflict.message", msg.toString()),
+ CompilerBundle.message("generate.ant.build.custom.compiler.conflict.title"), Messages.getErrorIcon());
+ if (rc != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void update(AnActionEvent event) {
+ Presentation presentation = event.getPresentation();
+ Project project = PlatformDataKeys.PROJECT.getData(event.getDataContext());
+ presentation.setEnabled(project != null);
+ }
+
+ private void generate(final Project project, final GenerationOptions genOptions) {
+ ApplicationManager.getApplication().saveAll();
+ final List<File> filesToRefresh = new ArrayList<File>();
+ final IOException[] _ex = new IOException[]{null};
+ final List<File> _generated = new ArrayList<File>();
+
+ try {
+ if (genOptions.generateSingleFile) {
+ final File projectBuildFileDestDir = VfsUtil.virtualToIoFile(project.getBaseDir());
+ final File destFile = new File(projectBuildFileDestDir, genOptions.getBuildFileName());
+ final File propertiesFile = new File(projectBuildFileDestDir, genOptions.getPropertiesFileName());
+
+ ensureFilesWritable(project, new File[]{destFile, propertiesFile});
+ }
+ else {
+ final List<File> allFiles = new ArrayList<File>();
+
+ final File projectBuildFileDestDir = VfsUtil.virtualToIoFile(project.getBaseDir());
+ allFiles.add(new File(projectBuildFileDestDir, genOptions.getBuildFileName()));
+ allFiles.add(new File(projectBuildFileDestDir, genOptions.getPropertiesFileName()));
+
+ final ModuleChunk[] chunks = genOptions.getModuleChunks();
+ for (final ModuleChunk chunk : chunks) {
+ final File chunkBaseDir = BuildProperties.getModuleChunkBaseDir(chunk);
+ allFiles.add(new File(chunkBaseDir, BuildProperties.getModuleChunkBuildFileName(chunk) + XML_EXTENSION));
+ }
+
+ ensureFilesWritable(project, allFiles.toArray(new File[allFiles.size()]));
+ }
+
+ new Task.Modal(project, CompilerBundle.message("generate.ant.build.title"), false) {
+ public void run(@NotNull final ProgressIndicator indicator) {
+ indicator.setIndeterminate(true);
+ indicator.setText(CompilerBundle.message("generate.ant.build.progress.message"));
+ try {
+ final File[] generated;
+ if (genOptions.generateSingleFile) {
+ generated = generateSingleFileBuild(project, genOptions, filesToRefresh);
+ }
+ else {
+ generated = generateMultipleFileBuild(project, genOptions, filesToRefresh);
+ }
+ if (generated != null) {
+ ContainerUtil.addAll(_generated, generated);
+ }
+ }
+ catch (IOException e) {
+ _ex[0] = e;
+ }
+ }
+ }.queue();
+
+ }
+ catch (IOException e) {
+ _ex[0] = e;
+ }
+
+ if (_ex[0] != null) {
+ Messages.showErrorDialog(project, CompilerBundle.message("error.ant.files.generate.failed", _ex[0].getMessage()),
+ CompilerBundle.message("generate.ant.build.title"));
+ }
+ else {
+ StringBuffer filesString = new StringBuffer();
+ for (int idx = 0; idx < _generated.size(); idx++) {
+ final File file = _generated.get(idx);
+ if (idx > 0) {
+ filesString.append(",\n");
+ }
+ filesString.append(file.getPath());
+ }
+ Messages.showInfoMessage(project, CompilerBundle.message("message.ant.files.generated.ok", filesString.toString()),
+ CompilerBundle.message("generate.ant.build.title"));
+ }
+
+ if (filesToRefresh.size() > 0) {
+ CompilerUtil.refreshIOFiles(filesToRefresh);
+ }
+ }
+
+ private boolean backup(final File file, final Project project, GenerationOptions genOptions, List<File> filesToRefresh) {
+ if (!genOptions.backupPreviouslyGeneratedFiles || !file.exists()) {
+ return true;
+ }
+ final String path = file.getPath();
+ final int extensionIndex = path.lastIndexOf(".");
+ final String extension = path.substring(extensionIndex, path.length());
+ //noinspection HardCodedStringLiteral
+ final String backupPath = path.substring(0, extensionIndex) +
+ "_" +
+ new Date(file.lastModified()).toString().replaceAll("\\s+", "_").replaceAll(":", "-") +
+ extension;
+ final File backupFile = new File(backupPath);
+ boolean ok;
+ try {
+ FileUtil.rename(file, backupFile);
+ ok = true;
+ }
+ catch (IOException e) {
+ Messages.showErrorDialog(project, CompilerBundle.message("error.ant.files.backup.failed", path),
+ CompilerBundle.message("generate.ant.build.title"));
+ ok = false;
+ }
+ filesToRefresh.add(backupFile);
+ return ok;
+ }
+
+ private File[] generateSingleFileBuild(Project project, GenerationOptions genOptions, List<File> filesToRefresh) throws IOException {
+ final File projectBuildFileDestDir = VfsUtil.virtualToIoFile(project.getBaseDir());
+ projectBuildFileDestDir.mkdirs();
+ final File destFile = new File(projectBuildFileDestDir, genOptions.getBuildFileName());
+ final File propertiesFile = new File(projectBuildFileDestDir, genOptions.getPropertiesFileName());
+
+ if (!backup(destFile, project, genOptions, filesToRefresh)) {
+ return null;
+ }
+ if (!backup(propertiesFile, project, genOptions, filesToRefresh)) {
+ return null;
+ }
+
+ generateSingleFileBuild(project, genOptions, destFile, propertiesFile);
+
+ filesToRefresh.add(destFile);
+ filesToRefresh.add(propertiesFile);
+ return new File[]{destFile, propertiesFile};
+ }
+
+ public static void generateSingleFileBuild(final Project project,
+ final GenerationOptions genOptions,
+ final File buildxmlFile,
+ final File propertiesFile) throws IOException {
+ FileUtil.createIfDoesntExist(buildxmlFile);
+ FileUtil.createIfDoesntExist(propertiesFile);
+ final PrintWriter dataOutput = makeWriter(buildxmlFile);
+ try {
+ new SingleFileProjectBuild(project, genOptions).generate(dataOutput);
+ }
+ finally {
+ dataOutput.close();
+ }
+ final PrintWriter propertiesOut = makeWriter(propertiesFile);
+ try {
+ new PropertyFileGeneratorImpl(project, genOptions).generate(propertiesOut);
+ }
+ finally {
+ propertiesOut.close();
+ }
+ }
+
+ /**
+ * Create print writer over file with UTF-8 encoding
+ *
+ * @param buildxmlFile a file to write to
+ * @return a created print writer
+ * @throws UnsupportedEncodingException if endcoding not found
+ * @throws FileNotFoundException if file not found
+ */
+ private static PrintWriter makeWriter(final File buildxmlFile) throws UnsupportedEncodingException, FileNotFoundException {
+ return new PrintWriter(new OutputStreamWriter(new FileOutputStream(buildxmlFile), "UTF-8"));
+ }
+
+ private void ensureFilesWritable(Project project, File[] files) throws IOException {
+ final List<VirtualFile> toCheck = new ArrayList<VirtualFile>(files.length);
+ final LocalFileSystem lfs = LocalFileSystem.getInstance();
+ for (File file : files) {
+ final VirtualFile vFile = lfs.findFileByIoFile(file);
+ if (vFile != null) {
+ toCheck.add(vFile);
+ }
+ }
+ final ReadonlyStatusHandler.OperationStatus status =
+ ReadonlyStatusHandler.getInstance(project).ensureFilesWritable(VfsUtil.toVirtualFileArray(toCheck));
+ if (status.hasReadonlyFiles()) {
+ throw new IOException(status.getReadonlyFilesMessage());
+ }
+ }
+
+ public File[] generateMultipleFileBuild(Project project, GenerationOptions genOptions, List<File> filesToRefresh) throws IOException {
+ final File projectBuildFileDestDir = VfsUtil.virtualToIoFile(project.getBaseDir());
+ projectBuildFileDestDir.mkdirs();
+ final List<File> generated = new ArrayList<File>();
+ final File projectBuildFile = new File(projectBuildFileDestDir, genOptions.getBuildFileName());
+ final File propertiesFile = new File(projectBuildFileDestDir, genOptions.getPropertiesFileName());
+ final ModuleChunk[] chunks = genOptions.getModuleChunks();
+
+ final File[] chunkFiles = new File[chunks.length];
+ for (int idx = 0; idx < chunks.length; idx++) {
+ final ModuleChunk chunk = chunks[idx];
+ final File chunkBaseDir = BuildProperties.getModuleChunkBaseDir(chunk);
+ chunkFiles[idx] = new File(chunkBaseDir, BuildProperties.getModuleChunkBuildFileName(chunk) + XML_EXTENSION);
+ }
+
+ if (!backup(projectBuildFile, project, genOptions, filesToRefresh)) {
+ return null;
+ }
+ if (!backup(propertiesFile, project, genOptions, filesToRefresh)) {
+ return null;
+ }
+
+ FileUtil.createIfDoesntExist(projectBuildFile);
+ final PrintWriter mainDataOutput = makeWriter(projectBuildFile);
+ try {
+ final MultipleFileProjectBuild build = new MultipleFileProjectBuild(project, genOptions);
+ build.generate(mainDataOutput);
+ generated.add(projectBuildFile);
+
+ // the sequence in which modules are imported is important cause output path properties for dependent modules should be defined first
+
+ for (int idx = 0; idx < chunks.length; idx++) {
+ final ModuleChunk chunk = chunks[idx];
+ final File chunkBuildFile = chunkFiles[idx];
+ final File chunkBaseDir = chunkBuildFile.getParentFile();
+ if (chunkBaseDir != null) {
+ chunkBaseDir.mkdirs();
+ }
+ final boolean moduleBackupOk = backup(chunkBuildFile, project, genOptions, filesToRefresh);
+ if (!moduleBackupOk) {
+ return null;
+ }
+
+ FileUtil.createIfDoesntExist(chunkBuildFile);
+ final PrintWriter out = makeWriter(chunkBuildFile);
+ try {
+ new ModuleChunkAntProject(project, chunk, genOptions).generate(out);
+ generated.add(chunkBuildFile);
+ }
+ finally {
+ out.close();
+ }
+ }
+ }
+ finally {
+ mainDataOutput.close();
+ }
+ // properties
+ final PrintWriter propertiesOut = makeWriter(propertiesFile);
+ try {
+ new PropertyFileGeneratorImpl(project, genOptions).generate(propertiesOut);
+ generated.add(propertiesFile);
+ }
+ finally {
+ propertiesOut.close();
+ }
+
+ filesToRefresh.addAll(generated);
+ return generated.toArray(new File[generated.size()]);
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/actions/GenerateAntBuildDialog.java b/java/compiler/impl/src/com/intellij/compiler/actions/GenerateAntBuildDialog.java
new file mode 100644
index 0000000..1cd1c39
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/actions/GenerateAntBuildDialog.java
@@ -0,0 +1,360 @@
+/*
+ * 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 com.intellij.compiler.actions;
+
+import com.intellij.compiler.HelpID;
+import com.intellij.compiler.ModuleCompilerUtil;
+import com.intellij.compiler.ant.BuildProperties;
+import com.intellij.ide.util.PropertiesComponent;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.help.HelpManager;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.ui.MultiLineLabelUI;
+import com.intellij.openapi.util.Pair;
+import com.intellij.ui.IdeBorderFactory;
+import com.intellij.ui.ScrollPaneFactory;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.Chunk;
+import com.intellij.util.ListWithSelection;
+import com.intellij.util.ui.ComboBoxTableCellEditor;
+import com.intellij.util.ui.Table;
+import org.jetbrains.annotations.NonNls;
+
+import javax.swing.*;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.TableCellEditor;
+import javax.swing.table.TableColumn;
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 29, 2004
+ */
+public class GenerateAntBuildDialog extends DialogWrapper {
+ private JPanel myPanel;
+ private JRadioButton myRbGenerateSingleFileBuild;
+ private JRadioButton myRbGenerateMultipleFilesBuild;
+ private JCheckBox myCbEnableUIFormsCompilation;
+ private JRadioButton myRbBackupFiles;
+ private JRadioButton myRbOverwriteFiles;
+ private JCheckBox myCbForceTargetJdk;
+ private JCheckBox myCbInlineRuntimeClasspath;
+ private JPanel myChunksPanel;
+ private JCheckBox myGenerateIdeaHomeProperty;
+ private JTextField myOutputFileNameField;
+ private final Project myProject;
+ @NonNls private static final String SINGLE_FILE_PROPERTY = "GenerateAntBuildDialog.generateSingleFile";
+ @NonNls private static final String UI_FORM_PROPERTY = "GenerateAntBuildDialog.enableUiFormCompile";
+ @NonNls private static final String FORCE_TARGET_JDK_PROPERTY = "GenerateAntBuildDialog.forceTargetJdk";
+ @NonNls private static final String BACKUP_FILES_PROPERTY = "GenerateAntBuildDialog.backupFiles";
+ @NonNls private static final String INLINE_RUNTIME_CLASSPATH_PROPERTY = "GenerateAntBuildDialog.inclineRuntiemClasspath";
+ @NonNls private static final String GENERATE_IDEA_HOME_PROPERTY = "GenerateAntBuildDialog.generateIdeaHomeProperty";
+ @NonNls private static final String OUTPUT_FILE_NAME_PROPERTY = "GenerateAntBuildDialog.outputFileNameProperty";
+ private MyTableModel myTableModel;
+ private Table myTable;
+
+ public GenerateAntBuildDialog(Project project) {
+ super(project, false);
+ myProject = project;
+ setTitle(CompilerBundle.message("generate.ant.build.title"));
+ init();
+ loadSettings();
+ }
+
+ private List<Chunk<Module>> getCycleChunks() {
+ List<Chunk<Module>> chunks =
+ ModuleCompilerUtil.getSortedModuleChunks(myProject, Arrays.asList(ModuleManager.getInstance(myProject).getModules()));
+ for (Iterator<Chunk<Module>> it = chunks.iterator(); it.hasNext();) {
+ final Chunk<Module> chunk = it.next();
+ if (chunk.getNodes().size() == 1) {
+ it.remove();
+ }
+ }
+ return chunks;
+ }
+
+ private void loadSettings() {
+ final PropertiesComponent properties = PropertiesComponent.getInstance(myProject);
+ if (properties.isValueSet(SINGLE_FILE_PROPERTY)) {
+ final boolean singleFile = properties.isTrueValue(SINGLE_FILE_PROPERTY);
+ myRbGenerateSingleFileBuild.setSelected(singleFile);
+ myRbGenerateMultipleFilesBuild.setSelected(!singleFile);
+ }
+ if (properties.isValueSet(UI_FORM_PROPERTY)) {
+ myCbEnableUIFormsCompilation.setSelected(properties.isTrueValue(UI_FORM_PROPERTY));
+ }
+ if (properties.isValueSet(FORCE_TARGET_JDK_PROPERTY)) {
+ myCbForceTargetJdk.setSelected(properties.isTrueValue(FORCE_TARGET_JDK_PROPERTY));
+ }
+ if (properties.isValueSet(BACKUP_FILES_PROPERTY)) {
+ final boolean backup = properties.isTrueValue(BACKUP_FILES_PROPERTY);
+ myRbBackupFiles.setSelected(backup);
+ myRbOverwriteFiles.setSelected(!backup);
+ }
+ if (properties.isValueSet(INLINE_RUNTIME_CLASSPATH_PROPERTY)) {
+ myCbInlineRuntimeClasspath.setSelected(properties.isTrueValue(INLINE_RUNTIME_CLASSPATH_PROPERTY));
+ }
+ if (properties.isValueSet(GENERATE_IDEA_HOME_PROPERTY)) {
+ myGenerateIdeaHomeProperty.setSelected(properties.isTrueValue(GENERATE_IDEA_HOME_PROPERTY));
+ }
+ if (properties.isValueSet(OUTPUT_FILE_NAME_PROPERTY)) {
+ myOutputFileNameField.setText(properties.getValue(OUTPUT_FILE_NAME_PROPERTY));
+ }
+ else {
+ myOutputFileNameField.setText(BuildProperties.getProjectBuildFileName(myProject));
+ }
+ }
+
+ private void saveSettings() {
+ final PropertiesComponent properties = PropertiesComponent.getInstance(myProject);
+ properties.setValue(SINGLE_FILE_PROPERTY, Boolean.toString(myRbGenerateSingleFileBuild.isSelected()));
+ properties.setValue(UI_FORM_PROPERTY, Boolean.toString(myCbEnableUIFormsCompilation.isSelected()));
+ properties.setValue(FORCE_TARGET_JDK_PROPERTY, Boolean.toString(myCbForceTargetJdk.isSelected()));
+ properties.setValue(BACKUP_FILES_PROPERTY, Boolean.toString(myRbBackupFiles.isSelected()));
+ properties.setValue(INLINE_RUNTIME_CLASSPATH_PROPERTY, Boolean.toString(myCbInlineRuntimeClasspath.isSelected()));
+ properties.setValue(GENERATE_IDEA_HOME_PROPERTY, Boolean.toString(myGenerateIdeaHomeProperty.isSelected()));
+ final String outputFileName = getOutputFileName();
+ if (outputFileName.length() > 0) {
+ properties.setValue(OUTPUT_FILE_NAME_PROPERTY, outputFileName);
+ }
+ else {
+ properties.unsetValue(OUTPUT_FILE_NAME_PROPERTY);
+ }
+ }
+
+ public void dispose() {
+ saveSettings();
+ super.dispose();
+ }
+
+ protected JComponent createCenterPanel() {
+ final ButtonGroup group = new ButtonGroup();
+ group.add(myRbGenerateMultipleFilesBuild);
+ group.add(myRbGenerateSingleFileBuild);
+
+ final ButtonGroup group1 = new ButtonGroup();
+ group1.add(myRbBackupFiles);
+ group1.add(myRbOverwriteFiles);
+
+ myRbGenerateMultipleFilesBuild.setSelected(true);
+ myRbBackupFiles.setSelected(true);
+ myCbEnableUIFormsCompilation.setSelected(true);
+ myCbForceTargetJdk.setSelected(true);
+ myCbInlineRuntimeClasspath.setSelected(false);
+
+ initChunksPanel();
+
+ return myPanel;
+ }
+
+ private void initChunksPanel() {
+ List<Chunk<Module>> chunks = getCycleChunks();
+ if (chunks.isEmpty()) {
+ return;
+ }
+ myChunksPanel.setLayout(new BorderLayout());
+ myChunksPanel.setBorder(
+ IdeBorderFactory.createTitledBorder(CompilerBundle.message("generate.ant.build.dialog.cyclic.modules.table.title"),
+ true));
+ JLabel textLabel = new JLabel(CompilerBundle.message("generate.ant.build.dialog.cyclic.modules.table.description"));
+ textLabel.setUI(new MultiLineLabelUI());
+ textLabel.setBorder(IdeBorderFactory.createEmptyBorder(4, 4, 6, 4));
+ myChunksPanel.add(textLabel, BorderLayout.NORTH);
+
+ myTableModel = new MyTableModel(chunks);
+ myTable = new Table(myTableModel);
+ final MyTableCellRenderer cellRenderer = new MyTableCellRenderer();
+ final TableColumn nameColumn = myTable.getColumnModel().getColumn(MyTableModel.NAME_COLUMN);
+ nameColumn.setCellEditor(ComboBoxTableCellEditor.INSTANCE);
+ nameColumn.setCellRenderer(cellRenderer);
+ final TableColumn labelColumn = myTable.getColumnModel().getColumn(MyTableModel.NUMBER_COLUMN);
+ labelColumn.setCellRenderer(cellRenderer);
+
+ final Dimension preferredSize = new Dimension(myTable.getPreferredSize());
+ preferredSize.height = (myTableModel.getRowCount() + 2) * myTable.getRowHeight() + myTable.getTableHeader().getHeight();
+
+ final JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myTable);
+ scrollPane.setPreferredSize(preferredSize);
+ myChunksPanel.add(scrollPane, BorderLayout.CENTER);
+ }
+
+ protected void doOKAction() {
+ if (myTable != null) {
+ TableCellEditor cellEditor = myTable.getCellEditor();
+ if (cellEditor != null) {
+ cellEditor.stopCellEditing();
+ }
+ }
+ super.doOKAction();
+ }
+
+ public boolean isGenerateSingleFileBuild() {
+ return myRbGenerateSingleFileBuild.isSelected();
+ }
+
+ public boolean isFormsCompilationEnabled() {
+ return myCbEnableUIFormsCompilation.isSelected();
+ }
+
+ public boolean isForceTargetJdk() {
+ return myCbForceTargetJdk.isSelected();
+ }
+
+ public boolean isBackupFiles() {
+ return myRbBackupFiles.isSelected();
+ }
+
+ public boolean isRuntimeClasspathInlined() {
+ return myCbInlineRuntimeClasspath.isSelected();
+ }
+
+ public String[] getRepresentativeModuleNames() {
+ return myTableModel != null ? myTableModel.getModuleRepresentatives() : ArrayUtil.EMPTY_STRING_ARRAY;
+ }
+
+ /**
+ * @return true if user has selected to generate IDEA_HOME property
+ */
+ public boolean isIdeaHomeGenerated() {
+ return myGenerateIdeaHomeProperty.isSelected();
+ }
+
+ public String getOutputFileName() {
+ return myOutputFileNameField.getText().trim();
+ }
+
+ private static class MyTableModel extends AbstractTableModel {
+ private static final int NUMBER_COLUMN = 0;
+ private static final int NAME_COLUMN = 1;
+
+ private final List<Pair<String, ListWithSelection>> myItems = new ArrayList<Pair<String, ListWithSelection>>();
+
+ private MyTableModel(List<Chunk<Module>> chunks) {
+ for (final Chunk<Module> chunk : chunks) {
+ final ListWithSelection<String> item = new ListWithSelection<String>();
+ for (final Module module : chunk.getNodes()) {
+ item.add(module.getName());
+ }
+ item.selectFirst();
+ myItems.add(new Pair<String, ListWithSelection>(createCycleName(chunk), item));
+ }
+ }
+
+ private static String createCycleName(Chunk<Module> chunk) {
+ final StringBuilder buf = new StringBuilder();
+ for (Module module : chunk.getNodes()) {
+ if (buf.length() > 0) {
+ buf.append(", ");
+ }
+ buf.append(module.getName());
+ }
+ buf.insert(0, "[");
+ buf.append("]");
+ return buf.toString();
+ }
+
+ public String[] getModuleRepresentatives() {
+ final String[] names = new String[myItems.size()];
+ int index = 0;
+ for (final Pair<String, ListWithSelection> pair : myItems) {
+ names[index++] = (String)pair.getSecond().getSelection();
+ }
+ return names;
+ }
+
+ public int getColumnCount() {
+ return 2;
+ }
+
+ public int getRowCount() {
+ return myItems.size();
+ }
+
+ public boolean isCellEditable(int rowIndex, int columnIndex) {
+ return columnIndex == 1;
+ }
+
+ public Class getColumnClass(int columnIndex) {
+ switch (columnIndex) {
+ case NUMBER_COLUMN:
+ return String.class;
+ case NAME_COLUMN:
+ return ListWithSelection.class;
+ default:
+ return super.getColumnClass(columnIndex);
+ }
+ }
+
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ switch (columnIndex) {
+ case NUMBER_COLUMN:
+ return myItems.get(rowIndex).getFirst();
+ case NAME_COLUMN:
+ return myItems.get(rowIndex).getSecond();
+ default:
+ return null;
+ }
+ }
+
+ public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
+ if (columnIndex == NAME_COLUMN) {
+ myItems.get(rowIndex).getSecond().select(aValue);
+ }
+ }
+
+ public String getColumnName(int columnIndex) {
+ switch (columnIndex) {
+ case NUMBER_COLUMN:
+ return CompilerBundle.message("generate.ant.build.dialog.cyclic.modules.table.number.column.header");
+ case NAME_COLUMN:
+ return CompilerBundle.message("generate.ant.build.dialog.cyclic.modules.table.name.column.header");
+ }
+ return super.getColumnName(columnIndex);
+ }
+ }
+
+ private static class MyTableCellRenderer extends DefaultTableCellRenderer {
+ public Component getTableCellRendererComponent(JTable table,
+ Object value,
+ boolean isSelected,
+ boolean hasFocus,
+ int row,
+ int column) {
+ if (value instanceof ListWithSelection) {
+ value = ((ListWithSelection)value).getSelection();
+ }
+ final JLabel component = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
+ component.setHorizontalAlignment(SwingConstants.CENTER);
+ return component;
+ }
+ }
+
+ protected Action[] createActions() {
+ return new Action[]{getOKAction(), getCancelAction(), getHelpAction()};
+ }
+
+ protected void doHelpAction() {
+ HelpManager.getInstance().invokeHelp(HelpID.GENERATE_ANT_BUILD);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/actions/MakeModuleAction.java b/java/compiler/impl/src/com/intellij/compiler/actions/MakeModuleAction.java
new file mode 100644
index 0000000..08b85a8
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/actions/MakeModuleAction.java
@@ -0,0 +1,84 @@
+/*
+ * 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 com.intellij.compiler.actions;
+
+import com.intellij.openapi.actionSystem.*;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.compiler.CompilerManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+
+public class MakeModuleAction extends CompileActionBase {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.actions.MakeModuleAction");
+
+ protected void doAction(DataContext dataContext, Project project) {
+ Module[] modules = LangDataKeys.MODULE_CONTEXT_ARRAY.getData(dataContext);
+ Module module;
+ if (modules == null) {
+ module = LangDataKeys.MODULE.getData(dataContext);
+ if (module == null) {
+ return;
+ }
+ modules = new Module[]{module};
+ }
+ try {
+ CompilerManager.getInstance(project).make(modules[0].getProject(), modules, null);
+ }
+ catch (Exception e) {
+ LOG.error(e);
+ }
+ }
+
+ public void update(AnActionEvent event){
+ super.update(event);
+ Presentation presentation = event.getPresentation();
+ if (!presentation.isEnabled()) {
+ return;
+ }
+ final DataContext dataContext = event.getDataContext();
+ final Module module = LangDataKeys.MODULE.getData(dataContext);
+ Module[] modules = LangDataKeys.MODULE_CONTEXT_ARRAY.getData(dataContext);
+ final boolean isEnabled = module != null || modules != null;
+ presentation.setEnabled(isEnabled);
+ final String actionName = getTemplatePresentation().getTextWithMnemonic();
+
+ String presentationText;
+ if (modules != null) {
+ String text = actionName;
+ for (int i = 0; i < modules.length; i++) {
+ if (text.length() > 30) {
+ text = CompilerBundle.message("action.make.selected.modules.text");
+ break;
+ }
+ Module toMake = modules[i];
+ if (i!=0) {
+ text += ",";
+ }
+ text += " '" + toMake.getName() + "'";
+ }
+ presentationText = text;
+ }
+ else if (module != null) {
+ presentationText = actionName + " '" + module.getName() + "'";
+ }
+ else {
+ presentationText = actionName;
+ }
+ presentation.setText(presentationText);
+ presentation.setVisible(isEnabled || !ActionPlaces.PROJECT_VIEW_POPUP.equals(event.getPlace()));
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/actions/ProcessAnnotationsAction.java b/java/compiler/impl/src/com/intellij/compiler/actions/ProcessAnnotationsAction.java
new file mode 100644
index 0000000..bb59707
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/actions/ProcessAnnotationsAction.java
@@ -0,0 +1,203 @@
+/*
+ * 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 com.intellij.compiler.actions;
+
+import org.jetbrains.jps.model.java.compiler.AnnotationProcessingConfiguration;
+import com.intellij.compiler.CompilerConfiguration;
+import com.intellij.compiler.impl.FileSetCompileScope;
+import com.intellij.compiler.impl.ModuleCompileScope;
+import com.intellij.compiler.impl.javaCompiler.AnnotationProcessingCompiler;
+import com.intellij.compiler.impl.resourceCompiler.ResourceCompiler;
+import com.intellij.idea.ActionsBundle;
+import com.intellij.openapi.actionSystem.*;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.compiler.CompilerFilter;
+import com.intellij.openapi.compiler.CompilerManager;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.FileTypeManager;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ProjectFileIndex;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.*;
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.Nullable;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class ProcessAnnotationsAction extends CompileActionBase {
+
+ protected void doAction(DataContext dataContext, Project project) {
+ final Module module = LangDataKeys.MODULE_CONTEXT.getData(dataContext);
+ final CompilerFilter filter = new CompilerFilter() {
+ public boolean acceptCompiler(com.intellij.openapi.compiler.Compiler compiler) {
+ // EclipseLink CanonicalModelProcessor reads input from output hence adding ResourcesCompiler
+ return compiler instanceof AnnotationProcessingCompiler || compiler instanceof ResourceCompiler;
+ }
+ };
+ if (module != null) {
+ CompilerManager.getInstance(project).make(new ModuleCompileScope(module, false), filter, null);
+ }
+ else {
+ final FileSetCompileScope scope = getCompilableFiles(project, PlatformDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext));
+ if (scope != null) {
+ CompilerManager.getInstance(project).make(scope, filter, null);
+ }
+ }
+ }
+
+ public void update(AnActionEvent event) {
+ super.update(event);
+ Presentation presentation = event.getPresentation();
+ if (!presentation.isEnabled()) {
+ return;
+ }
+ DataContext dataContext = event.getDataContext();
+ presentation.setVisible(false);
+
+ Project project = PlatformDataKeys.PROJECT.getData(dataContext);
+ if (project == null) {
+ presentation.setEnabled(false);
+ return;
+ }
+
+ CompilerConfiguration compilerConfiguration = CompilerConfiguration.getInstance(project);
+ final Module module = LangDataKeys.MODULE.getData(dataContext);
+ final Module moduleContext = LangDataKeys.MODULE_CONTEXT.getData(dataContext);
+
+ if (module == null) {
+ presentation.setEnabled(false);
+ return;
+ }
+ final AnnotationProcessingConfiguration profile = compilerConfiguration.getAnnotationProcessingConfiguration(module);
+ if (!profile.isEnabled() || (!profile.isObtainProcessorsFromClasspath() && profile.getProcessors().isEmpty())) {
+ presentation.setEnabled(false);
+ return;
+ }
+
+ presentation.setVisible(true);
+ presentation.setText(createPresentationText(""), true);
+ final FileSetCompileScope scope = getCompilableFiles(project, PlatformDataKeys.VIRTUAL_FILE_ARRAY.getData(dataContext));
+ if (moduleContext == null && scope == null) {
+ presentation.setEnabled(false);
+ return;
+ }
+
+ String elementDescription = null;
+ if (moduleContext != null) {
+ elementDescription = CompilerBundle.message("action.compile.description.module", moduleContext.getName());
+ }
+ else {
+ PsiPackage aPackage = null;
+ final Collection<VirtualFile> files = scope.getRootFiles();
+ if (files.size() == 1) {
+ final PsiDirectory directory = PsiManager.getInstance(project).findDirectory(files.iterator().next());
+ if (directory != null) {
+ aPackage = JavaDirectoryService.getInstance().getPackage(directory);
+ }
+ }
+ else {
+ PsiElement element = LangDataKeys.PSI_ELEMENT.getData(dataContext);
+ if (element instanceof PsiPackage) {
+ aPackage = (PsiPackage)element;
+ }
+ }
+
+ if (aPackage != null) {
+ String name = aPackage.getQualifiedName();
+ if (name.length() == 0) {
+ //noinspection HardCodedStringLiteral
+ name = "<default>";
+ }
+ elementDescription = "'" + name + "'";
+ }
+ else if (files.size() == 1) {
+ final VirtualFile file = files.iterator().next();
+ FileType fileType = file.getFileType();
+ if (CompilerManager.getInstance(project).isCompilableFileType(fileType)) {
+ elementDescription = "'" + file.getName() + "'";
+ }
+ else {
+ if (!ActionPlaces.MAIN_MENU.equals(event.getPlace())) {
+ // the action should be invisible in popups for non-java files
+ presentation.setEnabled(false);
+ presentation.setVisible(false);
+ return;
+ }
+ }
+ }
+ else {
+ elementDescription = CompilerBundle.message("action.compile.description.selected.files");
+ }
+ }
+
+ if (elementDescription == null) {
+ presentation.setEnabled(false);
+ return;
+ }
+
+ presentation.setText(createPresentationText(elementDescription), true);
+ presentation.setEnabled(true);
+ }
+
+ private static String createPresentationText(final String elementDescription) {
+ int length = elementDescription.length();
+ String target = length > 23 ? (StringUtil.startsWithChar(elementDescription, '\'') ? "'..." : "...") + elementDescription.substring(length - 20, length) : elementDescription;
+ return MessageFormat.format(ActionsBundle.actionText(StringUtil.isEmpty(target)? "RunAPT" : "RunAPT.1"), target);
+ }
+
+ @Nullable
+ private static FileSetCompileScope getCompilableFiles(Project project, VirtualFile[] files) {
+ if (files == null || files.length == 0) {
+ return null;
+ }
+ final PsiManager psiManager = PsiManager.getInstance(project);
+ final FileTypeManager typeManager = FileTypeManager.getInstance();
+ final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
+ final CompilerManager compilerManager = CompilerManager.getInstance(project);
+ final List<VirtualFile> filesToCompile = new ArrayList<VirtualFile>();
+ final List<Module> affectedModules = new ArrayList<Module>();
+ for (final VirtualFile file : files) {
+ if (!fileIndex.isInSourceContent(file)) {
+ continue;
+ }
+ if (!file.isInLocalFileSystem()) {
+ continue;
+ }
+ if (file.isDirectory()) {
+ final PsiDirectory directory = psiManager.findDirectory(file);
+ if (directory == null || JavaDirectoryService.getInstance().getPackage(directory) == null) {
+ continue;
+ }
+ }
+ else {
+ FileType fileType = file.getFileType();
+ if (!(compilerManager.isCompilableFileType(fileType))) {
+ continue;
+ }
+ }
+ filesToCompile.add(file);
+ ContainerUtil.addIfNotNull(fileIndex.getModuleForFile(file), affectedModules);
+ }
+ if (filesToCompile.isEmpty()) return null;
+ return new FileSetCompileScope(filesToCompile, affectedModules.toArray(new Module[affectedModules.size()]));
+ }
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/BuildPropertiesImpl.java b/java/compiler/impl/src/com/intellij/compiler/ant/BuildPropertiesImpl.java
new file mode 100644
index 0000000..d89cd15
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/BuildPropertiesImpl.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.ant;
+
+import com.intellij.compiler.ant.taskdefs.*;
+import com.intellij.compiler.impl.javaCompiler.javac.JavacConfiguration;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ex.ProjectEx;
+import com.intellij.openapi.projectRoots.JavaSdkType;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.projectRoots.SdkTypeId;
+import com.intellij.openapi.roots.OrderRootType;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.roots.libraries.LibraryTable;
+import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import org.jetbrains.jps.model.java.compiler.JpsJavaCompilerOptions;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 16, 2004
+ */
+// todo: move path variables properties and jdk home properties into te generated property file
+public class BuildPropertiesImpl extends BuildProperties {
+
+ public BuildPropertiesImpl(Project project, final GenerationOptions genOptions) {
+ add(new Property(genOptions.getPropertiesFileName()));
+
+ //noinspection HardCodedStringLiteral
+ add(new Comment(CompilerBundle.message("generated.ant.build.disable.tests.property.comment"),
+ new Property(PROPERTY_SKIP_TESTS, "true")));
+ final JpsJavaCompilerOptions javacSettings = JavacConfiguration.getOptions(project, JavacConfiguration.class);
+ add(new Comment(CompilerBundle.message("generated.ant.build.compiler.options.comment")), 1);
+ //noinspection HardCodedStringLiteral
+ add(new Property(PROPERTY_COMPILER_GENERATE_DEBUG_INFO, javacSettings.DEBUGGING_INFO ? "on" : "off"), 1);
+ //noinspection HardCodedStringLiteral
+ add(new Property(PROPERTY_COMPILER_GENERATE_NO_WARNINGS, javacSettings.GENERATE_NO_WARNINGS ? "on" : "off"));
+ add(new Property(PROPERTY_COMPILER_ADDITIONAL_ARGS, javacSettings.ADDITIONAL_OPTIONS_STRING));
+ //noinspection HardCodedStringLiteral
+ add(new Property(PROPERTY_COMPILER_MAX_MEMORY, Integer.toString(javacSettings.MAXIMUM_HEAP_SIZE) + "m"));
+
+ add(new IgnoredFiles());
+
+ if (CompilerExcludes.isAvailable(project)) {
+ add(new CompilerExcludes(project, genOptions));
+ }
+
+ if (!genOptions.expandJarDirectories) {
+ add(new LibraryPatterns(project, genOptions));
+ }
+
+ add(new CompilerResourcePatterns(project));
+
+ if (genOptions.forceTargetJdk) {
+ createJdkGenerators(project);
+ }
+
+ LibraryDefinitionsGeneratorFactory factory = new LibraryDefinitionsGeneratorFactory((ProjectEx)project, genOptions);
+
+ final LibraryTablesRegistrar registrar = LibraryTablesRegistrar.getInstance();
+ final Generator projectLibs = factory.create(registrar.getLibraryTable(project), getProjectBaseDir(project),
+ CompilerBundle.message("generated.ant.build.project.libraries.comment"));
+ if (projectLibs != null) {
+ add(projectLibs);
+ }
+
+ final Generator globalLibs =
+ factory.create(registrar.getLibraryTable(), null, CompilerBundle.message("generated.ant.build.global.libraries.comment"));
+ if (globalLibs != null) {
+ add(globalLibs);
+ }
+
+ for (final LibraryTable table : registrar.getCustomLibraryTables()) {
+ if (table.getLibraries().length != 0) {
+ final Generator appServerLibs = factory.create(table, null, table.getPresentation().getDisplayName(true));
+ if (appServerLibs != null) {
+ add(appServerLibs);
+ }
+ }
+ }
+
+ final ChunkCustomCompilerExtension[] customCompilers = genOptions.getCustomCompilers();
+ if (genOptions.enableFormCompiler || customCompilers.length > 0) {
+ add(new Comment(CompilerBundle.message("generated.ant.build.custom.compilers.comment")));
+ Target register = new Target(TARGET_REGISTER_CUSTOM_COMPILERS, null, null, null);
+ if (genOptions.enableFormCompiler) {
+ //noinspection HardCodedStringLiteral
+ add(new Property(PROPERTY_JAVAC2_HOME, propertyRelativePath(PROPERTY_IDEA_HOME, "lib")));
+ Path javac2 = new Path(PROPERTY_JAVAC2_CLASSPATH_ID);
+ javac2.add(new PathElement(propertyRelativePath(PROPERTY_JAVAC2_HOME, "javac2.jar")));
+ javac2.add(new PathElement(propertyRelativePath(PROPERTY_JAVAC2_HOME, "jdom.jar")));
+ javac2.add(new PathElement(propertyRelativePath(PROPERTY_JAVAC2_HOME, "asm4-all.jar")));
+ javac2.add(new PathElement(propertyRelativePath(PROPERTY_JAVAC2_HOME, "jgoodies-forms.jar")));
+ add(javac2);
+ //noinspection HardCodedStringLiteral
+ register.add(new Tag("taskdef", Pair.create("name", "javac2"), Pair.create("classname", "com.intellij.ant.Javac2"),
+ Pair.create("classpathref", PROPERTY_JAVAC2_CLASSPATH_ID)));
+ register.add(new Tag("taskdef", Pair.create("name", "instrumentIdeaExtensions"),
+ Pair.create("classname", "com.intellij.ant.InstrumentIdeaExtensions"),
+ Pair.create("classpathref", PROPERTY_JAVAC2_CLASSPATH_ID)));
+ }
+ if (customCompilers.length > 0) {
+ for (ChunkCustomCompilerExtension ext : customCompilers) {
+ ext.generateCustomCompilerTaskRegistration(project, genOptions, register);
+ }
+ }
+ add(register);
+ }
+ }
+
+ protected void createJdkGenerators(final Project project) {
+ final Sdk[] jdks = getUsedJdks(project);
+
+ if (jdks.length > 0) {
+ add(new Comment(CompilerBundle.message("generated.ant.build.jdk.definitions.comment")), 1);
+
+ for (final Sdk jdk : jdks) {
+ if (jdk.getHomeDirectory() == null) {
+ continue;
+ }
+ final SdkTypeId sdkType = jdk.getSdkType();
+ if (!(sdkType instanceof JavaSdkType) || ((JavaSdkType)sdkType).getBinPath(jdk) == null) {
+ continue;
+ }
+ final File home = VfsUtil.virtualToIoFile(jdk.getHomeDirectory());
+ File homeDir;
+ try {
+ // use canonical path in order to resolve symlinks
+ homeDir = home.getCanonicalFile();
+ }
+ catch (IOException e) {
+ homeDir = home;
+ }
+ final String jdkName = jdk.getName();
+ final String jdkHomeProperty = getJdkHomeProperty(jdkName);
+ final FileSet fileSet = new FileSet(propertyRef(jdkHomeProperty));
+ final String[] urls = jdk.getRootProvider().getUrls(OrderRootType.CLASSES);
+ for (String url : urls) {
+ final String path = GenerationUtils.trimJarSeparator(VirtualFileManager.extractPath(url));
+ final File pathElement = new File(path);
+ final String relativePath = FileUtil.getRelativePath(homeDir, pathElement);
+ if (relativePath != null) {
+ fileSet.add(new Include(relativePath.replace(File.separatorChar, '/')));
+ }
+ }
+
+ final File binPath = toCanonicalFile(new File(((JavaSdkType)sdkType).getBinPath(jdk)));
+ final String relativePath = FileUtil.getRelativePath(homeDir, binPath);
+ if (relativePath != null) {
+ add(new Property(BuildProperties.getJdkBinProperty(jdkName),
+ propertyRef(jdkHomeProperty) + "/" + FileUtil.toSystemIndependentName(relativePath)), 1);
+ }
+ else {
+ add(new Property(BuildProperties.getJdkBinProperty(jdkName), FileUtil.toSystemIndependentName(binPath.getPath())), 1);
+ }
+
+ final Path jdkPath = new Path(getJdkPathId(jdkName));
+ jdkPath.add(fileSet);
+ add(jdkPath);
+ }
+ }
+
+ final Sdk projectJdk = ProjectRootManager.getInstance(project).getProjectSdk();
+ add(new Property(PROPERTY_PROJECT_JDK_HOME, projectJdk != null ? propertyRef(getJdkHomeProperty(projectJdk.getName())) : ""), 1);
+ add(new Property(PROPERTY_PROJECT_JDK_BIN, projectJdk != null ? propertyRef(getJdkBinProperty(projectJdk.getName())) : ""));
+ add(new Property(PROPERTY_PROJECT_JDK_CLASSPATH, projectJdk != null ? getJdkPathId(projectJdk.getName()) : ""));
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/BuildTargetsFactoryImpl.java b/java/compiler/impl/src/com/intellij/compiler/ant/BuildTargetsFactoryImpl.java
new file mode 100644
index 0000000..0db7086
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/BuildTargetsFactoryImpl.java
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+/*
+ * User: anna
+ * Date: 19-Dec-2006
+ */
+package com.intellij.compiler.ant;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.util.ArrayUtil;
+import org.jetbrains.annotations.TestOnly;
+
+public class BuildTargetsFactoryImpl extends BuildTargetsFactory {
+
+
+ public Generator createComment(final String comment) {
+ return new Comment(comment);
+ }
+
+ @TestOnly
+ public GenerationOptions getDefaultOptions(Project project) {
+ return new GenerationOptionsImpl(project, true, false, false, true, ArrayUtil.EMPTY_STRING_ARRAY);
+ }
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/ChunkBuild.java b/java/compiler/impl/src/com/intellij/compiler/ant/ChunkBuild.java
new file mode 100644
index 0000000..cd20b1c
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/ChunkBuild.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.ant;
+
+import com.intellij.compiler.ant.taskdefs.Path;
+import com.intellij.compiler.ant.taskdefs.Property;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.vfs.VirtualFileManager;
+
+import java.io.File;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 22, 2004
+ */
+public class ChunkBuild extends CompositeGenerator{
+
+ public ChunkBuild(Project project, ModuleChunk chunk, GenerationOptions genOptions) {
+ final File chunkBaseDir = chunk.getBaseDir();
+ if (genOptions.forceTargetJdk) {
+ if (chunk.isJdkInherited()) {
+ add(new Property(BuildProperties.getModuleChunkJdkHomeProperty(chunk.getName()), BuildProperties.propertyRef(BuildProperties.PROPERTY_PROJECT_JDK_HOME)));
+ add(new Property(BuildProperties.getModuleChunkJdkBinProperty(chunk.getName()), BuildProperties.propertyRef(BuildProperties.PROPERTY_PROJECT_JDK_BIN)));
+ add(new Property(BuildProperties.getModuleChunkJdkClasspathProperty(chunk.getName()), BuildProperties.propertyRef(BuildProperties.PROPERTY_PROJECT_JDK_CLASSPATH)));
+ }
+ else {
+ final Sdk jdk = chunk.getJdk();
+ add(new Property(BuildProperties.getModuleChunkJdkHomeProperty(chunk.getName()), jdk != null? BuildProperties.propertyRef(BuildProperties.getJdkHomeProperty(jdk.getName())): ""));
+ add(new Property(BuildProperties.getModuleChunkJdkBinProperty(chunk.getName()), jdk != null? BuildProperties.propertyRef(BuildProperties.getJdkBinProperty(jdk.getName())): ""));
+ add(new Property(BuildProperties.getModuleChunkJdkClasspathProperty(chunk.getName()), jdk != null? BuildProperties.getJdkPathId(jdk.getName()) : ""));
+ }
+ }
+
+ add(new Property(BuildProperties.getModuleChunkCompilerArgsProperty(chunk.getName()), BuildProperties.propertyRef(BuildProperties.PROPERTY_COMPILER_ADDITIONAL_ARGS)), 1);
+
+ final String outputPathUrl = chunk.getOutputDirUrl();
+ String location = outputPathUrl != null?
+ GenerationUtils.toRelativePath(VirtualFileManager.extractPath(outputPathUrl), chunkBaseDir, BuildProperties.getModuleChunkBasedirProperty(chunk), genOptions) :
+ CompilerBundle.message("value.undefined");
+ add(new Property(BuildProperties.getOutputPathProperty(chunk.getName()), location), 1);
+
+ final String testOutputPathUrl = chunk.getTestsOutputDirUrl();
+ if (testOutputPathUrl != null) {
+ location = GenerationUtils.toRelativePath(VirtualFileManager.extractPath(testOutputPathUrl), chunkBaseDir, BuildProperties.getModuleChunkBasedirProperty(chunk), genOptions);
+ }
+ add(new Property(BuildProperties.getOutputPathForTestsProperty(chunk.getName()), location));
+
+ add(createBootclasspath(chunk), 1);
+ add(new ModuleChunkClasspath(chunk, genOptions, false, false), 1);
+ add(new ModuleChunkClasspath(chunk, genOptions, true, false), 1);
+ add(new ModuleChunkClasspath(chunk, genOptions, false, true), 1);
+ add(new ModuleChunkClasspath(chunk, genOptions, true, true), 1);
+
+ final ModuleChunkSourcePath moduleSources = new ModuleChunkSourcePath(project, chunk, genOptions);
+ add(moduleSources, 1);
+ add(new CompileModuleChunkTarget(project, chunk, moduleSources.getSourceRoots(), moduleSources.getTestSourceRoots(), chunkBaseDir, genOptions), 1);
+ add(new CleanModule(chunk), 1);
+
+ ChunkBuildExtension.process(this, chunk, genOptions);
+ }
+
+ private static Generator createBootclasspath(ModuleChunk chunk) {
+ final Path bootclasspath = new Path(BuildProperties.getBootClasspathProperty(chunk.getName()));
+ bootclasspath.add(new Comment(CompilerBundle.message("generated.ant.build.bootclasspath.comment")));
+ return bootclasspath;
+ }
+
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/CleanModule.java b/java/compiler/impl/src/com/intellij/compiler/ant/CleanModule.java
new file mode 100644
index 0000000..0b27fac
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/CleanModule.java
@@ -0,0 +1,34 @@
+/*
+ * 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 com.intellij.compiler.ant;
+
+import com.intellij.compiler.ant.taskdefs.Delete;
+import com.intellij.compiler.ant.taskdefs.Target;
+import com.intellij.openapi.compiler.CompilerBundle;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 24, 2004
+ */
+public class CleanModule extends Target {
+ public CleanModule(ModuleChunk chunk) {
+ super(BuildProperties.getModuleCleanTargetName(chunk.getName()), null,
+ CompilerBundle.message("generated.ant.build.cleanup.module.task.comment"), null);
+ final String chunkName = chunk.getName();
+ add(new Delete(BuildProperties.propertyRef(BuildProperties.getOutputPathProperty(chunkName))));
+ add(new Delete(BuildProperties.propertyRef(BuildProperties.getOutputPathForTestsProperty(chunkName))));
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/CleanProject.java b/java/compiler/impl/src/com/intellij/compiler/ant/CleanProject.java
new file mode 100644
index 0000000..5613da9
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/CleanProject.java
@@ -0,0 +1,54 @@
+/*
+ * 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 com.intellij.compiler.ant;
+
+import com.intellij.compiler.ant.artifacts.ArtifactsGenerator;
+import com.intellij.compiler.ant.taskdefs.Target;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 24, 2004
+ */
+public class CleanProject extends Generator {
+ private final Target myTarget;
+
+ public CleanProject(Project project, @NotNull GenerationOptions genOptions, @NotNull ArtifactsGenerator artifactsGenerator) {
+ List<String> dependencies = new ArrayList<String>();
+ final ModuleChunk[] chunks = genOptions.getModuleChunks();
+ for (ModuleChunk chunk : chunks) {
+ dependencies.add(BuildProperties.getModuleCleanTargetName(chunk.getName()));
+ }
+ dependencies.addAll(artifactsGenerator.getCleanTargetNames());
+ for (ChunkBuildExtension extension : ChunkBuildExtension.EP_NAME.getExtensions()) {
+ dependencies.addAll(extension.getCleanTargetNames(project, genOptions));
+ }
+ myTarget = new Target(BuildProperties.TARGET_CLEAN, StringUtil.join(dependencies, ", "),
+ CompilerBundle.message("generated.ant.build.clean.all.task.comment"), null);
+ }
+
+ public void generate(PrintWriter out) throws IOException {
+ myTarget.generate(out);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/Comment.java b/java/compiler/impl/src/com/intellij/compiler/ant/Comment.java
new file mode 100644
index 0000000..146d256
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/Comment.java
@@ -0,0 +1,59 @@
+/*
+ * 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 com.intellij.compiler.ant;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 19, 2004
+ */
+public class Comment extends Generator{
+ private final String myComment;
+ private final Generator myCommentedData;
+
+ public Comment(String comment) {
+ this(comment, null);
+ }
+
+ public Comment(Generator commentedData) {
+ this(null, commentedData);
+ }
+
+ public Comment(String comment, Generator commentedData) {
+ myComment = comment;
+ myCommentedData = commentedData;
+ }
+
+ public void generate(PrintWriter out) throws IOException {
+ if (myComment != null) {
+ out.print("<!-- ");
+ out.print(myComment);
+ out.print(" -->");
+ if (myCommentedData != null) {
+ crlf(out);
+ }
+ }
+ if (myCommentedData != null) {
+ out.print("<!-- ");
+ crlf(out);
+ myCommentedData.generate(out);
+ crlf(out);
+ out.print(" -->");
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/CompileModuleChunkTarget.java b/java/compiler/impl/src/com/intellij/compiler/ant/CompileModuleChunkTarget.java
new file mode 100644
index 0000000..e08040b
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/CompileModuleChunkTarget.java
@@ -0,0 +1,213 @@
+/*
+ * 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 com.intellij.compiler.ant;
+
+import com.intellij.compiler.ant.taskdefs.*;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ProjectFileIndex;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NonNls;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 19, 2004
+ */
+public class CompileModuleChunkTarget extends CompositeGenerator {
+
+ public CompileModuleChunkTarget(final Project project,
+ ModuleChunk moduleChunk,
+ VirtualFile[] sourceRoots,
+ VirtualFile[] testSourceRoots,
+ File baseDir,
+ GenerationOptions genOptions) {
+ final String moduleChunkName = moduleChunk.getName();
+ //noinspection HardCodedStringLiteral
+ final Tag compilerArgs = new Tag("compilerarg", Pair.create("line", BuildProperties.propertyRef(
+ BuildProperties.getModuleChunkCompilerArgsProperty(moduleChunkName))));
+ //noinspection HardCodedStringLiteral
+ final Pair<String, String> classpathRef = Pair.create("refid", BuildProperties.getClasspathProperty(moduleChunkName));
+ final Tag classpathTag = new Tag("classpath", classpathRef);
+ //noinspection HardCodedStringLiteral
+ final Tag bootclasspathTag =
+ new Tag("bootclasspath", Pair.create("refid", BuildProperties.getBootClasspathProperty(moduleChunkName)));
+ final PatternSetRef compilerExcludes = new PatternSetRef(BuildProperties.getExcludedFromCompilationProperty(moduleChunkName));
+
+ final String mainTargetName = BuildProperties.getCompileTargetName(moduleChunkName);
+ final @NonNls String productionTargetName = mainTargetName + ".production";
+ final @NonNls String testsTargetName = mainTargetName + ".tests";
+
+ final ChunkCustomCompilerExtension[] customCompilers = moduleChunk.getCustomCompilers();
+ final String customCompilersDependency = customCompilers.length != 0 || genOptions.enableFormCompiler ?
+ BuildProperties.TARGET_REGISTER_CUSTOM_COMPILERS : "";
+ final int modulesCount = moduleChunk.getModules().length;
+ Target mainTarget = new Target(mainTargetName, productionTargetName + "," + testsTargetName,
+ CompilerBundle.message("generated.ant.build.compile.modules.main.target.comment", modulesCount,
+ moduleChunkName), null);
+ String dependenciesProduction = getChunkDependenciesString(moduleChunk);
+ if (customCompilersDependency.length() > 0) {
+ if (dependenciesProduction != null && dependenciesProduction.length() > 0) {
+ dependenciesProduction = customCompilersDependency + "," + dependenciesProduction;
+ }
+ else {
+ dependenciesProduction = customCompilersDependency;
+ }
+ }
+ Target productionTarget = new Target(productionTargetName, dependenciesProduction,
+ CompilerBundle.message("generated.ant.build.compile.modules.production.classes.target.comment",
+ modulesCount, moduleChunkName), null);
+ String dependenciesTests = (customCompilersDependency.length() != 0 ? customCompilersDependency + "," : "") + productionTargetName;
+ Target testsTarget = new Target(testsTargetName, dependenciesTests,
+ CompilerBundle.message("generated.ant.build.compile.modules.tests.target.comment", modulesCount,
+ moduleChunkName), BuildProperties.PROPERTY_SKIP_TESTS);
+
+ if (sourceRoots.length > 0) {
+ final String outputPathRef = BuildProperties.propertyRef(BuildProperties.getOutputPathProperty(moduleChunkName));
+ final Tag srcTag = new Tag("src", Pair.create("refid", BuildProperties.getSourcepathProperty(moduleChunkName)));
+ productionTarget.add(new Mkdir(outputPathRef));
+ createCustomCompilerTasks(project, moduleChunk, genOptions, false, customCompilers, compilerArgs, bootclasspathTag,
+ classpathTag, compilerExcludes, srcTag, outputPathRef, productionTarget);
+ if (customCompilers.length == 0 || genOptions.enableFormCompiler) {
+ final Javac javac = new Javac(genOptions, moduleChunk, outputPathRef);
+ javac.add(compilerArgs);
+ javac.add(bootclasspathTag);
+ javac.add(classpathTag);
+ javac.add(srcTag);
+ javac.add(compilerExcludes);
+ productionTarget.add(javac);
+ }
+ productionTarget.add(createCopyTask(project, moduleChunk, sourceRoots, outputPathRef, baseDir, genOptions));
+ }
+
+ if (testSourceRoots.length > 0) {
+
+ final String testOutputPathRef = BuildProperties.propertyRef(BuildProperties.getOutputPathForTestsProperty(moduleChunkName));
+ final Tag srcTag = new Tag("src", Pair.create("refid", BuildProperties.getTestSourcepathProperty(moduleChunkName)));
+ final Pair<String, String> testClasspathRef = Pair.create("refid", BuildProperties.getTestClasspathProperty(moduleChunkName));
+ final Tag testClassPath = new Tag("classpath", testClasspathRef);
+ testsTarget.add(new Mkdir(testOutputPathRef));
+ createCustomCompilerTasks(project, moduleChunk, genOptions, true, customCompilers, compilerArgs, bootclasspathTag,
+ testClassPath, compilerExcludes, srcTag, testOutputPathRef, testsTarget);
+ if (customCompilers.length == 0 || genOptions.enableFormCompiler) {
+ final Javac javac = new Javac(genOptions, moduleChunk, testOutputPathRef);
+ javac.add(compilerArgs);
+ javac.add(bootclasspathTag);
+ javac.add(testClassPath);
+ javac.add(srcTag);
+ javac.add(compilerExcludes);
+ testsTarget.add(javac);
+ }
+ testsTarget.add(createCopyTask(project, moduleChunk, testSourceRoots, testOutputPathRef, baseDir, genOptions));
+ }
+
+ add(mainTarget);
+ add(productionTarget, 1);
+ add(testsTarget, 1);
+ }
+
+ /**
+ * Create custom compiler tasks
+ *
+ * @param project the project
+ * @param moduleChunk the module chunk
+ * @param genOptions generation options
+ * @param compileTests if true tests are being compiled
+ * @param customCompilers an array of custom compilers for this chunk
+ * @param compilerArgs the javac compiler arguments
+ * @param bootclasspathTag the boot classpath element for the javac compiler
+ * @param classpathTag the classpath tag for the javac compiler
+ * @param compilerExcludes the compiler excluded tag
+ * @param srcTag the source tag
+ * @param outputPathRef the output path references
+ * @param target the target where to add custom compiler
+ */
+ private static void createCustomCompilerTasks(Project project,
+ ModuleChunk moduleChunk,
+ GenerationOptions genOptions,
+ boolean compileTests,
+ ChunkCustomCompilerExtension[] customCompilers,
+ Tag compilerArgs,
+ Tag bootclasspathTag,
+ Tag classpathTag,
+ PatternSetRef compilerExcludes,
+ Tag srcTag,
+ String outputPathRef,
+ Target target) {
+ if (customCompilers.length > 1) {
+ target.add(new Tag("fail", Pair.create("message", CompilerBundle.message(
+ "generated.ant.build.compile.modules.fail.custom.compilers"))));
+ }
+ for (ChunkCustomCompilerExtension ext : customCompilers) {
+ ext.generateCustomCompile(project, moduleChunk, genOptions, compileTests, target, compilerArgs, bootclasspathTag,
+ classpathTag, compilerExcludes, srcTag, outputPathRef);
+ }
+ }
+
+ private static String getChunkDependenciesString(ModuleChunk moduleChunk) {
+ final StringBuffer moduleDependencies = new StringBuffer();
+ final ModuleChunk[] dependencies = moduleChunk.getDependentChunks();
+ for (int idx = 0; idx < dependencies.length; idx++) {
+ final ModuleChunk dependency = dependencies[idx];
+ if (idx > 0) {
+ moduleDependencies.append(",");
+ }
+ moduleDependencies.append(BuildProperties.getCompileTargetName(dependency.getName()));
+ }
+ return moduleDependencies.toString();
+ }
+
+ private static Generator createCopyTask(final Project project,
+ ModuleChunk chunk,
+ VirtualFile[] sourceRoots,
+ String toDir,
+ File baseDir,
+ final GenerationOptions genOptions) {
+ //noinspection HardCodedStringLiteral
+ final Tag filesSelector = new Tag("type", Pair.create("type", "file"));
+ final PatternSetRef excludes = CompilerExcludes.isAvailable(project) ? new PatternSetRef(
+ BuildProperties.getExcludedFromCompilationProperty(chunk.getName())) : null;
+ final PatternSetRef resourcePatternsPatternSet = new PatternSetRef(BuildProperties.PROPERTY_COMPILER_RESOURCE_PATTERNS);
+ final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
+ final CompositeGenerator composite = new CompositeGenerator();
+ final Map<String, Copy> outputDirToTaskMap = new HashMap<String, Copy>();
+ for (final VirtualFile root : sourceRoots) {
+ final String packagePrefix = fileIndex.getPackageNameByDirectory(root);
+ final String targetDir =
+ packagePrefix != null && packagePrefix.length() > 0 ? toDir + "/" + packagePrefix.replace('.', '/') : toDir;
+ Copy copy = outputDirToTaskMap.get(targetDir);
+ if (copy == null) {
+ copy = new Copy(targetDir);
+ outputDirToTaskMap.put(targetDir, copy);
+ composite.add(copy);
+ }
+ final FileSet fileSet = new FileSet(
+ GenerationUtils.toRelativePath(root, baseDir, BuildProperties.getModuleChunkBasedirProperty(chunk), genOptions));
+ fileSet.add(resourcePatternsPatternSet);
+ fileSet.add(filesSelector);
+ if (excludes != null) {
+ fileSet.add(excludes);
+ }
+ copy.add(fileSet);
+ }
+ return composite;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/CompilerExcludes.java b/java/compiler/impl/src/com/intellij/compiler/ant/CompilerExcludes.java
new file mode 100644
index 0000000..538fee9
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/CompilerExcludes.java
@@ -0,0 +1,74 @@
+/*
+ * 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 com.intellij.compiler.ant;
+
+import com.intellij.compiler.CompilerConfiguration;
+import com.intellij.compiler.CompilerConfigurationImpl;
+import com.intellij.compiler.ant.taskdefs.Exclude;
+import com.intellij.compiler.ant.taskdefs.PatternSet;
+import com.intellij.openapi.compiler.options.ExcludeEntryDescription;
+import com.intellij.openapi.project.Project;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 19, 2004
+ */
+public class CompilerExcludes extends Generator {
+ private final PatternSet myPatternSet;
+
+ public CompilerExcludes(Project project, GenerationOptions genOptions) {
+ final CompilerConfigurationImpl compilerConfiguration = (CompilerConfigurationImpl)CompilerConfiguration.getInstance(project);
+ final ExcludeEntryDescription[] excludeEntryDescriptions =
+ compilerConfiguration.getExcludedEntriesConfiguration().getExcludeEntryDescriptions();
+ myPatternSet = new PatternSet(BuildProperties.PROPERTY_COMPILER_EXCLUDES);
+ for (final ExcludeEntryDescription entry : excludeEntryDescriptions) {
+ final String path = GenerationUtils
+ .toRelativePath(entry.getVirtualFile(), BuildProperties.getProjectBaseDir(project), BuildProperties.getProjectBaseDirProperty(),
+ genOptions);
+ if (path == null) {
+ // entry is invalid, skip it
+ continue;
+ }
+ if (entry.isFile()) {
+ myPatternSet.add(new Exclude(path));
+ }
+ else {
+ if (entry.isIncludeSubdirectories()) {
+ myPatternSet.add(new Exclude(path + "/**"));
+ }
+ else {
+ myPatternSet.add(new Exclude(path + "/*"));
+ }
+ }
+ }
+ }
+
+
+ public void generate(PrintWriter out) throws IOException {
+ myPatternSet.generate(out);
+ }
+
+ public static boolean isAvailable(Project project) {
+ final CompilerConfigurationImpl compilerConfiguration = (CompilerConfigurationImpl)CompilerConfiguration.getInstance(project);
+ final ExcludeEntryDescription[] excludeEntryDescriptions =
+ compilerConfiguration.getExcludedEntriesConfiguration().getExcludeEntryDescriptions();
+ return excludeEntryDescriptions.length > 0;
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/CompilerResourcePatterns.java b/java/compiler/impl/src/com/intellij/compiler/ant/CompilerResourcePatterns.java
new file mode 100644
index 0000000..d63966b
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/CompilerResourcePatterns.java
@@ -0,0 +1,55 @@
+/*
+ * 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 com.intellij.compiler.ant;
+
+import com.intellij.compiler.CompilerConfiguration;
+import com.intellij.compiler.CompilerConfigurationImpl;
+import com.intellij.compiler.ant.taskdefs.Exclude;
+import com.intellij.compiler.ant.taskdefs.Include;
+import com.intellij.compiler.ant.taskdefs.PatternSet;
+import com.intellij.openapi.project.Project;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 19, 2004
+ */
+public class CompilerResourcePatterns extends Generator {
+ private final PatternSet myPatternSet;
+
+ public CompilerResourcePatterns(Project project) {
+ final CompilerConfigurationImpl compilerConfiguration = (CompilerConfigurationImpl)CompilerConfiguration.getInstance(project);
+ final String[] patterns = compilerConfiguration.getResourceFilePatterns();
+ myPatternSet = new PatternSet(BuildProperties.PROPERTY_COMPILER_RESOURCE_PATTERNS);
+ for (String pattern : patterns) {
+ if (CompilerConfigurationImpl.isPatternNegated(pattern)) {
+ myPatternSet.add(new Exclude("**/" + pattern.substring(1)));
+ }
+ else {
+ myPatternSet.add(new Include("**/" + pattern));
+ }
+ }
+ }
+
+
+ public void generate(PrintWriter out) throws IOException {
+ myPatternSet.generate(out);
+ }
+
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/GenerateAntApplication.java b/java/compiler/impl/src/com/intellij/compiler/ant/GenerateAntApplication.java
new file mode 100644
index 0000000..5a2fa70
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/GenerateAntApplication.java
@@ -0,0 +1,125 @@
+/*
+ * 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 com.intellij.compiler.ant;
+
+import com.intellij.codeInspection.InspectionsBundle;
+import com.intellij.compiler.actions.GenerateAntBuildAction;
+import com.intellij.openapi.application.PathManager;
+import com.intellij.openapi.application.ex.ApplicationEx;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ex.ProjectManagerEx;
+import com.intellij.openapi.roots.impl.DirectoryIndex;
+import com.intellij.openapi.roots.impl.DirectoryIndexImpl;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+
+import javax.swing.*;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author max
+ */
+public class GenerateAntApplication {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.ex.InspectionApplication");
+
+ public String myProjectPath = null;
+ public String myOutPath = null;
+ private Project myProject;
+ private int myVerboseLevel = 0;
+
+ public void startup() {
+ if (myProjectPath == null || myOutPath == null) {
+ GenerateAntMain.printHelp();
+ }
+
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ ApplicationEx application = ApplicationManagerEx.getApplicationEx();
+ try {
+ logMessage(0, "Starting app... ");
+ application.doNotSave();
+ application.load(PathManager.getOptionsPath());
+ logMessageLn(0, "done");
+
+ GenerateAntApplication.this.run();
+ }
+ catch (Exception e) {
+ GenerateAntApplication.LOG.error(e);
+ }
+ finally {
+ application.exit(true);
+ }
+ }
+ });
+ }
+
+ public void run() {
+ try {
+ myProjectPath = myProjectPath.replace(File.separatorChar, '/');
+ VirtualFile vfsProject = LocalFileSystem.getInstance().findFileByPath(myProjectPath);
+ if (vfsProject == null) {
+ logError(InspectionsBundle.message("inspection.application.file.cannot.be.found", myProjectPath));
+ GenerateAntMain.printHelp();
+ }
+
+ logMessage(0, "Loading project...");
+ myProject = ProjectManagerEx.getInstanceEx().loadProject(myProjectPath);
+
+ DirectoryIndexImpl dirIndex = (DirectoryIndexImpl)DirectoryIndex.getInstance(myProject);
+ dirIndex.initialize();
+
+ logMessageLn(0, " done");
+
+ GenerateAntBuildAction.generateSingleFileBuild(myProject,
+ new GenerationOptionsImpl(myProject, true, true, false, false, new String[] {}),
+ new File("/Users/max/build/build.xml"),
+ new File("/Users/max/build/build.properties"));
+
+ logMessage(0, "Hello!");
+ }
+ catch (IOException e) {
+ GenerateAntApplication.LOG.error(e);
+ GenerateAntMain.printHelp();
+ }
+ catch (Exception e) {
+ GenerateAntApplication.LOG.error(e);
+ System.exit(1);
+ }
+ }
+
+ public void setVerboseLevel(int verboseLevel) {
+ myVerboseLevel = verboseLevel;
+ }
+
+ private void logMessage(int minVerboseLevel, String message) {
+ if (myVerboseLevel >= minVerboseLevel) {
+ System.out.print(message);
+ }
+ }
+
+ private void logError(String message) {
+ System.err.println(message);
+ }
+
+ private void logMessageLn(int minVerboseLevel, String message) {
+ if (myVerboseLevel >= minVerboseLevel) {
+ System.out.println(message);
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/GenerateAntMain.java b/java/compiler/impl/src/com/intellij/compiler/ant/GenerateAntMain.java
new file mode 100644
index 0000000..b705a97
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/GenerateAntMain.java
@@ -0,0 +1,48 @@
+/*
+ * 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 com.intellij.compiler.ant;
+
+import com.intellij.openapi.application.ApplicationStarter;
+import org.jetbrains.annotations.NonNls;
+
+/**
+ * @author max
+ */
+public class GenerateAntMain implements ApplicationStarter {
+ private GenerateAntApplication myApplication;
+
+ @NonNls
+ public String getCommandName() {
+ return "ant";
+ }
+
+ public void premain(String[] args) {
+ System.setProperty("idea.load.plugins", "false");
+ myApplication = new GenerateAntApplication();
+
+ myApplication.myProjectPath = args[1];
+ myApplication.myOutPath = args[2];
+ }
+
+ public void main(String[] args) {
+ myApplication.startup();
+ }
+
+ public static void printHelp() {
+ System.out.println("Wrong params");
+ System.exit(1);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/GenerationOptionsImpl.java b/java/compiler/impl/src/com/intellij/compiler/ant/GenerationOptionsImpl.java
new file mode 100644
index 0000000..61158d5
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/GenerationOptionsImpl.java
@@ -0,0 +1,326 @@
+/*
+ * 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 com.intellij.compiler.ant;
+
+import com.intellij.application.options.ReplacePathToMacroMap;
+import com.intellij.compiler.ModuleCompilerUtil;
+import com.intellij.openapi.application.PathMacros;
+import com.intellij.openapi.application.PathManager;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.ProjectJdkTable;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.roots.CompilerModuleExtension;
+import com.intellij.openapi.roots.OrderRootType;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.util.Chunk;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.graph.CachingSemiGraph;
+import com.intellij.util.graph.Graph;
+import com.intellij.util.graph.GraphGenerator;
+
+import javax.naming.OperationNotSupportedException;
+import java.io.File;
+import java.util.*;
+
+/**
+ * Implementation class for Ant generation options
+ *
+ * @author Eugene Zhuravlev
+ * Date: Mar 25, 2004
+ */
+public class GenerationOptionsImpl extends GenerationOptions {
+
+ /**
+ * from absolute path to macro substitutions
+ */
+ private final ReplacePathToMacroMap myMacroReplacementMap;
+ /**
+ * from absolute path to macro substitutions
+ */
+ private final Map<String, String> myOutputUrlToPropertyRefMap;
+ /**
+ * module chunks
+ */
+ private final ModuleChunk[] myModuleChunks;
+ /**
+ * the project to be converted
+ */
+ private final Project myProject;
+ private final boolean myGenerateIdeaHomeProperty;
+ private final String myOutputFileName;
+ private Set<String> myJdkUrls;
+ /**
+ * Custom compilers used in the ant build.
+ */
+ private final Set<ChunkCustomCompilerExtension> myCustomCompilers = new HashSet<ChunkCustomCompilerExtension>();
+ /**
+ * map from modules to chunks
+ */
+ private final Map<Module, ModuleChunk> myModuleToChunkMap = new HashMap<Module, ModuleChunk>();
+
+ /**
+ * A constructor
+ *
+ * @param project a project to generate
+ * @param generateSingleFile a value of corresponding option
+ * @param enableFormCompiler a value of corresponding option
+ * @param backupPreviouslyGeneratedFiles a value of corresponding option
+ * @param forceTargetJdk a value of corresponding option
+ * @param inlineRuntimeClasspath if true a runtiem classpaths are inlined
+ * @param representativeModuleNames a module name that represents module chunks.
+ * @param outputFileName a name for the output file
+ */
+ public GenerationOptionsImpl(Project project,
+ boolean generateSingleFile,
+ boolean enableFormCompiler,
+ boolean backupPreviouslyGeneratedFiles,
+ boolean forceTargetJdk,
+ boolean inlineRuntimeClasspath,
+ boolean generateIdeaHomeProperty,
+ String[] representativeModuleNames, String outputFileName) {
+ super(forceTargetJdk, generateSingleFile, enableFormCompiler, backupPreviouslyGeneratedFiles, inlineRuntimeClasspath);
+ myProject = project;
+ myGenerateIdeaHomeProperty = generateIdeaHomeProperty;
+ myOutputFileName = outputFileName;
+ myMacroReplacementMap = createReplacementMap();
+ myModuleChunks = createModuleChunks(representativeModuleNames);
+ myOutputUrlToPropertyRefMap = createOutputUrlToPropertyRefMap(myModuleChunks);
+ }
+
+ /**
+ * A constructor
+ *
+ * @param project a project to generate
+ * @param forceTargetJdk a value of corresponding option
+ * @param generateSingleFile a value of corresponding option
+ * @param enableFormCompiler a value of corresponding option
+ * @param backupPreviouslyGeneratedFiles a value of corresponding option
+ * @param representativeModuleNames a module name that represents module chunks.
+ */
+ @Deprecated
+ public GenerationOptionsImpl(Project project,
+ boolean generateSingleFile,
+ boolean enableFormCompiler,
+ boolean backupPreviouslyGeneratedFiles,
+ boolean forceTargetJdk,
+ String[] representativeModuleNames) {
+ this(project, forceTargetJdk, generateSingleFile, enableFormCompiler, backupPreviouslyGeneratedFiles, false, false,
+ representativeModuleNames, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isIdeaHomeGenerated() {
+ return myGenerateIdeaHomeProperty;
+ }
+
+ public String getBuildFileName() {
+ return getOutputFileName() + ".xml";
+ }
+
+ public String getPropertiesFileName() {
+ return getOutputFileName() + ".properties";
+ }
+
+ private String getOutputFileName() {
+ if (myOutputFileName == null || myOutputFileName.length() == 0) {
+ return BuildProperties.getProjectBuildFileName(myProject);
+ }
+ return myOutputFileName;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ModuleChunk getChunkByModule(final Module module) {
+ if (myModuleToChunkMap.isEmpty()) {
+ for (ModuleChunk c : myModuleChunks) {
+ for (Module m : c.getModules()) {
+ myModuleToChunkMap.put(m, c);
+ }
+ }
+ }
+ return myModuleToChunkMap.get(module);
+ }
+
+ @Override
+ public String subsitutePathWithMacros(String path) {
+ return myMacroReplacementMap.substitute(path, SystemInfo.isFileSystemCaseSensitive);
+ }
+
+ public String getPropertyRefForUrl(String url) {
+ return myOutputUrlToPropertyRefMap.get(url);
+ }
+
+ private static ReplacePathToMacroMap createReplacementMap() {
+ final PathMacros pathMacros = PathMacros.getInstance();
+ final Set<String> macroNames = pathMacros.getUserMacroNames();
+ final ReplacePathToMacroMap map = new ReplacePathToMacroMap();
+ for (final String macroName : macroNames) {
+ map.put(GenerationUtils.normalizePath(pathMacros.getValue(macroName)),
+ BuildProperties.propertyRef(BuildProperties.getPathMacroProperty(macroName)));
+ }
+ map.put(GenerationUtils.normalizePath(PathManager.getHomePath()), BuildProperties.propertyRef(BuildProperties.PROPERTY_IDEA_HOME));
+ return map;
+ }
+
+ private static Map<String, String> createOutputUrlToPropertyRefMap(ModuleChunk[] chunks) {
+ final Map<String, String> map = new HashMap<String, String>();
+
+ for (final ModuleChunk chunk : chunks) {
+ final String outputPathRef = BuildProperties.propertyRef(BuildProperties.getOutputPathProperty(chunk.getName()));
+ final String testsOutputPathRef = BuildProperties.propertyRef(BuildProperties.getOutputPathForTestsProperty(chunk.getName()));
+
+ final Module[] modules = chunk.getModules();
+ for (final Module module : modules) {
+ final String outputPathUrl = CompilerModuleExtension.getInstance(module).getCompilerOutputUrl();
+ if (outputPathUrl != null) {
+ map.put(outputPathUrl, outputPathRef);
+ }
+ final String outputPathForTestsUrl = CompilerModuleExtension.getInstance(module).getCompilerOutputUrlForTests();
+ if (outputPathForTestsUrl != null) {
+ if (outputPathUrl == null || !outputPathForTestsUrl.equals(outputPathUrl)) {
+ map.put(outputPathForTestsUrl, testsOutputPathRef);
+ }
+ }
+ }
+ }
+ return map;
+ }
+
+ @Override
+ public ModuleChunk[] getModuleChunks() {
+ return myModuleChunks;
+ }
+
+ private ModuleChunk[] createModuleChunks(String[] representativeModuleNames) {
+ final Set<String> mainModuleNames = new HashSet<String>(Arrays.asList(representativeModuleNames));
+ final Graph<Chunk<Module>> chunkGraph = ModuleCompilerUtil.toChunkGraph(ModuleManager.getInstance(myProject).moduleGraph());
+ final Map<Chunk<Module>, ModuleChunk> map = new HashMap<Chunk<Module>, ModuleChunk>();
+ final Map<ModuleChunk, Chunk<Module>> reverseMap = new HashMap<ModuleChunk, Chunk<Module>>();
+ for (final Chunk<Module> chunk : chunkGraph.getNodes()) {
+ final Set<Module> modules = chunk.getNodes();
+ final ModuleChunk moduleChunk = new ModuleChunk(modules.toArray(new Module[modules.size()]));
+ for (final Module module : modules) {
+ if (mainModuleNames.contains(module.getName())) {
+ moduleChunk.setMainModule(module);
+ break;
+ }
+ }
+ map.put(chunk, moduleChunk);
+ reverseMap.put(moduleChunk, chunk);
+ }
+
+ final Graph<ModuleChunk> moduleChunkGraph =
+ GraphGenerator.create(CachingSemiGraph.create(new GraphGenerator.SemiGraph<ModuleChunk>() {
+ public Collection<ModuleChunk> getNodes() {
+ return map.values();
+ }
+
+ public Iterator<ModuleChunk> getIn(ModuleChunk n) {
+ final Chunk<Module> chunk = reverseMap.get(n);
+ final Iterator<Chunk<Module>> in = chunkGraph.getIn(chunk);
+ return new Iterator<ModuleChunk>() {
+ public boolean hasNext() {
+ return in.hasNext();
+ }
+
+ public ModuleChunk next() {
+ return map.get(in.next());
+ }
+
+ public void remove() {
+ new OperationNotSupportedException();
+ }
+ };
+ }
+ }));
+ final Collection<ModuleChunk> nodes = moduleChunkGraph.getNodes();
+ final ModuleChunk[] moduleChunks = nodes.toArray(new ModuleChunk[nodes.size()]);
+ for (ModuleChunk moduleChunk : moduleChunks) {
+ final Iterator<ModuleChunk> depsIterator = moduleChunkGraph.getIn(moduleChunk);
+ List<ModuleChunk> deps = new ArrayList<ModuleChunk>();
+ while (depsIterator.hasNext()) {
+ deps.add(depsIterator.next());
+ }
+ moduleChunk.setDependentChunks(deps.toArray(new ModuleChunk[deps.size()]));
+ ContainerUtil.addAll(myCustomCompilers, moduleChunk.getCustomCompilers());
+ }
+ Arrays.sort(moduleChunks, new ChunksComparator());
+ if (generateSingleFile) {
+ final File baseDir = BuildProperties.getProjectBaseDir(myProject);
+ for (ModuleChunk chunk : moduleChunks) {
+ chunk.setBaseDir(baseDir);
+ }
+ }
+ return moduleChunks;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ChunkCustomCompilerExtension[] getCustomCompilers() {
+ ChunkCustomCompilerExtension[] sorted = myCustomCompilers.toArray(new ChunkCustomCompilerExtension[myCustomCompilers.size()]);
+ Arrays.sort(sorted, ChunkCustomCompilerExtension.COMPARATOR);
+ return sorted;
+ }
+
+ Set<String> getAllJdkUrls() {
+ if (myJdkUrls != null) {
+ return myJdkUrls;
+ }
+ final Sdk[] projectJdks = ProjectJdkTable.getInstance().getAllJdks();
+ myJdkUrls = new HashSet<String>();
+ for (Sdk jdk : projectJdks) {
+ ContainerUtil.addAll(myJdkUrls, jdk.getRootProvider().getUrls(OrderRootType.CLASSES));
+ }
+ return myJdkUrls;
+ }
+
+ private static class ChunksComparator implements Comparator<ModuleChunk> {
+ final Map<ModuleChunk, Integer> myCachedLevels = new HashMap<ModuleChunk, Integer>();
+
+ public int compare(final ModuleChunk o1, final ModuleChunk o2) {
+ final int level1 = getChunkLevel(o1);
+ final int level2 = getChunkLevel(o2);
+ return (level1 == level2) ? o1.getName().compareToIgnoreCase(o2.getName()) : (level1 - level2);
+ }
+
+ private int getChunkLevel(ModuleChunk chunk) {
+ Integer level = myCachedLevels.get(chunk);
+ if (level == null) {
+ final ModuleChunk[] chunks = chunk.getDependentChunks();
+ if (chunks.length > 0) {
+ int maxLevel = 0;
+ for (ModuleChunk dependent : chunks) {
+ maxLevel = Math.max(maxLevel, getChunkLevel(dependent));
+ }
+ level = 1 + maxLevel;
+ }
+ else {
+ level = 0;
+ }
+ myCachedLevels.put(chunk, level);
+ }
+ return level.intValue();
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/IgnoredFiles.java b/java/compiler/impl/src/com/intellij/compiler/ant/IgnoredFiles.java
new file mode 100644
index 0000000..069ee94
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/IgnoredFiles.java
@@ -0,0 +1,47 @@
+/*
+ * 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 com.intellij.compiler.ant;
+
+import com.intellij.compiler.ant.taskdefs.Exclude;
+import com.intellij.compiler.ant.taskdefs.PatternSet;
+import com.intellij.openapi.fileTypes.FileTypeManager;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.StringTokenizer;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 19, 2004
+ */
+public class IgnoredFiles extends Generator{
+ private final PatternSet myPatternSet;
+
+ public IgnoredFiles() {
+ myPatternSet = new PatternSet(BuildProperties.PROPERTY_IGNORED_FILES);
+ final StringTokenizer tokenizer = new StringTokenizer(FileTypeManager.getInstance().getIgnoredFilesList(), ";", false);
+ while(tokenizer.hasMoreTokens()) {
+ final String filemask = tokenizer.nextToken();
+ myPatternSet.add(new Exclude("**/" + filemask + "/**"));
+ }
+ }
+
+
+
+ public void generate(PrintWriter out) throws IOException {
+ myPatternSet.generate(out);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/LibraryDefinitionsGeneratorFactory.java b/java/compiler/impl/src/com/intellij/compiler/ant/LibraryDefinitionsGeneratorFactory.java
new file mode 100644
index 0000000..5565f21
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/LibraryDefinitionsGeneratorFactory.java
@@ -0,0 +1,181 @@
+/*
+ * 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 com.intellij.compiler.ant;
+
+import com.intellij.compiler.ant.taskdefs.FileSet;
+import com.intellij.compiler.ant.taskdefs.Path;
+import com.intellij.compiler.ant.taskdefs.PathElement;
+import com.intellij.compiler.ant.taskdefs.PatternSetRef;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.ex.ProjectEx;
+import com.intellij.openapi.roots.ModuleRootManager;
+import com.intellij.openapi.roots.OrderRootType;
+import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.roots.libraries.LibraryTable;
+import com.intellij.openapi.vfs.JarFileSystem;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.Processor;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.util.*;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Nov 25, 2004
+ */
+public class LibraryDefinitionsGeneratorFactory {
+ private final ProjectEx myProject;
+ private final GenerationOptions myGenOptions;
+ private final Set<String> myUsedLibraries = new HashSet<String>();
+
+ public LibraryDefinitionsGeneratorFactory(ProjectEx project, GenerationOptions genOptions) {
+ myProject = project;
+ myGenOptions = genOptions;
+ final ModuleManager moduleManager = ModuleManager.getInstance(project);
+ final Module[] modules = moduleManager.getModules();
+ for (Module module : modules) {
+ ModuleRootManager.getInstance(module).orderEntries().forEachLibrary(new Processor<Library>() {
+ @Override
+ public boolean process(Library library) {
+ final String name = library.getName();
+ if (name != null) {
+ myUsedLibraries.add(name);
+ }
+ return true;
+ }
+ });
+ }
+ }
+
+ /**
+ * Create a generator for the specified libary type. It generates a list of library definitions.
+ *
+ * @param libraryTable a library table to examine
+ * @param baseDir base directory for ant build script
+ * @param comment a comment to use for the library
+ * @return the created generator or null if there is a nothing to generate
+ */
+ @Nullable
+ public Generator create(LibraryTable libraryTable, File baseDir, final String comment) {
+ final Library[] libraries = libraryTable.getLibraries();
+ if (libraries.length == 0) {
+ return null;
+ }
+
+ final CompositeGenerator gen = new CompositeGenerator();
+
+ gen.add(new Comment(comment), 1);
+ // sort libraries to ensure stable order of them.
+ TreeMap<String, Library> sortedLibs = new TreeMap<String, Library>();
+ for (final Library library : libraries) {
+ final String libraryName = library.getName();
+ if (!myUsedLibraries.contains(libraryName)) {
+ continue;
+ }
+ sortedLibs.put(BuildProperties.getLibraryPathId(libraryName), library);
+ }
+ for (final Library library : sortedLibs.values()) {
+ final String libraryName = library.getName();
+ final Path libraryPath = new Path(BuildProperties.getLibraryPathId(libraryName));
+ genLibraryContent(myProject, myGenOptions, library, baseDir, libraryPath);
+ gen.add(libraryPath, 1);
+ }
+ return gen.getGeneratorCount() > 0 ? gen : null;
+ }
+
+ /**
+ * Generate library content
+ *
+ * @param project the context project
+ * @param genOptions the generation options
+ * @param library the library which content is generated
+ * @param baseDir the base directory
+ * @param libraryPath the composite generator to update
+ */
+ public static void genLibraryContent(final ProjectEx project,
+ final GenerationOptions genOptions,
+ final Library library,
+ final File baseDir,
+ final CompositeGenerator libraryPath) {
+ genLibraryContent(genOptions, library, OrderRootType.CLASSES, baseDir, libraryPath);
+ }
+
+ public static void genLibraryContent(final GenerationOptions genOptions,
+ final Library library,
+ final OrderRootType rootType, final File baseDir,
+ final CompositeGenerator libraryPath) {
+ if (genOptions.expandJarDirectories) {
+ final VirtualFile[] files = library.getFiles(rootType);
+ // note that it is assumed that directory entries inside library path are unordered
+ TreeSet<String> visitedPaths = new TreeSet<String>();
+ for (final VirtualFile file : files) {
+ final String path = GenerationUtils
+ .toRelativePath(file, baseDir, BuildProperties.getProjectBaseDirProperty(), genOptions);
+ visitedPaths.add(path);
+ }
+ for (final String path : visitedPaths) {
+ libraryPath.add(new PathElement(path));
+ }
+ }
+ else {
+ TreeSet<String> urls = new TreeSet<String>(Arrays.asList(library.getUrls(rootType)));
+ for (String url : urls) {
+ File file = fileFromUrl(url);
+ final String path = GenerationUtils
+ .toRelativePath(file.getPath(), baseDir, BuildProperties.getProjectBaseDirProperty(), genOptions);
+ if (url.startsWith(JarFileSystem.PROTOCOL_PREFIX)) {
+ libraryPath.add(new PathElement(path));
+ }
+ else if (url.startsWith(LocalFileSystem.PROTOCOL_PREFIX)) {
+ if (library.isJarDirectory(url, rootType)) {
+ final FileSet fileSet = new FileSet(path);
+ fileSet.add(new PatternSetRef(BuildProperties.PROPERTY_LIBRARIES_PATTERNS));
+ libraryPath.add(fileSet);
+ }
+ else {
+ libraryPath.add(new PathElement(path));
+ }
+ }
+ else {
+ throw new IllegalStateException("Unknown url type: " + url);
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets file from jar of file URL
+ *
+ * @param url an url to parse
+ * @return
+ */
+ private static File fileFromUrl(String url) {
+ final String filePart;
+ if (url.startsWith(JarFileSystem.PROTOCOL_PREFIX) && url.endsWith(JarFileSystem.JAR_SEPARATOR)) {
+ filePart = url.substring(JarFileSystem.PROTOCOL_PREFIX.length(), url.length() - JarFileSystem.JAR_SEPARATOR.length());
+ }
+ else if (url.startsWith(LocalFileSystem.PROTOCOL_PREFIX)) {
+ filePart = url.substring(JarFileSystem.PROTOCOL_PREFIX.length());
+ }
+ else {
+ throw new IllegalArgumentException("Unknown url type: " + url);
+ }
+ return new File(filePart.replace('/', File.separatorChar));
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/LibraryPatterns.java b/java/compiler/impl/src/com/intellij/compiler/ant/LibraryPatterns.java
new file mode 100644
index 0000000..ee6ada4
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/LibraryPatterns.java
@@ -0,0 +1,65 @@
+/*
+ * 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 com.intellij.compiler.ant;
+
+import com.intellij.compiler.ant.taskdefs.Include;
+import com.intellij.compiler.ant.taskdefs.PatternSet;
+import com.intellij.openapi.fileTypes.*;
+import com.intellij.openapi.project.Project;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * Library pattern generation (based on {@link FileTypes#ARCHIVE} file type).
+ */
+public class LibraryPatterns extends Generator {
+ /**
+ * A pattern set to use
+ */
+ private final PatternSet myPatternSet;
+
+ /**
+ * A constructor
+ *
+ * @param project a context project
+ * @param genOptions a generation options
+ */
+ public LibraryPatterns(Project project, GenerationOptions genOptions) {
+ myPatternSet = new PatternSet(BuildProperties.PROPERTY_LIBRARIES_PATTERNS);
+ final FileType type = FileTypes.ARCHIVE;
+ final List<FileNameMatcher> matchers = FileTypeManager.getInstance().getAssociations(type);
+ for (FileNameMatcher m : matchers) {
+ if (m instanceof ExactFileNameMatcher) {
+ final String path = GenerationUtils
+ .toRelativePath(m.getPresentableString(), BuildProperties.getProjectBaseDir(project), BuildProperties.getProjectBaseDirProperty(),
+ genOptions);
+ myPatternSet.add(new Include(path));
+ }
+ else {
+ myPatternSet.add(new Include(m.getPresentableString()));
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void generate(final PrintWriter out) throws IOException {
+ myPatternSet.generate(out);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/ModuleChunkAntProject.java b/java/compiler/impl/src/com/intellij/compiler/ant/ModuleChunkAntProject.java
new file mode 100644
index 0000000..8408931
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/ModuleChunkAntProject.java
@@ -0,0 +1,45 @@
+/*
+ * 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 com.intellij.compiler.ant;
+
+import com.intellij.compiler.ant.taskdefs.AntProject;
+import com.intellij.compiler.ant.taskdefs.Dirname;
+import com.intellij.openapi.project.Project;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 16, 2004
+ */
+public class ModuleChunkAntProject extends Generator{
+ private final AntProject myAntProject;
+
+ public ModuleChunkAntProject(Project project, ModuleChunk moduleChunk, GenerationOptions genOptions) {
+ myAntProject = new AntProject(BuildProperties.getModuleChunkBuildFileName(moduleChunk), BuildProperties.getCompileTargetName(moduleChunk.getName()));
+ myAntProject.add(new Dirname(BuildProperties.getModuleChunkBasedirProperty(moduleChunk), BuildProperties.propertyRef("ant.file." + BuildProperties.getModuleChunkBuildFileName(moduleChunk))));
+ myAntProject.add(new ChunkBuild(project, moduleChunk, genOptions));
+
+ }
+
+ public void generate(PrintWriter out) throws IOException {
+ writeXmlHeader(out);
+ myAntProject.generate(out);
+ }
+
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/ModuleChunkClasspath.java b/java/compiler/impl/src/com/intellij/compiler/ant/ModuleChunkClasspath.java
new file mode 100644
index 0000000..0883913
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/ModuleChunkClasspath.java
@@ -0,0 +1,364 @@
+/*
+ * 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 com.intellij.compiler.ant;
+
+import com.intellij.compiler.ant.taskdefs.Path;
+import com.intellij.compiler.ant.taskdefs.PathElement;
+import com.intellij.compiler.ant.taskdefs.PathRef;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.ex.ProjectEx;
+import com.intellij.openapi.roots.*;
+import com.intellij.openapi.vfs.JarFileSystem;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.Processor;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.OrderedSet;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Generator of module chunk classspath. The class used to generate both runtime and compile time classpaths.
+ *
+ * @author Eugene Zhuravlev
+ */
+public class ModuleChunkClasspath extends Path {
+
+ /**
+ * A constructor
+ *
+ * @param chunk a chunk to process
+ * @param genOptions a generation options
+ * @param generateRuntimeClasspath if true, runtime classpath is being generated. Otherwise a compile time classpath is constructed
+ * @param generateTestClasspath if true, a test classpath is generated.
+ */
+ @SuppressWarnings({"unchecked"})
+ public ModuleChunkClasspath(final ModuleChunk chunk,
+ final GenerationOptions genOptions,
+ final boolean generateRuntimeClasspath,
+ final boolean generateTestClasspath) {
+ super(generateClasspathName(chunk, generateRuntimeClasspath, generateTestClasspath));
+
+ final OrderedSet<ClasspathItem> pathItems =
+ new OrderedSet<ClasspathItem>();
+ final String moduleChunkBasedirProperty = BuildProperties.getModuleChunkBasedirProperty(chunk);
+ final Module[] modules = chunk.getModules();
+ // processed chunks (used only in runtime classpath), every chunk is referenced exactly once
+ final Set<ModuleChunk> processedChunks = new HashSet<ModuleChunk>();
+ // pocessed modules
+ final Set<Module> processedModules = new HashSet<Module>();
+ for (final Module module : modules) {
+ new Object() {
+ /**
+ * Process the module. The logic is different for compile-time case and runtime case.
+ * In the case of runtime, only directly referenced objects are included in classpath.
+ * Indirectly referenced are
+ *
+ * @param module a module to process.
+ * @param dependencyLevel is increased with every of recursion.
+ * @param isModuleExported if true the module is exported from the previous level
+ */
+ public void processModule(final Module module, final int dependencyLevel, final boolean isModuleExported) {
+ if (processedModules.contains(module)) {
+ // the module is already processed, nothing should be done
+ return;
+ }
+ if (dependencyLevel > 1 && !isModuleExported && !(genOptions.inlineRuntimeClasspath && generateRuntimeClasspath)) {
+ // the module is not in exports and it is not directly included skip it in the case of library pathgeneration
+ return;
+ }
+ processedModules.add(module);
+ final ProjectEx project = (ProjectEx)chunk.getProject();
+ final File baseDir = BuildProperties.getProjectBaseDir(project);
+ OrderEnumerator enumerator = ModuleRootManager.getInstance(module).orderEntries();
+ if (generateRuntimeClasspath) {
+ enumerator = enumerator.runtimeOnly();
+ }
+ else {
+ enumerator = enumerator.compileOnly();
+ if (!generateTestClasspath && (dependencyLevel == 0 || chunk.contains(module))) {
+ // this is the entry for outpath of the currently processed module
+ // the root module is never included
+ enumerator = enumerator.withoutModuleSourceEntries();
+ }
+ }
+ if (!generateTestClasspath) {
+ enumerator = enumerator.productionOnly();
+ }
+ enumerator.forEach(new Processor<OrderEntry>() {
+ @Override
+ public boolean process(OrderEntry orderEntry) {
+ if (!orderEntry.isValid()) {
+ return true;
+ }
+
+ if (!generateRuntimeClasspath &&
+ !(orderEntry instanceof ModuleOrderEntry) &&
+ !(orderEntry instanceof ModuleSourceOrderEntry)) {
+ // needed for compilation classpath only
+ final boolean isExported = (orderEntry instanceof ExportableOrderEntry) && ((ExportableOrderEntry)orderEntry).isExported();
+ if (dependencyLevel > 0 && !isExported) {
+ // non-exported dependencies are excluded and not processed
+ return true;
+ }
+ }
+
+ if (orderEntry instanceof JdkOrderEntry) {
+ if (genOptions.forceTargetJdk && !generateRuntimeClasspath) {
+ pathItems
+ .add(new PathRefItem(BuildProperties.propertyRef(BuildProperties.getModuleChunkJdkClasspathProperty(chunk.getName()))));
+ }
+ }
+ else if (orderEntry instanceof ModuleOrderEntry) {
+ final ModuleOrderEntry moduleOrderEntry = (ModuleOrderEntry)orderEntry;
+ final Module dependentModule = moduleOrderEntry.getModule();
+ if (!chunk.contains(dependentModule)) {
+ if (generateRuntimeClasspath && !genOptions.inlineRuntimeClasspath) {
+ // in case of runtime classpath, just an referenced to corresponding classpath is created
+ final ModuleChunk depChunk = genOptions.getChunkByModule(dependentModule);
+ if (!processedChunks.contains(depChunk)) {
+ // chunk references are included in the runtime classpath only once
+ processedChunks.add(depChunk);
+ String property = generateTestClasspath ? BuildProperties.getTestRuntimeClasspathProperty(depChunk.getName())
+ : BuildProperties.getRuntimeClasspathProperty(depChunk.getName());
+ pathItems.add(new PathRefItem(property));
+ }
+ }
+ else {
+ // in case of compile classpath or inlined runtime classpath,
+ // the referenced module is processed recursively
+ processModule(dependentModule, dependencyLevel + 1, moduleOrderEntry.isExported());
+ }
+ }
+ }
+ else if (orderEntry instanceof LibraryOrderEntry) {
+ final LibraryOrderEntry libraryOrderEntry = (LibraryOrderEntry)orderEntry;
+ final String libraryName = libraryOrderEntry.getLibraryName();
+ if (((LibraryOrderEntry)orderEntry).isModuleLevel()) {
+ CompositeGenerator gen = new CompositeGenerator();
+ gen.setHasLeadingNewline(false);
+ LibraryDefinitionsGeneratorFactory.genLibraryContent(project, genOptions, libraryOrderEntry.getLibrary(), baseDir, gen);
+ pathItems.add(new GeneratorItem(libraryName, gen));
+ }
+ else {
+ pathItems.add(new PathRefItem(BuildProperties.getLibraryPathId(libraryName)));
+ }
+ }
+ else if (orderEntry instanceof ModuleSourceOrderEntry) {
+ // Module source entry?
+ for (String url : getCompilationClasses(module, ((GenerationOptionsImpl)genOptions), generateRuntimeClasspath,
+ generateTestClasspath, dependencyLevel == 0)) {
+ if (url.endsWith(JarFileSystem.JAR_SEPARATOR)) {
+ url = url.substring(0, url.length() - JarFileSystem.JAR_SEPARATOR.length());
+ }
+ final String propertyRef = genOptions.getPropertyRefForUrl(url);
+ if (propertyRef != null) {
+ pathItems.add(new PathElementItem(propertyRef));
+ }
+ else {
+ final String path = VirtualFileManager.extractPath(url);
+ pathItems.add(new PathElementItem(
+ GenerationUtils.toRelativePath(path, chunk.getBaseDir(), moduleChunkBasedirProperty, genOptions)));
+ }
+ }
+ }
+ else {
+ // Unknown order entry type. If it is actually encountered, extension point should be implemented
+ pathItems.add(new GeneratorItem(orderEntry.getClass().getName(),
+ new Comment("Unknown OrderEntryType: " + orderEntry.getClass().getName())));
+ }
+ return true;
+ }
+ });
+ }
+ }.processModule(module, 0, false);
+ }
+ // convert path items to generators
+ for (final ClasspathItem pathItem : pathItems) {
+ add(pathItem.toGenerator());
+ }
+ }
+
+ /**
+ * Generate classpath name
+ *
+ * @param chunk a chunk
+ * @param generateRuntimeClasspath
+ * @param generateTestClasspath
+ * @return a name for the classpath
+ */
+ private static String generateClasspathName(ModuleChunk chunk, boolean generateRuntimeClasspath, boolean generateTestClasspath) {
+ if (generateTestClasspath) {
+ return generateRuntimeClasspath
+ ? BuildProperties.getTestRuntimeClasspathProperty(chunk.getName())
+ : BuildProperties.getTestClasspathProperty(chunk.getName());
+ }
+ else {
+ return generateRuntimeClasspath
+ ? BuildProperties.getRuntimeClasspathProperty(chunk.getName())
+ : BuildProperties.getClasspathProperty(chunk.getName());
+ }
+ }
+
+ private static String[] getCompilationClasses(final Module module,
+ final GenerationOptionsImpl options,
+ final boolean forRuntime,
+ final boolean forTest,
+ final boolean firstLevel) {
+ final CompilerModuleExtension extension = CompilerModuleExtension.getInstance(module);
+ if (extension == null) return ArrayUtil.EMPTY_STRING_ARRAY;
+
+ if (!forRuntime) {
+ if (forTest) {
+ return extension.getOutputRootUrls(!firstLevel);
+ }
+ else {
+ return firstLevel ? ArrayUtil.EMPTY_STRING_ARRAY : extension.getOutputRootUrls(false);
+ }
+ }
+ final Set<String> jdkUrls = options.getAllJdkUrls();
+
+ final OrderedSet<String> urls = new OrderedSet<String>();
+ ContainerUtil.addAll(urls, extension.getOutputRootUrls(forTest));
+ urls.removeAll(jdkUrls);
+ return ArrayUtil.toStringArray(urls);
+ }
+
+ /**
+ * The base class for an item in the class path. Instances of the subclasses are used instead
+ * of generators when building the class path content. The subclasses implement {@link Object#equals(Object)}
+ * and {@link Object#hashCode()} methods in order to eliminate duplicates when building classpath.
+ */
+ private abstract static class ClasspathItem {
+ /**
+ * primary reference or path of the element
+ */
+ protected final String myValue;
+
+ /**
+ * A constructor
+ *
+ * @param value primary value of the element
+ */
+ public ClasspathItem(String value) {
+ myValue = value;
+ }
+
+ /**
+ * @return a generator for path elements.
+ */
+ public abstract Generator toGenerator();
+ }
+
+ /**
+ * Class path item that directly embeds generator
+ */
+ private static class GeneratorItem extends ClasspathItem {
+ /**
+ * An embedded generator
+ */
+ final Generator myGenerator;
+
+ /**
+ * A constructor
+ *
+ * @param value primary value of the element
+ * @param generator a generator to use
+ */
+ public GeneratorItem(String value, final Generator generator) {
+ super(value);
+ myGenerator = generator;
+ }
+
+ public Generator toGenerator() {
+ return myGenerator;
+ }
+ }
+
+ /**
+ * This path element directly references some location.
+ */
+ private static class PathElementItem extends ClasspathItem {
+ /**
+ * A constructor
+ *
+ * @param value a referenced location
+ */
+ public PathElementItem(String value) {
+ super(value);
+ }
+
+ @Override
+ public Generator toGenerator() {
+ return new PathElement(myValue);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof PathElementItem)) return false;
+
+ final PathElementItem pathElementItem = (PathElementItem)o;
+
+ if (!myValue.equals(pathElementItem.myValue)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return myValue.hashCode();
+ }
+ }
+
+ /**
+ * This path element references a path
+ */
+ private static class PathRefItem extends ClasspathItem {
+ /**
+ * A constructor
+ *
+ * @param value an indentifier of referenced classpath
+ */
+ public PathRefItem(String value) {
+ super(value);
+ }
+
+ @Override
+ public Generator toGenerator() {
+ return new PathRef(myValue);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof PathRefItem)) return false;
+
+ final PathRefItem pathRefItem = (PathRefItem)o;
+
+ if (!myValue.equals(pathRefItem.myValue)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return myValue.hashCode();
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/ModuleChunkSourcePath.java b/java/compiler/impl/src/com/intellij/compiler/ant/ModuleChunkSourcePath.java
new file mode 100644
index 0000000..ebe3f5d
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/ModuleChunkSourcePath.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.ant;
+
+import com.intellij.compiler.ant.taskdefs.*;
+import com.intellij.openapi.fileTypes.FileTypeManager;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ContentEntry;
+import com.intellij.openapi.roots.ModuleFileIndex;
+import com.intellij.openapi.roots.ModuleRootManager;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileVisitor;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Eugene Zhuravlev
+ * @since Nov 22, 2004
+ */
+public class ModuleChunkSourcePath extends CompositeGenerator{
+ private final VirtualFile[] mySourceRoots;
+ private final VirtualFile[] myTestSourceRoots;
+
+ public ModuleChunkSourcePath(final Project project, ModuleChunk chunk, final GenerationOptions genOptions) {
+ final Path sourcePath = new Path(BuildProperties.getSourcepathProperty(chunk.getName()));
+ final Path testSourcePath = new Path(BuildProperties.getTestSourcepathProperty(chunk.getName()));
+ final PatternSet excludedFromCompilation = new PatternSet(BuildProperties.getExcludedFromCompilationProperty(chunk.getName()));
+ final String moduleChunkBasedirProperty = BuildProperties.getModuleChunkBasedirProperty(chunk);
+ final Module[] modules = chunk.getModules();
+
+ if (CompilerExcludes.isAvailable(project)) {
+ excludedFromCompilation.add(new PatternSetRef(BuildProperties.PROPERTY_COMPILER_EXCLUDES));
+ }
+
+ final List<VirtualFile> sourceRootFiles = new ArrayList<VirtualFile>();
+ final List<VirtualFile> testSourceRootFiles = new ArrayList<VirtualFile>();
+
+ for (final Module module : modules) {
+ final String moduleName = module.getName();
+ final ModuleRootManager rootManager = ModuleRootManager.getInstance(module);
+ final ModuleFileIndex moduleFileIndex = rootManager.getFileIndex();
+
+
+ final PatternSet excludedFromModule = new PatternSet(BuildProperties.getExcludedFromModuleProperty(moduleName));
+ excludedFromModule.add(new PatternSetRef(BuildProperties.PROPERTY_IGNORED_FILES));
+
+ final ContentEntry[] contentEntries = rootManager.getContentEntries();
+ for (final ContentEntry contentEntry : contentEntries) {
+ final VirtualFile file = contentEntry.getFile();
+ if (file == null) {
+ continue; // filter invalid entries
+ }
+ if (!file.isInLocalFileSystem()) {
+ continue; // skip content roots inside jar and zip archives
+ }
+ final VirtualFile dirSetRoot = getDirSetRoot(contentEntry);
+
+ final String dirSetRootRelativeToBasedir = GenerationUtils
+ .toRelativePath(dirSetRoot, chunk.getBaseDir(), moduleChunkBasedirProperty, genOptions);
+ final DirSet sourcesDirSet = new DirSet(dirSetRootRelativeToBasedir);
+ final DirSet testSourcesDirSet = new DirSet(dirSetRootRelativeToBasedir);
+
+ final VirtualFile[] sourceRoots = contentEntry.getSourceFolderFiles();
+ for (final VirtualFile root : sourceRoots) {
+ if (!moduleFileIndex.isInContent(root)) {
+ continue; // skip library sources
+ }
+
+ addExcludePatterns(module, root, root, excludedFromModule, true);
+
+ final Include include = new Include(VfsUtilCore.getRelativePath(root, dirSetRoot, '/'));
+ if (moduleFileIndex.isInTestSourceContent(root)) {
+ testSourcesDirSet.add(include);
+ testSourceRootFiles.add(root);
+ }
+ else {
+ sourcesDirSet.add(include);
+ sourceRootFiles.add(root);
+ }
+ }
+ if (sourcesDirSet.getGeneratorCount() > 0) {
+ sourcePath.add(sourcesDirSet);
+ }
+ if (testSourcesDirSet.getGeneratorCount() > 0) {
+ testSourcePath.add(testSourcesDirSet);
+ }
+ }
+
+ if (excludedFromModule.getGeneratorCount() > 0) {
+ add(excludedFromModule);
+ excludedFromCompilation.add(new PatternSetRef(BuildProperties.getExcludedFromModuleProperty(moduleName)));
+ }
+ }
+
+ mySourceRoots = VfsUtilCore.toVirtualFileArray(sourceRootFiles);
+ myTestSourceRoots = VfsUtilCore.toVirtualFileArray(testSourceRootFiles);
+
+ if (excludedFromCompilation.getGeneratorCount() > 0) {
+ add(excludedFromCompilation, 1);
+ }
+ if (sourcePath.getGeneratorCount() > 0) {
+ add(sourcePath, 1);
+ }
+ if (testSourcePath.getGeneratorCount() != 0) {
+ add(testSourcePath, 1);
+ }
+ }
+
+ public VirtualFile[] getSourceRoots() {
+ return mySourceRoots;
+ }
+
+ public VirtualFile[] getTestSourceRoots() {
+ return myTestSourceRoots;
+ }
+
+ private VirtualFile getDirSetRoot(final ContentEntry contentEntry) {
+ final VirtualFile contentRoot = contentEntry.getFile();
+ final VirtualFile[] sourceFolderFiles = contentEntry.getSourceFolderFiles();
+ for (VirtualFile sourceFolderFile : sourceFolderFiles) {
+ if (contentRoot.equals(sourceFolderFile)) {
+ return contentRoot.getParent();
+ }
+ }
+ return contentRoot;
+ }
+
+ private static void addExcludePatterns(Module module,
+ final VirtualFile root,
+ VirtualFile dir,
+ final CompositeGenerator generator,
+ final boolean parentIncluded) {
+ final FileTypeManager fileTypeManager = FileTypeManager.getInstance();
+ final ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module);
+
+ VfsUtilCore.visitChildrenRecursively(dir, new VirtualFileVisitor() {
+ @Override
+ public boolean visitFile(@NotNull VirtualFile dir) {
+ if (!dir.isDirectory() || fileTypeManager.isFileIgnored(dir)) {
+ // ignored files are handled by global 'ignored' pattern set
+ return false;
+ }
+
+ final boolean isIncluded = moduleRootManager.getFileIndex().isInContent(dir);
+ if (isIncluded != parentIncluded) {
+ final String relativePath = VfsUtilCore.getRelativePath(dir, root, '/');
+ if (isIncluded) {
+ generator.add(new Include(relativePath + "/**"));
+ }
+ else {
+ if (!isExcludedByDefault(dir.getName())) {
+ generator.add(new Exclude(relativePath + "/**"));
+ }
+ }
+ }
+
+ return true;
+ }
+ });
+ }
+
+ private static boolean isExcludedByDefault(String name) {
+ //noinspection HardCodedStringLiteral
+ return "CVS".equals(name) || "SCCS".equals(name) || ".DS_Store".equals(name);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/ModuleSources.java b/java/compiler/impl/src/com/intellij/compiler/ant/ModuleSources.java
new file mode 100644
index 0000000..d9d6e07
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/ModuleSources.java
@@ -0,0 +1,127 @@
+/*
+ * 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 com.intellij.compiler.ant;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.roots.ContentEntry;
+import com.intellij.openapi.vfs.VirtualFile;
+
+import java.io.File;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 19, 2004
+ */
+public class ModuleSources extends CompositeGenerator{
+ private final VirtualFile[] mySourceRoots = VirtualFile.EMPTY_ARRAY;
+ private final VirtualFile[] myTestSourceRoots = VirtualFile.EMPTY_ARRAY;
+
+ public ModuleSources(Module module, File baseDir, final GenerationOptions genOptions) {
+ /*
+ final ModuleRootManager rootManager = ModuleRootManager.getInstance(module);
+ final ModuleFileIndex moduleFileIndex = rootManager.getFileIndex();
+
+ final List<VirtualFile> sourceRootFiles = new ArrayList<VirtualFile>();
+ final List<VirtualFile> testSourceRootFiles = new ArrayList<VirtualFile>();
+
+ final Path sourcepath = new Path(BuildProperties.getSourcepathProperty(module.getName()));
+ final Path testSourcepath = new Path(BuildProperties.getTestSourcepathProperty(module.getName()));
+ final PatternSet excludedFromModule = new PatternSet(BuildProperties.getExcludedFromModuleProperty(module.getName()));
+
+ final ContentEntry[] contentEntries = rootManager.getContentEntries();
+ for (int idx = 0; idx < contentEntries.length; idx++) {
+ final ContentEntry contentEntry = contentEntries[idx];
+ final VirtualFile file = contentEntry.getFile();
+ if (file == null) {
+ continue; // filter invalid entries
+ }
+ if (!(file.getFileSystem() instanceof LocalFileSystem)) {
+ continue; // skip content roots inside jar and zip archives
+ }
+ final VirtualFile dirSetRoot = getDirSetRoot(contentEntry);
+
+ final String dirSetRootRelativeToBasedir = GenerationUtils.toRelativePath(dirSetRoot, baseDir, BuildProperties.getModuleChunkBasedirProperty(module), genOptions, !module.isSavePathsRelative());
+ final DirSet sourcesDirSet = new DirSet(dirSetRootRelativeToBasedir);
+ final DirSet testSourcesDirSet = new DirSet(dirSetRootRelativeToBasedir);
+
+ final VirtualFile[] sourceRoots = contentEntry.getSourceFolderFiles();
+ for (int i = 0; i < sourceRoots.length; i++) {
+ final VirtualFile root = sourceRoots[i];
+ if (!moduleFileIndex.isInContent(root)) {
+ continue; // skip library sources
+ }
+
+ addExcludePatterns(module, root, root, excludedFromModule, true);
+
+ final Include include = new Include(VfsUtil.getRelativePath(root, dirSetRoot, '/'));
+ if (moduleFileIndex.isInTestSourceContent(root)) {
+ testSourcesDirSet.add(include);
+ testSourceRootFiles.add(root);
+ }
+ else {
+ sourcesDirSet.add(include);
+ sourceRootFiles.add(root);
+ }
+ }
+ if (sourcesDirSet.getGeneratorCount() > 0) {
+ sourcepath.add(sourcesDirSet);
+ }
+ if (testSourcesDirSet.getGeneratorCount() > 0) {
+ testSourcepath.add(testSourcesDirSet);
+ }
+ }
+
+ mySourceRoots = sourceRootFiles.toArray(new VirtualFile[sourceRootFiles.size()]);
+ myTestSourceRoots = testSourceRootFiles.toArray(new VirtualFile[testSourceRootFiles.size()]);
+
+ final String moduleName = module.getName();
+
+ add(excludedFromModule);
+
+ final PatternSet excludedFromCompilation = new PatternSet(BuildProperties.getExcludedFromCompilationProperty(moduleName));
+ excludedFromCompilation.add(new PatternSetRef(BuildProperties.getExcludedFromModuleProperty(moduleName)));
+ excludedFromCompilation.add(new PatternSetRef(BuildProperties.PROPERTY_COMPILER_EXCLUDES));
+ add(excludedFromCompilation, 1);
+
+ if (sourcepath.getGeneratorCount() > 0) {
+ add(sourcepath, 1);
+ }
+ if (testSourcepath.getGeneratorCount() != 0) {
+ add(testSourcepath, 1);
+ }
+ */
+ }
+
+ private VirtualFile getDirSetRoot(final ContentEntry contentEntry) {
+ final VirtualFile contentRoot = contentEntry.getFile();
+ final VirtualFile[] sourceFolderFiles = contentEntry.getSourceFolderFiles();
+ for (VirtualFile sourceFolderFile : sourceFolderFiles) {
+ if (contentRoot.equals(sourceFolderFile)) {
+ return contentRoot.getParent();
+ }
+ }
+ return contentRoot;
+ }
+
+ public VirtualFile[] getSourceRoots() {
+ return mySourceRoots;
+ }
+
+ public VirtualFile[] getTestSourceRoots() {
+ return myTestSourceRoots;
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/MultipleFileProjectBuild.java b/java/compiler/impl/src/com/intellij/compiler/ant/MultipleFileProjectBuild.java
new file mode 100644
index 0000000..9916ee0
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/MultipleFileProjectBuild.java
@@ -0,0 +1,41 @@
+/*
+ * 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 com.intellij.compiler.ant;
+
+import com.intellij.compiler.ant.taskdefs.Import;
+import com.intellij.openapi.project.Project;
+
+import java.io.File;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 16, 2004
+ */
+public class MultipleFileProjectBuild extends ProjectBuild{
+ public MultipleFileProjectBuild(Project project, GenerationOptions genOptions) {
+ super(project, genOptions);
+ }
+
+ protected Generator createModuleBuildGenerator(ModuleChunk chunk, GenerationOptions genOptions) {
+ //noinspection HardCodedStringLiteral
+ final String chunkBuildFile = BuildProperties.getModuleChunkBaseDir(chunk).getPath() + File.separator + BuildProperties.getModuleChunkBuildFileName(chunk) + ".xml";
+ final File projectBaseDir = BuildProperties.getProjectBaseDir(myProject);
+ final String pathToFile = GenerationUtils.toRelativePath(
+ chunkBuildFile, projectBaseDir, BuildProperties.getProjectBaseDirProperty(), genOptions);
+ return new Import(pathToFile);
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/ProjectBuild.java b/java/compiler/impl/src/com/intellij/compiler/ant/ProjectBuild.java
new file mode 100644
index 0000000..7d4c3d9
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/ProjectBuild.java
@@ -0,0 +1,103 @@
+/*
+ * 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 com.intellij.compiler.ant;
+
+import com.intellij.compiler.ant.artifacts.ArtifactsGenerator;
+import com.intellij.compiler.ant.taskdefs.AntProject;
+import com.intellij.compiler.ant.taskdefs.Target;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.project.Project;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.List;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 24, 2004
+ */
+public abstract class ProjectBuild extends Generator {
+ protected final Project myProject;
+ private final AntProject myAntProject;
+
+ public ProjectBuild(Project project, GenerationOptions genOptions) {
+ myProject = project;
+ myAntProject = new AntProject(BuildProperties.getProjectBuildFileName(myProject), BuildProperties.DEFAULT_TARGET);
+
+ myAntProject.add(new BuildPropertiesImpl(myProject, genOptions), 1);
+
+ // the sequence in which modules are imported is important cause output path properties for dependent modules should be defined first
+
+ final StringBuilder buildModulesTargetNames = new StringBuilder();
+ buildModulesTargetNames.append(BuildProperties.TARGET_INIT);
+ buildModulesTargetNames.append(", ");
+ buildModulesTargetNames.append(BuildProperties.TARGET_CLEAN);
+ final ModuleChunk[] chunks = genOptions.getModuleChunks();
+
+ if (chunks.length > 0) {
+ myAntProject.add(new Comment(CompilerBundle.message("generated.ant.build.modules.section.title")), 1);
+
+ for (final ModuleChunk chunk : chunks) {
+ myAntProject.add(createModuleBuildGenerator(chunk, genOptions), 1);
+ final String[] targets = ChunkBuildExtension.getAllTargets(chunk);
+ for (String target : targets) {
+ if (buildModulesTargetNames.length() > 0) {
+ buildModulesTargetNames.append(", ");
+ }
+ buildModulesTargetNames.append(target);
+ }
+ }
+ }
+ for (ChunkBuildExtension extension : ChunkBuildExtension.EP_NAME.getExtensions()) {
+ extension.generateProjectTargets(project, genOptions, myAntProject);
+ }
+
+ final Target initTarget = new Target(BuildProperties.TARGET_INIT, null,
+ CompilerBundle.message("generated.ant.build.initialization.section.title"), null);
+ initTarget.add(new Comment(CompilerBundle.message("generated.ant.build.initialization.section.comment")));
+ myAntProject.add(initTarget, 1);
+
+ ArtifactsGenerator artifactsGenerator = new ArtifactsGenerator(project, genOptions);
+
+ myAntProject.add(new CleanProject(project, genOptions, artifactsGenerator), 1);
+
+ myAntProject.add(new Target(BuildProperties.TARGET_BUILD_MODULES, buildModulesTargetNames.toString(),
+ CompilerBundle.message("generated.ant.build.build.all.modules.target.name"), null), 1);
+
+ StringBuilder buildAllTargetNames = new StringBuilder();
+ buildAllTargetNames.append(BuildProperties.TARGET_BUILD_MODULES);
+ if (artifactsGenerator.hasArtifacts()) {
+ List<Generator> generators = artifactsGenerator.generate();
+ for (Generator generator : generators) {
+ myAntProject.add(generator, 1);
+ }
+
+ buildAllTargetNames.append(", ").append(ArtifactsGenerator.BUILD_ALL_ARTIFACTS_TARGET);
+ }
+
+ myAntProject.add(new Target(BuildProperties.TARGET_ALL, buildAllTargetNames.toString(),
+ CompilerBundle.message("generated.ant.build.build.all.target.name"), null), 1);
+ }
+
+ public void generate(PrintWriter out) throws IOException {
+ //noinspection HardCodedStringLiteral
+ writeXmlHeader(out);
+ myAntProject.generate(out);
+ }
+
+ protected abstract Generator createModuleBuildGenerator(final ModuleChunk chunk, GenerationOptions genOptions);
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/PropertyFileGeneratorImpl.java b/java/compiler/impl/src/com/intellij/compiler/ant/PropertyFileGeneratorImpl.java
new file mode 100644
index 0000000..ab93b32
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/PropertyFileGeneratorImpl.java
@@ -0,0 +1,101 @@
+/*
+ * 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 com.intellij.compiler.ant;
+
+import com.intellij.openapi.application.PathMacros;
+import com.intellij.openapi.application.PathManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.util.ArrayUtil;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Generator for property files.
+ *
+ * @author Eugene Zhuravlev
+ * Date: Nov 27, 2004
+ */
+public class PropertyFileGeneratorImpl extends PropertyFileGenerator {
+ /**
+ * List of the properties
+ */
+ private final List<Pair<String, String>> myProperties = new ArrayList<Pair<String, String>>();
+
+ /**
+ * A constctor that extracts all neeed properties for ant build from the project.
+ *
+ * @param project a project to examine
+ * @param genOptions generation options
+ */
+ public PropertyFileGeneratorImpl(Project project, GenerationOptions genOptions) {
+ // path variables
+ final PathMacros pathMacros = PathMacros.getInstance();
+ final Set<String> macroNamesSet = pathMacros.getUserMacroNames();
+ if (macroNamesSet.size() > 0) {
+ final String[] macroNames = ArrayUtil.toStringArray(macroNamesSet);
+ Arrays.sort(macroNames);
+ for (final String macroName : macroNames) {
+ addProperty(BuildProperties.getPathMacroProperty(macroName), pathMacros.getValue(macroName));
+ }
+ }
+ // jdk homes
+ if (genOptions.forceTargetJdk) {
+ final Sdk[] usedJdks = BuildProperties.getUsedJdks(project);
+ for (Sdk jdk : usedJdks) {
+ if (jdk.getHomeDirectory() == null) {
+ continue;
+ }
+ final File homeDir = BuildProperties.toCanonicalFile(VfsUtil.virtualToIoFile(jdk.getHomeDirectory()));
+ addProperty(BuildProperties.getJdkHomeProperty(jdk.getName()), homeDir.getPath().replace(File.separatorChar, '/'));
+ }
+ }
+ // generate idea.home property
+ if (genOptions.isIdeaHomeGenerated()) {
+ addProperty(BuildProperties.PROPERTY_IDEA_HOME, PathManager.getHomePath());
+ }
+ ChunkBuildExtension.generateAllProperties(this, project, genOptions);
+ }
+
+ public void addProperty(String name, String value) {
+ myProperties.add(new Pair<String, String>(name, value));
+ }
+
+ @Override
+ public void generate(PrintWriter out) throws IOException {
+ boolean isFirst = true;
+ for (final Pair<String, String> pair : myProperties) {
+ if (!isFirst) {
+ crlf(out);
+ }
+ else {
+ isFirst = false;
+ }
+ out.print(StringUtil.escapeProperty(pair.getFirst(), true));
+ out.print("=");
+ out.print(StringUtil.escapeProperty(pair.getSecond(), false));
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/SingleFileProjectBuild.java b/java/compiler/impl/src/com/intellij/compiler/ant/SingleFileProjectBuild.java
new file mode 100644
index 0000000..d084879
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/SingleFileProjectBuild.java
@@ -0,0 +1,39 @@
+/*
+ * 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 com.intellij.compiler.ant;
+
+import com.intellij.compiler.ant.taskdefs.Dirname;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.compiler.CompilerBundle;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 16, 2004
+ */
+public class SingleFileProjectBuild extends ProjectBuild {
+ public SingleFileProjectBuild(Project project, GenerationOptions genOptions) {
+ super(project, genOptions);
+ }
+
+ protected Generator createModuleBuildGenerator(ModuleChunk chunk, GenerationOptions genOptions) {
+ final CompositeGenerator gen = new CompositeGenerator();
+ gen.add(new Comment(CompilerBundle.message("generated.ant.build.building.concrete.module.section.title", chunk.getName())));
+ gen.add(new Dirname(BuildProperties.getModuleChunkBasedirProperty(chunk), BuildProperties.propertyRef("ant.file")), 1);
+ gen.add(new ChunkBuild(myProject, chunk, genOptions), 1);
+ return gen;
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/artifacts/ArchiveAntCopyInstructionCreator.java b/java/compiler/impl/src/com/intellij/compiler/ant/artifacts/ArchiveAntCopyInstructionCreator.java
new file mode 100644
index 0000000..4ede60c
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/artifacts/ArchiveAntCopyInstructionCreator.java
@@ -0,0 +1,59 @@
+/*
+ * 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 com.intellij.compiler.ant.artifacts;
+
+import com.intellij.compiler.ant.Generator;
+import com.intellij.compiler.ant.Tag;
+import com.intellij.compiler.ant.taskdefs.ZipFileSet;
+import com.intellij.packaging.elements.AntCopyInstructionCreator;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author nik
+ */
+public class ArchiveAntCopyInstructionCreator implements AntCopyInstructionCreator {
+ private final String myPrefix;
+
+ public ArchiveAntCopyInstructionCreator(String prefix) {
+ myPrefix = prefix;
+ }
+
+ @NotNull
+ public Tag createDirectoryContentCopyInstruction(@NotNull String dirPath) {
+ return new ZipFileSet(dirPath, myPrefix, true);
+ }
+
+ @NotNull
+ public Tag createFileCopyInstruction(@NotNull String filePath, String outputFileName) {
+ final String relativePath = myPrefix + "/" + outputFileName;
+ return new ZipFileSet(filePath, relativePath, false);
+ }
+
+ @NotNull
+ public AntCopyInstructionCreator subFolder(@NotNull String directoryName) {
+ return new ArchiveAntCopyInstructionCreator(myPrefix + "/" + directoryName);
+ }
+
+ public Generator createSubFolderCommand(@NotNull String directoryName) {
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public Generator createExtractedDirectoryInstruction(@NotNull String jarPath) {
+ return ZipFileSet.createUnpackedSet(jarPath, myPrefix, true);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/artifacts/ArtifactAntGenerationContextImpl.java b/java/compiler/impl/src/com/intellij/compiler/ant/artifacts/ArtifactAntGenerationContextImpl.java
new file mode 100644
index 0000000..b422760
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/artifacts/ArtifactAntGenerationContextImpl.java
@@ -0,0 +1,166 @@
+/*
+ * 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 com.intellij.compiler.ant.artifacts;
+
+import com.intellij.compiler.ant.BuildProperties;
+import com.intellij.compiler.ant.GenerationOptions;
+import com.intellij.compiler.ant.GenerationUtils;
+import com.intellij.compiler.ant.Generator;
+import com.intellij.compiler.ant.taskdefs.Mkdir;
+import com.intellij.compiler.ant.taskdefs.Property;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.elements.ArtifactAntGenerationContext;
+import com.intellij.packaging.impl.artifacts.ArtifactUtil;
+import gnu.trove.THashMap;
+import gnu.trove.THashSet;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+
+/**
+ * @author nik
+ */
+public class ArtifactAntGenerationContextImpl implements ArtifactAntGenerationContext {
+ @NonNls public static final String ARTIFACTS_TEMP_DIR_PROPERTY = "artifacts.temp.dir";
+ private final Map<Artifact, String> myArtifact2Target = new THashMap<Artifact, String>();
+ private final List<Generator> myBeforeBuildGenerators = new ArrayList<Generator>();
+ private final List<Generator> myAfterBuildGenerators = new ArrayList<Generator>();
+ private final Set<String> myTempFileNames = new THashSet<String>();
+ private final Set<String> myCreatedTempSubdirs = new THashSet<String>();
+ private final Set<String> myProperties = new LinkedHashSet<String>();
+ private final Project myProject;
+ private final GenerationOptions myGenerationOptions;
+ private final List<Generator> myBeforeCurrentArtifact = new ArrayList<Generator>();
+ private final Set<Artifact> myArtifactsToClean = new THashSet<Artifact>();
+
+ public ArtifactAntGenerationContextImpl(Project project, GenerationOptions generationOptions, List<Artifact> allArtifacts) {
+ myProject = project;
+ myGenerationOptions = generationOptions;
+ for (Artifact artifact : allArtifacts) {
+ if (ArtifactUtil.shouldClearArtifactOutputBeforeRebuild(artifact)) {
+ myArtifactsToClean.add(artifact);
+ }
+ }
+ }
+
+ @Override
+ public Project getProject() {
+ return myProject;
+ }
+
+ @Override
+ public GenerationOptions getGenerationOptions() {
+ return myGenerationOptions;
+ }
+
+ public String getConfiguredArtifactOutputProperty(@NotNull Artifact artifact) {
+ return "artifact.output." + BuildProperties.convertName(artifact.getName());
+ }
+
+ public String getArtifactOutputProperty(@NotNull Artifact artifact) {
+ if (shouldBuildIntoTempDirectory(artifact)) {
+ return "artifact.temp.output." + BuildProperties.convertName(artifact.getName());
+ }
+ return getConfiguredArtifactOutputProperty(artifact);
+ }
+
+ public boolean shouldBuildIntoTempDirectory(@NotNull Artifact artifact) {
+ return !myArtifactsToClean.contains(artifact);
+ }
+
+ public String getCleanTargetName(@NotNull Artifact artifact) {
+ return "clean.artifact." + BuildProperties.convertName(artifact.getName());
+ }
+
+ public String getTargetName(@NotNull Artifact artifact) {
+ String target = myArtifact2Target.get(artifact);
+ if (target == null) {
+ target = generateTargetName(artifact.getName());
+ myArtifact2Target.put(artifact, target);
+ }
+ return target;
+ }
+
+ private static String generateTargetName(String artifactName) {
+ return "artifact." + BuildProperties.convertName(artifactName);
+ }
+
+ public String getSubstitutedPath(String path) {
+ return GenerationUtils.toRelativePath(path, VfsUtil.virtualToIoFile(myProject.getBaseDir()), BuildProperties.getProjectBaseDirProperty(), myGenerationOptions);
+ }
+
+ public void runBeforeCurrentArtifact(Generator generator) {
+ myBeforeCurrentArtifact.add(generator);
+ }
+
+ public void runBeforeBuild(Generator generator) {
+ myBeforeBuildGenerators.add(generator);
+ }
+
+ public void runAfterBuild(Generator generator) {
+ myAfterBuildGenerators.add(generator);
+ }
+
+ public String createNewTempFileProperty(String basePropertyName, String fileName) {
+ String tempFileName = fileName;
+ int i = 1;
+ String tempSubdir = null;
+ while (myTempFileNames.contains(tempFileName)) {
+ tempSubdir = String.valueOf(i++);
+ tempFileName = tempSubdir + "/" + fileName;
+ }
+
+ String propertyName = basePropertyName;
+ i = 2;
+ while (myProperties.contains(propertyName)) {
+ propertyName = basePropertyName + i++;
+ }
+
+ runBeforeBuild(new Property(propertyName, BuildProperties.propertyRelativePath(ARTIFACTS_TEMP_DIR_PROPERTY, tempFileName)));
+ if (tempSubdir != null && myCreatedTempSubdirs.add(tempSubdir)) {
+ runBeforeBuild(new Mkdir(BuildProperties.propertyRelativePath(ARTIFACTS_TEMP_DIR_PROPERTY, tempSubdir)));
+ }
+ myTempFileNames.add(tempFileName);
+ myProperties.add(propertyName);
+ return propertyName;
+ }
+
+ public Generator[] getAndClearBeforeCurrentArtifact() {
+ final Generator[] generators = myBeforeCurrentArtifact.toArray(new Generator[myBeforeCurrentArtifact.size()]);
+ myBeforeCurrentArtifact.clear();
+ return generators;
+ }
+
+ public String getModuleOutputPath(String moduleName) {
+ return BuildProperties.getOutputPathProperty(moduleName);
+ }
+
+ @Override
+ public String getModuleTestOutputPath(@NonNls String moduleName) {
+ return BuildProperties.getOutputPathForTestsProperty(moduleName);
+ }
+
+ public List<Generator> getBeforeBuildGenerators() {
+ return myBeforeBuildGenerators;
+ }
+
+ public List<Generator> getAfterBuildGenerators() {
+ return myAfterBuildGenerators;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/artifacts/ArtifactsGenerator.java b/java/compiler/impl/src/com/intellij/compiler/ant/artifacts/ArtifactsGenerator.java
new file mode 100644
index 0000000..51ce1f4
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/artifacts/ArtifactsGenerator.java
@@ -0,0 +1,190 @@
+/*
+ * 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 com.intellij.compiler.ant.artifacts;
+
+import com.intellij.compiler.ant.*;
+import com.intellij.compiler.ant.taskdefs.*;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.artifacts.ArtifactManager;
+import com.intellij.packaging.elements.ComplexPackagingElement;
+import com.intellij.packaging.elements.PackagingElement;
+import com.intellij.packaging.elements.PackagingElementResolvingContext;
+import com.intellij.packaging.impl.artifacts.ArtifactUtil;
+import com.intellij.packaging.impl.artifacts.PackagingElementPath;
+import com.intellij.packaging.impl.artifacts.PackagingElementProcessor;
+import com.intellij.packaging.impl.elements.ArtifactPackagingElement;
+import com.intellij.packaging.impl.elements.ModuleOutputPackagingElement;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public class ArtifactsGenerator {
+ @NonNls public static final String BUILD_ALL_ARTIFACTS_TARGET = "build.all.artifacts";
+ @NonNls private static final String INIT_ARTIFACTS_TARGET = "init.artifacts";
+ private final PackagingElementResolvingContext myResolvingContext;
+ private final ArtifactAntGenerationContextImpl myContext;
+ private final List<Artifact> myAllArtifacts;
+
+ public ArtifactsGenerator(Project project, GenerationOptions genOptions) {
+ myResolvingContext = ArtifactManager.getInstance(project).getResolvingContext();
+
+ myAllArtifacts = new ArrayList<Artifact>(Arrays.asList(ArtifactManager.getInstance(project).getSortedArtifacts()));
+
+ myContext = new ArtifactAntGenerationContextImpl(project, genOptions, myAllArtifacts);
+ }
+
+ public boolean hasArtifacts() {
+ return !myAllArtifacts.isEmpty();
+ }
+
+ public List<Generator> generate() {
+ final List<Generator> generators = new ArrayList<Generator>();
+
+ final Target initTarget = new Target(INIT_ARTIFACTS_TARGET, null, null, null);
+ generators.add(initTarget);
+ initTarget.add(new Property(ArtifactAntGenerationContextImpl.ARTIFACTS_TEMP_DIR_PROPERTY, BuildProperties.propertyRelativePath(BuildProperties.getProjectBaseDirProperty(), "__artifacts_temp")));
+
+ for (Artifact artifact : myAllArtifacts) {
+ if (!myContext.shouldBuildIntoTempDirectory(artifact)) {
+ generators.add(new CleanArtifactTarget(artifact, myContext));
+ }
+ final String outputPath = artifact.getOutputPath();
+ if (!StringUtil.isEmpty(outputPath)) {
+ initTarget.add(new Property(myContext.getConfiguredArtifactOutputProperty(artifact), myContext.getSubstitutedPath(outputPath)));
+ }
+ }
+ initTarget.add(new Mkdir(BuildProperties.propertyRef(ArtifactAntGenerationContextImpl.ARTIFACTS_TEMP_DIR_PROPERTY)));
+
+ StringBuilder depends = new StringBuilder();
+ for (Artifact artifact : myAllArtifacts) {
+ Target target = createArtifactTarget(artifact);
+ generators.add(target);
+
+ if (!StringUtil.isEmpty(artifact.getOutputPath())) {
+ if (depends.length() > 0) depends.append(", ");
+ depends.append(myContext.getTargetName(artifact));
+ }
+ }
+
+ for (Generator generator : myContext.getBeforeBuildGenerators()) {
+ initTarget.add(generator);
+ }
+
+ Target buildAllArtifacts = new Target(BUILD_ALL_ARTIFACTS_TARGET, depends.toString(), "Build all artifacts", null);
+ for (Artifact artifact : myAllArtifacts) {
+ final String artifactOutputPath = artifact.getOutputPath();
+ if (!StringUtil.isEmpty(artifactOutputPath) && myContext.shouldBuildIntoTempDirectory(artifact)) {
+ final String outputPath = BuildProperties.propertyRef(myContext.getConfiguredArtifactOutputProperty(artifact));
+ buildAllArtifacts.add(new Mkdir(outputPath));
+ final Copy copy = new Copy(outputPath);
+ copy.add(new FileSet(BuildProperties.propertyRef(myContext.getArtifactOutputProperty(artifact))));
+ buildAllArtifacts.add(copy);
+ }
+ }
+
+ buildAllArtifacts.add(new Comment("Delete temporary files"), 1);
+ for (Generator generator : myContext.getAfterBuildGenerators()) {
+ buildAllArtifacts.add(generator);
+ }
+ buildAllArtifacts.add(new Delete(BuildProperties.propertyRef(ArtifactAntGenerationContextImpl.ARTIFACTS_TEMP_DIR_PROPERTY)));
+
+ generators.add(buildAllArtifacts);
+ return generators;
+ }
+
+ private Target createArtifactTarget(Artifact artifact) {
+ final StringBuilder depends = new StringBuilder(INIT_ARTIFACTS_TARGET);
+
+ ArtifactUtil.processPackagingElements(artifact, null, new PackagingElementProcessor<PackagingElement<?>>() {
+ @Override
+ public boolean shouldProcessSubstitution(ComplexPackagingElement<?> element) {
+ return !(element instanceof ArtifactPackagingElement);
+ }
+
+ @Override
+ public boolean process(@NotNull PackagingElement<?> packagingElement, @NotNull PackagingElementPath path) {
+ if (packagingElement instanceof ArtifactPackagingElement) {
+ final Artifact included = ((ArtifactPackagingElement)packagingElement).findArtifact(myResolvingContext);
+ if (included != null) {
+ if (depends.length() > 0) depends.append(", ");
+ depends.append(myContext.getTargetName(included));
+ }
+ }
+ else if (packagingElement instanceof ModuleOutputPackagingElement) {
+ final Module module = ((ModuleOutputPackagingElement)packagingElement).findModule(myResolvingContext);
+ if (module != null) {
+ if (depends.length() > 0) depends.append(", ");
+ depends.append(BuildProperties.getCompileTargetName(module.getName()));
+ }
+ }
+ return true;
+ }
+ }, myResolvingContext, true);
+
+ final Target artifactTarget =
+ new Target(myContext.getTargetName(artifact), depends.toString(), "Build '" + artifact.getName() + "' artifact", null);
+
+ if (myContext.shouldBuildIntoTempDirectory(artifact)) {
+ final String outputDirectory = BuildProperties.propertyRelativePath(ArtifactAntGenerationContextImpl.ARTIFACTS_TEMP_DIR_PROPERTY,
+ FileUtil.sanitizeFileName(artifact.getName()));
+ artifactTarget.add(new Property(myContext.getArtifactOutputProperty(artifact), outputDirectory));
+ }
+
+ final String outputPath = BuildProperties.propertyRef(myContext.getArtifactOutputProperty(artifact));
+ artifactTarget.add(new Mkdir(outputPath));
+ generateTasksForArtifacts(artifact, artifactTarget, true);
+
+ final DirectoryAntCopyInstructionCreator creator = new DirectoryAntCopyInstructionCreator(outputPath);
+
+ List<Generator> copyInstructions = new ArrayList<Generator>();
+ copyInstructions.addAll(artifact.getRootElement().computeAntInstructions(myResolvingContext, creator, myContext, artifact.getArtifactType()));
+
+ for (Generator generator : myContext.getAndClearBeforeCurrentArtifact()) {
+ artifactTarget.add(generator);
+ }
+ for (Generator tag : copyInstructions) {
+ artifactTarget.add(tag);
+ }
+ generateTasksForArtifacts(artifact, artifactTarget, false);
+ return artifactTarget;
+ }
+
+ private void generateTasksForArtifacts(Artifact artifact, Target artifactTarget, final boolean preprocessing) {
+ for (ChunkBuildExtension extension : ChunkBuildExtension.EP_NAME.getExtensions()) {
+ extension.generateTasksForArtifact(artifact, preprocessing, myContext, artifactTarget);
+ }
+ }
+
+ public List<String> getCleanTargetNames() {
+ final List<String> targets = new ArrayList<String>();
+ for (Artifact artifact : myAllArtifacts) {
+ if (!myContext.shouldBuildIntoTempDirectory(artifact)) {
+ targets.add(myContext.getCleanTargetName(artifact));
+ }
+ }
+ return targets;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/artifacts/CleanArtifactTarget.java b/java/compiler/impl/src/com/intellij/compiler/ant/artifacts/CleanArtifactTarget.java
new file mode 100644
index 0000000..8a205af
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/artifacts/CleanArtifactTarget.java
@@ -0,0 +1,31 @@
+/*
+ * 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 com.intellij.compiler.ant.artifacts;
+
+import com.intellij.compiler.ant.taskdefs.Target;
+import com.intellij.compiler.ant.taskdefs.Delete;
+import com.intellij.compiler.ant.BuildProperties;
+import com.intellij.packaging.artifacts.Artifact;
+
+/**
+ * @author nik
+ */
+public class CleanArtifactTarget extends Target {
+ public CleanArtifactTarget(Artifact artifact, ArtifactAntGenerationContextImpl context) {
+ super(context.getCleanTargetName(artifact), null, "clean " + artifact.getName() + " artifact output", null);
+ add(new Delete(BuildProperties.propertyRef(context.getConfiguredArtifactOutputProperty(artifact))));
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/ant/artifacts/DirectoryAntCopyInstructionCreator.java b/java/compiler/impl/src/com/intellij/compiler/ant/artifacts/DirectoryAntCopyInstructionCreator.java
new file mode 100644
index 0000000..900dff4
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/ant/artifacts/DirectoryAntCopyInstructionCreator.java
@@ -0,0 +1,64 @@
+/*
+ * 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 com.intellij.compiler.ant.artifacts;
+
+import com.intellij.compiler.ant.Generator;
+import com.intellij.compiler.ant.Tag;
+import com.intellij.compiler.ant.taskdefs.*;
+import com.intellij.packaging.elements.AntCopyInstructionCreator;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author nik
+ */
+public class DirectoryAntCopyInstructionCreator implements AntCopyInstructionCreator {
+ private final String myOutputDirectory;
+
+ public DirectoryAntCopyInstructionCreator(String outputDirectory) {
+ myOutputDirectory = outputDirectory;
+ }
+
+ public String getOutputDirectory() {
+ return myOutputDirectory;
+ }
+
+ @NotNull
+ public Tag createDirectoryContentCopyInstruction(@NotNull String dirPath) {
+ final Copy copy = new Copy(myOutputDirectory);
+ copy.add(new FileSet(dirPath));
+ return copy;
+ }
+
+ @NotNull
+ public Tag createFileCopyInstruction(@NotNull String filePath, String outputFileName) {
+ return new Copy(filePath, myOutputDirectory + "/" + outputFileName);
+ }
+
+ @NotNull
+ public AntCopyInstructionCreator subFolder(@NotNull String directoryName) {
+ return new DirectoryAntCopyInstructionCreator(myOutputDirectory + "/" + directoryName);
+ }
+
+ public Generator createSubFolderCommand(@NotNull String directoryName) {
+ return new Mkdir(myOutputDirectory + "/" + directoryName);
+ }
+
+ @NotNull
+ @Override
+ public Generator createExtractedDirectoryInstruction(@NotNull String jarPath) {
+ return new Unzip(jarPath, myOutputDirectory);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/classParsing/AnnotationConstantValue.java b/java/compiler/impl/src/com/intellij/compiler/classParsing/AnnotationConstantValue.java
new file mode 100644
index 0000000..4d4bfc4
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/classParsing/AnnotationConstantValue.java
@@ -0,0 +1,85 @@
+/*
+ * 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 com.intellij.compiler.classParsing;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Apr 2, 2004
+ */
+public class AnnotationConstantValue extends ConstantValue {
+ public static final AnnotationConstantValue[] EMPTY_ARRAY = new AnnotationConstantValue[0];
+ public static final AnnotationConstantValue[][] EMPTY_ARRAY_ARRAY = new AnnotationConstantValue[0][];
+ public final int myQName;
+ public final AnnotationNameValuePair[] myMemberValues;
+
+ public AnnotationConstantValue(int qName, AnnotationNameValuePair[] memberValues) {
+ myQName = qName;
+ myMemberValues = memberValues;
+ }
+
+ public int getAnnotationQName() {
+ return myQName;
+ }
+
+ /**
+ * @return an array of Integer -> ConstantValue pairs
+ */
+ public AnnotationNameValuePair[] getMemberValues() {
+ return myMemberValues;
+ }
+
+ public AnnotationConstantValue(DataInput in) throws IOException {
+ myQName = in.readInt();
+ final int size = in.readInt();
+ myMemberValues = new AnnotationNameValuePair[size];
+ for (int idx = 0; idx < myMemberValues.length; idx++) {
+ final int name = in.readInt();
+ final ConstantValue constantValue = MemberInfoExternalizer.loadConstantValue(in);
+ myMemberValues[idx] = new AnnotationNameValuePair(name, constantValue);
+ }
+ }
+
+ public void save(DataOutput out) throws IOException {
+ out.writeInt(myQName);
+ out.writeInt(myMemberValues.length);
+ for (int idx = 0; idx < myMemberValues.length; idx++) {
+ final AnnotationNameValuePair nameValuePair = myMemberValues[idx];
+ out.writeInt(nameValuePair.getName());
+ MemberInfoExternalizer.saveConstantValue(out, nameValuePair.getValue());
+ }
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof AnnotationConstantValue)) return false;
+
+ final AnnotationConstantValue annotationConstantValue = (AnnotationConstantValue)o;
+
+ if (myQName != annotationConstantValue.myQName) return false;
+ if (!Arrays.equals(myMemberValues, annotationConstantValue.myMemberValues)) return false;
+
+ return true;
+ }
+
+ public int hashCode() {
+ return myQName;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/classParsing/AnnotationNameValuePair.java b/java/compiler/impl/src/com/intellij/compiler/classParsing/AnnotationNameValuePair.java
new file mode 100644
index 0000000..e559875
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/classParsing/AnnotationNameValuePair.java
@@ -0,0 +1,38 @@
+/*
+ * 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 com.intellij.compiler.classParsing;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Apr 8, 2004
+ */
+public class AnnotationNameValuePair {
+ private final int myName;
+ private final ConstantValue myValue;
+
+ public AnnotationNameValuePair(int name, ConstantValue value) {
+ myName = name;
+ myValue = value;
+ }
+
+ public int getName() {
+ return myName;
+ }
+
+ public ConstantValue getValue() {
+ return myValue;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/classParsing/AnnotationPrimitiveConstantValue.java b/java/compiler/impl/src/com/intellij/compiler/classParsing/AnnotationPrimitiveConstantValue.java
new file mode 100644
index 0000000..24ade71
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/classParsing/AnnotationPrimitiveConstantValue.java
@@ -0,0 +1,72 @@
+/*
+ * 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 com.intellij.compiler.classParsing;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Apr 2, 2004
+ */
+public class AnnotationPrimitiveConstantValue extends ConstantValue{
+ private final char myValueTag;
+ private final ConstantValue myValue;
+
+ public AnnotationPrimitiveConstantValue(char valueTag, ConstantValue value) {
+ myValueTag = valueTag;
+ myValue = value;
+ }
+
+ public AnnotationPrimitiveConstantValue(DataInput in) throws IOException {
+ myValueTag = in.readChar();
+ myValue = MemberInfoExternalizer.loadConstantValue(in);
+ }
+
+ public char getValueTag() {
+ return myValueTag;
+ }
+
+ public ConstantValue getValue() {
+ return myValue;
+ }
+
+ public void save(DataOutput out) throws IOException {
+ out.writeChar(myValueTag);
+ MemberInfoExternalizer.saveConstantValue(out, myValue);
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof AnnotationPrimitiveConstantValue)) return false;
+
+ final AnnotationPrimitiveConstantValue memberValue = (AnnotationPrimitiveConstantValue)o;
+
+ if (myValueTag != memberValue.myValueTag) return false;
+ if (!myValue.equals(memberValue.myValue)) return false;
+
+ return true;
+ }
+
+ public int hashCode() {
+ int result;
+ result = (int)myValueTag;
+ result = 29 * result + myValue.hashCode();
+ return result;
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/classParsing/ClassFileReader.java b/java/compiler/impl/src/com/intellij/compiler/classParsing/ClassFileReader.java
new file mode 100644
index 0000000..bd80570
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/classParsing/ClassFileReader.java
@@ -0,0 +1,770 @@
+/*
+ * 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.
+ */
+
+/**
+ * created at Jan 2, 2002
+ * @author Jeka
+ */
+package com.intellij.compiler.classParsing;
+
+import com.intellij.compiler.SymbolTable;
+import com.intellij.compiler.make.CacheCorruptedException;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.psi.CommonClassNames;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.cls.BytePointer;
+import com.intellij.util.cls.ClsFormatException;
+import com.intellij.util.cls.ClsUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+public class ClassFileReader {
+ private final File myFile;
+ private byte[] myData;
+ private int[] myConstantPoolOffsets = null; // the last offset points to the constant pool end
+
+ private String myQualifiedName;
+ private String myGenericSignature;
+ private List<ReferenceInfo> myReferences;
+ private List<FieldInfo> myFields;
+ private List<MethodInfo> myMethods;
+ private String mySourceFileName;
+ private String mySuperClassName;
+ private String[] mySuperInterfaces;
+ private final SymbolTable mySymbolTable;
+ private AnnotationConstantValue[] myRuntimeVisibleAnnotations;
+ private AnnotationConstantValue[] myRuntimeInvisibleAnnotations;
+ private static final String CONSTRUCTOR_NAME = "<init>";
+ private boolean myParsingDone;
+
+ public ClassFileReader(@NotNull File file, SymbolTable symbolTable, @Nullable final byte[] fileContent) {
+ mySymbolTable = symbolTable;
+ myFile = file;
+ myData = fileContent;
+ }
+
+ public String getPath() {
+ return myFile.getAbsolutePath();
+ }
+
+ public Collection<ReferenceInfo> getReferences() throws ClsFormatException {
+ parseConstantPool();
+ return myReferences;
+ }
+
+ public MethodInfo[] getMethods() throws ClsFormatException{
+ parseMembers();
+ return myMethods.toArray(new MethodInfo[myMethods.size()]);
+ }
+
+ public FieldInfo[] getFields() throws ClsFormatException{
+ parseMembers();
+ return myFields.toArray(new FieldInfo[myFields.size()]);
+ }
+
+ private void parseMembers() throws ClsFormatException {
+ if (myParsingDone) {
+ return;
+ }
+ initConstantPool();
+ myMethods = new ArrayList<MethodInfo>();
+ myFields = new ArrayList<FieldInfo>();
+ BytePointer ptr = new BytePointer(getData(), getConstantPoolEnd());
+ ptr.offset += 2; // access flags
+ ptr.offset += 2; // this class
+ ptr.offset += 2; // super class
+ int count = ClsUtil.readU2(ptr); // interface count
+ ptr.offset += 2 * count; // skip interface infos
+ count = ClsUtil.readU2(ptr); // field count
+ while (count-- > 0) {
+ FieldInfo field = (FieldInfo)readMemberStructure(ptr, true);
+ String name = getSymbol(field.getName());
+ if (name.indexOf('$') < 0 && name.indexOf('<') < 0){ // skip synthetic fields
+ myFields.add(field);
+ }
+ }
+ count = ClsUtil.readU2(ptr); // method count
+ while (count-- > 0) {
+ MethodInfo method = (MethodInfo)readMemberStructure(ptr, false);
+ String name = getSymbol(method.getName());
+ if (name.indexOf('$') < 0 && name.indexOf('<') < 0) { // skip synthetic methods
+ myMethods.add(method);
+ }
+ else
+ if (CONSTRUCTOR_NAME.equals(name)) { // store constructors
+ myMethods.add(method);
+ }
+ }
+
+ final ClsAttributeTable attributeTable = readAttributes(ptr);
+ mySourceFileName = attributeTable.sourceFile;
+ myGenericSignature = attributeTable.genericSignature;
+ myRuntimeVisibleAnnotations = attributeTable.runtimeVisibleAnnotations;
+ myRuntimeInvisibleAnnotations = attributeTable.runtimeInvisibleAnnotations;
+ myParsingDone = true;
+ }
+
+ private String getSymbol(final int id) throws ClsFormatException {
+ try {
+ return mySymbolTable.getSymbol(id);
+ }
+ catch (CacheCorruptedException e) {
+ throw new ClsFormatException(e.getLocalizedMessage());
+ }
+ }
+
+ private MemberInfo readMemberStructure(BytePointer ptr, boolean isField) throws ClsFormatException {
+ int flags = ClsUtil.readU2(ptr);
+ int nameIndex = ClsUtil.readU2(ptr);
+ int descriptorIndex = ClsUtil.readU2(ptr);
+
+ BytePointer p = new BytePointer(getData(), getOffsetInConstantPool(nameIndex));
+ String name = ClsUtil.readUtf8Info(p);
+ p.offset = getOffsetInConstantPool(descriptorIndex);
+ String descriptor = ClsUtil.readUtf8Info(p);
+
+ if (isField) {
+ final ClsAttributeTable attributeTable = readAttributes(ptr);
+ return new FieldInfo(
+ getSymbolId(name),
+ getSymbolId(descriptor),
+ attributeTable.genericSignature != null? getSymbolId(attributeTable.genericSignature) : -1,
+ flags,
+ attributeTable.constantValue,
+ attributeTable.runtimeVisibleAnnotations,
+ attributeTable.runtimeInvisibleAnnotations
+ );
+ }
+ else {
+ final ClsAttributeTable attributeTable = readAttributes(ptr);
+ int[] intExceptions = null;
+ if (attributeTable.exceptions != null) {
+ intExceptions = ArrayUtil.newIntArray(attributeTable.exceptions.length);
+ for (int idx = 0; idx < intExceptions.length; idx++) {
+ intExceptions[idx] = getSymbolId(attributeTable.exceptions[idx]);
+ }
+ }
+ return new MethodInfo(
+ getSymbolId(name),
+ getSymbolId(descriptor),
+ attributeTable.genericSignature != null? getSymbolId(attributeTable.genericSignature) : -1,
+ flags,
+ intExceptions,
+ CONSTRUCTOR_NAME.equals(name),
+ attributeTable.runtimeVisibleAnnotations,
+ attributeTable.runtimeInvisibleAnnotations,
+ attributeTable.runtimeVisibleParameterAnnotations,
+ attributeTable.runtimeInvisibleParameterAnnotations,
+ attributeTable.annotationDefault
+ );
+ }
+ }
+
+ private int getSymbolId(final String symbol) throws ClsFormatException{
+ try {
+ return mySymbolTable.getId(symbol);
+ }
+ catch (CacheCorruptedException e) {
+ throw new ClsFormatException(e.getLocalizedMessage());
+ }
+ }
+
+ public String getQualifiedName() throws ClsFormatException {
+ if (myQualifiedName == null) {
+ BytePointer ptr = new BytePointer(getData(), getConstantPoolEnd() + 2);
+ ptr.offset = getOffsetInConstantPool(ClsUtil.readU2(ptr));
+ int tag = ClsUtil.readU1(ptr);
+ if (tag != ClsUtil.CONSTANT_Class){
+ throw new ClsFormatException();
+ }
+ ptr.offset = getOffsetInConstantPool(ClsUtil.readU2(ptr));
+ myQualifiedName = ClsUtil.readUtf8Info(ptr, '/', '.'); // keep '$' in the names
+ }
+ return myQualifiedName;
+ }
+
+ /**
+ * @return fully qualified name of the class' superclass. In case there is no super return ""
+ */
+ public String getSuperClass() throws ClsFormatException {
+ if (mySuperClassName == null) {
+ BytePointer ptr = new BytePointer(getData(), getConstantPoolEnd() + 4);
+ int index = ClsUtil.readU2(ptr);
+ if (index == 0) {
+ if (CommonClassNames.JAVA_LANG_OBJECT.equals(getQualifiedName())) {
+ mySuperClassName = "";
+ }
+ else {
+ throw new ClsFormatException();
+ }
+ }
+ else {
+ ptr.offset = getOffsetInConstantPool(index);
+ mySuperClassName = readClassInfo(ptr); // keep '$' in the name for anonymous classes
+ if (isInterface()) {
+ if (!CommonClassNames.JAVA_LANG_OBJECT.equals(mySuperClassName)) {
+ throw new ClsFormatException();
+ }
+ }
+ /*
+ else {
+ if (!MakeUtil.isAnonymous(mySuperClassName)) {
+ mySuperClassName = mySuperClassName.replace('$', '.');
+ }
+ }
+ */
+ }
+ }
+ return mySuperClassName;
+ }
+
+ public String[] getSuperInterfaces() throws ClsFormatException {
+ if (mySuperInterfaces == null) {
+ BytePointer ptr = new BytePointer(getData(), getConstantPoolEnd() + 6);
+ int count = ClsUtil.readU2(ptr);
+ mySuperInterfaces = ArrayUtil.newStringArray(count);
+ BytePointer auxPtr = new BytePointer(ptr.bytes, 0);
+ for (int idx = 0; idx < mySuperInterfaces.length; idx++) {
+ auxPtr.offset = getOffsetInConstantPool(ClsUtil.readU2(ptr));
+ mySuperInterfaces[idx] = readClassInfo(auxPtr);
+ }
+ }
+ return mySuperInterfaces;
+ }
+
+
+ public String getSourceFileName() throws ClsFormatException {
+ parseMembers();
+ final String fName = mySourceFileName;
+ return fName != null? fName : "";
+ }
+
+ public String getGenericSignature() throws ClsFormatException {
+ parseMembers();
+ final String genericSignature = myGenericSignature;
+ return genericSignature != null && !genericSignature.isEmpty() ? genericSignature : null;
+ }
+
+ public AnnotationConstantValue[] getRuntimeVisibleAnnotations() throws ClsFormatException {
+ parseMembers();
+ final AnnotationConstantValue[] annotations = myRuntimeVisibleAnnotations;
+ return annotations != null? annotations : AnnotationConstantValue.EMPTY_ARRAY;
+ }
+
+ public AnnotationConstantValue[] getRuntimeInvisibleAnnotations() throws ClsFormatException {
+ parseMembers();
+ final AnnotationConstantValue[] annotations = myRuntimeInvisibleAnnotations;
+ return annotations != null? annotations : AnnotationConstantValue.EMPTY_ARRAY;
+ }
+
+ private boolean isInterface(){
+ return (getAccessFlags() & ClsUtil.ACC_INTERFACE) != 0;
+ }
+
+// helper methods
+ private void parseConstantPool() throws ClsFormatException {
+ if (myReferences != null) {
+ return;
+ }
+ myReferences = new ArrayList<ReferenceInfo>();
+ initConstantPool();
+ final BytePointer ptr = new BytePointer(getData(), 0);
+ ConstantPoolIterator iterator = new ConstantPoolIterator(ptr);
+ while (iterator.hasMoreEntries()) {
+ final int tag = ClsUtil.readU1(ptr);
+ if (tag == ClsUtil.CONSTANT_Fieldref || tag == ClsUtil.CONSTANT_Methodref || tag == ClsUtil.CONSTANT_InterfaceMethodref) {
+ //ptr.offset -= 1; // position to the beginning of the structure
+ MemberReferenceInfo refInfo = readRefStructure(tag, ptr);
+ if (refInfo != null) {
+ myReferences.add(refInfo);
+ }
+ /*
+ String name = mySymbolTable.getSymbol(refInfo.getMemberInfo().getName());
+ if (name.indexOf('$') < 0 && name.indexOf('<') < 0) { // skip refs to synthetic members
+ myReferences.add(refInfo);
+ }
+ else if ("<init>".equals(name)) { // add instance initializers (constructors)
+ myReferences.add(refInfo);
+ }
+ else {
+ System.out.println("ReferenceInfo thrown out: " + mySymbolTable.getSymbol(refInfo.getClassName()) + "." + mySymbolTable.getSymbol(refInfo.getMemberInfo().getName()));
+ ourWasteReferenceObjectsCounter += 1;
+ }
+ */
+ }
+ else if (tag == ClsUtil.CONSTANT_Class) {
+ ptr.offset -= 1; // position to the beginning of the structure
+ String className = readClassInfo(ptr);
+ myReferences.add(new ReferenceInfo(getSymbolId(className)));
+ }
+ iterator.next();
+ }
+ //System.out.println("ourWasteReferenceObjectsCounter = " + ourWasteReferenceObjectsCounter);
+ }
+
+ private MemberReferenceInfo readRefStructure(int tag, BytePointer ptr) throws ClsFormatException {
+ /*
+ if (tag != ClsUtil.CONSTANT_Fieldref && tag != ClsUtil.CONSTANT_Methodref && tag != ClsUtil.CONSTANT_InterfaceMethodref) {
+ throw new ClsFormatException();
+ }
+ */
+ int classInfoIndex = ClsUtil.readU2(ptr);
+ int nameTypeInfoIndex = ClsUtil.readU2(ptr);
+
+ ptr.offset = getOffsetInConstantPool(classInfoIndex);
+ if (ClsUtil.CONSTANT_Class != ClsUtil.readU1(ptr)) {
+ throw new ClsFormatException();
+ }
+ ptr.offset = getOffsetInConstantPool(ClsUtil.readU2(ptr));
+ String className = ClsUtil.readUtf8Info(ptr, '/', '.'); // keep '$' in names
+
+ ptr.offset = getOffsetInConstantPool(nameTypeInfoIndex);
+ if (ClsUtil.CONSTANT_NameAndType != ClsUtil.readU1(ptr)) {
+ throw new ClsFormatException();
+ }
+ int memberNameIndex = ClsUtil.readU2(ptr);
+ int descriptorIndex = ClsUtil.readU2(ptr);
+
+ ptr.offset = getOffsetInConstantPool(memberNameIndex);
+ String memberName = ClsUtil.readUtf8Info(ptr);
+
+ if ((memberName.indexOf('$') >= 0 || memberName.indexOf('<') >= 0) && !CONSTRUCTOR_NAME.equals(memberName)) { // skip refs to synthetic members
+ return null;
+ }
+
+ ptr.offset = getOffsetInConstantPool(descriptorIndex);
+ String descriptor = ClsUtil.readUtf8Info(ptr);
+
+ MemberInfo info = ClsUtil.CONSTANT_Fieldref == tag? new FieldInfo(getSymbolId(memberName), getSymbolId(descriptor)) : new MethodInfo(getSymbolId(memberName), getSymbolId(descriptor), CONSTRUCTOR_NAME.equals(memberName));
+ return new MemberReferenceInfo(getSymbolId(className), info);
+ }
+
+ public int getAccessFlags(){
+ try{
+ int offset = getConstantPoolEnd();
+ byte[] data = getData();
+ if (offset + 2 > data.length){
+ throw new ClsFormatException();
+ }
+ int b1 = data[offset++] & 0xFF;
+ int b2 = data[offset++] & 0xFF;
+ return (b1 << 8) + b2;
+ }
+ catch(ClsFormatException e){
+ return 0;
+ }
+ }
+
+ private byte[] getData(){
+ if (myData == null) {
+ try{
+ myData = FileUtil.loadFileBytes(myFile);
+ }
+ catch(IOException e){
+ myData = ArrayUtil.EMPTY_BYTE_ARRAY;
+ }
+ }
+ return myData;
+ }
+
+ private int getOffsetInConstantPool(int index) throws ClsFormatException {
+ initConstantPool();
+ if (index < 1 || index >= myConstantPoolOffsets.length){
+ throw new ClsFormatException();
+ }
+ return myConstantPoolOffsets[index - 1];
+ }
+
+ private int getConstantPoolEnd() throws ClsFormatException {
+ initConstantPool();
+ return myConstantPoolOffsets[myConstantPoolOffsets.length - 1];
+ }
+
+ private void initConstantPool() throws ClsFormatException {
+ if (myConstantPoolOffsets == null){
+ BytePointer ptr = new BytePointer(getData(), 0);
+ ConstantPoolIterator iterator = new ConstantPoolIterator(ptr);
+ myConstantPoolOffsets = new int[iterator.getEntryCount()];
+ myConstantPoolOffsets[0] = iterator.getCurrentOffset();
+ int index = 1;
+ while (iterator.hasMoreEntries()) {
+ int tag = ClsUtil.readU1(ptr);
+ if (tag == ClsUtil.CONSTANT_Long || tag == ClsUtil.CONSTANT_Double) {
+ myConstantPoolOffsets[index++] = ptr.offset + 8; // takes 2 entries!
+ }
+ iterator.next();
+ myConstantPoolOffsets[index++] = iterator.getCurrentOffset();
+ }
+ }
+ }
+
+ private String readClassInfo(BytePointer ptr) throws ClsFormatException{
+ final int tag = ClsUtil.readU1(ptr);
+ if (tag != ClsUtil.CONSTANT_Class){
+ throw new ClsFormatException(CompilerBundle.message("class.parsing.error.wrong.record.tag.expected.another", tag, ClsUtil.CONSTANT_Class));
+ }
+ int index = ClsUtil.readU2(ptr);
+ return ClsUtil.readUtf8Info(new BytePointer(ptr.bytes, getOffsetInConstantPool(index)), '/', '.');
+ }
+
+ @SuppressWarnings({"HardCodedStringLiteral"})
+ private ClsAttributeTable readAttributes(BytePointer ptr) throws ClsFormatException {
+ int count = ClsUtil.readU2(ptr); // attributeCount
+ final ClsAttributeTable attributes = new ClsAttributeTable();
+ while (count-- > 0) {
+ final String attrName = readAttributeName(ptr);
+ if ("Exceptions".equals(attrName)) {
+ attributes.exceptions = readExceptions(ptr);
+ }
+ else if ("Signature".equals(attrName)) {
+ attributes.genericSignature = readSignatureAttribute(ptr);
+ }
+ else if ("SourceFile".equals(attrName)) {
+ attributes.sourceFile = readSourceFileAttribute(ptr);
+ }
+ else if ("ConstantValue".equals(attrName)){
+ attributes.constantValue = readFieldConstantValue(ptr);
+ }
+ else if ("RuntimeVisibleAnnotations".equals(attrName)) {
+ attributes.runtimeVisibleAnnotations = readAnnotations(ptr);
+ }
+ else if ("RuntimeInvisibleAnnotations".equals(attrName)) {
+ attributes.runtimeInvisibleAnnotations = readAnnotations(ptr);
+ }
+ else if ("RuntimeVisibleParameterAnnotations".equals(attrName)) {
+ attributes.runtimeVisibleParameterAnnotations = readParameterAnnotations(ptr);
+ }
+ else if ("RuntimeInvisibleParameterAnnotations".equals(attrName)) {
+ attributes.runtimeInvisibleParameterAnnotations = readParameterAnnotations(ptr);
+ }
+ else if ("AnnotationDefault".equals(attrName)) {
+ attributes.annotationDefault = readAnnotationMemberValue(new BytePointer(ptr.bytes, ptr.offset + 6));
+ }
+ gotoNextAttribute(ptr);
+ }
+ return attributes;
+ }
+
+ private String readAttributeName(BytePointer p) throws ClsFormatException {
+ final BytePointer ptr = new BytePointer(p.bytes, p.offset);
+ final int nameIndex = ClsUtil.readU2(ptr);
+ ptr.offset = getOffsetInConstantPool(nameIndex);
+ return ClsUtil.readUtf8Info(ptr);
+ }
+
+ private static void gotoNextAttribute(BytePointer ptr) throws ClsFormatException {
+ ptr.offset += 2; // skip name index
+ final int length = ClsUtil.readU4(ptr); // important! Do not inline since ptr.offset is also changed inside ClsUtil.readU4() method
+ ptr.offset += length;
+ }
+
+ /**
+ Signature_attribute {
+ u2 attribute_name_index; (must be equal to "Signature")
+ u4 attribute_length; (must be equal to 2)
+ u2 signature_index;
+ }
+ */
+ private String readSignatureAttribute(BytePointer p) throws ClsFormatException {
+ final BytePointer ptr = new BytePointer(p.bytes, p.offset + 2); // position to the length
+ if (ClsUtil.readU4(ptr) != 2) {
+ return null;
+ }
+ ptr.offset = getOffsetInConstantPool(ClsUtil.readU2(ptr));
+ return ClsUtil.readUtf8Info(ptr);
+ }
+
+ private String[] readExceptions(BytePointer p) throws ClsFormatException{
+ final BytePointer ptr = new BytePointer(p.bytes, p.offset + 6); // position to the count of exceptions
+ int count = ClsUtil.readU2(ptr);
+ final ArrayList<String> array = new ArrayList<String>(count);
+ while (count-- > 0) {
+ int idx = ClsUtil.readU2(ptr);
+ if (idx != 0) {
+ final String exceptionClass = readClassInfo(new BytePointer(ptr.bytes, getOffsetInConstantPool(idx)));
+ array.add(exceptionClass);
+ }
+ }
+ return ArrayUtil.toStringArray(array);
+ }
+
+ private String readSourceFileAttribute(BytePointer p) throws ClsFormatException {
+ BytePointer ptr = new BytePointer(p.bytes, p.offset + 2); // position to the length
+ if (ClsUtil.readU4(ptr) != 2) {
+ return null;
+ }
+ ptr.offset = getOffsetInConstantPool(ClsUtil.readU2(ptr));
+ String path = ClsUtil.readUtf8Info(ptr);
+ // jdk version 1.3.0 puts full path to the source, but later versions store only short name
+ final int slashIndex = path.lastIndexOf('/');
+ if (slashIndex >= 0) {
+ path = path.substring(slashIndex + 1, path.length());
+ }
+ return path;
+ }
+
+ private ConstantValue readFieldConstantValue(BytePointer p) throws ClsFormatException{
+ final BytePointer ptr = new BytePointer(p.bytes, p.offset + 2);
+ if (ClsUtil.readU4(ptr) != 2) {
+ throw new ClsFormatException(); // attribute length must be 2
+ }
+ int valueIndex = ClsUtil.readU2(ptr);
+ ptr.offset = getOffsetInConstantPool(valueIndex);
+ return readConstant(ptr);
+ }
+
+ private ConstantValue readConstant(final BytePointer ptr) throws ClsFormatException {
+ final int tag = ClsUtil.readU1(ptr);
+ switch (tag) {
+ case ClsUtil.CONSTANT_Integer :
+ int value = ClsUtil.readU4(ptr);
+ return new IntegerConstantValue(value);
+ case ClsUtil.CONSTANT_Float:
+ float floatValue = ClsUtil.readFloat(ptr);
+ return new FloatConstantValue(floatValue);
+ case ClsUtil.CONSTANT_Long :
+ int high = ClsUtil.readU4(ptr);
+ int low = ClsUtil.readU4(ptr);
+ long v = ((long)high << 32) | (low & 0xFFFFFFFFL);
+ return new LongConstantValue(v);
+ case ClsUtil.CONSTANT_Double :
+ double doubleValue = ClsUtil.readDouble(ptr);
+ return new DoubleConstantValue(doubleValue);
+ case ClsUtil.CONSTANT_String :
+ int stringIndex = ClsUtil.readU2(ptr);
+ ptr.offset = getOffsetInConstantPool(stringIndex);
+ return new StringConstantValue(ClsUtil.readUtf8Info(ptr));
+ default : throw new ClsFormatException();
+ }
+ }
+
+ private AnnotationConstantValue[] readAnnotations(BytePointer p) throws ClsFormatException {
+ final BytePointer ptr = new BytePointer(p.bytes, p.offset + 6);
+ return readAnnotationsArray(ptr);
+ }
+
+ private AnnotationConstantValue[][] readParameterAnnotations(BytePointer p) throws ClsFormatException {
+ final BytePointer ptr = new BytePointer(p.bytes, p.offset + 6); // position to the number of parameters
+ final int numberOfParams = ClsUtil.readU1(ptr);
+ if (numberOfParams == 0) {
+ return null;
+ }
+ final AnnotationConstantValue[][] annotations = new AnnotationConstantValue[numberOfParams][];
+ for (int parameterIndex = 0; parameterIndex < numberOfParams; parameterIndex++) {
+ annotations[parameterIndex] = readAnnotationsArray(ptr);
+ }
+ return annotations;
+ }
+
+ private AnnotationConstantValue[] readAnnotationsArray(BytePointer ptr) throws ClsFormatException {
+ final int numberOfAnnotations = ClsUtil.readU2(ptr);
+ if (numberOfAnnotations == 0) {
+ return AnnotationConstantValue.EMPTY_ARRAY;
+ }
+ AnnotationConstantValue[] annotations = new AnnotationConstantValue[numberOfAnnotations];
+ for (int attributeIndex = 0; attributeIndex < numberOfAnnotations; attributeIndex++) {
+ annotations[attributeIndex] = readAnnotation(ptr);
+ }
+ return annotations;
+ }
+
+ private AnnotationConstantValue readAnnotation(BytePointer ptr) throws ClsFormatException {
+ final int classInfoIndex = ClsUtil.readU2(ptr);
+ final String qName = readAnnotationClassName(new BytePointer(ptr.bytes, getOffsetInConstantPool(classInfoIndex)));
+ final List<AnnotationNameValuePair> memberValues = new ArrayList<AnnotationNameValuePair>();
+ final int numberOfPairs = ClsUtil.readU2(ptr);
+ for (int idx = 0; idx < numberOfPairs; idx++) {
+ final int memberNameIndex = ClsUtil.readU2(ptr);
+ final String memberName = ClsUtil.readUtf8Info(ptr.bytes, getOffsetInConstantPool(memberNameIndex));
+ final ConstantValue memberValue = readAnnotationMemberValue(ptr);
+ memberValues.add(new AnnotationNameValuePair(getSymbolId(memberName), memberValue));
+ }
+ return new AnnotationConstantValue(getSymbolId(qName), memberValues.toArray(new AnnotationNameValuePair[memberValues.size()]));
+ }
+
+ private String readAnnotationClassName(BytePointer ptr) throws ClsFormatException {
+ // TODO: need this method because because of incomplete class format spec at the moment of writing
+ // it is not clear what structure is expected: CONSTANT_Utf8 or CONSTANT_Class
+ final int tag = ClsUtil.readU1(ptr);
+ if (tag == ClsUtil.CONSTANT_Utf8) {
+ return ClsUtil.getTypeText(ptr.bytes, ptr.offset + 2); //skip length
+ }
+ if (tag == ClsUtil.CONSTANT_Class){
+ ptr.offset -= 1; // rollback
+ return readClassInfo(ptr);
+ }
+ //noinspection HardCodedStringLiteral
+ throw new ClsFormatException(CompilerBundle.message("class.parsing.error.wrong.record.tag.expected.another", tag, "CONSTANT_Utf8(" + ClsUtil.CONSTANT_Utf8 + ") / CONSTANT_Class(" + ClsUtil.CONSTANT_Class + ")"));
+ }
+
+ private ConstantValue readAnnotationMemberValue(BytePointer ptr) throws ClsFormatException {
+ final char tag = (char)ClsUtil.readU1(ptr);
+ switch (tag) {
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'F':
+ case 'I':
+ case 'J':
+ case 'S':
+ case 'Z': {
+ final int valueIndex = ClsUtil.readU2(ptr);
+ return new AnnotationPrimitiveConstantValue(tag, readConstant(new BytePointer(ptr.bytes, getOffsetInConstantPool(valueIndex))));
+ }
+ case 's': {
+ final int valueIndex = ClsUtil.readU2(ptr);
+ return new StringConstantValue(ClsUtil.readUtf8Info(ptr.bytes, getOffsetInConstantPool(valueIndex)));
+ }
+ case 'e': {
+ final int typeNameIndex = ClsUtil.readU2(ptr);
+ final int constantNameIndex = ClsUtil.readU2(ptr);
+ final String typeName = ClsUtil.readUtf8Info(ptr.bytes, getOffsetInConstantPool(typeNameIndex));
+ final String constantName = ClsUtil.readUtf8Info(ptr.bytes, getOffsetInConstantPool(constantNameIndex));
+ return new EnumConstantValue(getSymbolId(typeName), getSymbolId(constantName));
+ }
+ case 'c' : {
+ final int classInfoIndex = ClsUtil.readU2(ptr);
+ BytePointer p = new BytePointer(ptr.bytes, getOffsetInConstantPool(classInfoIndex));
+ final int recordTag = ClsUtil.readU1(p);
+ if (recordTag != ClsUtil.CONSTANT_Utf8) {
+ throw new ClsFormatException(CompilerBundle.message("class.parsing.error.wrong.record.tag.expected.another", recordTag, ClsUtil.CONSTANT_Utf8));
+ }
+ p.offset += 2; //Skip length
+ final String className = ClsUtil.getTypeText(p.bytes, p.offset);
+ return new ClassInfoConstantValue(getSymbolId(className));
+ }
+ case '@' : {
+ return readAnnotation(ptr);
+ }
+ case '[' : {
+ final int numberOfValues = ClsUtil.readU2(ptr);
+ final ConstantValue[] values = new ConstantValue[numberOfValues];
+ for (int idx = 0; idx < numberOfValues; idx++) {
+ values[idx] = readAnnotationMemberValue(ptr);
+ }
+ return new ConstantValueArray(values);
+ }
+ default : throw new ClsFormatException(CompilerBundle.message("class.parsing.error.wrong.tag.annotation.member.value", tag));
+ }
+ }
+
+ private static class ClsAttributeTable {
+ public String[] exceptions;
+ public String genericSignature;
+ public String sourceFile;
+ public ConstantValue constantValue;
+ public AnnotationConstantValue[] runtimeVisibleAnnotations;
+ public AnnotationConstantValue[] runtimeInvisibleAnnotations;
+ public AnnotationConstantValue[][] runtimeVisibleParameterAnnotations;
+ public AnnotationConstantValue[][] runtimeInvisibleParameterAnnotations;
+ public ConstantValue annotationDefault;
+ }
+
+ private static class ConstantPoolIterator {
+ private final BytePointer myPtr;
+ private final int myEntryCount;
+ private int myCurrentEntryIndex;
+ private int myCurrentOffset;
+
+ public ConstantPoolIterator(BytePointer ptr) throws ClsFormatException {
+ myPtr = ptr;
+ myPtr.offset = 0;
+ int magic = ClsUtil.readU4(myPtr);
+ if (magic != ClsUtil.MAGIC){
+ throw new ClsFormatException();
+ }
+ myPtr.offset += 2; // minor version
+ myPtr.offset += 2; // major version
+ myEntryCount = ClsUtil.readU2(myPtr);
+ if (myEntryCount < 1){
+ throw new ClsFormatException();
+ }
+ myCurrentEntryIndex = 1; // Entry at index 0 is included in the count but is not present in the constant pool
+ myCurrentOffset = myPtr.offset;
+ }
+
+ public int getEntryCount() {
+ return myEntryCount;
+ }
+
+ public int getCurrentOffset() {
+ return myCurrentOffset;
+ }
+
+ /**
+ * tests if there are unread entries
+ */
+ public boolean hasMoreEntries() {
+ return myCurrentEntryIndex < myEntryCount;
+ }
+
+ /**
+ * Positions the pointer to the next entry
+ */
+ public void next() throws ClsFormatException {
+ myPtr.offset = myCurrentOffset;
+ int tag = ClsUtil.readU1(myPtr);
+ switch(tag){
+ default:
+ throw new ClsFormatException();
+
+ case ClsUtil.CONSTANT_Class:
+ case ClsUtil.CONSTANT_String:
+ myPtr.offset += 2;
+ break;
+
+ case ClsUtil.CONSTANT_Fieldref:
+ case ClsUtil.CONSTANT_Methodref:
+ case ClsUtil.CONSTANT_InterfaceMethodref:
+ case ClsUtil.CONSTANT_Integer:
+ case ClsUtil.CONSTANT_Float:
+ case ClsUtil.CONSTANT_NameAndType:
+ myPtr.offset += 4;
+ break;
+
+ case ClsUtil.CONSTANT_Long:
+ case ClsUtil.CONSTANT_Double:
+ myPtr.offset += 8;
+ myCurrentEntryIndex++; // takes 2 entries
+ break;
+
+ case ClsUtil.CONSTANT_Utf8:
+ int length = ClsUtil.readU2(myPtr);
+ myPtr.offset += length;
+ break;
+
+ case ClsUtil.CONSTANT_MethodHandle:
+ myPtr.offset += 3;
+ break;
+ case ClsUtil.CONSTANT_MethodType:
+ myPtr.offset += 2;
+ break;
+ case ClsUtil.CONSTANT_InvokeDynamic:
+ myPtr.offset += 4;
+ break;
+ }
+ myCurrentEntryIndex++;
+ myCurrentOffset = myPtr.offset;
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/classParsing/ClassInfo.java b/java/compiler/impl/src/com/intellij/compiler/classParsing/ClassInfo.java
new file mode 100644
index 0000000..2d816ef
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/classParsing/ClassInfo.java
@@ -0,0 +1,229 @@
+/*
+ * 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 com.intellij.compiler.classParsing;
+
+import com.intellij.compiler.SymbolTable;
+import com.intellij.compiler.make.CacheCorruptedException;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.Comparing;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.cls.ClsFormatException;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Collection;
+
+/**
+ @author Eugene Zhuravlev
+*/
+public final class ClassInfo implements Cloneable {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.classParsing.ClassInfo");
+
+ private static final ReferenceInfo[] EMPTY_REF_ARRAY = new ReferenceInfo[0];
+
+ private final int myQualifiedName;
+ private final int myGenericSignature;
+ private final int mySuperQualifiedName;
+ private final int myFlags;
+ private String myPath;
+ private final String mySourceFileName;
+ private final int[] mySuperInterfaces;
+ private final FieldInfo[] myFields;
+ private final MethodInfo[] myMethods;
+ private ReferenceInfo[] myReferences;
+ private final AnnotationConstantValue[] myRuntimeVisibleAnnotations;
+ private final AnnotationConstantValue[] myRuntimeInvisibleAnnotations;
+ private boolean myIsDirty = false;
+
+ public ClassInfo(ClassFileReader reader, SymbolTable symbolTable) throws CacheCorruptedException {
+ try {
+ final int qName = symbolTable.getId(reader.getQualifiedName());
+ myQualifiedName = qName;
+
+ final String genericSignature = reader.getGenericSignature();
+ myGenericSignature = genericSignature != null? symbolTable.getId(genericSignature) : -1;
+
+ myPath = reader.getPath();
+
+ final String superClass = reader.getSuperClass();
+ final int superQName = "".equals(superClass)? -1 : symbolTable.getId(superClass);
+ mySuperQualifiedName = superQName;
+
+ LOG.assertTrue(superQName != qName);
+
+ final String[] superInterfaces = reader.getSuperInterfaces();
+ mySuperInterfaces = ArrayUtil.newIntArray(superInterfaces.length);
+ for (int idx = 0; idx < superInterfaces.length; idx++) {
+ mySuperInterfaces[idx] = symbolTable.getId(superInterfaces[idx]);
+ }
+
+ final String sourceFileName = reader.getSourceFileName();
+ mySourceFileName = sourceFileName != null? sourceFileName : "";
+
+ myFlags = reader.getAccessFlags();
+
+ myRuntimeVisibleAnnotations = reader.getRuntimeVisibleAnnotations();
+ myRuntimeInvisibleAnnotations = reader.getRuntimeInvisibleAnnotations();
+
+ final Collection<ReferenceInfo> refs = reader.getReferences();
+ myReferences = refs.toArray(new ReferenceInfo[refs.size()]);
+
+ myFields = reader.getFields();
+ myMethods = reader.getMethods();
+ }
+ catch (ClsFormatException e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public ClassInfo(DataInput in) throws IOException {
+ myQualifiedName = in.readInt();
+ mySuperQualifiedName = in.readInt();
+ myGenericSignature = in.readInt();
+ myFlags = in.readInt();
+ myPath = in.readUTF();
+ mySourceFileName = in.readUTF();
+
+ final int ifaceCount = in.readInt();
+ mySuperInterfaces = new int[ifaceCount];
+ for (int idx = 0; idx < ifaceCount; idx++) {
+ mySuperInterfaces[idx] = in.readInt();
+ }
+
+ final int fieldCount = in.readInt();
+ myFields = new FieldInfo[fieldCount];
+ for (int idx = 0; idx < fieldCount; idx++) {
+ myFields[idx] = new FieldInfo(in);
+ }
+
+ final int methodCount = in.readInt();
+ myMethods = new MethodInfo[methodCount];
+ for (int idx = 0; idx < methodCount; idx++) {
+ myMethods[idx] = new MethodInfo(in);
+ }
+
+ final int refCount = in.readInt();
+ myReferences = refCount > 0? new ReferenceInfo[refCount] : EMPTY_REF_ARRAY;
+ for (int idx = 0; idx < refCount; idx++) {
+ myReferences[idx] = MemberInfoExternalizer.loadReferenceInfo(in);
+ }
+
+ myRuntimeVisibleAnnotations = MemberInfoExternalizer.readAnnotationConstantValueArray1(in);
+ myRuntimeInvisibleAnnotations = MemberInfoExternalizer.readAnnotationConstantValueArray1(in);
+ }
+
+ public ClassInfo clone() {
+ try {
+ return (ClassInfo)super.clone();
+ }
+ catch (CloneNotSupportedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public void save(DataOutput out) throws IOException {
+ out.writeInt(myQualifiedName);
+ out.writeInt(mySuperQualifiedName);
+ out.writeInt(myGenericSignature);
+ out.writeInt(myFlags);
+ out.writeUTF(myPath);
+ out.writeUTF(mySourceFileName);
+ out.writeInt(mySuperInterfaces.length);
+ for (int ifaceQName : mySuperInterfaces) {
+ out.writeInt(ifaceQName);
+ }
+
+ out.writeInt(myFields.length);
+ for (FieldInfo field : myFields) {
+ field.save(out);
+ }
+
+ out.writeInt(myMethods.length);
+ for (MethodInfo method : myMethods) {
+ method.save(out);
+ }
+
+ out.writeInt(myReferences.length);
+ for (ReferenceInfo info : myReferences) {
+ MemberInfoExternalizer.saveReferenceInfo(out, info);
+ }
+
+ MemberInfoExternalizer.writeConstantValueArray1(out, myRuntimeVisibleAnnotations);
+ MemberInfoExternalizer.writeConstantValueArray1(out, myRuntimeInvisibleAnnotations);
+ }
+
+ public boolean isDirty() {
+ return myIsDirty;
+ }
+
+ public int getQualifiedName() throws IOException {
+ return myQualifiedName;
+ }
+
+ public int getGenericSignature() throws IOException {
+ return myGenericSignature;
+ }
+
+ public int getSuperQualifiedName() throws IOException {
+ return mySuperQualifiedName;
+ }
+
+ public int[] getSuperInterfaces() throws IOException {
+ return mySuperInterfaces;
+ }
+
+ public int getFlags() throws IOException {
+ return myFlags;
+ }
+
+ public String getPath() throws IOException {
+ return myPath;
+ }
+
+ public void setPath(String path) {
+ myIsDirty |= !Comparing.equal(myPath, path);
+ myPath = path;
+ }
+
+ public String getSourceFileName() throws IOException {
+ return mySourceFileName;
+ }
+
+ public AnnotationConstantValue[] getRuntimeVisibleAnnotations() throws IOException {
+ return myRuntimeVisibleAnnotations;
+ }
+
+ public AnnotationConstantValue[] getRuntimeInvisibleAnnotations() throws IOException {
+ return myRuntimeInvisibleAnnotations;
+ }
+
+ public ReferenceInfo[] getReferences() {
+ return myReferences;
+ }
+
+ public void clearReferences() {
+ myReferences = EMPTY_REF_ARRAY;
+ }
+
+ public FieldInfo[] getFields() {
+ return myFields;
+ }
+
+ public MethodInfo[] getMethods() {
+ return myMethods;
+ }
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/classParsing/ClassInfoConstantValue.java b/java/compiler/impl/src/com/intellij/compiler/classParsing/ClassInfoConstantValue.java
new file mode 100644
index 0000000..a444d32
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/classParsing/ClassInfoConstantValue.java
@@ -0,0 +1,56 @@
+/*
+ * 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 com.intellij.compiler.classParsing;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class ClassInfoConstantValue extends ConstantValue{
+ private final int myValue;
+
+ public ClassInfoConstantValue(int value) {
+ myValue = value;
+ }
+
+ public ClassInfoConstantValue(DataInput in) throws IOException{
+ myValue = in.readInt();
+ }
+
+ public int getValue() {
+ return myValue;
+ }
+
+ public void save(DataOutput out) throws IOException {
+ super.save(out);
+ out.writeInt(myValue);
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ClassInfoConstantValue)) return false;
+
+ final ClassInfoConstantValue classInfoConstantValue = (ClassInfoConstantValue)o;
+
+ if (myValue != classInfoConstantValue.myValue) return false;
+
+ return true;
+ }
+
+ public int hashCode() {
+ return myValue;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/classParsing/ConstantValue.java b/java/compiler/impl/src/com/intellij/compiler/classParsing/ConstantValue.java
new file mode 100644
index 0000000..31615b2
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/classParsing/ConstantValue.java
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+/**
+ * created at Feb 24, 2002
+ * @author Jeka
+ */
+package com.intellij.compiler.classParsing;
+
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class ConstantValue {
+ public static final ConstantValue EMPTY_CONSTANT_VALUE = new ConstantValue();
+
+ protected ConstantValue() {
+ }
+
+ public void save(DataOutput out) throws IOException {
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/classParsing/ConstantValueArray.java b/java/compiler/impl/src/com/intellij/compiler/classParsing/ConstantValueArray.java
new file mode 100644
index 0000000..850564a
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/classParsing/ConstantValueArray.java
@@ -0,0 +1,68 @@
+/*
+ * 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 com.intellij.compiler.classParsing;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Arrays;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Apr 2, 2004
+ */
+public class ConstantValueArray extends ConstantValue{
+ private final ConstantValue[] myValue;
+
+ public ConstantValueArray(ConstantValue[] value) {
+ myValue = value;
+ }
+
+ public ConstantValueArray(DataInput in) throws IOException {
+ final int size = in.readInt();
+ myValue = new ConstantValue[size];
+ for (int idx = 0; idx < size; idx++) {
+ myValue[idx] = MemberInfoExternalizer.loadConstantValue(in);
+ }
+ }
+
+ public ConstantValue[] getValue() {
+ return myValue;
+ }
+
+ public void save(DataOutput out) throws IOException {
+ out.writeInt(myValue.length);
+ for (int idx = 0; idx < myValue.length; idx++) {
+ MemberInfoExternalizer.saveConstantValue(out, myValue[idx]);
+ }
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ConstantValueArray)) return false;
+
+ final ConstantValueArray constantValueArray = (ConstantValueArray)o;
+
+ if (!Arrays.equals(myValue, constantValueArray.myValue)) return false;
+
+ return true;
+ }
+
+ public int hashCode() {
+ return 0;
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/classParsing/DoubleConstantValue.java b/java/compiler/impl/src/com/intellij/compiler/classParsing/DoubleConstantValue.java
new file mode 100644
index 0000000..7e7b8e6
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/classParsing/DoubleConstantValue.java
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+/**
+ * created at Feb 24, 2002
+ * @author Jeka
+ */
+package com.intellij.compiler.classParsing;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class DoubleConstantValue extends ConstantValue{
+ private final double myValue;
+
+ public DoubleConstantValue(double value) {
+ myValue = value;
+ }
+
+ public DoubleConstantValue(DataInput in) throws IOException{
+ myValue = in.readDouble();
+ }
+
+ public double getValue() {
+ return myValue;
+ }
+
+ public void save(DataOutput out) throws IOException {
+ super.save(out);
+ out.writeDouble(myValue);
+ }
+
+ public boolean equals(Object obj) {
+ return (obj instanceof DoubleConstantValue) && (((DoubleConstantValue)obj).myValue == myValue);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/classParsing/EnumConstantValue.java b/java/compiler/impl/src/com/intellij/compiler/classParsing/EnumConstantValue.java
new file mode 100644
index 0000000..548bda1
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/classParsing/EnumConstantValue.java
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+/**
+ * created at Feb 24, 2002
+ * @author Jeka
+ */
+package com.intellij.compiler.classParsing;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class EnumConstantValue extends ConstantValue{
+ private final int myTypeName;
+ private final int myConstantName;
+
+ public EnumConstantValue(int typeName, int constantName) {
+ myTypeName = typeName;
+ myConstantName = constantName;
+ }
+
+ public EnumConstantValue(DataInput in) throws IOException{
+ myTypeName = in.readInt();
+ myConstantName = in.readInt();
+ }
+
+ public int getTypeName() {
+ return myTypeName;
+ }
+
+ public int getConstantName() {
+ return myConstantName;
+ }
+
+ public void save(DataOutput out) throws IOException {
+ super.save(out);
+ out.writeInt(myTypeName);
+ out.writeInt(myConstantName);
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof EnumConstantValue)) return false;
+
+ final EnumConstantValue enumConstantValue = (EnumConstantValue)o;
+
+ if (myConstantName != enumConstantValue.myConstantName) return false;
+ if (myTypeName != enumConstantValue.myTypeName) return false;
+
+ return true;
+ }
+
+ public int hashCode() {
+ int result;
+ result = myTypeName;
+ result = 29 * result + myConstantName;
+ return result;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/classParsing/FieldInfo.java b/java/compiler/impl/src/com/intellij/compiler/classParsing/FieldInfo.java
new file mode 100644
index 0000000..4611f08
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/classParsing/FieldInfo.java
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+/**
+ * created at Jan 10, 2002
+ * @author Jeka
+ */
+package com.intellij.compiler.classParsing;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class FieldInfo extends MemberInfo {
+ public static final FieldInfo[] EMPTY_ARRAY = new FieldInfo[0];
+ private final ConstantValue myConstantValue;
+
+ public FieldInfo(int name, int descriptor) {
+ super(name, descriptor);
+ myConstantValue = ConstantValue.EMPTY_CONSTANT_VALUE;
+ }
+
+ public FieldInfo(int name, int descriptor, final int genericSignature, int flags, ConstantValue value, final AnnotationConstantValue[] runtimeVisibleAnnotations, final AnnotationConstantValue[] runtimeInvisibleAnnotations) {
+ super(name, descriptor, genericSignature, flags, runtimeVisibleAnnotations, runtimeInvisibleAnnotations);
+ myConstantValue = value == null ? ConstantValue.EMPTY_CONSTANT_VALUE : value;
+ }
+
+ public FieldInfo(DataInput in) throws IOException {
+ super(in);
+ myConstantValue = MemberInfoExternalizer.loadConstantValue(in);
+ }
+
+ public ConstantValue getConstantValue() {
+ return myConstantValue;
+ }
+
+ public void save(DataOutput out) throws IOException {
+ super.save(out);
+ MemberInfoExternalizer.saveConstantValue(out, myConstantValue);
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/classParsing/FloatConstantValue.java b/java/compiler/impl/src/com/intellij/compiler/classParsing/FloatConstantValue.java
new file mode 100644
index 0000000..f818cbf
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/classParsing/FloatConstantValue.java
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+/**
+ * created at Feb 24, 2002
+ * @author Jeka
+ */
+package com.intellij.compiler.classParsing;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class FloatConstantValue extends ConstantValue{
+ private final float myValue;
+
+ public FloatConstantValue(float value) {
+ myValue = value;
+ }
+
+ public FloatConstantValue(DataInput in) throws IOException{
+ myValue = in.readFloat();
+ }
+
+ public float getValue() {
+ return myValue;
+ }
+
+ public void save(DataOutput out) throws IOException {
+ super.save(out);
+ out.writeFloat(myValue);
+ }
+
+ public boolean equals(Object obj) {
+ return (obj instanceof FloatConstantValue) && (((FloatConstantValue)obj).myValue == myValue);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/classParsing/GenericMethodSignature.java b/java/compiler/impl/src/com/intellij/compiler/classParsing/GenericMethodSignature.java
new file mode 100644
index 0000000..10c01b4
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/classParsing/GenericMethodSignature.java
@@ -0,0 +1,98 @@
+/*
+ * 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 com.intellij.compiler.classParsing;
+
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.util.ArrayUtil;
+
+import java.text.CharacterIterator;
+import java.text.StringCharacterIterator;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 4, 2004
+ */
+public class GenericMethodSignature {
+ private final String myFormalTypeParams;
+ private final String[] myParamSignatures;
+ private final String myReturnTypeSignature;
+ private final String myThrowsSignature;
+
+ private GenericMethodSignature(String formalTypeParams, String[] paramSignatures, String returnTypeSignature, String throwsSignature) {
+ myFormalTypeParams = formalTypeParams;
+ myParamSignatures = paramSignatures;
+ myReturnTypeSignature = returnTypeSignature;
+ myThrowsSignature = throwsSignature;
+ }
+
+ public String getFormalTypeParams() {
+ return myFormalTypeParams;
+ }
+
+ public String[] getParamSignatures() {
+ return myParamSignatures;
+ }
+
+ public String getReturnTypeSignature() {
+ return myReturnTypeSignature;
+ }
+
+ public String getThrowsSignature() {
+ return myThrowsSignature;
+ }
+
+ public static GenericMethodSignature parse(String methodSignature) throws SignatureParsingException {
+ final StringCharacterIterator it = new StringCharacterIterator(methodSignature);
+
+ final StringBuilder formals = new StringBuilder();
+ if (it.current() == '<') {
+ SignatureParser.INSTANCE.parseFormalTypeParameters(it, formals);
+ }
+
+ if (it.current() != '(') {
+ throw new SignatureParsingException(CompilerBundle.message("error.signature.parsing.expected.other.symbol", "(", formals.toString()));
+ }
+
+ it.next(); // skip '('
+
+ final String[] paramSignatures;
+ if (it.current() != ')') {
+ final List<String> params = new ArrayList<String>();
+ while (it.current() != ')') {
+ final StringBuilder typeSignature = new StringBuilder();
+ SignatureParser.INSTANCE.parseTypeSignature(it, typeSignature);
+ params.add(typeSignature.toString());
+ }
+ paramSignatures = ArrayUtil.toStringArray(params);
+ }
+ else {
+ paramSignatures = ArrayUtil.EMPTY_STRING_ARRAY;
+ }
+ it.next(); // skip ')'
+
+ final StringBuilder returnTypeSignature = new StringBuilder();
+ SignatureParser.INSTANCE.parseReturnType(it, returnTypeSignature);
+
+ final StringBuilder throwsSignature = new StringBuilder();
+ if (it.current() != CharacterIterator.DONE) {
+ SignatureParser.INSTANCE.parseThrowsSignature(it, throwsSignature);
+ }
+
+ return new GenericMethodSignature(formals.toString(), paramSignatures, returnTypeSignature.toString(), throwsSignature.toString());
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/classParsing/IntegerConstantValue.java b/java/compiler/impl/src/com/intellij/compiler/classParsing/IntegerConstantValue.java
new file mode 100644
index 0000000..d59a727
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/classParsing/IntegerConstantValue.java
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+/**
+ * created at Feb 24, 2002
+ * @author Jeka
+ */
+package com.intellij.compiler.classParsing;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class IntegerConstantValue extends ConstantValue{
+ private final int myValue;
+
+ public IntegerConstantValue(int value) {
+ myValue = value;
+ }
+
+ public IntegerConstantValue(DataInput in) throws IOException{
+ myValue = in.readInt();
+ }
+
+ public int getValue() {
+ return myValue;
+ }
+
+ public void save(DataOutput out) throws IOException {
+ super.save(out);
+ out.writeInt(myValue);
+ }
+
+ public boolean equals(Object obj) {
+ return (obj instanceof IntegerConstantValue) && (((IntegerConstantValue)obj).myValue == myValue);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/classParsing/LongConstantValue.java b/java/compiler/impl/src/com/intellij/compiler/classParsing/LongConstantValue.java
new file mode 100644
index 0000000..2cbe2d7
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/classParsing/LongConstantValue.java
@@ -0,0 +1,49 @@
+/*
+ * 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.
+ */
+
+/**
+ * created at Feb 24, 2002
+ * @author Jeka
+ */
+package com.intellij.compiler.classParsing;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class LongConstantValue extends ConstantValue{
+ private final long myValue;
+
+ public LongConstantValue(long value) {
+ myValue = value;
+ }
+ public LongConstantValue(DataInput in) throws IOException{
+ myValue = in.readLong();
+ }
+
+ public long getValue() {
+ return myValue;
+ }
+
+ public void save(DataOutput out) throws IOException {
+ super.save(out);
+ out.writeLong(myValue);
+ }
+
+ public boolean equals(Object obj) {
+ return (obj instanceof LongConstantValue) && (((LongConstantValue)obj).myValue == myValue);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/classParsing/MemberInfo.java b/java/compiler/impl/src/com/intellij/compiler/classParsing/MemberInfo.java
new file mode 100644
index 0000000..3394a58
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/classParsing/MemberInfo.java
@@ -0,0 +1,152 @@
+/*
+ * 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.
+ */
+
+/**
+ * created at Jan 8, 2002
+ * @author Jeka
+ */
+package com.intellij.compiler.classParsing;
+
+import com.intellij.util.cls.ClsUtil;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public abstract class MemberInfo {
+ public static final MemberInfo[] EMPTY_MEMBER_INFO_ARRAY = new MemberInfo[0];
+ private static final int FLAG_INFO_UNAVAILABLE = 0x8000;
+ private final int myName;
+ private final int myDescriptor;
+ private final int myGenericSignature;
+ private final int myFlags;
+ private final AnnotationConstantValue[] myRuntimeVisibleAnnotations;
+ private final AnnotationConstantValue[] myRuntimeInvisibleAnnotations;
+
+ protected MemberInfo(int name, int descriptor) {
+ this(name, descriptor, -1, FLAG_INFO_UNAVAILABLE, AnnotationConstantValue.EMPTY_ARRAY, AnnotationConstantValue.EMPTY_ARRAY);
+ }
+
+ protected MemberInfo(int name, int descriptor, int genericSignature, int flags, final AnnotationConstantValue[] runtimeVisibleAnnotations, final AnnotationConstantValue[] runtimeInvisibleAnnotations) {
+ myDescriptor = descriptor;
+ myGenericSignature = genericSignature;
+ myName = name;
+ myFlags = flags;
+ myRuntimeVisibleAnnotations = runtimeVisibleAnnotations != null? runtimeVisibleAnnotations : AnnotationConstantValue.EMPTY_ARRAY;
+ myRuntimeInvisibleAnnotations = runtimeInvisibleAnnotations != null? runtimeInvisibleAnnotations : AnnotationConstantValue.EMPTY_ARRAY;
+ }
+
+ protected MemberInfo(DataInput in) throws IOException {
+ myName = in.readInt();
+ myDescriptor = in.readInt();
+ myGenericSignature = in.readInt();
+ myFlags = in.readInt();
+ myRuntimeVisibleAnnotations = loadAnnotations(in);
+ myRuntimeInvisibleAnnotations = loadAnnotations(in);
+ }
+
+ public void save(DataOutput out) throws IOException {
+ out.writeInt(myName);
+ out.writeInt(myDescriptor);
+ out.writeInt(myGenericSignature);
+ out.writeInt(myFlags);
+ saveAnnotations(out, myRuntimeVisibleAnnotations);
+ saveAnnotations(out, myRuntimeInvisibleAnnotations);
+ }
+
+ public int getName() {
+ return myName;
+ }
+
+ public int getDescriptor() {
+ return myDescriptor;
+ }
+
+ public int getGenericSignature() {
+ return myGenericSignature;
+ }
+
+ public boolean isFlagInfoAvailable() {
+ return myFlags != FLAG_INFO_UNAVAILABLE;
+ }
+
+ public int getFlags() {
+ return myFlags;
+ }
+
+ public boolean isPublic() {
+ return ClsUtil.isPublic(myFlags);
+ }
+
+ public boolean isProtected() {
+ return ClsUtil.isProtected(myFlags);
+ }
+
+ public boolean isFinal() {
+ return ClsUtil.isFinal(myFlags);
+ }
+
+ public boolean isPrivate() {
+ return ClsUtil.isPrivate(myFlags);
+ }
+
+ public boolean isPackageLocal() {
+ return ClsUtil.isPackageLocal(myFlags);
+ }
+
+ public boolean isStatic() {
+ return ClsUtil.isStatic(myFlags);
+ }
+
+ public boolean equals(Object obj) {
+ if (!(obj instanceof MemberInfo)) return false;
+ MemberInfo info = (MemberInfo)obj;
+ return (myName == info.myName) && (myDescriptor == info.myDescriptor);
+ }
+
+ public int hashCode() {
+ return myName + myDescriptor;
+ }
+
+ public AnnotationConstantValue[] getRuntimeVisibleAnnotations() {
+ return myRuntimeVisibleAnnotations;
+ }
+
+ public AnnotationConstantValue[] getRuntimeInvisibleAnnotations() {
+ return myRuntimeInvisibleAnnotations;
+ }
+
+ protected final void saveAnnotations(DataOutput out, final AnnotationConstantValue[] annotations) throws IOException {
+ out.writeInt(annotations.length);
+ for (AnnotationConstantValue annotation : annotations) {
+ MemberInfoExternalizer.saveConstantValue(out, annotation);
+ }
+ }
+
+ protected final AnnotationConstantValue[] loadAnnotations(DataInput in) throws IOException {
+ final int size = in.readInt();
+ if (size == 0) {
+ return AnnotationConstantValue.EMPTY_ARRAY;
+ }
+ final AnnotationConstantValue[] annotations = new AnnotationConstantValue[size];
+ for (int idx = 0; idx < size; idx++) {
+ annotations[idx] = (AnnotationConstantValue)MemberInfoExternalizer.loadConstantValue(in);
+ }
+ return annotations;
+ }
+
+}
+
diff --git a/java/compiler/impl/src/com/intellij/compiler/classParsing/MemberInfoExternalizer.java b/java/compiler/impl/src/com/intellij/compiler/classParsing/MemberInfoExternalizer.java
new file mode 100644
index 0000000..a807bd5
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/classParsing/MemberInfoExternalizer.java
@@ -0,0 +1,219 @@
+/*
+ * 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.
+ */
+
+/**
+ * created at Jan 10, 2002
+ * @author Jeka
+ */
+package com.intellij.compiler.classParsing;
+
+import com.intellij.openapi.diagnostic.Logger;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class MemberInfoExternalizer {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.classParsing.MemberInfoExternalizer");
+
+ public static final byte FIELD_INFO_TAG = 1;
+ public static final byte METHOD_INFO_TAG = 2;
+
+ public static final byte DECLARATION_INFO_TAG = 9;
+
+ public static final byte REFERENCE_INFO_TAG = 11;
+ public static final byte MEMBER_REFERENCE_INFO_TAG = 12;
+
+ public static final byte LONG_CONSTANT_TAG = 3;
+ public static final byte FLOAT_CONSTANT_TAG = 4;
+ public static final byte DOUBLE_CONSTANT_TAG = 5;
+ public static final byte INTEGER_CONSTANT_TAG = 6;
+ public static final byte STRING_CONSTANT_TAG = 7;
+ public static final byte CONSTANT_TAG = 8;
+ public static final byte ANNOTATION_CONSTANT_TAG = 13;
+ public static final byte ANNOTATION_PRIMITIVE_CONSTANT_TAG = 14;
+ public static final byte CONSTANT_VALUE_ARRAY_TAG = 15;
+ public static final byte CLASS_CONSTANT_VALUE_TAG = 16;
+ public static final byte ENUM_CONSTANT_VALUE_TAG = 17;
+
+ public static MemberInfo loadMemberInfo(DataInput in) throws IOException {
+ byte tag = in.readByte();
+ if (tag == METHOD_INFO_TAG) {
+ return new MethodInfo(in);
+ }
+ else if (tag == FIELD_INFO_TAG) {
+ return new FieldInfo(in);
+ }
+ LOG.error("Unknown member info");
+ return null;
+ }
+
+ public static ReferenceInfo loadReferenceInfo(DataInput in) throws IOException {
+ final byte tag = in.readByte();
+ if (tag == REFERENCE_INFO_TAG) {
+ return new ReferenceInfo(in);
+ }
+ else if (tag == MEMBER_REFERENCE_INFO_TAG) {
+ return new MemberReferenceInfo(in);
+ }
+ LOG.error("Unknown declaration info tag: " + tag);
+ return null;
+ }
+
+ public static ConstantValue loadConstantValue(DataInput in) throws IOException {
+ final byte tag = in.readByte();
+ if (tag == LONG_CONSTANT_TAG) {
+ return new LongConstantValue(in);
+ }
+ else if (tag == FLOAT_CONSTANT_TAG) {
+ return new FloatConstantValue(in);
+ }
+ else if (tag == DOUBLE_CONSTANT_TAG) {
+ return new DoubleConstantValue(in);
+ }
+ else if (tag == INTEGER_CONSTANT_TAG) {
+ return new IntegerConstantValue(in);
+ }
+ else if (tag == STRING_CONSTANT_TAG) {
+ return new StringConstantValue(in);
+ }
+ else if (tag == CONSTANT_TAG) {
+ return ConstantValue.EMPTY_CONSTANT_VALUE;
+ }
+ else if (tag == ANNOTATION_CONSTANT_TAG) {
+ return new AnnotationConstantValue(in);
+ }
+ else if (tag == ANNOTATION_PRIMITIVE_CONSTANT_TAG) {
+ return new AnnotationPrimitiveConstantValue(in);
+ }
+ else if (tag == CONSTANT_VALUE_ARRAY_TAG) {
+ return new ConstantValueArray(in);
+ }
+ else if (tag == CLASS_CONSTANT_VALUE_TAG) {
+ return new ClassInfoConstantValue(in);
+ }
+ else if (tag == ENUM_CONSTANT_VALUE_TAG) {
+ return new EnumConstantValue(in);
+ }
+ LOG.error("Unknown constant value type " + tag);
+ return null;
+ }
+
+ public static void saveMemberInfo(DataOutput out, MemberInfo info) throws IOException {
+ if (info instanceof MethodInfo) {
+ out.writeByte(METHOD_INFO_TAG);
+ }
+ else if (info instanceof FieldInfo){
+ out.writeByte(FIELD_INFO_TAG);
+ }
+ else {
+ LOG.error("Unknown member info");
+ }
+ info.save(out);
+ }
+
+ public static void saveReferenceInfo(DataOutput out, ReferenceInfo info) throws IOException {
+ if (info instanceof MemberReferenceInfo) {
+ out.writeByte(MEMBER_REFERENCE_INFO_TAG);
+ }
+ else{
+ out.writeByte(REFERENCE_INFO_TAG);
+ }
+ info.save(out);
+ }
+
+ public static void saveConstantValue(DataOutput out, ConstantValue value) throws IOException {
+ if (value instanceof LongConstantValue) {
+ out.writeByte(LONG_CONSTANT_TAG);
+ }
+ else if (value instanceof FloatConstantValue){
+ out.writeByte(FLOAT_CONSTANT_TAG);
+ }
+ else if (value instanceof DoubleConstantValue){
+ out.writeByte(DOUBLE_CONSTANT_TAG);
+ }
+ else if (value instanceof IntegerConstantValue){
+ out.writeByte(INTEGER_CONSTANT_TAG);
+ }
+ else if (value instanceof StringConstantValue){
+ out.writeByte(STRING_CONSTANT_TAG);
+ }
+ else if (value instanceof AnnotationConstantValue) {
+ out.writeByte(ANNOTATION_CONSTANT_TAG);
+ }
+ else if (value instanceof AnnotationPrimitiveConstantValue) {
+ out.writeByte(ANNOTATION_PRIMITIVE_CONSTANT_TAG);
+ }
+ else if (value instanceof ConstantValueArray) {
+ out.writeByte(CONSTANT_VALUE_ARRAY_TAG);
+ }
+ else if (value instanceof ClassInfoConstantValue) {
+ out.writeByte(CLASS_CONSTANT_VALUE_TAG);
+ }
+ else if (value instanceof EnumConstantValue) {
+ out.writeByte(ENUM_CONSTANT_VALUE_TAG);
+ }
+ else {
+ out.writeByte(CONSTANT_TAG);
+ }
+ if (value != null) {
+ value.save(out);
+ }
+ }
+
+ public static AnnotationConstantValue[] readAnnotationConstantValueArray1(DataInput in) throws IOException {
+ final int size = in.readInt();
+ final AnnotationConstantValue[] array = size > 0? new AnnotationConstantValue[size] : AnnotationConstantValue.EMPTY_ARRAY;
+ for (int idx = 0; idx < size; idx++) {
+ array[idx] = (AnnotationConstantValue)loadConstantValue(in);
+ }
+ return array;
+ }
+
+ public static AnnotationConstantValue[][] readAnnotationConstantValueArray2(DataInput in) throws IOException {
+ final int size = in.readInt();
+ final AnnotationConstantValue[][] array = size > 0? new AnnotationConstantValue[size][] : AnnotationConstantValue.EMPTY_ARRAY_ARRAY;
+ for (int idx = 0; idx < size; idx++) {
+ array[idx] = readAnnotationConstantValueArray1(in);
+ }
+ return array;
+ }
+
+ public static void writeConstantValueArray1(DataOutput writer, final ConstantValue[] array) throws IOException {
+ if (array != null && array.length > 0) {
+ writer.writeInt(array.length);
+ for (ConstantValue value : array) {
+ saveConstantValue(writer, value);
+ }
+ }
+ else {
+ writer.writeInt(0);
+ }
+ }
+
+ public static void writeConstantValueArray2(DataOutput writer, final ConstantValue[][] array) throws IOException {
+ if (array != null && array.length > 0) {
+ writer.writeInt(array.length);
+ for (ConstantValue[] aArray : array) {
+ writeConstantValueArray1(writer, aArray);
+ }
+ }
+ else {
+ writer.writeInt(0);
+ }
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/classParsing/MemberReferenceInfo.java b/java/compiler/impl/src/com/intellij/compiler/classParsing/MemberReferenceInfo.java
new file mode 100644
index 0000000..63d7c54
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/classParsing/MemberReferenceInfo.java
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+/**
+ * created at Jan 8, 2002
+ * @author Jeka
+ */
+package com.intellij.compiler.classParsing;
+
+import org.jetbrains.annotations.NonNls;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class MemberReferenceInfo extends ReferenceInfo {
+ private final MemberInfo myMemberInfo;
+
+ public MemberReferenceInfo(int declaringClass, MemberInfo memberInfo) {
+ super(declaringClass);
+ myMemberInfo = memberInfo;
+ }
+
+ public MemberReferenceInfo(DataInput in) throws IOException {
+ super(in);
+ myMemberInfo = MemberInfoExternalizer.loadMemberInfo(in);
+ }
+
+ public MemberInfo getMemberInfo() {
+ return myMemberInfo;
+ }
+
+ public boolean isFieldReference() {
+ return myMemberInfo instanceof FieldInfo;
+ }
+
+ public boolean isMethodReference() {
+ return myMemberInfo instanceof MethodInfo;
+ }
+
+ public void save(DataOutput out) throws IOException {
+ super.save(out);
+ MemberInfoExternalizer.saveMemberInfo(out, myMemberInfo);
+ }
+
+ public boolean equals(Object o) {
+ if (!super.equals(o)) {
+ return false;
+ }
+ return myMemberInfo.equals(((MemberReferenceInfo)o).myMemberInfo);
+ }
+
+ public int hashCode() {
+ return super.hashCode() + myMemberInfo.hashCode();
+ }
+
+ public @NonNls String toString() { // for debug purposes
+ return "Member reference: [class name=" + getClassName() + ", member name = " + myMemberInfo.getName() + ", member descriptor=" + myMemberInfo.getDescriptor() + ", member signature=" + myMemberInfo.getGenericSignature() + "]";
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/classParsing/MethodInfo.java b/java/compiler/impl/src/com/intellij/compiler/classParsing/MethodInfo.java
new file mode 100644
index 0000000..c18d9ae
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/classParsing/MethodInfo.java
@@ -0,0 +1,263 @@
+/*
+ * 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.
+ */
+
+/**
+ * created at Jan 10, 2002
+ * @author Jeka
+ */
+package com.intellij.compiler.classParsing;
+
+import com.intellij.compiler.SymbolTable;
+import com.intellij.compiler.make.CacheCorruptedException;
+import com.intellij.compiler.make.CacheUtils;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.cls.ClsUtil;
+import gnu.trove.TIntHashSet;
+import org.jetbrains.annotations.NonNls;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.ArrayList;
+
+public class MethodInfo extends MemberInfo {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.classParsing.MethodInfo");
+
+ private static final int[] EXCEPTION_INFO_UNAVAILABLE = ArrayUtil.EMPTY_INT_ARRAY;
+ public static final MethodInfo[] EMPTY_ARRAY = new MethodInfo[0];
+
+ private final int[] myThrownExceptions;
+ // cached (lazy initialized) data
+ private String mySignature = null;
+ private String[] myParameterDescriptors = null;
+ private String myReturnTypeSignature = null;
+ private final boolean myIsConstructor;
+ private final AnnotationConstantValue[][] myRuntimeVisibleParameterAnnotations;
+ private final AnnotationConstantValue[][] myRuntimeInvisibleParameterAnnotations;
+ private final ConstantValue myAnnotationDefault;
+
+ public MethodInfo(int name, int descriptor, boolean isConstructor) {
+ super(name, descriptor);
+ myIsConstructor = isConstructor;
+ myThrownExceptions = EXCEPTION_INFO_UNAVAILABLE;
+ myRuntimeVisibleParameterAnnotations = AnnotationConstantValue.EMPTY_ARRAY_ARRAY;
+ myRuntimeInvisibleParameterAnnotations = AnnotationConstantValue.EMPTY_ARRAY_ARRAY;
+ myAnnotationDefault = ConstantValue.EMPTY_CONSTANT_VALUE;
+ }
+
+ public MethodInfo(int name,
+ int descriptor,
+ final int genericSignature,
+ int flags,
+ int[] exceptions,
+ boolean isConstructor,
+ final AnnotationConstantValue[] runtimeVisibleAnnotations,
+ final AnnotationConstantValue[] runtimeInvisibleAnnotations,
+ final AnnotationConstantValue[][] runtimeVisibleParameterAnnotations,
+ final AnnotationConstantValue[][] runtimeInvisibleParameterAnnotations, ConstantValue annotationDefault) {
+
+ super(name, descriptor, genericSignature, flags, runtimeVisibleAnnotations, runtimeInvisibleAnnotations);
+ myThrownExceptions = exceptions != null? exceptions : ArrayUtil.EMPTY_INT_ARRAY;
+ myIsConstructor = isConstructor;
+ myRuntimeVisibleParameterAnnotations = runtimeVisibleParameterAnnotations == null? AnnotationConstantValue.EMPTY_ARRAY_ARRAY : runtimeVisibleParameterAnnotations;
+ myRuntimeInvisibleParameterAnnotations = runtimeInvisibleParameterAnnotations == null? AnnotationConstantValue.EMPTY_ARRAY_ARRAY : runtimeInvisibleParameterAnnotations;
+ myAnnotationDefault = annotationDefault;
+ }
+
+ public MethodInfo(DataInput in) throws IOException {
+ super(in);
+ myIsConstructor = in.readBoolean();
+ int count = in.readInt();
+ if (count == -1) {
+ myThrownExceptions = EXCEPTION_INFO_UNAVAILABLE;
+ }
+ else {
+ myThrownExceptions = ArrayUtil.newIntArray(count);
+ for (int idx = 0; idx < count; idx++) {
+ myThrownExceptions[idx] = in.readInt();
+ }
+ }
+ myRuntimeVisibleParameterAnnotations = loadParameterAnnotations(in);
+ myRuntimeInvisibleParameterAnnotations = loadParameterAnnotations(in);
+ myAnnotationDefault = MemberInfoExternalizer.loadConstantValue(in);
+ }
+
+ public void save(DataOutput out) throws IOException {
+ super.save(out);
+ out.writeBoolean(myIsConstructor);
+ if (isExceptionInfoAvailable()) {
+ out.writeInt(myThrownExceptions.length);
+ }
+ else {
+ out.writeInt(-1);
+ }
+ for (int thrownException : myThrownExceptions) {
+ out.writeInt(thrownException);
+ }
+ saveParameterAnnotations(out, myRuntimeVisibleParameterAnnotations);
+ saveParameterAnnotations(out, myRuntimeInvisibleParameterAnnotations);
+ MemberInfoExternalizer.saveConstantValue(out, myAnnotationDefault);
+ }
+
+ private boolean isExceptionInfoAvailable() {
+ return myThrownExceptions != EXCEPTION_INFO_UNAVAILABLE;
+ }
+
+ public boolean areExceptionsEqual(MethodInfo info) {
+ if (myThrownExceptions.length != info.myThrownExceptions.length) {
+ return false;
+ }
+ if (myThrownExceptions.length != 0) { // optimization
+ TIntHashSet exceptionsSet = new TIntHashSet();
+ for (int thrownException : myThrownExceptions) {
+ exceptionsSet.add(thrownException);
+ }
+ for (int exception : info.myThrownExceptions) {
+ if (!exceptionsSet.contains(exception)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ public int[] getThrownExceptions() {
+ return myThrownExceptions;
+ }
+
+ public String getDescriptor(SymbolTable symbolTable) throws CacheCorruptedException {
+ if (mySignature == null) {
+ final String descriptor = symbolTable.getSymbol(getDescriptor());
+ final String name = symbolTable.getSymbol(getName());
+ mySignature = CacheUtils.getMethodSignature(name, descriptor);
+ }
+ return mySignature;
+ }
+
+ public String getReturnTypeDescriptor(SymbolTable symbolTable) throws CacheCorruptedException {
+ if (myReturnTypeSignature == null) {
+ String descriptor = symbolTable.getSymbol(getDescriptor());
+ myReturnTypeSignature = descriptor.substring(descriptor.indexOf(')') + 1, descriptor.length());
+ }
+ return myReturnTypeSignature;
+ }
+
+ public String[] getParameterDescriptors(SymbolTable symbolTable) throws CacheCorruptedException {
+ if (myParameterDescriptors == null) {
+ String descriptor = symbolTable.getSymbol(getDescriptor());
+ int endIndex = descriptor.indexOf(')');
+ if (endIndex <= 0) {
+ LOG.error("Corrupted method descriptor: " + descriptor);
+ }
+ myParameterDescriptors = parseParameterDescriptors(descriptor.substring(1, endIndex));
+ }
+ return myParameterDescriptors;
+ }
+
+ public boolean isAbstract() {
+ return ClsUtil.isAbstract(getFlags());
+ }
+
+ public boolean isConstructor() {
+ return myIsConstructor;
+ }
+
+ private String[] parseParameterDescriptors(String signature) {
+ ArrayList<String> list = new ArrayList<String>();
+ String paramSignature = parseFieldType(signature);
+ while (paramSignature != null && !"".equals(paramSignature)) {
+ list.add(paramSignature);
+ signature = signature.substring(paramSignature.length());
+ paramSignature = parseFieldType(signature);
+ }
+ return ArrayUtil.toStringArray(list);
+ }
+
+ private @NonNls String parseFieldType(@NonNls String signature) {
+ if (signature.length() == 0) {
+ return null;
+ }
+ if (signature.charAt(0) == 'B') {
+ return "B";
+ }
+ if (signature.charAt(0) == 'C') {
+ return "C";
+ }
+ if (signature.charAt(0) == 'D') {
+ return "D";
+ }
+ if (signature.charAt(0) == 'F') {
+ return "F";
+ }
+ if (signature.charAt(0) == 'I') {
+ return "I";
+ }
+ if (signature.charAt(0) == 'J') {
+ return "J";
+ }
+ if (signature.charAt(0) == 'S') {
+ return "S";
+ }
+ if (signature.charAt(0) == 'Z') {
+ return "Z";
+ }
+ if (signature.charAt(0) == 'L') {
+ return signature.substring(0, signature.indexOf(";") + 1);
+ }
+ if (signature.charAt(0) == '[') {
+ String s = parseFieldType(signature.substring(1));
+ return (s != null)? ("[" + s) : null;
+ }
+ return null;
+ }
+
+ public AnnotationConstantValue[][] getRuntimeVisibleParameterAnnotations() {
+ return myRuntimeVisibleParameterAnnotations;
+ }
+
+ public AnnotationConstantValue[][] getRuntimeInvisibleParameterAnnotations() {
+ return myRuntimeInvisibleParameterAnnotations;
+ }
+
+ public String toString() {
+ return mySignature;
+ }
+
+ private AnnotationConstantValue[][] loadParameterAnnotations(DataInput in) throws IOException {
+ final int size = in.readInt();
+ if (size == 0) {
+ return AnnotationConstantValue.EMPTY_ARRAY_ARRAY;
+ }
+ final AnnotationConstantValue[][] paramAnnotations = new AnnotationConstantValue[size][];
+ for (int idx = 0; idx < size; idx++) {
+ paramAnnotations[idx] = loadAnnotations(in);
+ }
+ return paramAnnotations;
+ }
+
+ private void saveParameterAnnotations(DataOutput out, AnnotationConstantValue[][] parameterAnnotations) throws IOException {
+ out.writeInt(parameterAnnotations.length);
+ for (AnnotationConstantValue[] parameterAnnotation : parameterAnnotations) {
+ saveAnnotations(out, parameterAnnotation);
+ }
+ }
+
+ public ConstantValue getAnnotationDefault() {
+ return myAnnotationDefault;
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/classParsing/ReferenceInfo.java b/java/compiler/impl/src/com/intellij/compiler/classParsing/ReferenceInfo.java
new file mode 100644
index 0000000..3cf9a19
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/classParsing/ReferenceInfo.java
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+/*
+ * @author Eugene Zhuravlev
+ */
+package com.intellij.compiler.classParsing;
+
+import org.jetbrains.annotations.NonNls;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class ReferenceInfo {
+ private final int myClassName;
+
+ public ReferenceInfo(int declaringClassName) {
+ myClassName = declaringClassName;
+ }
+
+ public ReferenceInfo(DataInput in) throws IOException {
+ this(in.readInt());
+ }
+
+ public @NonNls String toString() { // for debug purposes
+ return "Class reference[class name=" + String.valueOf(getClassName()) + "]";
+ }
+
+ public void save(DataOutput out) throws IOException {
+ out.writeInt(myClassName);
+ }
+
+ public int getClassName() {
+ return myClassName;
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ ReferenceInfo that = (ReferenceInfo)o;
+
+ if (myClassName != that.myClassName) return false;
+
+ return true;
+ }
+
+ public int hashCode() {
+ return myClassName;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/classParsing/SignatureParser.java b/java/compiler/impl/src/com/intellij/compiler/classParsing/SignatureParser.java
new file mode 100644
index 0000000..d195cf3
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/classParsing/SignatureParser.java
@@ -0,0 +1,259 @@
+/*
+ * 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 com.intellij.compiler.classParsing;
+
+import com.intellij.openapi.compiler.CompilerBundle;
+
+import java.text.CharacterIterator;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 4, 2004
+ */
+
+public class SignatureParser {
+ public static final SignatureParser INSTANCE = new SignatureParser();
+
+ public void parseIdentifier(CharacterIterator it, final StringBuilder buf) {
+ while (Character.isJavaIdentifierPart(it.current())) {
+ buf.append(it.current());
+ it.next();
+ }
+ }
+
+ public void parseFormalTypeParameters(CharacterIterator it, final StringBuilder buf) throws SignatureParsingException {
+ if (it.current() != '<') {
+ throw new SignatureParsingException(CompilerBundle.message("error.signature.parsing.expected.other.symbol", "<", buf.toString()));
+ }
+
+ buf.append(it.current()); // skip '<'
+ it.next();
+
+ while (it.current() != '>') {
+ parseFormalTypeParameter(it, buf);
+ }
+
+ buf.append(it.current());
+ it.next();
+ }
+
+ public void parseFormalTypeParameter(CharacterIterator it, final StringBuilder buf) throws SignatureParsingException {
+ parseIdentifier(it, buf);
+ parseClassBound(it, buf);
+ while (it.current() == ':') {
+ parseInterfaceBound(it, buf);
+ }
+ }
+
+ public void parseClassBound(CharacterIterator it, final StringBuilder buf) throws SignatureParsingException {
+ if (it.current() != ':') {
+ throw new SignatureParsingException(CompilerBundle.message("error.signature.parsing.expected.other.symbol", ":", buf.toString()));
+ }
+ buf.append(it.current());
+ it.next();
+
+ final char current = it.current();
+ if (current != CharacterIterator.DONE && current != ':') {
+ parseFieldTypeSignature(it, buf);
+ }
+ }
+
+ public void parseInterfaceBound(CharacterIterator it, final StringBuilder buf) throws SignatureParsingException {
+ if (it.current() != ':') {
+ throw new SignatureParsingException(CompilerBundle.message("error.signature.parsing.expected.other.symbol", ":", buf.toString()));
+ }
+ buf.append(it.current());
+ it.next();
+ parseFieldTypeSignature(it, buf);
+ }
+
+ public void parseSuperclassSignature(CharacterIterator it, final StringBuilder buf) throws SignatureParsingException {
+ parseClassTypeSignature(it, buf);
+ }
+
+ public void parseSuperinterfaceSignature(CharacterIterator it, final StringBuilder buf) throws SignatureParsingException {
+ parseClassTypeSignature(it, buf);
+ }
+
+ public void parseFieldTypeSignature(CharacterIterator it, final StringBuilder buf) throws SignatureParsingException {
+ if (it.current() == 'L') {
+ parseClassTypeSignature(it, buf);
+ }
+ else if (it.current() == '[') {
+ parseArrayTypeSignature(it, buf);
+ }
+ else if (it.current() == 'T') {
+ parseTypeVariableSignature(it, buf);
+ }
+ else {
+ //noinspection HardCodedStringLiteral
+ throw new SignatureParsingException(CompilerBundle.message("error.signature.parsing.expected.other.symbol", "'L' / '[' / 'T'", buf.toString()));
+ }
+ }
+
+ public void parseClassTypeSignature(CharacterIterator it, final StringBuilder buf) throws SignatureParsingException {
+ buf.append(it.current());
+ it.next(); // consume 'L'
+ parseSimpleClassTypeSignature(it, buf);
+ while (it.current() == '/' || it.current() == '.') { // Eclipse compiler generates '.' for inner classes
+ parseClassTypeSignatureSuffix(it, buf);
+ }
+ if (it.current() != ';') {
+ throw new SignatureParsingException(CompilerBundle.message("error.signature.parsing.expected.other.symbol", ";", buf.toString()));
+ }
+ buf.append(it.current());
+ it.next(); // consume ';'
+ }
+
+ public void parseSimpleClassTypeSignature(CharacterIterator it, final StringBuilder buf) throws SignatureParsingException {
+ parseIdentifier(it, buf);
+ if (it.current() == '<') {
+ parseTypeArguments(it, buf);
+ }
+ }
+
+ public void parseClassTypeSignatureSuffix(CharacterIterator it, final StringBuilder buf) throws SignatureParsingException {
+ buf.append(it.current());
+ it.next();
+ parseSimpleClassTypeSignature(it, buf);
+ }
+
+ public void parseTypeVariableSignature(CharacterIterator it, final StringBuilder buf) throws SignatureParsingException {
+ buf.append(it.current());
+ it.next(); // consume 'T'
+ parseIdentifier(it, buf);
+ if (it.current() != ';') {
+ throw new SignatureParsingException(CompilerBundle.message("error.signature.parsing.expected.other.symbol", ";", buf.toString()));
+ }
+ buf.append(it.current());
+ it.next(); // consume ';'
+ }
+
+ public void parseTypeArguments(CharacterIterator it, final StringBuilder buf) throws SignatureParsingException {
+ buf.append(it.current());
+ it.next(); // consume '<'
+ while (it.current() != '>') {
+ parseTypeArgument(it, buf);
+ }
+ buf.append(it.current());
+ it.next(); // consume '>'
+ }
+
+ public void parseTypeArgument(CharacterIterator it, final StringBuilder buf) throws SignatureParsingException {
+ if (it.current() == '*') {
+ parseWildcardIndicator(it, buf);
+ }
+ else {
+ if (it.current() == '+' || it.current() == '-') {
+ parseWildcardIndicator(it, buf);
+ }
+ parseFieldTypeSignature(it, buf);
+ }
+ }
+
+ public void parseWildcardIndicator(CharacterIterator it, final StringBuilder buf) {
+ buf.append(it.current());
+ it.next();
+ }
+
+ public void parseArrayTypeSignature(CharacterIterator it, final StringBuilder buf) throws SignatureParsingException {
+ buf.append(it.current());
+ it.next(); // consume '['
+ parseTypeSignature(it, buf);
+ }
+
+ public void parseTypeSignature(CharacterIterator it, final StringBuilder buf) throws SignatureParsingException {
+ char current = it.current();
+ if (current == 'B' || current == 'C' || current == 'D' || current == 'F' || current == 'I' || current == 'J' || current == 'S' || current == 'Z') {
+ buf.append(it.current());
+ it.next(); // base type
+ }
+ else if (current == 'L' || current == '[' || current == 'T') {
+ parseFieldTypeSignature(it, buf);
+ }
+ else {
+ throw new SignatureParsingException(CompilerBundle.message("error.signature.parsing.unknown.type.signature"));
+ }
+ }
+
+ public void parseReturnType(CharacterIterator it, final StringBuilder buf) throws SignatureParsingException {
+ if (it.current() == 'V') {
+ buf.append(it.current());
+ it.next();
+ }
+ else {
+ parseTypeSignature(it, buf);
+ }
+ }
+
+ public void parseThrowsSignature(CharacterIterator it, final StringBuilder buf) throws SignatureParsingException {
+ if (it.current() != '^') {
+ throw new SignatureParsingException(CompilerBundle.message("error.signature.parsing.expected.other.symbol", "^", buf.toString()));
+ }
+ buf.append(it.current());
+ it.next();
+ if (it.current() == 'T') {
+ parseTypeVariableSignature(it, buf);
+ }
+ else {
+ parseClassTypeSignature(it, buf);
+ }
+ }
+
+ public void parseMethodSignature(CharacterIterator it, final StringBuilder buf) throws SignatureParsingException {
+ if (it.current() == '<') {
+ parseFormalTypeParameters(it, buf);
+ }
+
+ if (it.current() != '(') {
+ throw new SignatureParsingException(CompilerBundle.message("error.signature.parsing.expected.other.symbol", "(", buf.toString()));
+ }
+
+ buf.append(it.current());
+ it.next(); // skip '('
+
+ while (it.current() != ')') {
+ parseTypeSignature(it, buf);
+ }
+
+ buf.append(it.current());
+ it.next(); // skip ')'
+
+ parseReturnType(it, buf);
+
+ if (it.current() != CharacterIterator.DONE) {
+ parseThrowsSignature(it, buf);
+ }
+ }
+
+ public void parseClassSignature(CharacterIterator it, final StringBuilder buf) throws SignatureParsingException {
+ if (it.current() == '<') {
+ buf.append(it.current());
+ it.next();
+ while (it.current() != '>') {
+ parseFormalTypeParameter(it, buf);
+ }
+ buf.append(it.current());
+ it.next();
+ }
+
+ parseClassTypeSignature(it, buf);
+
+ while (it.current() != CharacterIterator.DONE) {
+ parseClassTypeSignature(it, buf);
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/classParsing/SignatureParsingException.java b/java/compiler/impl/src/com/intellij/compiler/classParsing/SignatureParsingException.java
new file mode 100644
index 0000000..7246b19
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/classParsing/SignatureParsingException.java
@@ -0,0 +1,26 @@
+/*
+ * 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 com.intellij.compiler.classParsing;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 4, 2004
+ */
+public class SignatureParsingException extends Exception{
+ public SignatureParsingException(String message) {
+ super(message);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/classParsing/StringConstantValue.java b/java/compiler/impl/src/com/intellij/compiler/classParsing/StringConstantValue.java
new file mode 100644
index 0000000..faf560f
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/classParsing/StringConstantValue.java
@@ -0,0 +1,52 @@
+/*
+ * 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.
+ */
+
+/**
+ * created at Feb 24, 2002
+ * @author Jeka
+ */
+package com.intellij.compiler.classParsing;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+public class StringConstantValue extends ConstantValue{
+ private final String myValue;
+
+ public StringConstantValue(String value) {
+ myValue = value;
+ }
+
+ public StringConstantValue(DataInput in) throws IOException{
+ myValue = in.readUTF();
+ }
+
+ public String getValue() {
+ return myValue;
+ }
+
+ public void save(DataOutput out) throws IOException {
+ super.save(out);
+ out.writeUTF(myValue);
+ }
+
+ public boolean equals(Object obj) {
+ return
+ (obj instanceof StringConstantValue) &&
+ ((myValue == null)? ((StringConstantValue)obj).myValue == null : myValue.equals(((StringConstantValue)obj).myValue));
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/AdditionalCompileScopeProvider.java b/java/compiler/impl/src/com/intellij/compiler/impl/AdditionalCompileScopeProvider.java
new file mode 100644
index 0000000..0b9ed31
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/AdditionalCompileScopeProvider.java
@@ -0,0 +1,34 @@
+/*
+ * 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 com.intellij.compiler.impl;
+
+import com.intellij.openapi.compiler.CompilerFilter;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.compiler.CompileScope;
+import com.intellij.openapi.project.Project;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author nik
+ */
+public abstract class AdditionalCompileScopeProvider {
+ public static final ExtensionPointName<AdditionalCompileScopeProvider> EXTENSION_POINT_NAME = ExtensionPointName.create("com.intellij.compiler.additionalCompileScopeProvider");
+
+ @Nullable
+ public abstract CompileScope getAdditionalScope(@NotNull CompileScope baseScope, @NotNull CompilerFilter filter, @NotNull Project project);
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/BuildTargetScopeProvider.java b/java/compiler/impl/src/com/intellij/compiler/impl/BuildTargetScopeProvider.java
new file mode 100644
index 0000000..2f55990
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/BuildTargetScopeProvider.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl;
+
+import com.intellij.openapi.compiler.CompileScope;
+import com.intellij.openapi.compiler.CompilerFilter;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.project.Project;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+import static org.jetbrains.jps.api.CmdlineRemoteProto.Message.ControllerMessage.ParametersMessage.TargetTypeBuildScope;
+
+/**
+ * @author nik
+ */
+public abstract class BuildTargetScopeProvider {
+ public static final ExtensionPointName<BuildTargetScopeProvider> EP_NAME = ExtensionPointName.create("com.intellij.compiler.buildTargetScopeProvider");
+
+ @NotNull
+ public abstract List<TargetTypeBuildScope> getBuildTargetScopes(@NotNull CompileScope baseScope, @NotNull CompilerFilter filter,
+ @NotNull Project project);
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompileContextExProxy.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompileContextExProxy.java
new file mode 100644
index 0000000..7341089
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompileContextExProxy.java
@@ -0,0 +1,174 @@
+/*
+ * 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 com.intellij.compiler.impl;
+
+import com.intellij.compiler.make.DependencyCache;
+import com.intellij.openapi.compiler.CompileScope;
+import com.intellij.openapi.compiler.CompilerMessage;
+import com.intellij.openapi.compiler.CompilerMessageCategory;
+import com.intellij.openapi.compiler.ex.CompileContextEx;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.pom.Navigatable;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+import java.util.Set;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Dec 4, 2007
+ */
+public class CompileContextExProxy implements CompileContextEx {
+ private final CompileContextEx myDelegate;
+
+ public CompileContextExProxy(CompileContextEx delegate) {
+ myDelegate = delegate;
+ }
+
+ public Project getProject() {
+ return myDelegate.getProject();
+ }
+
+ public DependencyCache getDependencyCache() {
+ return myDelegate.getDependencyCache();
+ }
+
+ public VirtualFile getSourceFileByOutputFile(final VirtualFile outputFile) {
+ return myDelegate.getSourceFileByOutputFile(outputFile);
+ }
+
+ public void addMessage(final CompilerMessage message) {
+ myDelegate.addMessage(message);
+ }
+
+ @NotNull
+ public Set<VirtualFile> getTestOutputDirectories() {
+ return myDelegate.getTestOutputDirectories();
+ }
+
+ public boolean isInTestSourceContent(@NotNull final VirtualFile fileOrDir) {
+ return myDelegate.isInTestSourceContent(fileOrDir);
+ }
+
+ public boolean isInSourceContent(@NotNull final VirtualFile fileOrDir) {
+ return myDelegate.isInSourceContent(fileOrDir);
+ }
+
+ public void addScope(final CompileScope additionalScope) {
+ myDelegate.addScope(additionalScope);
+ }
+
+ public void addMessage(final CompilerMessageCategory category,
+ final String message, @Nullable final String url, final int lineNum, final int columnNum) {
+ myDelegate.addMessage(category, message, url, lineNum, columnNum);
+ }
+
+ public void addMessage(final CompilerMessageCategory category, final String message, @Nullable final String url,
+ final int lineNum,
+ final int columnNum,
+ final Navigatable navigatable) {
+ myDelegate.addMessage(category, message, url, lineNum, columnNum, navigatable);
+ }
+
+ public CompilerMessage[] getMessages(final CompilerMessageCategory category) {
+ return myDelegate.getMessages(category);
+ }
+
+ public int getMessageCount(final CompilerMessageCategory category) {
+ return myDelegate.getMessageCount(category);
+ }
+
+ public ProgressIndicator getProgressIndicator() {
+ return myDelegate.getProgressIndicator();
+ }
+
+ public CompileScope getCompileScope() {
+ return myDelegate.getCompileScope();
+ }
+
+ public CompileScope getProjectCompileScope() {
+ return myDelegate.getProjectCompileScope();
+ }
+
+ public void requestRebuildNextTime(final String message) {
+ myDelegate.requestRebuildNextTime(message);
+ }
+
+ public Module getModuleByFile(final VirtualFile file) {
+ return myDelegate.getModuleByFile(file);
+ }
+
+ public VirtualFile[] getSourceRoots(final Module module) {
+ return myDelegate.getSourceRoots(module);
+ }
+
+ public VirtualFile[] getAllOutputDirectories() {
+ return myDelegate.getAllOutputDirectories();
+ }
+
+ public VirtualFile getModuleOutputDirectory(final Module module) {
+ return myDelegate.getModuleOutputDirectory(module);
+ }
+
+ public VirtualFile getModuleOutputDirectoryForTests(final Module module) {
+ return myDelegate.getModuleOutputDirectoryForTests(module);
+ }
+
+ public boolean isMake() {
+ return myDelegate.isMake();
+ }
+
+ public boolean isAnnotationProcessorsEnabled() {
+ return myDelegate.isAnnotationProcessorsEnabled();
+ }
+
+ public boolean isRebuild() {
+ return myDelegate.isRebuild();
+ }
+
+ public <T> T getUserData(@NotNull final Key<T> key) {
+ return myDelegate.getUserData(key);
+ }
+
+ public <T> void putUserData(@NotNull final Key<T> key, final T value) {
+ myDelegate.putUserData(key, value);
+ }
+
+ public void recalculateOutputDirs() {
+ myDelegate.recalculateOutputDirs();
+ }
+
+ public void markGenerated(Collection<VirtualFile> files) {
+ myDelegate.markGenerated(files);
+ }
+
+ public boolean isGenerated(VirtualFile file) {
+ return myDelegate.isGenerated(file);
+ }
+
+ public long getStartCompilationStamp() {
+ return myDelegate.getStartCompilationStamp();
+ }
+
+ public void assignModule(@NotNull VirtualFile root, @NotNull Module module, boolean isTestSource, com.intellij.openapi.compiler.Compiler compiler) {
+ myDelegate.assignModule(root, module, isTestSource, compiler);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompileContextImpl.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompileContextImpl.java
new file mode 100644
index 0000000..298ce1a
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompileContextImpl.java
@@ -0,0 +1,480 @@
+/*
+ * 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.
+ */
+
+/*
+ * @author: Eugene Zhuravlev
+ * Date: Jan 21, 2003
+ * Time: 4:19:03 PM
+ */
+package com.intellij.compiler.impl;
+
+import com.intellij.compiler.CompilerConfiguration;
+import com.intellij.compiler.CompilerMessageImpl;
+import com.intellij.compiler.CompilerWorkspaceConfiguration;
+import com.intellij.compiler.make.DependencyCache;
+import com.intellij.compiler.progress.CompilerTask;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.*;
+import com.intellij.openapi.compiler.Compiler;
+import com.intellij.openapi.compiler.ex.CompileContextEx;
+import com.intellij.openapi.diagnostic.Logger;
+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.CompilerModuleExtension;
+import com.intellij.openapi.roots.ModuleRootManager;
+import com.intellij.openapi.roots.ProjectFileIndex;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.UserDataHolderBase;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.intellij.pom.Navigatable;
+import com.intellij.util.containers.HashMap;
+import com.intellij.util.containers.HashSet;
+import com.intellij.util.containers.OrderedSet;
+import com.intellij.util.indexing.FileBasedIndex;
+import gnu.trove.TIntHashSet;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.util.*;
+
+public class CompileContextImpl extends UserDataHolderBase implements CompileContextEx {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.CompileContextImpl");
+ private final Project myProject;
+ private final CompilerTask myTask;
+ private final Map<CompilerMessageCategory, Collection<CompilerMessage>> myMessages = new EnumMap<CompilerMessageCategory, Collection<CompilerMessage>>(CompilerMessageCategory.class);
+ private final boolean myShouldUpdateProblemsView;
+ private CompileScope myCompileScope;
+ private final DependencyCache myDependencyCache;
+ private final boolean myMake;
+ private final boolean myIsRebuild;
+ private final boolean myIsAnnotationProcessorsEnabled;
+ private boolean myRebuildRequested = false;
+ private String myRebuildReason;
+ private final Map<VirtualFile, Module> myRootToModuleMap = new HashMap<VirtualFile, Module>();
+ private final Map<Module, Set<VirtualFile>> myModuleToRootsMap = new HashMap<Module, Set<VirtualFile>>();
+ private final Map<VirtualFile, Pair<SourceGeneratingCompiler, Module>> myOutputRootToSourceGeneratorMap = new HashMap<VirtualFile, Pair<SourceGeneratingCompiler, Module>>();
+ private final Set<VirtualFile> myGeneratedTestRoots = new java.util.HashSet<VirtualFile>();
+ private VirtualFile[] myOutputDirectories;
+ private Set<VirtualFile> myTestOutputDirectories;
+ private final TIntHashSet myGeneratedSources = new TIntHashSet();
+ private final ProjectFileIndex myProjectFileIndex; // cached for performance reasons
+ private final ProjectCompileScope myProjectCompileScope;
+ private final long myStartCompilationStamp;
+ private final UUID mySessionId = UUID.randomUUID();
+
+ public CompileContextImpl(final Project project,
+ final CompilerTask compilerSession,
+ CompileScope compileScope,
+ DependencyCache dependencyCache, boolean isMake, boolean isRebuild) {
+ myProject = project;
+ myTask = compilerSession;
+ myCompileScope = compileScope;
+ myDependencyCache = dependencyCache;
+ myMake = isMake;
+ myIsRebuild = isRebuild;
+ myStartCompilationStamp = System.currentTimeMillis();
+ myProjectFileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
+ myProjectCompileScope = new ProjectCompileScope(myProject);
+ myIsAnnotationProcessorsEnabled = CompilerConfiguration.getInstance(project).isAnnotationProcessorsEnabled();
+
+ if (compilerSession != null) {
+ compilerSession.setContentIdKey(compileScope.getUserData(CompilerManager.CONTENT_ID_KEY));
+ }
+ recalculateOutputDirs();
+ final CompilerWorkspaceConfiguration workspaceConfig = CompilerWorkspaceConfiguration.getInstance(myProject);
+ myShouldUpdateProblemsView = workspaceConfig.useOutOfProcessBuild() && workspaceConfig.MAKE_PROJECT_ON_SAVE;
+ }
+
+ public boolean shouldUpdateProblemsView() {
+ return myShouldUpdateProblemsView;
+ }
+
+ public void recalculateOutputDirs() {
+ final Module[] allModules = ModuleManager.getInstance(myProject).getModules();
+
+ final Set<VirtualFile> allDirs = new OrderedSet<VirtualFile>();
+ final Set<VirtualFile> testOutputDirs = new java.util.HashSet<VirtualFile>();
+ final Set<VirtualFile> productionOutputDirs = new java.util.HashSet<VirtualFile>();
+
+ for (Module module : allModules) {
+ final CompilerModuleExtension manager = CompilerModuleExtension.getInstance(module);
+ final VirtualFile output = manager.getCompilerOutputPath();
+ if (output != null && output.isValid()) {
+ allDirs.add(output);
+ productionOutputDirs.add(output);
+ }
+ final VirtualFile testsOutput = manager.getCompilerOutputPathForTests();
+ if (testsOutput != null && testsOutput.isValid()) {
+ allDirs.add(testsOutput);
+ testOutputDirs.add(testsOutput);
+ }
+ }
+ myOutputDirectories = VfsUtil.toVirtualFileArray(allDirs);
+ // need this to ensure that the sent contains only _dedicated_ test output dirs
+ // Directories that are configured for both test and production classes must not be added in the resulting set
+ testOutputDirs.removeAll(productionOutputDirs);
+ myTestOutputDirectories = Collections.unmodifiableSet(testOutputDirs);
+ }
+
+ public void markGenerated(Collection<VirtualFile> files) {
+ for (final VirtualFile file : files) {
+ myGeneratedSources.add(FileBasedIndex.getFileId(file));
+ }
+ }
+
+ public long getStartCompilationStamp() {
+ return myStartCompilationStamp;
+ }
+
+ public boolean isGenerated(VirtualFile file) {
+ if (myGeneratedSources.contains(FileBasedIndex.getFileId(file))) {
+ return true;
+ }
+ if (isUnderRoots(myRootToModuleMap.keySet(), file)) {
+ return true;
+ }
+ final Module module = getModuleByFile(file);
+ if (module != null) {
+ final String procGenRoot = CompilerPaths.getAnnotationProcessorsGenerationPath(module);
+ if (procGenRoot != null && VfsUtil.isAncestor(new File(procGenRoot), new File(file.getPath()), true)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /*
+ private JBZipFile lookupZip(String outputDir) {
+ synchronized (myOpenZipFiles) {
+ JBZipFile zip = myOpenZipFiles.get(outputDir);
+ if (zip == null) {
+ final File zipFile = CompilerPathsEx.getZippedOutputPath(myProject, outputDir);
+ try {
+ try {
+ zip = new JBZipFile(zipFile);
+ }
+ catch (FileNotFoundException e) {
+ try {
+ zipFile.createNewFile();
+ zip = new JBZipFile(zipFile);
+ }
+ catch (IOException e1) {
+ zipFile.getParentFile().mkdirs();
+ zipFile.createNewFile();
+ zip = new JBZipFile(zipFile);
+ }
+ }
+ myOpenZipFiles.put(outputDir, zip);
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ addMessage(CompilerMessageCategory.ERROR, "Cannot create zip file " + zipFile.getPath() + ": " + e.getMessage(), null, -1, -1);
+ }
+ }
+ return zip;
+ }
+ }
+ */
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ public DependencyCache getDependencyCache() {
+ return myDependencyCache;
+ }
+
+ public CompilerMessage[] getMessages(CompilerMessageCategory category) {
+ Collection<CompilerMessage> collection = myMessages.get(category);
+ if (collection == null) {
+ return CompilerMessage.EMPTY_ARRAY;
+ }
+ return collection.toArray(new CompilerMessage[collection.size()]);
+ }
+
+ public void addMessage(CompilerMessageCategory category, String message, String url, int lineNum, int columnNum) {
+ CompilerMessageImpl msg = new CompilerMessageImpl(myProject, category, message, findPresentableFileForMessage(url), lineNum, columnNum, null);
+ addMessage(msg);
+ }
+
+ public void addMessage(CompilerMessageCategory category, String message, String url, int lineNum, int columnNum,
+ Navigatable navigatable) {
+ CompilerMessageImpl msg = new CompilerMessageImpl(myProject, category, message, findPresentableFileForMessage(url), lineNum, columnNum, navigatable);
+ addMessage(msg);
+ }
+
+ @Nullable
+ private VirtualFile findPresentableFileForMessage(@Nullable final String url) {
+ final VirtualFile file = findFileByUrl(url);
+ if (file == null) {
+ return null;
+ }
+ return ApplicationManager.getApplication().runReadAction(new Computable<VirtualFile>() {
+ @Override
+ public VirtualFile compute() {
+ if (file.isValid()) {
+ for (final Map.Entry<VirtualFile, Pair<SourceGeneratingCompiler, Module>> entry : myOutputRootToSourceGeneratorMap.entrySet()) {
+ final VirtualFile root = entry.getKey();
+ if (VfsUtilCore.isAncestor(root, file, false)) {
+ final Pair<SourceGeneratingCompiler, Module> pair = entry.getValue();
+ final VirtualFile presentableFile = pair.getFirst().getPresentableFile(CompileContextImpl.this, pair.getSecond(), root, file);
+ return presentableFile != null ? presentableFile : file;
+ }
+ }
+ }
+ return file;
+ }
+ });
+ }
+
+ @Nullable
+ private static VirtualFile findFileByUrl(@Nullable String url) {
+ if (url == null) {
+ return null;
+ }
+ VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(url);
+ if (file == null) {
+ // groovy stubs may be placed in completely random directories which aren't refreshed automatically
+ return VirtualFileManager.getInstance().refreshAndFindFileByUrl(url);
+ }
+ return file;
+ }
+
+ public void addMessage(CompilerMessage msg) {
+ if (ApplicationManager.getApplication().isUnitTestMode()) {
+ LOG.info("addMessage: " + msg + " this=" + this);
+ }
+
+ Collection<CompilerMessage> messages = myMessages.get(msg.getCategory());
+ if (messages == null) {
+ messages = new LinkedHashSet<CompilerMessage>();
+ myMessages.put(msg.getCategory(), messages);
+ }
+ if (messages.add(msg)) {
+ myTask.addMessage(msg);
+ }
+ if (myShouldUpdateProblemsView && msg.getCategory() == CompilerMessageCategory.ERROR) {
+ ProblemsViewImpl.SERVICE.getInstance(myProject).addMessage(msg, mySessionId);
+ }
+ }
+
+ public int getMessageCount(CompilerMessageCategory category) {
+ if (category != null) {
+ Collection<CompilerMessage> collection = myMessages.get(category);
+ return collection != null ? collection.size() : 0;
+ }
+ int count = 0;
+ for (Collection<CompilerMessage> collection : myMessages.values()) {
+ if (collection != null) {
+ count += collection.size();
+ }
+ }
+ return count;
+ }
+
+ public CompileScope getCompileScope() {
+ return myCompileScope;
+ }
+
+ public CompileScope getProjectCompileScope() {
+ return myProjectCompileScope;
+ }
+
+ public void requestRebuildNextTime(String message) {
+ if (!myRebuildRequested) {
+ myRebuildRequested = true;
+ myRebuildReason = message;
+ addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1);
+ }
+ }
+
+ public boolean isRebuildRequested() {
+ return myRebuildRequested;
+ }
+
+ public String getRebuildReason() {
+ return myRebuildReason;
+ }
+
+ public ProgressIndicator getProgressIndicator() {
+ //if (myProgressIndicatorProxy != null) {
+ // return myProgressIndicatorProxy;
+ //}
+ return myTask.getIndicator();
+ }
+
+ public void assignModule(@NotNull VirtualFile root, @NotNull Module module, final boolean isTestSource, @Nullable Compiler compiler) {
+ try {
+ myRootToModuleMap.put(root, module);
+ Set<VirtualFile> set = myModuleToRootsMap.get(module);
+ if (set == null) {
+ set = new HashSet<VirtualFile>();
+ myModuleToRootsMap.put(module, set);
+ }
+ set.add(root);
+ if (isTestSource) {
+ myGeneratedTestRoots.add(root);
+ }
+ if (compiler instanceof SourceGeneratingCompiler) {
+ myOutputRootToSourceGeneratorMap.put(root, new Pair<SourceGeneratingCompiler, Module>((SourceGeneratingCompiler)compiler, module));
+ }
+ }
+ finally {
+ myModuleToRootsCache.remove(module);
+ }
+ }
+
+ @Nullable
+ public VirtualFile getSourceFileByOutputFile(VirtualFile outputFile) {
+ return TranslatingCompilerFilesMonitor.getSourceFileByOutput(outputFile);
+ }
+
+ public Module getModuleByFile(VirtualFile file) {
+ final Module module = myProjectFileIndex.getModuleForFile(file);
+ if (module != null) {
+ LOG.assertTrue(!module.isDisposed());
+ return module;
+ }
+ for (final VirtualFile root : myRootToModuleMap.keySet()) {
+ if (VfsUtil.isAncestor(root, file, false)) {
+ final Module mod = myRootToModuleMap.get(root);
+ if (mod != null) {
+ LOG.assertTrue(!mod.isDisposed());
+ }
+ return mod;
+ }
+ }
+ return null;
+ }
+
+
+ private final Map<Module, VirtualFile[]> myModuleToRootsCache = new HashMap<Module, VirtualFile[]>();
+
+ public VirtualFile[] getSourceRoots(Module module) {
+ VirtualFile[] cachedRoots = myModuleToRootsCache.get(module);
+ if (cachedRoots != null) {
+ if (areFilesValid(cachedRoots)) {
+ return cachedRoots;
+ }
+ else {
+ myModuleToRootsCache.remove(module); // clear cache for this module and rebuild list of roots
+ }
+ }
+
+ Set<VirtualFile> additionalRoots = myModuleToRootsMap.get(module);
+ VirtualFile[] moduleRoots = ModuleRootManager.getInstance(module).getSourceRoots();
+ if (additionalRoots == null || additionalRoots.isEmpty()) {
+ myModuleToRootsCache.put(module, moduleRoots);
+ return moduleRoots;
+ }
+
+ final VirtualFile[] allRoots = new VirtualFile[additionalRoots.size() + moduleRoots.length];
+ System.arraycopy(moduleRoots, 0, allRoots, 0, moduleRoots.length);
+ int index = moduleRoots.length;
+ for (final VirtualFile additionalRoot : additionalRoots) {
+ allRoots[index++] = additionalRoot;
+ }
+ myModuleToRootsCache.put(module, allRoots);
+ return allRoots;
+ }
+
+ private static boolean areFilesValid(VirtualFile[] files) {
+ for (VirtualFile file : files) {
+ if (!file.isValid()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public VirtualFile[] getAllOutputDirectories() {
+ return myOutputDirectories;
+ }
+
+ @NotNull
+ public Set<VirtualFile> getTestOutputDirectories() {
+ return myTestOutputDirectories;
+ }
+
+ public VirtualFile getModuleOutputDirectory(Module module) {
+ return CompilerPaths.getModuleOutputDirectory(module, false);
+ }
+
+ public VirtualFile getModuleOutputDirectoryForTests(Module module) {
+ return CompilerPaths.getModuleOutputDirectory(module, true);
+ }
+
+ public boolean isMake() {
+ return myMake;
+ }
+
+ public boolean isRebuild() {
+ return myIsRebuild;
+ }
+
+ public boolean isAnnotationProcessorsEnabled() {
+ return myIsAnnotationProcessorsEnabled;
+ }
+
+ public void addScope(final CompileScope additionalScope) {
+ myCompileScope = new CompositeScope(myCompileScope, additionalScope);
+ }
+
+ public boolean isInTestSourceContent(@NotNull final VirtualFile fileOrDir) {
+ if (myProjectFileIndex.isInTestSourceContent(fileOrDir)) {
+ return true;
+ }
+ if (isUnderRoots(myGeneratedTestRoots, fileOrDir)) {
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isInSourceContent(@NotNull final VirtualFile fileOrDir) {
+ if (myProjectFileIndex.isInSourceContent(fileOrDir)) {
+ return true;
+ }
+ if (isUnderRoots(myRootToModuleMap.keySet(), fileOrDir)) {
+ return true;
+ }
+ return false;
+ }
+
+ public static boolean isUnderRoots(@NotNull Set<VirtualFile> roots, @NotNull VirtualFile file) {
+ VirtualFile parent = file;
+ while (true) {
+ if (parent == null) {
+ return false;
+ }
+ if (roots.contains(parent)) {
+ return true;
+ }
+ parent = parent.getParent();
+ }
+ }
+
+ public UUID getSessionId() {
+ return mySessionId;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java
new file mode 100644
index 0000000..568bb42
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompileDriver.java
@@ -0,0 +1,2851 @@
+/*
+ * Copyright 2000-2012 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.
+ */
+
+/**
+ * @author: Eugene Zhuravlev
+ * Date: Jan 17, 2003
+ * Time: 1:42:26 PM
+ */
+package com.intellij.compiler.impl;
+
+import com.intellij.CommonBundle;
+import com.intellij.compiler.*;
+import com.intellij.compiler.make.CacheCorruptedException;
+import com.intellij.compiler.make.CacheUtils;
+import com.intellij.compiler.make.ChangedConstantsDependencyProcessor;
+import com.intellij.compiler.make.DependencyCache;
+import com.intellij.compiler.progress.CompilerTask;
+import com.intellij.compiler.server.BuildManager;
+import com.intellij.compiler.server.CustomBuilderMessageHandler;
+import com.intellij.compiler.server.DefaultMessageHandler;
+import com.intellij.diagnostic.IdeErrorsDialog;
+import com.intellij.diagnostic.PluginException;
+import com.intellij.openapi.application.*;
+import com.intellij.openapi.compiler.*;
+import com.intellij.openapi.compiler.Compiler;
+import com.intellij.openapi.compiler.ex.CompileContextEx;
+import com.intellij.openapi.compiler.ex.CompilerPathsEx;
+import com.intellij.openapi.compiler.generic.GenericCompiler;
+import com.intellij.openapi.deployment.DeploymentUtil;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.extensions.Extensions;
+import com.intellij.openapi.extensions.PluginId;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.module.LanguageLevelUtil;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.project.DumbService;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectBundle;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.roots.*;
+import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
+import com.intellij.openapi.roots.ui.configuration.CommonContentEntriesEditor;
+import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService;
+import com.intellij.openapi.ui.MessageType;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.*;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.registry.Registry;
+import com.intellij.openapi.vfs.*;
+import com.intellij.openapi.vfs.newvfs.ManagingFS;
+import com.intellij.openapi.vfs.newvfs.RefreshQueue;
+import com.intellij.openapi.wm.StatusBar;
+import com.intellij.openapi.wm.ToolWindowId;
+import com.intellij.openapi.wm.ToolWindowManager;
+import com.intellij.openapi.wm.WindowManager;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.artifacts.ArtifactManager;
+import com.intellij.packaging.impl.artifacts.ArtifactImpl;
+import com.intellij.packaging.impl.artifacts.ArtifactUtil;
+import com.intellij.packaging.impl.compiler.ArtifactCompileScope;
+import com.intellij.packaging.impl.compiler.ArtifactCompilerUtil;
+import com.intellij.packaging.impl.compiler.ArtifactsCompiler;
+import com.intellij.pom.java.LanguageLevel;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.util.Chunk;
+import com.intellij.util.Function;
+import com.intellij.util.StringBuilderSpinAllocator;
+import com.intellij.util.ThrowableRunnable;
+import com.intellij.util.concurrency.Semaphore;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.HashMap;
+import com.intellij.util.containers.MultiMap;
+import com.intellij.util.containers.OrderedSet;
+import com.intellij.util.messages.MessageBus;
+import gnu.trove.THashSet;
+import gnu.trove.TIntHashSet;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.TestOnly;
+import org.jetbrains.jps.api.CmdlineProtoUtil;
+import org.jetbrains.jps.api.CmdlineRemoteProto;
+import org.jetbrains.jps.api.RequestFuture;
+import org.jetbrains.jps.incremental.Utils;
+
+import javax.swing.*;
+import java.io.*;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+import static org.jetbrains.jps.api.CmdlineRemoteProto.Message.ControllerMessage.ParametersMessage.TargetTypeBuildScope;
+
+public class CompileDriver {
+
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.CompileDriver");
+ // to be used in tests only for debug output
+ public static volatile boolean ourDebugMode = false;
+
+ private final Project myProject;
+ private final Map<Pair<IntermediateOutputCompiler, Module>, Pair<VirtualFile, VirtualFile>> myGenerationCompilerModuleToOutputDirMap; // [IntermediateOutputCompiler, Module] -> [ProductionSources, TestSources]
+ private final String myCachesDirectoryPath;
+ private boolean myShouldClearOutputDirectory;
+
+ private final Map<Module, String> myModuleOutputPaths = new HashMap<Module, String>();
+ private final Map<Module, String> myModuleTestOutputPaths = new HashMap<Module, String>();
+
+ @NonNls private static final String VERSION_FILE_NAME = "version.dat";
+ @NonNls private static final String LOCK_FILE_NAME = "in_progress.dat";
+
+ private static final boolean GENERATE_CLASSPATH_INDEX = "true".equals(System.getProperty("generate.classpath.index"));
+ private static final String PROP_PERFORM_INITIAL_REFRESH = "compiler.perform.outputs.refresh.on.start";
+ private static final Key<Boolean> REFRESH_DONE_KEY = Key.create("_compiler.initial.refresh.done_");
+
+ private static final FileProcessingCompilerAdapterFactory FILE_PROCESSING_COMPILER_ADAPTER_FACTORY = new FileProcessingCompilerAdapterFactory() {
+ public FileProcessingCompilerAdapter create(CompileContext context, FileProcessingCompiler compiler) {
+ return new FileProcessingCompilerAdapter(context, compiler);
+ }
+ };
+ private static final FileProcessingCompilerAdapterFactory FILE_PACKAGING_COMPILER_ADAPTER_FACTORY = new FileProcessingCompilerAdapterFactory() {
+ public FileProcessingCompilerAdapter create(CompileContext context, FileProcessingCompiler compiler) {
+ return new PackagingCompilerAdapter(context, (PackagingCompiler)compiler);
+ }
+ };
+ private CompilerFilter myCompilerFilter = CompilerFilter.ALL;
+ private static final CompilerFilter SOURCE_PROCESSING_ONLY = new CompilerFilter() {
+ public boolean acceptCompiler(Compiler compiler) {
+ return compiler instanceof SourceProcessingCompiler;
+ }
+ };
+ private static final CompilerFilter ALL_EXCEPT_SOURCE_PROCESSING = new CompilerFilter() {
+ public boolean acceptCompiler(Compiler compiler) {
+ return !SOURCE_PROCESSING_ONLY.acceptCompiler(compiler);
+ }
+ };
+
+ private Set<File> myAllOutputDirectories;
+ private static final long ONE_MINUTE_MS = 60L /*sec*/ * 1000L /*millisec*/;
+
+ public CompileDriver(Project project) {
+ myProject = project;
+ myCachesDirectoryPath = CompilerPaths.getCacheStoreDirectory(myProject).getPath().replace('/', File.separatorChar);
+ myShouldClearOutputDirectory = CompilerWorkspaceConfiguration.getInstance(myProject).CLEAR_OUTPUT_DIRECTORY;
+
+ myGenerationCompilerModuleToOutputDirMap = new HashMap<Pair<IntermediateOutputCompiler, Module>, Pair<VirtualFile, VirtualFile>>();
+
+ if (!useOutOfProcessBuild()) {
+ final LocalFileSystem lfs = LocalFileSystem.getInstance();
+ final IntermediateOutputCompiler[] generatingCompilers = CompilerManager.getInstance(myProject).getCompilers(IntermediateOutputCompiler.class, myCompilerFilter);
+ final Module[] allModules = ModuleManager.getInstance(myProject).getModules();
+ final CompilerConfiguration config = CompilerConfiguration.getInstance(project);
+ for (Module module : allModules) {
+ for (IntermediateOutputCompiler compiler : generatingCompilers) {
+ final VirtualFile productionOutput = lookupVFile(lfs, CompilerPaths.getGenerationOutputPath(compiler, module, false));
+ final VirtualFile testOutput = lookupVFile(lfs, CompilerPaths.getGenerationOutputPath(compiler, module, true));
+ final Pair<IntermediateOutputCompiler, Module> pair = new Pair<IntermediateOutputCompiler, Module>(compiler, module);
+ final Pair<VirtualFile, VirtualFile> outputs = new Pair<VirtualFile, VirtualFile>(productionOutput, testOutput);
+ myGenerationCompilerModuleToOutputDirMap.put(pair, outputs);
+ }
+ if (config.getAnnotationProcessingConfiguration(module).isEnabled()) {
+ final String path = CompilerPaths.getAnnotationProcessorsGenerationPath(module);
+ if (path != null) {
+ lookupVFile(lfs, path); // ensure the file is created and added to VFS
+ }
+ }
+ }
+ }
+ }
+
+ public void setCompilerFilter(CompilerFilter compilerFilter) {
+ myCompilerFilter = compilerFilter == null? CompilerFilter.ALL : compilerFilter;
+ }
+
+ public void rebuild(CompileStatusNotification callback) {
+ final CompileScope compileScope;
+ ProjectCompileScope projectScope = new ProjectCompileScope(myProject);
+ if (useOutOfProcessBuild()) {
+ compileScope = projectScope;
+ }
+ else {
+ CompileScope scopeWithArtifacts = ArtifactCompileScope.createScopeWithArtifacts(projectScope, ArtifactUtil.getArtifactWithOutputPaths(myProject), false);
+ compileScope = addAdditionalRoots(scopeWithArtifacts, ALL_EXCEPT_SOURCE_PROCESSING);
+ }
+ doRebuild(callback, null, true, compileScope);
+ }
+
+ public void make(CompileScope scope, CompileStatusNotification callback) {
+ if (!useOutOfProcessBuild()) {
+ scope = addAdditionalRoots(scope, ALL_EXCEPT_SOURCE_PROCESSING);
+ }
+ if (validateCompilerConfiguration(scope, false)) {
+ startup(scope, false, false, callback, null, true);
+ }
+ else {
+ callback.finished(true, 0, 0, DummyCompileContext.getInstance());
+ }
+ }
+
+ public boolean isUpToDate(CompileScope scope) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("isUpToDate operation started");
+ }
+ if (!useOutOfProcessBuild()) {
+ scope = addAdditionalRoots(scope, ALL_EXCEPT_SOURCE_PROCESSING);
+ }
+
+ final CompilerTask task = new CompilerTask(myProject, "Classes up-to-date check", true, false);
+ final DependencyCache cache = useOutOfProcessBuild()? null : createDependencyCache();
+ final CompileContextImpl compileContext = new CompileContextImpl(myProject, task, scope, cache, true, false);
+
+ if (!useOutOfProcessBuild()) {
+ checkCachesVersion(compileContext, ManagingFS.getInstance().getCreationTimestamp());
+ if (compileContext.isRebuildRequested()) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Rebuild requested, up-to-date=false");
+ }
+ return false;
+ }
+
+ for (Map.Entry<Pair<IntermediateOutputCompiler, Module>, Pair<VirtualFile, VirtualFile>> entry : myGenerationCompilerModuleToOutputDirMap.entrySet()) {
+ final Pair<VirtualFile, VirtualFile> outputs = entry.getValue();
+ final Pair<IntermediateOutputCompiler, Module> key = entry.getKey();
+ final Module module = key.getSecond();
+ compileContext.assignModule(outputs.getFirst(), module, false, key.getFirst());
+ compileContext.assignModule(outputs.getSecond(), module, true, key.getFirst());
+ }
+ }
+
+ final Ref<ExitStatus> result = new Ref<ExitStatus>();
+
+ final Runnable compileWork;
+ if (useOutOfProcessBuild()) {
+ compileWork = new Runnable() {
+ public void run() {
+ final ProgressIndicator indicator = compileContext.getProgressIndicator();
+ if (indicator.isCanceled() || myProject.isDisposed()) {
+ return;
+ }
+ try {
+ final Collection<String> paths = CompileScopeUtil.fetchFiles(compileContext);
+ List<TargetTypeBuildScope> scopes = new ArrayList<TargetTypeBuildScope>();
+ if (paths.isEmpty()) {
+ if (!compileContext.isRebuild() && !CompileScopeUtil.allProjectModulesAffected(compileContext)) {
+ CompileScopeUtil.addScopesForModules(Arrays.asList(compileContext.getCompileScope().getAffectedModules()), scopes);
+ }
+ else {
+ scopes.addAll(CmdlineProtoUtil.createAllModulesScopes());
+ }
+ for (BuildTargetScopeProvider provider : BuildTargetScopeProvider.EP_NAME.getExtensions()) {
+ scopes = CompileScopeUtil.mergeScopes(scopes, provider.getBuildTargetScopes(compileContext.getCompileScope(), myCompilerFilter, myProject));
+ }
+ }
+ final RequestFuture future = compileInExternalProcess(compileContext, scopes, paths, true);
+ if (future != null) {
+ while (!future.waitFor(200L , TimeUnit.MILLISECONDS)) {
+ if (indicator.isCanceled()) {
+ future.cancel(false);
+ }
+ }
+ }
+ }
+ catch (Throwable e) {
+ LOG.error(e);
+ }
+ finally {
+ result.set(COMPILE_SERVER_BUILD_STATUS.get(compileContext));
+ CompilerCacheManager.getInstance(myProject).flushCaches();
+ }
+ }
+ };
+ }
+ else {
+ compileWork = new Runnable() {
+ public void run() {
+ try {
+ myAllOutputDirectories = getAllOutputDirectories(compileContext);
+ // need this for updating zip archives experiment, uncomment if the feature is turned on
+ //myOutputFinder = new OutputPathFinder(myAllOutputDirectories);
+ result.set(doCompile(compileContext, false, false, true));
+ }
+ finally {
+ CompilerCacheManager.getInstance(myProject).flushCaches();
+ }
+ }
+ };
+ }
+ task.start(compileWork, null);
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("isUpToDate operation finished");
+ }
+
+ return ExitStatus.UP_TO_DATE.equals(result.get());
+ }
+
+ private DependencyCache createDependencyCache() {
+ return new DependencyCache(myCachesDirectoryPath + File.separator + ".dependency-info");
+ }
+
+ public void compile(CompileScope scope, CompileStatusNotification callback, boolean clearingOutputDirsPossible) {
+ myShouldClearOutputDirectory &= clearingOutputDirsPossible;
+ if (containsFileIndexScopes(scope)) {
+ scope = addAdditionalRoots(scope, ALL_EXCEPT_SOURCE_PROCESSING);
+ }
+ if (validateCompilerConfiguration(scope, false)) {
+ startup(scope, false, true, callback, null, true);
+ }
+ else {
+ callback.finished(true, 0, 0, DummyCompileContext.getInstance());
+ }
+ }
+
+ private static boolean containsFileIndexScopes(CompileScope scope) {
+ if (scope instanceof CompositeScope) {
+ for (CompileScope childScope : ((CompositeScope)scope).getScopes()) {
+ if (containsFileIndexScopes(childScope)) {
+ return true;
+ }
+ }
+ }
+ return scope instanceof FileIndexCompileScope;
+ }
+
+ private static class CompileStatus {
+ final int CACHE_FORMAT_VERSION;
+ final boolean COMPILATION_IN_PROGRESS;
+ final long VFS_CREATION_STAMP;
+
+ private CompileStatus(int cacheVersion, boolean isCompilationInProgress, long vfsStamp) {
+ CACHE_FORMAT_VERSION = cacheVersion;
+ COMPILATION_IN_PROGRESS = isCompilationInProgress;
+ VFS_CREATION_STAMP = vfsStamp;
+ }
+ }
+
+ private CompileStatus readStatus() {
+ final boolean isInProgress = getLockFile().exists();
+ int version = -1;
+ long vfsStamp = -1L;
+ try {
+ final File versionFile = new File(myCachesDirectoryPath, VERSION_FILE_NAME);
+ DataInputStream in = new DataInputStream(new FileInputStream(versionFile));
+ try {
+ version = in.readInt();
+ try {
+ vfsStamp = in.readLong();
+ }
+ catch (IOException ignored) {
+ }
+ }
+ finally {
+ in.close();
+ }
+ }
+ catch (FileNotFoundException e) {
+ // ignore
+ }
+ catch (IOException e) {
+ LOG.info(e); // may happen in case of IDEA crashed and the file is not written properly
+ return null;
+ }
+ return new CompileStatus(version, isInProgress, vfsStamp);
+ }
+
+ private void writeStatus(CompileStatus status, CompileContext context) {
+ final File statusFile = new File(myCachesDirectoryPath, VERSION_FILE_NAME);
+
+ final File lockFile = getLockFile();
+ try {
+ FileUtil.createIfDoesntExist(statusFile);
+ DataOutputStream out = new DataOutputStream(new FileOutputStream(statusFile));
+ try {
+ out.writeInt(status.CACHE_FORMAT_VERSION);
+ out.writeLong(status.VFS_CREATION_STAMP);
+ }
+ finally {
+ out.close();
+ }
+ if (status.COMPILATION_IN_PROGRESS) {
+ FileUtil.createIfDoesntExist(lockFile);
+ }
+ else {
+ deleteFile(lockFile);
+ }
+ }
+ catch (IOException e) {
+ context.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("compiler.error.exception", e.getMessage()), null, -1, -1);
+ }
+ }
+
+ private File getLockFile() {
+ return new File(CompilerPaths.getCompilerSystemDirectory(myProject), LOCK_FILE_NAME);
+ }
+
+ private void doRebuild(CompileStatusNotification callback,
+ CompilerMessage message,
+ final boolean checkCachesVersion,
+ final CompileScope compileScope) {
+ if (validateCompilerConfiguration(compileScope, !useOutOfProcessBuild())) {
+ startup(compileScope, true, false, callback, message, checkCachesVersion);
+ }
+ else {
+ callback.finished(true, 0, 0, DummyCompileContext.getInstance());
+ }
+ }
+
+ private CompileScope addAdditionalRoots(CompileScope originalScope, final CompilerFilter filter) {
+ CompileScope scope = attachIntermediateOutputDirectories(originalScope, filter);
+
+ final AdditionalCompileScopeProvider[] scopeProviders = Extensions.getExtensions(AdditionalCompileScopeProvider.EXTENSION_POINT_NAME);
+ CompileScope baseScope = scope;
+ for (AdditionalCompileScopeProvider scopeProvider : scopeProviders) {
+ final CompileScope additionalScope = scopeProvider.getAdditionalScope(baseScope, filter, myProject);
+ if (additionalScope != null) {
+ scope = new CompositeScope(scope, additionalScope);
+ }
+ }
+ return scope;
+ }
+
+ private CompileScope attachIntermediateOutputDirectories(CompileScope originalScope, CompilerFilter filter) {
+ CompileScope scope = originalScope;
+ final Set<Module> affected = new HashSet<Module>(Arrays.asList(originalScope.getAffectedModules()));
+ for (Map.Entry<Pair<IntermediateOutputCompiler, Module>, Pair<VirtualFile, VirtualFile>> entry : myGenerationCompilerModuleToOutputDirMap.entrySet()) {
+ final Module module = entry.getKey().getSecond();
+ if (affected.contains(module) && filter.acceptCompiler(entry.getKey().getFirst())) {
+ final Pair<VirtualFile, VirtualFile> outputs = entry.getValue();
+ scope = new CompositeScope(scope, new FileSetCompileScope(Arrays.asList(outputs.getFirst(), outputs.getSecond()), new Module[]{module}));
+ }
+ }
+ return scope;
+ }
+
+ private void attachAnnotationProcessorsOutputDirectories(CompileContextEx context) {
+ final LocalFileSystem lfs = LocalFileSystem.getInstance();
+ final CompilerConfiguration config = CompilerConfiguration.getInstance(myProject);
+ final Set<Module> affected = new HashSet<Module>(Arrays.asList(context.getCompileScope().getAffectedModules()));
+ for (Module module : affected) {
+ if (!config.getAnnotationProcessingConfiguration(module).isEnabled()) {
+ continue;
+ }
+ final String path = CompilerPaths.getAnnotationProcessorsGenerationPath(module);
+ if (path == null) {
+ continue;
+ }
+ final VirtualFile vFile = lfs.findFileByPath(path);
+ if (vFile == null) {
+ continue;
+ }
+ if (ModuleRootManager.getInstance(module).getFileIndex().isInSourceContent(vFile)) {
+ // no need to add, is already marked as source
+ continue;
+ }
+ context.addScope(new FileSetCompileScope(Collections.singletonList(vFile), new Module[]{module}));
+ context.assignModule(vFile, module, false, null);
+ }
+ }
+
+ @Nullable
+ private RequestFuture compileInExternalProcess(final @NotNull CompileContextImpl compileContext,
+ @NotNull List<TargetTypeBuildScope> scopes,
+ final @NotNull Collection<String> paths,
+ final boolean onlyCheckUpToDate)
+ throws Exception {
+ final CompileScope scope = compileContext.getCompileScope();
+ // need to pass scope's user data to server
+ final Map<String, String> builderParams;
+ if (onlyCheckUpToDate) {
+ builderParams = Collections.emptyMap();
+ }
+ else {
+ final Map<Key, Object> exported = scope.exportUserData();
+ if (!exported.isEmpty()) {
+ builderParams = new HashMap<String, String>();
+ for (Map.Entry<Key, Object> entry : exported.entrySet()) {
+ final String _key = entry.getKey().toString();
+ final String _value = entry.getValue().toString();
+ builderParams.put(_key, _value);
+ }
+ }
+ else {
+ builderParams = Collections.emptyMap();
+ }
+ }
+
+ final MessageBus messageBus = myProject.getMessageBus();
+ final MultiMap<String, Artifact> outputToArtifact = ArtifactCompilerUtil.containsArtifacts(scopes) ? ArtifactCompilerUtil.createOutputToArtifactMap(myProject) : null;
+ final BuildManager buildManager = BuildManager.getInstance();
+ buildManager.cancelAutoMakeTasks(myProject);
+ return buildManager.scheduleBuild(myProject, compileContext.isRebuild(), compileContext.isMake(), onlyCheckUpToDate, scopes, paths, builderParams, new DefaultMessageHandler(myProject) {
+
+ @Override
+ public void buildStarted(UUID sessionId) {
+ }
+
+ @Override
+ public void sessionTerminated(final UUID sessionId) {
+ if (compileContext.shouldUpdateProblemsView()) {
+ final ProblemsView view = ProblemsViewImpl.SERVICE.getInstance(myProject);
+ view.clearProgress();
+ view.clearOldMessages(compileContext.getCompileScope(), compileContext.getSessionId());
+ }
+ }
+
+ @Override
+ public void handleFailure(UUID sessionId, CmdlineRemoteProto.Message.Failure failure) {
+ compileContext.addMessage(CompilerMessageCategory.ERROR, failure.getDescription(), null, -1, -1);
+ final String trace = failure.getStacktrace();
+ if (trace != null) {
+ LOG.info(trace);
+ System.out.println(trace);
+ }
+ compileContext.putUserData(COMPILE_SERVER_BUILD_STATUS, ExitStatus.ERRORS);
+ }
+
+ @Override
+ protected void handleCompileMessage(UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.CompileMessage message) {
+ final CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind kind = message.getKind();
+ //System.out.println(compilerMessage.getText());
+ final String messageText = message.getText();
+ if (kind == CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind.PROGRESS) {
+ final ProgressIndicator indicator = compileContext.getProgressIndicator();
+ indicator.setText(messageText);
+ if (message.hasDone()) {
+ indicator.setFraction(message.getDone());
+ }
+ }
+ else {
+ final CompilerMessageCategory category = kind == CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind.ERROR ? CompilerMessageCategory.ERROR
+ : kind == CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind.WARNING ? CompilerMessageCategory.WARNING : CompilerMessageCategory.INFORMATION;
+
+ String sourceFilePath = message.hasSourceFilePath() ? message.getSourceFilePath() : null;
+ if (sourceFilePath != null) {
+ sourceFilePath = FileUtil.toSystemIndependentName(sourceFilePath);
+ }
+ final long line = message.hasLine() ? message.getLine() : -1;
+ final long column = message.hasColumn() ? message.getColumn() : -1;
+ final String srcUrl = sourceFilePath != null ? VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, sourceFilePath) : null;
+ compileContext.addMessage(category, messageText, srcUrl, (int)line, (int)column);
+ }
+ }
+
+ @Override
+ protected void handleBuildEvent(UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event) {
+ final CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.Type eventType = event.getEventType();
+ switch (eventType) {
+ case FILES_GENERATED:
+ final List<CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.GeneratedFile> generated = event.getGeneratedFilesList();
+ final CompilationStatusListener publisher = messageBus.syncPublisher(CompilerTopics.COMPILATION_STATUS);
+ Set<String> writtenArtifactOutputPaths = outputToArtifact != null ? new THashSet<String>(FileUtil.PATH_HASHING_STRATEGY) : null;
+ for (CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.GeneratedFile generatedFile : generated) {
+ final String root = FileUtil.toSystemIndependentName(generatedFile.getOutputRoot());
+ final String relativePath = FileUtil.toSystemIndependentName(generatedFile.getRelativePath());
+ publisher.fileGenerated(root, relativePath);
+ if (outputToArtifact != null) {
+ Collection<Artifact> artifacts = outputToArtifact.get(root);
+ if (!artifacts.isEmpty()) {
+ for (Artifact artifact : artifacts) {
+ ArtifactsCompiler.addChangedArtifact(compileContext, artifact);
+ }
+ writtenArtifactOutputPaths.add(FileUtil.toSystemDependentName(DeploymentUtil.appendToPath(root, relativePath)));
+ }
+ }
+ }
+ if (writtenArtifactOutputPaths != null && !writtenArtifactOutputPaths.isEmpty()) {
+ ArtifactsCompiler.addWrittenPaths(compileContext, writtenArtifactOutputPaths);
+ }
+ break;
+ case BUILD_COMPLETED:
+ ExitStatus status = ExitStatus.SUCCESS;
+ if (event.hasCompletionStatus()) {
+ final CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.Status completionStatus = event.getCompletionStatus();
+ switch (completionStatus) {
+ case CANCELED:
+ status = ExitStatus.CANCELLED;
+ break;
+ case ERRORS:
+ status = ExitStatus.ERRORS;
+ break;
+ case SUCCESS:
+ status = ExitStatus.SUCCESS;
+ break;
+ case UP_TO_DATE:
+ status = ExitStatus.UP_TO_DATE;
+ break;
+ }
+ }
+ compileContext.putUserDataIfAbsent(COMPILE_SERVER_BUILD_STATUS, status);
+ break;
+ case CUSTOM_BUILDER_MESSAGE:
+ if (event.hasCustomBuilderMessage()) {
+ CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.CustomBuilderMessage message = event.getCustomBuilderMessage();
+ messageBus.syncPublisher(CustomBuilderMessageHandler.TOPIC).messageReceived(message.getBuilderId(), message.getMessageType(),
+ message.getMessageText());
+ }
+ break;
+ }
+ }
+ });
+ }
+
+
+ private static final Key<ExitStatus> COMPILE_SERVER_BUILD_STATUS = Key.create("COMPILE_SERVER_BUILD_STATUS");
+
+ private void startup(final CompileScope scope,
+ final boolean isRebuild,
+ final boolean forceCompile,
+ final CompileStatusNotification callback,
+ final CompilerMessage message,
+ final boolean checkCachesVersion) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+
+ final boolean useExtProcessBuild = useOutOfProcessBuild();
+
+ final String contentName =
+ forceCompile ? CompilerBundle.message("compiler.content.name.compile") : CompilerBundle.message("compiler.content.name.make");
+ final CompilerTask compileTask = new CompilerTask(myProject, contentName, ApplicationManager.getApplication().isUnitTestMode(), true);
+
+ StatusBar.Info.set("", myProject, "Compiler");
+ if (useExtProcessBuild) {
+ // ensure the project model seen by build process is up-to-date
+ myProject.save();
+ }
+ PsiDocumentManager.getInstance(myProject).commitAllDocuments();
+ FileDocumentManager.getInstance().saveAllDocuments();
+
+ final DependencyCache dependencyCache = useExtProcessBuild ? null: createDependencyCache();
+ final CompileContextImpl compileContext =
+ new CompileContextImpl(myProject, compileTask, scope, dependencyCache, !isRebuild && !forceCompile, isRebuild);
+
+ if (!useExtProcessBuild) {
+ for (Map.Entry<Pair<IntermediateOutputCompiler, Module>, Pair<VirtualFile, VirtualFile>> entry : myGenerationCompilerModuleToOutputDirMap.entrySet()) {
+ final Pair<VirtualFile, VirtualFile> outputs = entry.getValue();
+ final Pair<IntermediateOutputCompiler, Module> key = entry.getKey();
+ final Module module = key.getSecond();
+ compileContext.assignModule(outputs.getFirst(), module, false, key.getFirst());
+ compileContext.assignModule(outputs.getSecond(), module, true, key.getFirst());
+ }
+ attachAnnotationProcessorsOutputDirectories(compileContext);
+ }
+
+ final Runnable compileWork;
+ if (useExtProcessBuild) {
+ compileWork = new Runnable() {
+ public void run() {
+ final ProgressIndicator indicator = compileContext.getProgressIndicator();
+ if (indicator.isCanceled() || myProject.isDisposed()) {
+ if (callback != null) {
+ callback.finished(true, 0, 0, compileContext);
+ }
+ return;
+ }
+ try {
+ LOG.info("COMPILATION STARTED (BUILD PROCESS)");
+ if (message != null) {
+ compileContext.addMessage(message);
+ }
+ if (!executeCompileTasks(compileContext, true)) {
+ COMPILE_SERVER_BUILD_STATUS.set(compileContext, ExitStatus.CANCELLED);
+ return;
+ }
+
+ final Collection<String> paths = CompileScopeUtil.fetchFiles(compileContext);
+ List<TargetTypeBuildScope> scopes = new ArrayList<TargetTypeBuildScope>();
+ if (paths.isEmpty()) {
+ if (!isRebuild && !CompileScopeUtil.allProjectModulesAffected(compileContext)) {
+ CompileScopeUtil.addScopesForModules(Arrays.asList(compileContext.getCompileScope().getAffectedModules()), scopes);
+ }
+ else {
+ scopes.addAll(CmdlineProtoUtil.createAllModulesScopes());
+ }
+ for (BuildTargetScopeProvider provider : BuildTargetScopeProvider.EP_NAME.getExtensions()) {
+ scopes = CompileScopeUtil.mergeScopes(scopes, provider.getBuildTargetScopes(scope, myCompilerFilter, myProject));
+ }
+ }
+ final RequestFuture future = compileInExternalProcess(compileContext, scopes, paths, false);
+ if (future != null) {
+ while (!future.waitFor(200L , TimeUnit.MILLISECONDS)) {
+ if (indicator.isCanceled()) {
+ future.cancel(false);
+ }
+ }
+ if (!executeCompileTasks(compileContext, false)) {
+ COMPILE_SERVER_BUILD_STATUS.set(compileContext, ExitStatus.CANCELLED);
+ return;
+ }
+ }
+ }
+ catch (Throwable e) {
+ LOG.error(e); // todo
+ }
+ finally {
+ CompilerCacheManager.getInstance(myProject).flushCaches();
+
+ final long duration = notifyCompilationCompleted(compileContext, callback, COMPILE_SERVER_BUILD_STATUS.get(compileContext));
+ CompilerUtil.logDuration(
+ "\tCOMPILATION FINISHED (BUILD PROCESS); Errors: " +
+ compileContext.getMessageCount(CompilerMessageCategory.ERROR) +
+ "; warnings: " +
+ compileContext.getMessageCount(CompilerMessageCategory.WARNING),
+ duration
+ );
+
+ // refresh on output roots is required in order for the order enumerator to see all roots via VFS
+ final Set<File> outputs = new HashSet<File>();
+ for (final String path : CompilerPathsEx.getOutputPaths(ModuleManager.getInstance(myProject).getModules())) {
+ outputs.add(new File(path));
+ }
+ if (!outputs.isEmpty()) {
+ LocalFileSystem.getInstance().refreshIoFiles(outputs, true, false, null);
+ }
+ }
+ }
+ };
+ }
+ else {
+ compileWork = new Runnable() {
+ public void run() {
+ if (compileContext.getProgressIndicator().isCanceled()) {
+ if (callback != null) {
+ callback.finished(true, 0, 0, compileContext);
+ }
+ return;
+ }
+ try {
+ if (myProject.isDisposed()) {
+ return;
+ }
+ LOG.info("COMPILATION STARTED");
+ if (message != null) {
+ compileContext.addMessage(message);
+ }
+ TranslatingCompilerFilesMonitor.getInstance().ensureInitializationCompleted(myProject, compileContext.getProgressIndicator());
+ doCompile(compileContext, isRebuild, forceCompile, callback, checkCachesVersion);
+ }
+ finally {
+ FileUtil.delete(CompilerPaths.getRebuildMarkerFile(myProject));
+ }
+ }
+ };
+ }
+
+ compileTask.start(compileWork, new Runnable() {
+ public void run() {
+ if (isRebuild) {
+ final int rv = Messages.showOkCancelDialog(
+ myProject, "You are about to rebuild the whole project.\nRun 'Make Project' instead?", "Confirm Project Rebuild",
+ "Make", "Rebuild", Messages.getQuestionIcon()
+ );
+ if (rv == 0 /*yes, please, do run make*/) {
+ startup(scope, false, false, callback, null, checkCachesVersion);
+ return;
+ }
+ }
+ startup(scope, isRebuild, forceCompile, callback, message, checkCachesVersion);
+ }
+ });
+ }
+
+ @Nullable @TestOnly
+ public static ExitStatus getExternalBuildExitStatus(CompileContext context) {
+ return context.getUserData(COMPILE_SERVER_BUILD_STATUS);
+ }
+
+ private void doCompile(final CompileContextImpl compileContext,
+ final boolean isRebuild,
+ final boolean forceCompile,
+ final CompileStatusNotification callback,
+ final boolean checkCachesVersion) {
+ ExitStatus status = ExitStatus.ERRORS;
+ boolean wereExceptions = false;
+ final long vfsTimestamp = (ManagingFS.getInstance()).getCreationTimestamp();
+ try {
+ if (checkCachesVersion) {
+ checkCachesVersion(compileContext, vfsTimestamp);
+ if (compileContext.isRebuildRequested()) {
+ return;
+ }
+ }
+ writeStatus(new CompileStatus(CompilerConfigurationImpl.DEPENDENCY_FORMAT_VERSION, true, vfsTimestamp), compileContext);
+ if (compileContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
+ return;
+ }
+
+ myAllOutputDirectories = getAllOutputDirectories(compileContext);
+ status = doCompile(compileContext, isRebuild, forceCompile, false);
+ }
+ catch (Throwable ex) {
+ if (ApplicationManager.getApplication().isUnitTestMode()) {
+ throw new RuntimeException(ex);
+ }
+ wereExceptions = true;
+ final PluginId pluginId = IdeErrorsDialog.findPluginId(ex);
+
+ final StringBuilder message = new StringBuilder();
+ message.append("Internal error");
+ if (pluginId != null) {
+ message.append(" (Plugin: ").append(pluginId).append(")");
+ }
+ message.append(": ").append(ex.getMessage());
+ compileContext.addMessage(CompilerMessageCategory.ERROR, message.toString(), null, -1, -1);
+
+ if (pluginId != null) {
+ throw new PluginException(ex, pluginId);
+ }
+ throw new RuntimeException(ex);
+ }
+ finally {
+ dropDependencyCache(compileContext);
+ CompilerCacheManager.getInstance(myProject).flushCaches();
+ if (compileContext.isRebuildRequested()) {
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ public void run() {
+ final CompilerMessageImpl msg = new CompilerMessageImpl(myProject, CompilerMessageCategory.INFORMATION, compileContext.getRebuildReason());
+ doRebuild(callback, msg, false, compileContext.getCompileScope());
+ }
+ }, ModalityState.NON_MODAL);
+ }
+ else {
+ if (!myProject.isDisposed()) {
+ writeStatus(new CompileStatus(CompilerConfigurationImpl.DEPENDENCY_FORMAT_VERSION, wereExceptions, vfsTimestamp), compileContext);
+ }
+ final long duration = notifyCompilationCompleted(compileContext, callback, status);
+ CompilerUtil.logDuration(
+ "\tCOMPILATION FINISHED; Errors: " +
+ compileContext.getMessageCount(CompilerMessageCategory.ERROR) +
+ "; warnings: " +
+ compileContext.getMessageCount(CompilerMessageCategory.WARNING),
+ duration
+ );
+ }
+ }
+ }
+
+ /** @noinspection SSBasedInspection*/
+ private long notifyCompilationCompleted(final CompileContextImpl compileContext, final CompileStatusNotification callback, final ExitStatus _status) {
+ final long duration = System.currentTimeMillis() - compileContext.getStartCompilationStamp();
+ SwingUtilities.invokeLater(new Runnable() {
+ public void run() {
+ int errorCount = 0;
+ int warningCount = 0;
+ try {
+ errorCount = compileContext.getMessageCount(CompilerMessageCategory.ERROR);
+ warningCount = compileContext.getMessageCount(CompilerMessageCategory.WARNING);
+ if (!myProject.isDisposed()) {
+ final String statusMessage = createStatusMessage(_status, warningCount, errorCount, duration);
+ final MessageType messageType = errorCount > 0 ? MessageType.ERROR : warningCount > 0 ? MessageType.WARNING : MessageType.INFO;
+ if (duration > ONE_MINUTE_MS) {
+ ToolWindowManager.getInstance(myProject).notifyByBalloon(ToolWindowId.MESSAGES_WINDOW, messageType, statusMessage);
+ }
+ CompilerManager.NOTIFICATION_GROUP.createNotification(statusMessage, messageType).notify(myProject);
+ if (_status != ExitStatus.UP_TO_DATE && compileContext.getMessageCount(null) > 0) {
+ compileContext.addMessage(CompilerMessageCategory.INFORMATION, statusMessage, null, -1, -1);
+ }
+ }
+ }
+ finally {
+ if (callback != null) {
+ callback.finished(_status == ExitStatus.CANCELLED, errorCount, warningCount, compileContext);
+ }
+ }
+ }
+ });
+ return duration;
+ }
+
+ private void checkCachesVersion(final CompileContextImpl compileContext, final long currentVFSTimestamp) {
+ if (CompilerPaths.getRebuildMarkerFile(compileContext.getProject()).exists()) {
+ compileContext.requestRebuildNextTime("Compiler caches are out of date, project rebuild is required");
+ return;
+ }
+ final CompileStatus compileStatus = readStatus();
+ if (compileStatus == null) {
+ compileContext.requestRebuildNextTime(CompilerBundle.message("error.compiler.caches.corrupted"));
+ }
+ else if (compileStatus.CACHE_FORMAT_VERSION != -1 &&
+ compileStatus.CACHE_FORMAT_VERSION != CompilerConfigurationImpl.DEPENDENCY_FORMAT_VERSION) {
+ compileContext.requestRebuildNextTime(CompilerBundle.message("error.caches.old.format"));
+ }
+ else if (compileStatus.COMPILATION_IN_PROGRESS) {
+ compileContext.requestRebuildNextTime(CompilerBundle.message("error.previous.compilation.failed"));
+ }
+ else if (compileStatus.VFS_CREATION_STAMP >= 0L){
+ if (currentVFSTimestamp != compileStatus.VFS_CREATION_STAMP) {
+ compileContext.requestRebuildNextTime(CompilerBundle.message("error.vfs.was.rebuilt"));
+ }
+ }
+ }
+
+ private static String createStatusMessage(final ExitStatus status, final int warningCount, final int errorCount, long duration) {
+ String message;
+ if (status == ExitStatus.CANCELLED) {
+ message = CompilerBundle.message("status.compilation.aborted");
+ }
+ else if (status == ExitStatus.UP_TO_DATE) {
+ message = CompilerBundle.message("status.all.up.to.date");
+ }
+ else {
+ if (status == ExitStatus.SUCCESS) {
+ message = warningCount > 0
+ ? CompilerBundle.message("status.compilation.completed.successfully.with.warnings", warningCount)
+ : CompilerBundle.message("status.compilation.completed.successfully");
+ }
+ else {
+ message = CompilerBundle.message("status.compilation.completed.successfully.with.warnings.and.errors", errorCount, warningCount);
+ }
+ message = message + " in " + Utils.formatDuration(duration);
+ }
+ return message;
+ }
+
+ private ExitStatus doCompile(final CompileContextEx context, boolean isRebuild, final boolean forceCompile, final boolean onlyCheckStatus) {
+ try {
+ if (isRebuild) {
+ deleteAll(context);
+ }
+ else if (forceCompile) {
+ if (myShouldClearOutputDirectory) {
+ clearAffectedOutputPathsIfPossible(context);
+ }
+ }
+ if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
+ if (LOG.isDebugEnabled()) {
+ logErrorMessages(context);
+ }
+ return ExitStatus.ERRORS;
+ }
+
+ if (!onlyCheckStatus) {
+ if (!executeCompileTasks(context, true)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Compilation cancelled");
+ }
+ return ExitStatus.CANCELLED;
+ }
+ }
+
+ if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
+ if (LOG.isDebugEnabled()) {
+ logErrorMessages(context);
+ }
+ return ExitStatus.ERRORS;
+ }
+
+ boolean needRecalcOutputDirs = false;
+ if (Registry.is(PROP_PERFORM_INITIAL_REFRESH) || !Boolean.valueOf(REFRESH_DONE_KEY.get(myProject, Boolean.FALSE))) {
+ REFRESH_DONE_KEY.set(myProject, Boolean.TRUE);
+ final long refreshStart = System.currentTimeMillis();
+
+ //need this to make sure the VFS is built
+ final List<VirtualFile> outputsToRefresh = new ArrayList<VirtualFile>();
+
+ final VirtualFile[] all = context.getAllOutputDirectories();
+
+ final ProgressIndicator progressIndicator = context.getProgressIndicator();
+
+ //final int totalCount = all.length + myGenerationCompilerModuleToOutputDirMap.size() * 2;
+ progressIndicator.pushState();
+ progressIndicator.setText("Inspecting output directories...");
+ try {
+ for (VirtualFile output : all) {
+ if (output.isValid()) {
+ walkChildren(output, context);
+ }
+ else {
+ needRecalcOutputDirs = true;
+ final File file = new File(output.getPath());
+ if (!file.exists()) {
+ final boolean created = file.mkdirs();
+ if (!created) {
+ context.addMessage(CompilerMessageCategory.ERROR, "Failed to create output directory " + file.getPath(), null, 0, 0);
+ return ExitStatus.ERRORS;
+ }
+ }
+ output = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file);
+ if (output == null) {
+ context.addMessage(CompilerMessageCategory.ERROR, "Failed to locate output directory " + file.getPath(), null, 0, 0);
+ return ExitStatus.ERRORS;
+ }
+ }
+ outputsToRefresh.add(output);
+ }
+ for (Pair<IntermediateOutputCompiler, Module> pair : myGenerationCompilerModuleToOutputDirMap.keySet()) {
+ final Pair<VirtualFile, VirtualFile> generated = myGenerationCompilerModuleToOutputDirMap.get(pair);
+ walkChildren(generated.getFirst(), context);
+ outputsToRefresh.add(generated.getFirst());
+ walkChildren(generated.getSecond(), context);
+ outputsToRefresh.add(generated.getSecond());
+ }
+
+ RefreshQueue.getInstance().refresh(false, true, null, outputsToRefresh);
+ if (progressIndicator.isCanceled()) {
+ return ExitStatus.CANCELLED;
+ }
+ }
+ finally {
+ progressIndicator.popState();
+ }
+
+ final long initialRefreshTime = System.currentTimeMillis() - refreshStart;
+ CompilerUtil.logDuration("Initial VFS refresh", initialRefreshTime);
+ }
+
+ //DumbService.getInstance(myProject).waitForSmartMode();
+ final Semaphore semaphore = new Semaphore();
+ semaphore.down();
+ DumbService.getInstance(myProject).runWhenSmart(new Runnable() {
+ public void run() {
+ semaphore.up();
+ }
+ });
+ while (!semaphore.waitFor(500)) {
+ if (context.getProgressIndicator().isCanceled()) {
+ return ExitStatus.CANCELLED;
+ }
+ }
+
+ if (needRecalcOutputDirs) {
+ context.recalculateOutputDirs();
+ }
+
+ boolean didSomething = false;
+
+ final CompilerManager compilerManager = CompilerManager.getInstance(myProject);
+ GenericCompilerRunner runner = new GenericCompilerRunner(context, isRebuild, onlyCheckStatus,
+ compilerManager.getCompilers(GenericCompiler.class, myCompilerFilter));
+ try {
+ didSomething |= generateSources(compilerManager, context, forceCompile, onlyCheckStatus);
+
+ didSomething |= invokeFileProcessingCompilers(compilerManager, context, SourceInstrumentingCompiler.class,
+ FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, forceCompile, true, onlyCheckStatus);
+
+ didSomething |= invokeFileProcessingCompilers(compilerManager, context, SourceProcessingCompiler.class,
+ FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, forceCompile, true, onlyCheckStatus);
+
+ final CompileScope intermediateSources = attachIntermediateOutputDirectories(new CompositeScope(CompileScope.EMPTY_ARRAY) {
+ @NotNull
+ public Module[] getAffectedModules() {
+ return context.getCompileScope().getAffectedModules();
+ }
+ }, SOURCE_PROCESSING_ONLY);
+ context.addScope(intermediateSources);
+
+ didSomething |= translate(context, compilerManager, forceCompile, isRebuild, onlyCheckStatus);
+
+ didSomething |= invokeFileProcessingCompilers(compilerManager, context, ClassInstrumentingCompiler.class,
+ FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, isRebuild, false, onlyCheckStatus);
+ didSomething |= runner.invokeCompilers(GenericCompiler.CompileOrderPlace.CLASS_INSTRUMENTING);
+
+ // explicitly passing forceCompile = false because in scopes that is narrower than ProjectScope it is impossible
+ // to understand whether the class to be processed is in scope or not. Otherwise compiler may process its items even if
+ // there were changes in completely independent files.
+ didSomething |= invokeFileProcessingCompilers(compilerManager, context, ClassPostProcessingCompiler.class,
+ FILE_PROCESSING_COMPILER_ADAPTER_FACTORY, isRebuild, false, onlyCheckStatus);
+ didSomething |= runner.invokeCompilers(GenericCompiler.CompileOrderPlace.CLASS_POST_PROCESSING);
+
+ didSomething |= invokeFileProcessingCompilers(compilerManager, context, PackagingCompiler.class,
+ FILE_PACKAGING_COMPILER_ADAPTER_FACTORY,
+ isRebuild, false, onlyCheckStatus);
+ didSomething |= runner.invokeCompilers(GenericCompiler.CompileOrderPlace.PACKAGING);
+
+ didSomething |= invokeFileProcessingCompilers(compilerManager, context, Validator.class, FILE_PROCESSING_COMPILER_ADAPTER_FACTORY,
+ forceCompile, true, onlyCheckStatus);
+ didSomething |= runner.invokeCompilers(GenericCompiler.CompileOrderPlace.VALIDATING);
+ }
+ catch (ExitException e) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(e);
+ logErrorMessages(context);
+ }
+ return e.getExitStatus();
+ }
+ finally {
+ // drop in case it has not been dropped yet.
+ dropDependencyCache(context);
+ final VirtualFile[] allOutputDirs = context.getAllOutputDirectories();
+
+ if (didSomething && GENERATE_CLASSPATH_INDEX) {
+ CompilerUtil.runInContext(context, "Generating classpath index...", new ThrowableRunnable<RuntimeException>(){
+ public void run() {
+ int count = 0;
+ for (VirtualFile file : allOutputDirs) {
+ context.getProgressIndicator().setFraction((double)++count / allOutputDirs.length);
+ createClasspathIndex(file);
+ }
+ }
+ });
+ }
+
+ }
+
+ if (!onlyCheckStatus) {
+ if (!executeCompileTasks(context, false)) {
+ return ExitStatus.CANCELLED;
+ }
+ final int constantSearchesCount = ChangedConstantsDependencyProcessor.getConstantSearchesCount(context);
+ LOG.debug("Constants searches: " + constantSearchesCount);
+ }
+
+ if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
+ if (LOG.isDebugEnabled()) {
+ logErrorMessages(context);
+ }
+ return ExitStatus.ERRORS;
+ }
+ if (!didSomething) {
+ return ExitStatus.UP_TO_DATE;
+ }
+ return ExitStatus.SUCCESS;
+ }
+ catch (ProcessCanceledException e) {
+ return ExitStatus.CANCELLED;
+ }
+ }
+
+ private void clearAffectedOutputPathsIfPossible(final CompileContextEx context) {
+ final List<File> scopeOutputs = new ReadAction<List<File>>() {
+ protected void run(final Result<List<File>> result) {
+ final MultiMap<File, Module> outputToModulesMap = new MultiMap<File, Module>();
+ for (Module module : ModuleManager.getInstance(myProject).getModules()) {
+ final CompilerModuleExtension compilerModuleExtension = CompilerModuleExtension.getInstance(module);
+ if (compilerModuleExtension == null) {
+ continue;
+ }
+ final String outputPathUrl = compilerModuleExtension.getCompilerOutputUrl();
+ if (outputPathUrl != null) {
+ final String path = VirtualFileManager.extractPath(outputPathUrl);
+ outputToModulesMap.putValue(new File(path), module);
+ }
+
+ final String outputPathForTestsUrl = compilerModuleExtension.getCompilerOutputUrlForTests();
+ if (outputPathForTestsUrl != null) {
+ final String path = VirtualFileManager.extractPath(outputPathForTestsUrl);
+ outputToModulesMap.putValue(new File(path), module);
+ }
+ }
+ final Set<Module> affectedModules = new HashSet<Module>(Arrays.asList(context.getCompileScope().getAffectedModules()));
+ List<File> scopeOutputs = new ArrayList<File>(affectedModules.size() * 2);
+ for (File output : outputToModulesMap.keySet()) {
+ if (affectedModules.containsAll(outputToModulesMap.get(output))) {
+ scopeOutputs.add(output);
+ }
+ }
+
+ final Set<Artifact> artifactsToBuild = ArtifactCompileScope.getArtifactsToBuild(myProject, context.getCompileScope(), true);
+ for (Artifact artifact : artifactsToBuild) {
+ final String outputFilePath = ((ArtifactImpl)artifact).getOutputDirectoryPathToCleanOnRebuild();
+ if (outputFilePath != null) {
+ scopeOutputs.add(new File(FileUtil.toSystemDependentName(outputFilePath)));
+ }
+ }
+ result.setResult(scopeOutputs);
+ }
+ }.execute().getResultObject();
+ if (scopeOutputs.size() > 0) {
+ CompilerUtil.runInContext(context, CompilerBundle.message("progress.clearing.output"), new ThrowableRunnable<RuntimeException>() {
+ public void run() {
+ CompilerUtil.clearOutputDirectories(scopeOutputs);
+ }
+ });
+ }
+ }
+
+ private static void logErrorMessages(final CompileContext context) {
+ final CompilerMessage[] errors = context.getMessages(CompilerMessageCategory.ERROR);
+ if (errors.length > 0) {
+ LOG.debug("Errors reported: ");
+ for (CompilerMessage error : errors) {
+ LOG.debug("\t" + error.getMessage());
+ }
+ }
+ }
+
+ private static void walkChildren(VirtualFile from, final CompileContext context) {
+ VfsUtilCore.visitChildrenRecursively(from, new VirtualFileVisitor() {
+ @Override
+ public boolean visitFile(@NotNull VirtualFile file) {
+ if (file.isDirectory()) {
+ context.getProgressIndicator().checkCanceled();
+ context.getProgressIndicator().setText2(file.getPresentableUrl());
+ }
+ return true;
+ }
+ });
+ }
+
+ private static void createClasspathIndex(final VirtualFile file) {
+ try {
+ BufferedWriter writer = new BufferedWriter(new FileWriter(new File(VfsUtilCore.virtualToIoFile(file), "classpath.index")));
+ try {
+ writeIndex(writer, file, file);
+ }
+ finally {
+ writer.close();
+ }
+ }
+ catch (IOException e) {
+ // Ignore. Failed to create optional classpath index
+ }
+ }
+
+ private static void writeIndex(final BufferedWriter writer, final VirtualFile root, final VirtualFile file) throws IOException {
+ VfsUtilCore.visitChildrenRecursively(file, new VirtualFileVisitor() {
+ @Override
+ public boolean visitFile(@NotNull VirtualFile file) {
+ try {
+ writer.write(VfsUtilCore.getRelativePath(file, root, '/'));
+ writer.write('\n');
+ return true;
+ }
+ catch (IOException e) {
+ throw new VisitorException(e);
+ }
+ }
+ }, IOException.class);
+ }
+
+ private static void dropDependencyCache(final CompileContextEx context) {
+ CompilerUtil.runInContext(context, CompilerBundle.message("progress.saving.caches"), new ThrowableRunnable<RuntimeException>(){
+ public void run() {
+ context.getDependencyCache().resetState();
+ }
+ });
+ }
+
+ private boolean generateSources(final CompilerManager compilerManager,
+ CompileContextEx context,
+ final boolean forceCompile,
+ final boolean onlyCheckStatus) throws ExitException {
+ boolean didSomething = false;
+
+ final SourceGeneratingCompiler[] sourceGenerators = compilerManager.getCompilers(SourceGeneratingCompiler.class, myCompilerFilter);
+ for (final SourceGeneratingCompiler sourceGenerator : sourceGenerators) {
+ if (context.getProgressIndicator().isCanceled()) {
+ throw new ExitException(ExitStatus.CANCELLED);
+ }
+
+ final boolean generatedSomething = generateOutput(context, sourceGenerator, forceCompile, onlyCheckStatus);
+
+ if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
+ throw new ExitException(ExitStatus.ERRORS);
+ }
+ didSomething |= generatedSomething;
+ }
+ return didSomething;
+ }
+
+ private boolean translate(final CompileContextEx context,
+ final CompilerManager compilerManager,
+ final boolean forceCompile,
+ boolean isRebuild,
+ final boolean onlyCheckStatus) throws ExitException {
+
+ boolean didSomething = false;
+
+ final TranslatingCompiler[] translators = compilerManager.getCompilers(TranslatingCompiler.class, myCompilerFilter);
+
+
+ final List<Chunk<Module>> sortedChunks = Collections.unmodifiableList(ApplicationManager.getApplication().runReadAction(new Computable<List<Chunk<Module>>>() {
+ public List<Chunk<Module>> compute() {
+ final ModuleManager moduleManager = ModuleManager.getInstance(myProject);
+ return ModuleCompilerUtil.getSortedModuleChunks(myProject, Arrays.asList(moduleManager.getModules()));
+ }
+ }));
+
+ final DumbService dumbService = DumbService.getInstance(myProject);
+ try {
+ final Set<Module> processedModules = new HashSet<Module>();
+ VirtualFile[] snapshot = null;
+ final Map<Chunk<Module>, Collection<VirtualFile>> chunkMap = new HashMap<Chunk<Module>, Collection<VirtualFile>>();
+ int total = 0;
+ int processed = 0;
+ for (final Chunk<Module> currentChunk : sortedChunks) {
+ final TranslatorsOutputSink sink = new TranslatorsOutputSink(context, translators);
+ final Set<FileType> generatedTypes = new HashSet<FileType>();
+ Collection<VirtualFile> chunkFiles = chunkMap.get(currentChunk);
+ final Set<VirtualFile> filesToRecompile = new HashSet<VirtualFile>();
+ final Set<VirtualFile> allDependent = new HashSet<VirtualFile>();
+ try {
+ int round = 0;
+ boolean compiledSomethingForThisChunk = false;
+ Collection<VirtualFile> dependentFiles = Collections.emptyList();
+ final Function<Pair<int[], Set<VirtualFile>>, Pair<int[], Set<VirtualFile>>> dependencyFilter = new DependentClassesCumulativeFilter();
+
+ do {
+ for (int currentCompiler = 0, translatorsLength = translators.length; currentCompiler < translatorsLength; currentCompiler++) {
+ sink.setCurrentCompilerIndex(currentCompiler);
+ final TranslatingCompiler compiler = translators[currentCompiler];
+ if (context.getProgressIndicator().isCanceled()) {
+ throw new ExitException(ExitStatus.CANCELLED);
+ }
+
+ dumbService.waitForSmartMode();
+
+ if (snapshot == null || ContainerUtil.intersects(generatedTypes, compilerManager.getRegisteredInputTypes(compiler))) {
+ // rescan snapshot if previously generated files may influence the input of this compiler
+ final Set<VirtualFile> prevSnapshot = round > 0 && snapshot != null? new HashSet<VirtualFile>(Arrays.asList(snapshot)) : Collections.<VirtualFile>emptySet();
+ snapshot = ApplicationManager.getApplication().runReadAction(new Computable<VirtualFile[]>() {
+ public VirtualFile[] compute() {
+ return context.getCompileScope().getFiles(null, true);
+ }
+ });
+ recalculateChunkToFilesMap(context, sortedChunks, snapshot, chunkMap);
+ if (round == 0) {
+ chunkFiles = chunkMap.get(currentChunk);
+ }
+ else {
+ final Set<VirtualFile> newFiles = new HashSet<VirtualFile>(chunkMap.get(currentChunk));
+ newFiles.removeAll(prevSnapshot);
+ newFiles.removeAll(chunkFiles);
+ if (!newFiles.isEmpty()) {
+ final ArrayList<VirtualFile> merged = new ArrayList<VirtualFile>(chunkFiles.size() + newFiles.size());
+ merged.addAll(chunkFiles);
+ merged.addAll(newFiles);
+ chunkFiles = merged;
+ }
+ }
+ total = snapshot.length * translatorsLength;
+ }
+
+ final CompileContextEx _context;
+ if (compiler instanceof IntermediateOutputCompiler) {
+ // wrap compile context so that output goes into intermediate directories
+ final IntermediateOutputCompiler _compiler = (IntermediateOutputCompiler)compiler;
+ _context = new CompileContextExProxy(context) {
+ public VirtualFile getModuleOutputDirectory(final Module module) {
+ return getGenerationOutputDir(_compiler, module, false);
+ }
+
+ public VirtualFile getModuleOutputDirectoryForTests(final Module module) {
+ return getGenerationOutputDir(_compiler, module, true);
+ }
+ };
+ }
+ else {
+ _context = context;
+ }
+ final boolean compiledSomething =
+ compileSources(_context, currentChunk, compiler, chunkFiles, round == 0? forceCompile : true, isRebuild, onlyCheckStatus, sink);
+
+ processed += chunkFiles.size();
+ _context.getProgressIndicator().setFraction(((double)processed) / total);
+
+ if (compiledSomething) {
+ generatedTypes.addAll(compilerManager.getRegisteredOutputTypes(compiler));
+ }
+
+ didSomething |= compiledSomething;
+ compiledSomethingForThisChunk |= didSomething;
+
+ if (_context.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
+ break; // break the loop over compilers
+ }
+ }
+
+ final boolean hasUnprocessedTraverseRoots = context.getDependencyCache().hasUnprocessedTraverseRoots();
+ if (!isRebuild && (compiledSomethingForThisChunk || hasUnprocessedTraverseRoots)) {
+ final Set<VirtualFile> compiledWithErrors = CacheUtils.getFilesCompiledWithErrors(context);
+ filesToRecompile.removeAll(sink.getCompiledSources());
+ filesToRecompile.addAll(compiledWithErrors);
+
+ dependentFiles = CacheUtils.findDependentFiles(context, compiledWithErrors, dependencyFilter);
+ if (!processedModules.isEmpty()) {
+ for (Iterator<VirtualFile> it = dependentFiles.iterator(); it.hasNext();) {
+ final VirtualFile next = it.next();
+ final Module module = context.getModuleByFile(next);
+ if (module != null && processedModules.contains(module)) {
+ it.remove();
+ }
+ }
+ }
+
+ if (ourDebugMode) {
+ if (!dependentFiles.isEmpty()) {
+ for (VirtualFile dependentFile : dependentFiles) {
+ System.out.println("FOUND TO RECOMPILE: " + dependentFile.getPresentableUrl());
+ }
+ }
+ else {
+ System.out.println("NO FILES TO RECOMPILE");
+ }
+ }
+
+ if (!dependentFiles.isEmpty()) {
+ filesToRecompile.addAll(dependentFiles);
+ allDependent.addAll(dependentFiles);
+ if (context.getProgressIndicator().isCanceled() || context.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
+ break;
+ }
+ final List<VirtualFile> filesInScope = getFilesInScope(context, currentChunk, dependentFiles);
+ if (filesInScope.isEmpty()) {
+ break;
+ }
+ context.getDependencyCache().clearTraverseRoots();
+ chunkFiles = filesInScope;
+ total += chunkFiles.size() * translators.length;
+ }
+
+ didSomething |= (hasUnprocessedTraverseRoots != context.getDependencyCache().hasUnprocessedTraverseRoots());
+ }
+
+ round++;
+ }
+ while (!dependentFiles.isEmpty() && context.getMessageCount(CompilerMessageCategory.ERROR) == 0);
+
+ if (CompilerConfiguration.MAKE_ENABLED) {
+ if (!context.getProgressIndicator().isCanceled()) {
+ // when cancelled pretend nothing was compiled and next compile will compile everything from the scratch
+ final ProgressIndicator indicator = context.getProgressIndicator();
+ final DependencyCache cache = context.getDependencyCache();
+
+ indicator.pushState();
+ indicator.setText(CompilerBundle.message("progress.updating.caches"));
+ indicator.setText2("");
+
+ cache.update();
+
+ indicator.setText(CompilerBundle.message("progress.saving.caches"));
+ cache.resetState();
+ processedModules.addAll(currentChunk.getNodes());
+ indicator.popState();
+ }
+ }
+
+ if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
+ throw new ExitException(ExitStatus.ERRORS);
+ }
+
+ }
+ catch (CacheCorruptedException e) {
+ LOG.info(e);
+ context.requestRebuildNextTime(e.getMessage());
+ }
+ finally {
+ final int errorCount = context.getMessageCount(CompilerMessageCategory.ERROR);
+ if (errorCount != 0) {
+ filesToRecompile.addAll(allDependent);
+ }
+ if (filesToRecompile.size() > 0) {
+ sink.add(null, Collections.<TranslatingCompiler.OutputItem>emptyList(), VfsUtilCore.toVirtualFileArray(filesToRecompile));
+ }
+ if (errorCount == 0) {
+ // perform update only if there were no errors, so it is guaranteed that the file was processd by all neccesary compilers
+ sink.flushPostponedItems();
+ }
+ }
+ }
+ }
+ catch (ProcessCanceledException e) {
+ ProgressManager.getInstance().executeNonCancelableSection(new Runnable() {
+ public void run() {
+ try {
+ final Collection<VirtualFile> deps = CacheUtils.findDependentFiles(context, Collections.<VirtualFile>emptySet(), null);
+ if (deps.size() > 0) {
+ TranslatingCompilerFilesMonitor.getInstance().update(context, null, Collections.<TranslatingCompiler.OutputItem>emptyList(),
+ VfsUtilCore.toVirtualFileArray(deps));
+ }
+ }
+ catch (IOException ignored) {
+ LOG.info(ignored);
+ }
+ catch (CacheCorruptedException ignored) {
+ LOG.info(ignored);
+ }
+ catch (ExitException e1) {
+ LOG.info(e1);
+ }
+ }
+ });
+ throw e;
+ }
+ finally {
+ dropDependencyCache(context);
+ if (didSomething) {
+ TranslatingCompilerFilesMonitor.getInstance().updateOutputRootsLayout(myProject);
+ }
+ }
+ return didSomething;
+ }
+
+ private static List<VirtualFile> getFilesInScope(final CompileContextEx context, final Chunk<Module> chunk, final Collection<VirtualFile> files) {
+ final List<VirtualFile> filesInScope = new ArrayList<VirtualFile>(files.size());
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ for (VirtualFile file : files) {
+ if (context.getCompileScope().belongs(file.getUrl())) {
+ final Module module = context.getModuleByFile(file);
+ if (chunk.getNodes().contains(module)) {
+ filesInScope.add(file);
+ }
+ }
+ }
+ }
+ });
+ return filesInScope;
+ }
+
+ private static void recalculateChunkToFilesMap(CompileContextEx context, List<Chunk<Module>> allChunks, VirtualFile[] snapshot, Map<Chunk<Module>, Collection<VirtualFile>> chunkMap) {
+ final Map<Module, List<VirtualFile>> moduleToFilesMap = CompilerUtil.buildModuleToFilesMap(context, snapshot);
+ for (Chunk<Module> moduleChunk : allChunks) {
+ List<VirtualFile> files = Collections.emptyList();
+ for (Module module : moduleChunk.getNodes()) {
+ final List<VirtualFile> moduleFiles = moduleToFilesMap.get(module);
+ if (moduleFiles != null) {
+ files = ContainerUtil.concat(files, moduleFiles);
+ }
+ }
+ chunkMap.put(moduleChunk, files);
+ }
+ }
+
+ private interface FileProcessingCompilerAdapterFactory {
+ FileProcessingCompilerAdapter create(CompileContext context, FileProcessingCompiler compiler);
+ }
+
+ private boolean invokeFileProcessingCompilers(final CompilerManager compilerManager,
+ CompileContextEx context,
+ Class<? extends FileProcessingCompiler> fileProcessingCompilerClass,
+ FileProcessingCompilerAdapterFactory factory,
+ boolean forceCompile,
+ final boolean checkScope,
+ final boolean onlyCheckStatus) throws ExitException {
+ boolean didSomething = false;
+ final FileProcessingCompiler[] compilers = compilerManager.getCompilers(fileProcessingCompilerClass, myCompilerFilter);
+ if (compilers.length > 0) {
+ try {
+ CacheDeferredUpdater cacheUpdater = new CacheDeferredUpdater();
+ try {
+ for (final FileProcessingCompiler compiler : compilers) {
+ if (context.getProgressIndicator().isCanceled()) {
+ throw new ExitException(ExitStatus.CANCELLED);
+ }
+
+ CompileContextEx _context = context;
+ if (compiler instanceof IntermediateOutputCompiler) {
+ final IntermediateOutputCompiler _compiler = (IntermediateOutputCompiler)compiler;
+ _context = new CompileContextExProxy(context) {
+ public VirtualFile getModuleOutputDirectory(final Module module) {
+ return getGenerationOutputDir(_compiler, module, false);
+ }
+
+ public VirtualFile getModuleOutputDirectoryForTests(final Module module) {
+ return getGenerationOutputDir(_compiler, module, true);
+ }
+ };
+ }
+
+ final boolean processedSomething = processFiles(factory.create(_context, compiler), forceCompile, checkScope, onlyCheckStatus, cacheUpdater);
+
+ if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
+ throw new ExitException(ExitStatus.ERRORS);
+ }
+
+ didSomething |= processedSomething;
+ }
+ }
+ finally {
+ cacheUpdater.doUpdate();
+ }
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ context.requestRebuildNextTime(e.getMessage());
+ throw new ExitException(ExitStatus.ERRORS);
+ }
+ catch (ProcessCanceledException e) {
+ throw e;
+ }
+ catch (ExitException e) {
+ throw e;
+ }
+ catch (Exception e) {
+ context.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("compiler.error.exception", e.getMessage()), null, -1, -1);
+ LOG.error(e);
+ }
+ }
+
+ return didSomething;
+ }
+
+ private static Map<Module, Set<GeneratingCompiler.GenerationItem>> buildModuleToGenerationItemMap(GeneratingCompiler.GenerationItem[] items) {
+ final Map<Module, Set<GeneratingCompiler.GenerationItem>> map = new HashMap<Module, Set<GeneratingCompiler.GenerationItem>>();
+ for (GeneratingCompiler.GenerationItem item : items) {
+ Module module = item.getModule();
+ LOG.assertTrue(module != null);
+ Set<GeneratingCompiler.GenerationItem> itemSet = map.get(module);
+ if (itemSet == null) {
+ itemSet = new HashSet<GeneratingCompiler.GenerationItem>();
+ map.put(module, itemSet);
+ }
+ itemSet.add(item);
+ }
+ return map;
+ }
+
+ private void deleteAll(final CompileContextEx context) {
+ CompilerUtil.runInContext(context, CompilerBundle.message("progress.clearing.output"), new ThrowableRunnable<RuntimeException>() {
+ public void run() {
+ final boolean isTestMode = ApplicationManager.getApplication().isUnitTestMode();
+ final VirtualFile[] allSources = context.getProjectCompileScope().getFiles(null, true);
+ if (myShouldClearOutputDirectory) {
+ CompilerUtil.clearOutputDirectories(myAllOutputDirectories);
+ }
+ else { // refresh is still required
+ try {
+ for (final Compiler compiler : CompilerManager.getInstance(myProject).getCompilers(Compiler.class)) {
+ try {
+ if (compiler instanceof GeneratingCompiler) {
+ final StateCache<ValidityState> cache = getGeneratingCompilerCache((GeneratingCompiler)compiler);
+ final Iterator<String> urlIterator = cache.getUrlsIterator();
+ while (urlIterator.hasNext()) {
+ context.getProgressIndicator().checkCanceled();
+ deleteFile(new File(VirtualFileManager.extractPath(urlIterator.next())));
+ }
+ }
+ else if (compiler instanceof TranslatingCompiler) {
+ final ArrayList<Trinity<File, String, Boolean>> toDelete = new ArrayList<Trinity<File, String, Boolean>>();
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ TranslatingCompilerFilesMonitor.getInstance().collectFiles(
+ context,
+ (TranslatingCompiler)compiler, Arrays.<VirtualFile>asList(allSources).iterator(),
+ true /*pass true to make sure that every source in scope file is processed*/,
+ false /*important! should pass false to enable collection of files to delete*/,
+ new ArrayList<VirtualFile>(),
+ toDelete
+ );
+ }
+ });
+ for (Trinity<File, String, Boolean> trinity : toDelete) {
+ context.getProgressIndicator().checkCanceled();
+ final File file = trinity.getFirst();
+ deleteFile(file);
+ if (isTestMode) {
+ CompilerManagerImpl.addDeletedPath(file.getPath());
+ }
+ }
+ }
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ }
+ }
+ pruneEmptyDirectories(context.getProgressIndicator(), myAllOutputDirectories); // to avoid too much files deleted events
+ }
+ finally {
+ CompilerUtil.refreshIODirectories(myAllOutputDirectories);
+ }
+ }
+ dropScopesCaches();
+
+ clearCompilerSystemDirectory(context);
+ }
+ });
+ }
+
+ private void dropScopesCaches() {
+ // hack to be sure the classpath will include the output directories
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ ((ProjectRootManagerEx)ProjectRootManager.getInstance(myProject)).clearScopesCachesForModules();
+ }
+ });
+ }
+
+ private static void pruneEmptyDirectories(ProgressIndicator progress, final Set<File> directories) {
+ for (File directory : directories) {
+ doPrune(progress, directory, directories);
+ }
+ }
+
+ private static boolean doPrune(ProgressIndicator progress, final File directory, final Set<File> outPutDirectories) {
+ progress.checkCanceled();
+ final File[] files = directory.listFiles();
+ boolean isEmpty = true;
+ if (files != null) {
+ for (File file : files) {
+ if (!outPutDirectories.contains(file)) {
+ if (doPrune(progress, file, outPutDirectories)) {
+ deleteFile(file);
+ }
+ else {
+ isEmpty = false;
+ }
+ }
+ else {
+ isEmpty = false;
+ }
+ }
+ }
+ else {
+ isEmpty = false;
+ }
+
+ return isEmpty;
+ }
+
+ private Set<File> getAllOutputDirectories(CompileContext context) {
+ final Set<File> outputDirs = new OrderedSet<File>();
+ final Module[] modules = ModuleManager.getInstance(myProject).getModules();
+ for (final String path : CompilerPathsEx.getOutputPaths(modules)) {
+ outputDirs.add(new File(path));
+ }
+ for (Pair<IntermediateOutputCompiler, Module> pair : myGenerationCompilerModuleToOutputDirMap.keySet()) {
+ outputDirs.add(new File(CompilerPaths.getGenerationOutputPath(pair.getFirst(), pair.getSecond(), false)));
+ outputDirs.add(new File(CompilerPaths.getGenerationOutputPath(pair.getFirst(), pair.getSecond(), true)));
+ }
+ final CompilerConfiguration config = CompilerConfiguration.getInstance(myProject);
+ if (context.isAnnotationProcessorsEnabled()) {
+ for (Module module : modules) {
+ if (config.getAnnotationProcessingConfiguration(module).isEnabled()) {
+ final String path = CompilerPaths.getAnnotationProcessorsGenerationPath(module);
+ if (path != null) {
+ outputDirs.add(new File(path));
+ }
+ }
+ }
+ }
+ for (Artifact artifact : ArtifactManager.getInstance(myProject).getArtifacts()) {
+ final String path = ((ArtifactImpl)artifact).getOutputDirectoryPathToCleanOnRebuild();
+ if (path != null) {
+ outputDirs.add(new File(FileUtil.toSystemDependentName(path)));
+ }
+ }
+ return outputDirs;
+ }
+
+ private void clearCompilerSystemDirectory(final CompileContextEx context) {
+ CompilerCacheManager.getInstance(myProject).clearCaches(context);
+ FileUtil.delete(CompilerPathsEx.getZipStoreDirectory(myProject));
+ dropDependencyCache(context);
+
+ for (Pair<IntermediateOutputCompiler, Module> pair : myGenerationCompilerModuleToOutputDirMap.keySet()) {
+ final File[] outputs = {
+ new File(CompilerPaths.getGenerationOutputPath(pair.getFirst(), pair.getSecond(), false)),
+ new File(CompilerPaths.getGenerationOutputPath(pair.getFirst(), pair.getSecond(), true))
+ };
+ for (File output : outputs) {
+ final File[] files = output.listFiles();
+ if (files != null) {
+ for (final File file : files) {
+ final boolean deleteOk = deleteFile(file);
+ if (!deleteOk) {
+ context.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("compiler.error.failed.to.delete", file.getPath()),
+ null, -1, -1);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * @param file a file to delete
+ * @return true if and only if the file existed and was successfully deleted
+ * Note: the behaviour is different from FileUtil.delete() which returns true if the file absent on the disk
+ */
+ private static boolean deleteFile(final File file) {
+ File[] files = file.listFiles();
+ if (files != null) {
+ for (File file1 : files) {
+ deleteFile(file1);
+ }
+ }
+
+ for (int i = 0; i < 10; i++){
+ if (file.delete()) {
+ return true;
+ }
+ if (!file.exists()) {
+ return false;
+ }
+ try {
+ Thread.sleep(50);
+ }
+ catch (InterruptedException ignored) {
+ }
+ }
+ return false;
+ }
+
+ private VirtualFile getGenerationOutputDir(final IntermediateOutputCompiler compiler, final Module module, final boolean forTestSources) {
+ final Pair<VirtualFile, VirtualFile> outputs =
+ myGenerationCompilerModuleToOutputDirMap.get(new Pair<IntermediateOutputCompiler, Module>(compiler, module));
+ return forTestSources? outputs.getSecond() : outputs.getFirst();
+ }
+
+ private boolean generateOutput(final CompileContextEx context,
+ final GeneratingCompiler compiler,
+ final boolean forceGenerate,
+ final boolean onlyCheckStatus) throws ExitException {
+ final GeneratingCompiler.GenerationItem[] allItems = compiler.getGenerationItems(context);
+ final List<GeneratingCompiler.GenerationItem> toGenerate = new ArrayList<GeneratingCompiler.GenerationItem>();
+ final List<File> filesToRefresh = new ArrayList<File>();
+ final List<File> generatedFiles = new ArrayList<File>();
+ final List<Module> affectedModules = new ArrayList<Module>();
+ try {
+ final StateCache<ValidityState> cache = getGeneratingCompilerCache(compiler);
+ final Set<String> pathsToRemove = new HashSet<String>(cache.getUrls());
+
+ final Map<GeneratingCompiler.GenerationItem, String> itemToOutputPathMap = new HashMap<GeneratingCompiler.GenerationItem, String>();
+ final IOException[] ex = {null};
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ for (final GeneratingCompiler.GenerationItem item : allItems) {
+ final Module itemModule = item.getModule();
+ final String outputDirPath = CompilerPaths.getGenerationOutputPath(compiler, itemModule, item.isTestSource());
+ final String outputPath = outputDirPath + "/" + item.getPath();
+ itemToOutputPathMap.put(item, outputPath);
+
+ try {
+ final ValidityState savedState = cache.getState(outputPath);
+
+ if (forceGenerate || savedState == null || !savedState.equalsTo(item.getValidityState())) {
+ final String outputPathUrl = VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, outputPath);
+ if (context.getCompileScope().belongs(outputPathUrl)) {
+ toGenerate.add(item);
+ }
+ else {
+ pathsToRemove.remove(outputPath);
+ }
+ }
+ else {
+ pathsToRemove.remove(outputPath);
+ }
+ }
+ catch (IOException e) {
+ ex[0] = e;
+ }
+ }
+ }
+ });
+ if (ex[0] != null) {
+ throw ex[0];
+ }
+
+ if (onlyCheckStatus) {
+ if (toGenerate.isEmpty() && pathsToRemove.isEmpty()) {
+ return false;
+ }
+ if (LOG.isDebugEnabled()) {
+ if (!toGenerate.isEmpty()) {
+ LOG.debug("Found items to generate, compiler " + compiler.getDescription());
+ }
+ if (!pathsToRemove.isEmpty()) {
+ LOG.debug("Found paths to remove, compiler " + compiler.getDescription());
+ }
+ }
+ throw new ExitException(ExitStatus.CANCELLED);
+ }
+
+ if (!pathsToRemove.isEmpty()) {
+ CompilerUtil.runInContext(context, CompilerBundle.message("progress.synchronizing.output.directory"), new ThrowableRunnable<IOException>(){
+ public void run() throws IOException {
+ for (final String path : pathsToRemove) {
+ final File file = new File(path);
+ final boolean deleted = deleteFile(file);
+ if (deleted) {
+ cache.remove(path);
+ filesToRefresh.add(file);
+ }
+ }
+ }
+ });
+ }
+
+ final Map<Module, Set<GeneratingCompiler.GenerationItem>> moduleToItemMap =
+ buildModuleToGenerationItemMap(toGenerate.toArray(new GeneratingCompiler.GenerationItem[toGenerate.size()]));
+ List<Module> modules = new ArrayList<Module>(moduleToItemMap.size());
+ for (final Module module : moduleToItemMap.keySet()) {
+ modules.add(module);
+ }
+ ModuleCompilerUtil.sortModules(myProject, modules);
+
+ for (final Module module : modules) {
+ CompilerUtil.runInContext(context, "Generating output from "+compiler.getDescription(),new ThrowableRunnable<IOException>(){
+ public void run() throws IOException {
+ final Set<GeneratingCompiler.GenerationItem> items = moduleToItemMap.get(module);
+ if (items != null && !items.isEmpty()) {
+ final GeneratingCompiler.GenerationItem[][] productionAndTestItems = splitGenerationItems(items);
+ for (GeneratingCompiler.GenerationItem[] _items : productionAndTestItems) {
+ if (_items.length == 0) continue;
+ final VirtualFile outputDir = getGenerationOutputDir(compiler, module, _items[0].isTestSource());
+ final GeneratingCompiler.GenerationItem[] successfullyGenerated = compiler.generate(context, _items, outputDir);
+
+ CompilerUtil.runInContext(context, CompilerBundle.message("progress.updating.caches"), new ThrowableRunnable<IOException>() {
+ public void run() throws IOException {
+ if (successfullyGenerated.length > 0) {
+ affectedModules.add(module);
+ }
+ for (final GeneratingCompiler.GenerationItem item : successfullyGenerated) {
+ final String fullOutputPath = itemToOutputPathMap.get(item);
+ cache.update(fullOutputPath, item.getValidityState());
+ final File file = new File(fullOutputPath);
+ filesToRefresh.add(file);
+ generatedFiles.add(file);
+ context.getProgressIndicator().setText2(file.getPath());
+ }
+ }
+ });
+ }
+ }
+ }
+ });
+ }
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ context.requestRebuildNextTime(e.getMessage());
+ throw new ExitException(ExitStatus.ERRORS);
+ }
+ finally {
+ CompilerUtil.refreshIOFiles(filesToRefresh);
+ if (!generatedFiles.isEmpty()) {
+ DumbService.getInstance(myProject).waitForSmartMode();
+ List<VirtualFile> vFiles = ApplicationManager.getApplication().runReadAction(new Computable<List<VirtualFile>>() {
+ public List<VirtualFile> compute() {
+ final ArrayList<VirtualFile> vFiles = new ArrayList<VirtualFile>(generatedFiles.size());
+ for (File generatedFile : generatedFiles) {
+ final VirtualFile vFile = LocalFileSystem.getInstance().findFileByIoFile(generatedFile);
+ if (vFile != null) {
+ vFiles.add(vFile);
+ }
+ }
+ return vFiles;
+ }
+ });
+ if (forceGenerate) {
+ context.addScope(new FileSetCompileScope(vFiles, affectedModules.toArray(new Module[affectedModules.size()])));
+ }
+ context.markGenerated(vFiles);
+ }
+ }
+ return !toGenerate.isEmpty() || !filesToRefresh.isEmpty();
+ }
+
+ private static GeneratingCompiler.GenerationItem[][] splitGenerationItems(final Set<GeneratingCompiler.GenerationItem> items) {
+ final List<GeneratingCompiler.GenerationItem> production = new ArrayList<GeneratingCompiler.GenerationItem>();
+ final List<GeneratingCompiler.GenerationItem> tests = new ArrayList<GeneratingCompiler.GenerationItem>();
+ for (GeneratingCompiler.GenerationItem item : items) {
+ if (item.isTestSource()) {
+ tests.add(item);
+ }
+ else {
+ production.add(item);
+ }
+ }
+ return new GeneratingCompiler.GenerationItem[][]{
+ production.toArray(new GeneratingCompiler.GenerationItem[production.size()]),
+ tests.toArray(new GeneratingCompiler.GenerationItem[tests.size()])
+ };
+ }
+
+ private boolean compileSources(final CompileContextEx context,
+ final Chunk<Module> moduleChunk,
+ final TranslatingCompiler compiler,
+ final Collection<VirtualFile> srcSnapshot,
+ final boolean forceCompile,
+ final boolean isRebuild,
+ final boolean onlyCheckStatus,
+ TranslatingCompiler.OutputSink sink) throws ExitException {
+
+ final Set<VirtualFile> toCompile = new HashSet<VirtualFile>();
+ final List<Trinity<File, String, Boolean>> toDelete = new ArrayList<Trinity<File, String, Boolean>>();
+ context.getProgressIndicator().pushState();
+
+ final boolean[] wereFilesDeleted = {false};
+ try {
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ TranslatingCompilerFilesMonitor.getInstance().collectFiles(
+ context, compiler, srcSnapshot.iterator(), forceCompile, isRebuild, toCompile, toDelete
+ );
+ }
+ });
+
+ if (onlyCheckStatus) {
+ if (toDelete.isEmpty() && toCompile.isEmpty()) {
+ return false;
+ }
+ if (LOG.isDebugEnabled() || ourDebugMode) {
+ if (!toDelete.isEmpty()) {
+ final StringBuilder message = new StringBuilder();
+ message.append("Found items to delete, compiler ").append(compiler.getDescription());
+ for (Trinity<File, String, Boolean> trinity : toDelete) {
+ message.append("\n").append(trinity.getFirst());
+ }
+ LOG.debug(message.toString());
+ if (ourDebugMode) {
+ System.out.println(message);
+ }
+ }
+ if (!toCompile.isEmpty()) {
+ final String message = "Found items to compile, compiler " + compiler.getDescription();
+ LOG.debug(message);
+ if (ourDebugMode) {
+ System.out.println(message);
+ }
+ }
+ }
+ throw new ExitException(ExitStatus.CANCELLED);
+ }
+
+ if (!toDelete.isEmpty()) {
+ try {
+ wereFilesDeleted[0] = syncOutputDir(context, toDelete);
+ }
+ catch (CacheCorruptedException e) {
+ LOG.info(e);
+ context.requestRebuildNextTime(e.getMessage());
+ }
+ }
+
+ if ((wereFilesDeleted[0] || !toCompile.isEmpty()) && context.getMessageCount(CompilerMessageCategory.ERROR) == 0) {
+ compiler.compile(context, moduleChunk, VfsUtilCore.toVirtualFileArray(toCompile), sink);
+ }
+ }
+ finally {
+ context.getProgressIndicator().popState();
+ }
+ return !toCompile.isEmpty() || wereFilesDeleted[0];
+ }
+
+ private static boolean syncOutputDir(final CompileContextEx context, final Collection<Trinity<File, String, Boolean>> toDelete) throws CacheCorruptedException {
+ final DependencyCache dependencyCache = context.getDependencyCache();
+ final boolean isTestMode = ApplicationManager.getApplication().isUnitTestMode();
+
+ final List<File> filesToRefresh = new ArrayList<File>();
+ final boolean[] wereFilesDeleted = {false};
+ CompilerUtil.runInContext(context, CompilerBundle.message("progress.synchronizing.output.directory"), new ThrowableRunnable<CacheCorruptedException>(){
+ public void run() throws CacheCorruptedException {
+ final long start = System.currentTimeMillis();
+ try {
+ for (final Trinity<File, String, Boolean> trinity : toDelete) {
+ final File outputPath = trinity.getFirst();
+ context.getProgressIndicator().checkCanceled();
+ context.getProgressIndicator().setText2(outputPath.getPath());
+ filesToRefresh.add(outputPath);
+ if (isTestMode) {
+ LOG.assertTrue(outputPath.exists());
+ }
+ if (!deleteFile(outputPath)) {
+ if (isTestMode) {
+ if (outputPath.exists()) {
+ LOG.error("Was not able to delete output file: " + outputPath.getPath());
+ }
+ else {
+ CompilerManagerImpl.addDeletedPath(outputPath.getPath());
+ }
+ }
+ continue;
+ }
+ wereFilesDeleted[0] = true;
+
+ // update zip here
+ //final String outputDir = myOutputFinder.lookupOutputPath(outputPath);
+ //if (outputDir != null) {
+ // try {
+ // context.updateZippedOuput(outputDir, FileUtil.toSystemIndependentName(outputPath.getPath()).substring(outputDir.length() + 1));
+ // }
+ // catch (IOException e) {
+ // LOG.info(e);
+ // }
+ //}
+
+ final String className = trinity.getSecond();
+ if (className != null) {
+ final int id = dependencyCache.getSymbolTable().getId(className);
+ dependencyCache.addTraverseRoot(id);
+ final boolean sourcePresent = trinity.getThird().booleanValue();
+ if (!sourcePresent) {
+ dependencyCache.markSourceRemoved(id);
+ }
+ }
+ if (isTestMode) {
+ CompilerManagerImpl.addDeletedPath(outputPath.getPath());
+ }
+ }
+ }
+ finally {
+ CompilerUtil.logDuration("Sync output directory", System.currentTimeMillis() - start);
+ CompilerUtil.refreshIOFiles(filesToRefresh);
+ }
+ }
+ });
+ return wereFilesDeleted[0];
+ }
+
+ // [mike] performance optimization - this method is accessed > 15,000 times in Aurora
+ private String getModuleOutputPath(final Module module, boolean inTestSourceContent) {
+ final Map<Module, String> map = inTestSourceContent ? myModuleTestOutputPaths : myModuleOutputPaths;
+ String path = map.get(module);
+ if (path == null) {
+ path = CompilerPaths.getModuleOutputPath(module, inTestSourceContent);
+ map.put(module, path);
+ }
+
+ return path;
+ }
+
+ private boolean processFiles(final FileProcessingCompilerAdapter adapter,
+ final boolean forceCompile,
+ final boolean checkScope,
+ final boolean onlyCheckStatus, final CacheDeferredUpdater cacheUpdater) throws ExitException, IOException {
+ final CompileContextEx context = (CompileContextEx)adapter.getCompileContext();
+ final FileProcessingCompilerStateCache cache = getFileProcessingCompilerCache(adapter.getCompiler());
+ final FileProcessingCompiler.ProcessingItem[] items = adapter.getProcessingItems();
+ if (context.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
+ return false;
+ }
+ if (LOG.isDebugEnabled() && items.length > 0) {
+ LOG.debug("Start processing files by " + adapter.getCompiler().getDescription());
+ }
+ final CompileScope scope = context.getCompileScope();
+ final List<FileProcessingCompiler.ProcessingItem> toProcess = new ArrayList<FileProcessingCompiler.ProcessingItem>();
+ final Set<String> allUrls = new HashSet<String>();
+ final IOException[] ex = {null};
+ DumbService.getInstance(myProject).waitForSmartMode();
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ try {
+ for (FileProcessingCompiler.ProcessingItem item : items) {
+ final VirtualFile file = item.getFile();
+ final String url = file.getUrl();
+ allUrls.add(url);
+ if (!forceCompile && cache.getTimestamp(url) == file.getTimeStamp()) {
+ final ValidityState state = cache.getExtState(url);
+ final ValidityState itemState = item.getValidityState();
+ if (state != null ? state.equalsTo(itemState) : itemState == null) {
+ continue;
+ }
+ }
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Adding item to process: " + url + "; saved ts= " + cache.getTimestamp(url) + "; VFS ts=" + file.getTimeStamp());
+ }
+ toProcess.add(item);
+ }
+ }
+ catch (IOException e) {
+ ex[0] = e;
+ }
+ }
+ });
+
+ if (ex[0] != null) {
+ throw ex[0];
+ }
+
+ final Collection<String> urls = cache.getUrls();
+ final List<String> urlsToRemove = new ArrayList<String>();
+ if (!urls.isEmpty()) {
+ CompilerUtil.runInContext(context, CompilerBundle.message("progress.processing.outdated.files"), new ThrowableRunnable<IOException>(){
+ public void run() throws IOException {
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ for (final String url : urls) {
+ if (!allUrls.contains(url)) {
+ if (!checkScope || scope.belongs(url)) {
+ urlsToRemove.add(url);
+ }
+ }
+ }
+ }
+ });
+ if (!onlyCheckStatus && !urlsToRemove.isEmpty()) {
+ for (final String url : urlsToRemove) {
+ adapter.processOutdatedItem(context, url, cache.getExtState(url));
+ cache.remove(url);
+ }
+ }
+ }
+ });
+ }
+
+ if (onlyCheckStatus) {
+ if (urlsToRemove.isEmpty() && toProcess.isEmpty()) {
+ return false;
+ }
+ if (LOG.isDebugEnabled()) {
+ if (!urlsToRemove.isEmpty()) {
+ LOG.debug("Found urls to remove, compiler " + adapter.getCompiler().getDescription());
+ for (String url : urlsToRemove) {
+ LOG.debug("\t" + url);
+ }
+ }
+ if (!toProcess.isEmpty()) {
+ LOG.debug("Found items to compile, compiler " + adapter.getCompiler().getDescription());
+ for (FileProcessingCompiler.ProcessingItem item : toProcess) {
+ LOG.debug("\t" + item.getFile().getPresentableUrl());
+ }
+ }
+ }
+ throw new ExitException(ExitStatus.CANCELLED);
+ }
+
+ if (toProcess.isEmpty()) {
+ return false;
+ }
+
+ final FileProcessingCompiler.ProcessingItem[] processed =
+ adapter.process(toProcess.toArray(new FileProcessingCompiler.ProcessingItem[toProcess.size()]));
+
+ if (processed.length == 0) {
+ return true;
+ }
+ CompilerUtil.runInContext(context, CompilerBundle.message("progress.updating.caches"), new ThrowableRunnable<IOException>() {
+ public void run() {
+ final List<VirtualFile> vFiles = new ArrayList<VirtualFile>(processed.length);
+ for (FileProcessingCompiler.ProcessingItem aProcessed : processed) {
+ final VirtualFile file = aProcessed.getFile();
+ vFiles.add(file);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("\tFile processed " + file.getPresentableUrl() + "; ts=" + file.getTimeStamp());
+ }
+
+ //final String path = file.getPath();
+ //final String outputDir = myOutputFinder.lookupOutputPath(path);
+ //if (outputDir != null) {
+ // context.updateZippedOuput(outputDir, path.substring(outputDir.length() + 1));
+ //}
+ }
+ LocalFileSystem.getInstance().refreshFiles(vFiles);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Files after VFS refresh:");
+ for (VirtualFile file : vFiles) {
+ LOG.debug("\t" + file.getPresentableUrl() + "; ts=" + file.getTimeStamp());
+ }
+ }
+ for (FileProcessingCompiler.ProcessingItem item : processed) {
+ cacheUpdater.addFileForUpdate(item, cache);
+ }
+ }
+ });
+ return true;
+ }
+
+ private FileProcessingCompilerStateCache getFileProcessingCompilerCache(FileProcessingCompiler compiler) throws IOException {
+ return CompilerCacheManager.getInstance(myProject).getFileProcessingCompilerCache(compiler);
+ }
+
+ private StateCache<ValidityState> getGeneratingCompilerCache(final GeneratingCompiler compiler) throws IOException {
+ return CompilerCacheManager.getInstance(myProject).getGeneratingCompilerCache(compiler);
+ }
+
+ public void executeCompileTask(final CompileTask task, final CompileScope scope, final String contentName, final Runnable onTaskFinished) {
+ final CompilerTask progressManagerTask =
+ new CompilerTask(myProject, contentName, false, false);
+ final CompileContextImpl compileContext = new CompileContextImpl(myProject, progressManagerTask, scope, null, false, false);
+
+ FileDocumentManager.getInstance().saveAllDocuments();
+
+ progressManagerTask.start(new Runnable() {
+ public void run() {
+ try {
+ task.execute(compileContext);
+ }
+ catch (ProcessCanceledException ex) {
+ // suppressed
+ }
+ finally {
+ if (onTaskFinished != null) {
+ onTaskFinished.run();
+ }
+ }
+ }
+ }, null);
+ }
+
+ private boolean executeCompileTasks(final CompileContext context, final boolean beforeTasks) {
+ final CompilerManager manager = CompilerManager.getInstance(myProject);
+ final ProgressIndicator progressIndicator = context.getProgressIndicator();
+ progressIndicator.pushState();
+ try {
+ CompileTask[] tasks = beforeTasks ? manager.getBeforeTasks() : manager.getAfterTasks();
+ if (tasks.length > 0) {
+ progressIndicator.setText(beforeTasks
+ ? CompilerBundle.message("progress.executing.precompile.tasks")
+ : CompilerBundle.message("progress.executing.postcompile.tasks"));
+ for (CompileTask task : tasks) {
+ if (!task.execute(context)) {
+ return false;
+ }
+ }
+ }
+ }
+ finally {
+ progressIndicator.popState();
+ WindowManager.getInstance().getStatusBar(myProject).setInfo("");
+ if (progressIndicator instanceof CompilerTask) {
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ public void run() {
+ ((CompilerTask)progressIndicator).showCompilerContent();
+ }
+ });
+ }
+ }
+ return true;
+ }
+
+ private boolean validateCompilerConfiguration(final CompileScope scope, boolean checkOutputAndSourceIntersection) {
+ try {
+ final Module[] scopeModules = scope.getAffectedModules()/*ModuleManager.getInstance(myProject).getModules()*/;
+ final List<String> modulesWithoutOutputPathSpecified = new ArrayList<String>();
+ boolean isProjectCompilePathSpecified = true;
+ final List<String> modulesWithoutJdkAssigned = new ArrayList<String>();
+ final Set<File> nonExistingOutputPaths = new HashSet<File>();
+ final CompilerConfiguration config = CompilerConfiguration.getInstance(myProject);
+ final CompilerManager compilerManager = CompilerManager.getInstance(myProject);
+ final boolean useOutOfProcessBuild = useOutOfProcessBuild();
+ for (final Module module : scopeModules) {
+ if (!compilerManager.isValidationEnabled(module)) {
+ continue;
+ }
+ final boolean hasSources = hasSources(module, false);
+ final boolean hasTestSources = hasSources(module, true);
+ if (!hasSources && !hasTestSources) {
+ // If module contains no sources, shouldn't have to select JDK or output directory (SCR #19333)
+ // todo still there may be problems with this approach if some generated files are attributed by this module
+ continue;
+ }
+ final Sdk jdk = ModuleRootManager.getInstance(module).getSdk();
+ if (jdk == null) {
+ modulesWithoutJdkAssigned.add(module.getName());
+ }
+ final String outputPath = getModuleOutputPath(module, false);
+ final String testsOutputPath = getModuleOutputPath(module, true);
+ if (outputPath == null && testsOutputPath == null) {
+ modulesWithoutOutputPathSpecified.add(module.getName());
+ }
+ else {
+ if (outputPath != null) {
+ if (!useOutOfProcessBuild) {
+ final File file = new File(outputPath.replace('/', File.separatorChar));
+ if (!file.exists()) {
+ nonExistingOutputPaths.add(file);
+ }
+ }
+ }
+ else {
+ if (hasSources) {
+ modulesWithoutOutputPathSpecified.add(module.getName());
+ }
+ }
+ if (testsOutputPath != null) {
+ if (!useOutOfProcessBuild) {
+ final File f = new File(testsOutputPath.replace('/', File.separatorChar));
+ if (!f.exists()) {
+ nonExistingOutputPaths.add(f);
+ }
+ }
+ }
+ else {
+ if (hasTestSources) {
+ modulesWithoutOutputPathSpecified.add(module.getName());
+ }
+ }
+ if (!useOutOfProcessBuild) {
+ if (config.getAnnotationProcessingConfiguration(module).isEnabled()) {
+ final String path = CompilerPaths.getAnnotationProcessorsGenerationPath(module);
+ if (path == null) {
+ final CompilerProjectExtension extension = CompilerProjectExtension.getInstance(module.getProject());
+ if (extension == null || extension.getCompilerOutputUrl() == null) {
+ isProjectCompilePathSpecified = false;
+ }
+ else {
+ modulesWithoutOutputPathSpecified.add(module.getName());
+ }
+ }
+ else {
+ final File file = new File(path);
+ if (!file.exists()) {
+ nonExistingOutputPaths.add(file);
+ }
+ }
+ }
+ }
+ }
+ }
+ if (!modulesWithoutJdkAssigned.isEmpty()) {
+ showNotSpecifiedError("error.jdk.not.specified", modulesWithoutJdkAssigned, ProjectBundle.message("modules.classpath.title"));
+ return false;
+ }
+
+ if (!isProjectCompilePathSpecified) {
+ final String message = CompilerBundle.message("error.project.output.not.specified");
+ if (ApplicationManager.getApplication().isUnitTestMode()) {
+ LOG.error(message);
+ }
+
+ Messages.showMessageDialog(myProject, message, CommonBundle.getErrorTitle(), Messages.getErrorIcon());
+ ProjectSettingsService.getInstance(myProject).openProjectSettings();
+ return false;
+ }
+
+ if (!modulesWithoutOutputPathSpecified.isEmpty()) {
+ showNotSpecifiedError("error.output.not.specified", modulesWithoutOutputPathSpecified, CommonContentEntriesEditor.NAME);
+ return false;
+ }
+
+ if (!nonExistingOutputPaths.isEmpty()) {
+ for (File file : nonExistingOutputPaths) {
+ final boolean succeeded = file.mkdirs();
+ if (!succeeded) {
+ if (file.exists()) {
+ // for overlapping paths, this one might have been created as an intermediate path on a previous iteration
+ continue;
+ }
+ Messages.showMessageDialog(myProject, CompilerBundle.message("error.failed.to.create.directory", file.getPath()),
+ CommonBundle.getErrorTitle(), Messages.getErrorIcon());
+ return false;
+ }
+ }
+ final Boolean refreshSuccess =
+ new WriteAction<Boolean>() {
+ @Override
+ protected void run(Result<Boolean> result) throws Throwable {
+ LocalFileSystem.getInstance().refreshIoFiles(nonExistingOutputPaths);
+ Boolean res = Boolean.TRUE;
+ for (File file : nonExistingOutputPaths) {
+ if (LocalFileSystem.getInstance().findFileByIoFile(file) == null) {
+ res = Boolean.FALSE;
+ break;
+ }
+ }
+ result.setResult(res);
+ }
+ }.execute().getResultObject();
+
+ if (!refreshSuccess.booleanValue()) {
+ return false;
+ }
+ dropScopesCaches();
+ }
+
+ if (checkOutputAndSourceIntersection && myShouldClearOutputDirectory) {
+ if (!validateOutputAndSourcePathsIntersection()) {
+ return false;
+ }
+ // myShouldClearOutputDirectory may change in validateOutputAndSourcePathsIntersection()
+ CompilerPathsEx.CLEAR_ALL_OUTPUTS_KEY.set(scope, myShouldClearOutputDirectory);
+ }
+ else {
+ CompilerPathsEx.CLEAR_ALL_OUTPUTS_KEY.set(scope, false);
+ }
+ final List<Chunk<Module>> chunks = ModuleCompilerUtil.getSortedModuleChunks(myProject, Arrays.asList(scopeModules));
+ for (final Chunk<Module> chunk : chunks) {
+ final Set<Module> chunkModules = chunk.getNodes();
+ if (chunkModules.size() <= 1) {
+ continue; // no need to check one-module chunks
+ }
+ for (Module chunkModule : chunkModules) {
+ if (config.getAnnotationProcessingConfiguration(chunkModule).isEnabled()) {
+ showCyclesNotSupportedForAnnotationProcessors(chunkModules.toArray(new Module[chunkModules.size()]));
+ return false;
+ }
+ }
+ Sdk jdk = null;
+ LanguageLevel languageLevel = null;
+ for (final Module module : chunkModules) {
+ final Sdk moduleJdk = ModuleRootManager.getInstance(module).getSdk();
+ if (jdk == null) {
+ jdk = moduleJdk;
+ }
+ else {
+ if (!jdk.equals(moduleJdk)) {
+ showCyclicModulesHaveDifferentJdksError(chunkModules.toArray(new Module[chunkModules.size()]));
+ return false;
+ }
+ }
+
+ LanguageLevel moduleLanguageLevel = LanguageLevelUtil.getEffectiveLanguageLevel(module);
+ if (languageLevel == null) {
+ languageLevel = moduleLanguageLevel;
+ }
+ else {
+ if (!languageLevel.equals(moduleLanguageLevel)) {
+ showCyclicModulesHaveDifferentLanguageLevel(chunkModules.toArray(new Module[chunkModules.size()]));
+ return false;
+ }
+ }
+ }
+ }
+ if (!useOutOfProcessBuild) {
+ final Compiler[] allCompilers = compilerManager.getCompilers(Compiler.class);
+ for (Compiler compiler : allCompilers) {
+ if (!compiler.validateConfiguration(scope)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ catch (Throwable e) {
+ LOG.info(e);
+ return false;
+ }
+ }
+
+ private boolean useOutOfProcessBuild() {
+ return CompilerWorkspaceConfiguration.getInstance(myProject).useOutOfProcessBuild();
+ }
+
+ private void showCyclicModulesHaveDifferentLanguageLevel(Module[] modulesInChunk) {
+ LOG.assertTrue(modulesInChunk.length > 0);
+ String moduleNameToSelect = modulesInChunk[0].getName();
+ final String moduleNames = getModulesString(modulesInChunk);
+ Messages.showMessageDialog(myProject, CompilerBundle.message("error.chunk.modules.must.have.same.language.level", moduleNames),
+ CommonBundle.getErrorTitle(), Messages.getErrorIcon());
+ showConfigurationDialog(moduleNameToSelect, null);
+ }
+
+ private void showCyclicModulesHaveDifferentJdksError(Module[] modulesInChunk) {
+ LOG.assertTrue(modulesInChunk.length > 0);
+ String moduleNameToSelect = modulesInChunk[0].getName();
+ final String moduleNames = getModulesString(modulesInChunk);
+ Messages.showMessageDialog(myProject, CompilerBundle.message("error.chunk.modules.must.have.same.jdk", moduleNames),
+ CommonBundle.getErrorTitle(), Messages.getErrorIcon());
+ showConfigurationDialog(moduleNameToSelect, null);
+ }
+
+ private void showCyclesNotSupportedForAnnotationProcessors(Module[] modulesInChunk) {
+ LOG.assertTrue(modulesInChunk.length > 0);
+ String moduleNameToSelect = modulesInChunk[0].getName();
+ final String moduleNames = getModulesString(modulesInChunk);
+ Messages.showMessageDialog(myProject, CompilerBundle.message("error.annotation.processing.not.supported.for.module.cycles", moduleNames),
+ CommonBundle.getErrorTitle(), Messages.getErrorIcon());
+ showConfigurationDialog(moduleNameToSelect, null);
+ }
+
+ private static String getModulesString(Module[] modulesInChunk) {
+ final StringBuilder moduleNames = StringBuilderSpinAllocator.alloc();
+ try {
+ for (Module module : modulesInChunk) {
+ if (moduleNames.length() > 0) {
+ moduleNames.append("\n");
+ }
+ moduleNames.append("\"").append(module.getName()).append("\"");
+ }
+ return moduleNames.toString();
+ }
+ finally {
+ StringBuilderSpinAllocator.dispose(moduleNames);
+ }
+ }
+
+ private static boolean hasSources(Module module, boolean checkTestSources) {
+ final ContentEntry[] contentEntries = ModuleRootManager.getInstance(module).getContentEntries();
+ for (final ContentEntry contentEntry : contentEntries) {
+ final SourceFolder[] sourceFolders = contentEntry.getSourceFolders();
+ for (final SourceFolder sourceFolder : sourceFolders) {
+ if (sourceFolder.getFile() == null) {
+ continue; // skip invalid source folders
+ }
+ if (checkTestSources) {
+ if (sourceFolder.isTestSource()) {
+ return true;
+ }
+ }
+ else {
+ if (!sourceFolder.isTestSource()) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private void showNotSpecifiedError(@NonNls final String resourceId, List<String> modules, String editorNameToSelect) {
+ String nameToSelect = null;
+ final StringBuilder names = StringBuilderSpinAllocator.alloc();
+ final String message;
+ try {
+ final int maxModulesToShow = 10;
+ for (String name : modules.size() > maxModulesToShow ? modules.subList(0, maxModulesToShow) : modules) {
+ if (nameToSelect == null) {
+ nameToSelect = name;
+ }
+ if (names.length() > 0) {
+ names.append(",\n");
+ }
+ names.append("\"");
+ names.append(name);
+ names.append("\"");
+ }
+ if (modules.size() > maxModulesToShow) {
+ names.append(",\n...");
+ }
+ message = CompilerBundle.message(resourceId, modules.size(), names.toString());
+ }
+ finally {
+ StringBuilderSpinAllocator.dispose(names);
+ }
+
+ if (ApplicationManager.getApplication().isUnitTestMode()) {
+ LOG.error(message);
+ }
+
+ Messages.showMessageDialog(myProject, message, CommonBundle.getErrorTitle(), Messages.getErrorIcon());
+ showConfigurationDialog(nameToSelect, editorNameToSelect);
+ }
+
+ private boolean validateOutputAndSourcePathsIntersection() {
+ final Module[] allModules = ModuleManager.getInstance(myProject).getModules();
+ List<VirtualFile> allOutputs = new ArrayList<VirtualFile>();
+ ContainerUtil.addAll(allOutputs, CompilerPathsEx.getOutputDirectories(allModules));
+ for (Artifact artifact : ArtifactManager.getInstance(myProject).getArtifacts()) {
+ ContainerUtil.addIfNotNull(artifact.getOutputFile(), allOutputs);
+ }
+ final Set<VirtualFile> affectedOutputPaths = new HashSet<VirtualFile>();
+ CompilerUtil.computeIntersectingPaths(myProject, allOutputs, affectedOutputPaths);
+ affectedOutputPaths.addAll(ArtifactCompilerUtil.getArtifactOutputsContainingSourceFiles(myProject));
+
+ if (!affectedOutputPaths.isEmpty()) {
+ if (CompilerUtil.askUserToContinueWithNoClearing(myProject, affectedOutputPaths)) {
+ myShouldClearOutputDirectory = false;
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void showConfigurationDialog(String moduleNameToSelect, String tabNameToSelect) {
+ ProjectSettingsService.getInstance(myProject).showModuleConfigurationDialog(moduleNameToSelect, tabNameToSelect);
+ }
+
+ private static VirtualFile lookupVFile(final LocalFileSystem lfs, final String path) {
+ final File file = new File(path);
+
+ VirtualFile vFile = lfs.findFileByIoFile(file);
+ if (vFile != null) {
+ return vFile;
+ }
+
+ final boolean justCreated = file.mkdirs();
+ vFile = lfs.refreshAndFindFileByIoFile(file);
+
+ if (vFile == null) {
+ assert false: "Virtual file not found for " + file.getPath() + "; mkdirs() exit code is " + justCreated + "; file exists()? " + file.exists();
+ }
+
+ return vFile;
+ }
+
+ private static class CacheDeferredUpdater {
+ private final Map<VirtualFile, List<Pair<FileProcessingCompilerStateCache, FileProcessingCompiler.ProcessingItem>>> myData = new java.util.HashMap<VirtualFile, List<Pair<FileProcessingCompilerStateCache, FileProcessingCompiler.ProcessingItem>>>();
+
+ public void addFileForUpdate(final FileProcessingCompiler.ProcessingItem item, FileProcessingCompilerStateCache cache) {
+ final VirtualFile file = item.getFile();
+ List<Pair<FileProcessingCompilerStateCache, FileProcessingCompiler.ProcessingItem>> list = myData.get(file);
+ if (list == null) {
+ list = new ArrayList<Pair<FileProcessingCompilerStateCache, FileProcessingCompiler.ProcessingItem>>();
+ myData.put(file, list);
+ }
+ list.add(new Pair<FileProcessingCompilerStateCache, FileProcessingCompiler.ProcessingItem>(cache, item));
+ }
+
+ public void doUpdate() throws IOException{
+ final IOException[] ex = {null};
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ try {
+ for (Map.Entry<VirtualFile, List<Pair<FileProcessingCompilerStateCache, FileProcessingCompiler.ProcessingItem>>> entry : myData.entrySet()) {
+ for (Pair<FileProcessingCompilerStateCache, FileProcessingCompiler.ProcessingItem> pair : entry.getValue()) {
+ final FileProcessingCompiler.ProcessingItem item = pair.getSecond();
+ pair.getFirst().update(entry.getKey(), item.getValidityState());
+ }
+ }
+ }
+ catch (IOException e) {
+ ex[0] = e;
+ }
+ }
+ });
+ if (ex[0] != null) {
+ throw ex[0];
+ }
+ }
+ }
+
+ private static class TranslatorsOutputSink implements TranslatingCompiler.OutputSink {
+ final Map<String, Collection<TranslatingCompiler.OutputItem>> myPostponedItems = new HashMap<String, Collection<TranslatingCompiler.OutputItem>>();
+ private final CompileContextEx myContext;
+ private final TranslatingCompiler[] myCompilers;
+ private int myCurrentCompilerIdx;
+ private final Set<VirtualFile> myCompiledSources = new HashSet<VirtualFile>();
+ //private LinkedBlockingQueue<Future> myFutures = new LinkedBlockingQueue<Future>();
+
+ private TranslatorsOutputSink(CompileContextEx context, TranslatingCompiler[] compilers) {
+ myContext = context;
+ myCompilers = compilers;
+ }
+
+ public void setCurrentCompilerIndex(int index) {
+ myCurrentCompilerIdx = index;
+ }
+
+ public Set<VirtualFile> getCompiledSources() {
+ return Collections.unmodifiableSet(myCompiledSources);
+ }
+
+ public void add(final String outputRoot, final Collection<TranslatingCompiler.OutputItem> items, final VirtualFile[] filesToRecompile) {
+ for (TranslatingCompiler.OutputItem item : items) {
+ final VirtualFile file = item.getSourceFile();
+ if (file != null) {
+ myCompiledSources.add(file);
+ }
+ }
+ final TranslatingCompiler compiler = myCompilers[myCurrentCompilerIdx];
+ if (compiler instanceof IntermediateOutputCompiler) {
+ final LocalFileSystem lfs = LocalFileSystem.getInstance();
+ final List<VirtualFile> outputs = new ArrayList<VirtualFile>();
+ for (TranslatingCompiler.OutputItem item : items) {
+ final VirtualFile vFile = lfs.findFileByPath(item.getOutputPath());
+ if (vFile != null) {
+ outputs.add(vFile);
+ }
+ }
+ myContext.markGenerated(outputs);
+ }
+ final int nextCompilerIdx = myCurrentCompilerIdx + 1;
+ try {
+ if (nextCompilerIdx < myCompilers.length ) {
+ final Map<String, Collection<TranslatingCompiler.OutputItem>> updateNow = new java.util.HashMap<String, Collection<TranslatingCompiler.OutputItem>>();
+ // process postponed
+ for (Map.Entry<String, Collection<TranslatingCompiler.OutputItem>> entry : myPostponedItems.entrySet()) {
+ final String outputDir = entry.getKey();
+ final Collection<TranslatingCompiler.OutputItem> postponed = entry.getValue();
+ for (Iterator<TranslatingCompiler.OutputItem> it = postponed.iterator(); it.hasNext();) {
+ TranslatingCompiler.OutputItem item = it.next();
+ boolean shouldPostpone = false;
+ for (int idx = nextCompilerIdx; idx < myCompilers.length; idx++) {
+ shouldPostpone = myCompilers[idx].isCompilableFile(item.getSourceFile(), myContext);
+ if (shouldPostpone) {
+ break;
+ }
+ }
+ if (!shouldPostpone) {
+ // the file is not compilable by the rest of compilers, so it is safe to update it now
+ it.remove();
+ addItemToMap(updateNow, outputDir, item);
+ }
+ }
+ }
+ // process items from current compilation
+ for (TranslatingCompiler.OutputItem item : items) {
+ boolean shouldPostpone = false;
+ for (int idx = nextCompilerIdx; idx < myCompilers.length; idx++) {
+ shouldPostpone = myCompilers[idx].isCompilableFile(item.getSourceFile(), myContext);
+ if (shouldPostpone) {
+ break;
+ }
+ }
+ if (shouldPostpone) {
+ // the file is compilable by the next compiler in row, update should be postponed
+ addItemToMap(myPostponedItems, outputRoot, item);
+ }
+ else {
+ addItemToMap(updateNow, outputRoot, item);
+ }
+ }
+
+ if (updateNow.size() == 1) {
+ final Map.Entry<String, Collection<TranslatingCompiler.OutputItem>> entry = updateNow.entrySet().iterator().next();
+ final String outputDir = entry.getKey();
+ final Collection<TranslatingCompiler.OutputItem> itemsToUpdate = entry.getValue();
+ TranslatingCompilerFilesMonitor.getInstance().update(myContext, outputDir, itemsToUpdate, filesToRecompile);
+ }
+ else {
+ for (Map.Entry<String, Collection<TranslatingCompiler.OutputItem>> entry : updateNow.entrySet()) {
+ final String outputDir = entry.getKey();
+ final Collection<TranslatingCompiler.OutputItem> itemsToUpdate = entry.getValue();
+ TranslatingCompilerFilesMonitor.getInstance().update(myContext, outputDir, itemsToUpdate, VirtualFile.EMPTY_ARRAY);
+ }
+ if (filesToRecompile.length > 0) {
+ TranslatingCompilerFilesMonitor.getInstance().update(myContext, null, Collections.<TranslatingCompiler.OutputItem>emptyList(), filesToRecompile);
+ }
+ }
+ }
+ else {
+ TranslatingCompilerFilesMonitor.getInstance().update(myContext, outputRoot, items, filesToRecompile);
+ }
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ myContext.requestRebuildNextTime(e.getMessage());
+ }
+ }
+
+ private static void addItemToMap(Map<String, Collection<TranslatingCompiler.OutputItem>> map, String outputDir, TranslatingCompiler.OutputItem item) {
+ Collection<TranslatingCompiler.OutputItem> collection = map.get(outputDir);
+ if (collection == null) {
+ collection = new ArrayList<TranslatingCompiler.OutputItem>();
+ map.put(outputDir, collection);
+ }
+ collection.add(item);
+ }
+
+ public void flushPostponedItems() {
+ final TranslatingCompilerFilesMonitor filesMonitor = TranslatingCompilerFilesMonitor.getInstance();
+ try {
+ for (Map.Entry<String, Collection<TranslatingCompiler.OutputItem>> entry : myPostponedItems.entrySet()) {
+ final String outputDir = entry.getKey();
+ final Collection<TranslatingCompiler.OutputItem> items = entry.getValue();
+ filesMonitor.update(myContext, outputDir, items, VirtualFile.EMPTY_ARRAY);
+ }
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ myContext.requestRebuildNextTime(e.getMessage());
+ }
+ }
+ }
+
+ private static class DependentClassesCumulativeFilter implements Function<Pair<int[], Set<VirtualFile>>, Pair<int[], Set<VirtualFile>>> {
+
+ private final TIntHashSet myProcessedNames = new TIntHashSet();
+ private final Set<VirtualFile> myProcessedFiles = new HashSet<VirtualFile>();
+
+ public Pair<int[], Set<VirtualFile>> fun(Pair<int[], Set<VirtualFile>> deps) {
+ final TIntHashSet currentDeps = new TIntHashSet(deps.getFirst());
+ currentDeps.removeAll(myProcessedNames.toArray());
+ myProcessedNames.addAll(deps.getFirst());
+
+ final Set<VirtualFile> depFiles = new HashSet<VirtualFile>(deps.getSecond());
+ depFiles.removeAll(myProcessedFiles);
+ myProcessedFiles.addAll(deps.getSecond());
+ return new Pair<int[], Set<VirtualFile>>(currentDeps.toArray(), depFiles);
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompileScopeUtil.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompileScopeUtil.java
new file mode 100644
index 0000000..102db7a
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompileScopeUtil.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl;
+
+import com.intellij.openapi.compiler.CompileScope;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.jps.api.CmdlineRemoteProto.Message.ControllerMessage.ParametersMessage.TargetTypeBuildScope;
+import org.jetbrains.jps.builders.java.JavaModuleBuildTargetType;
+
+import java.util.*;
+
+/**
+ * @author nik
+ */
+public class CompileScopeUtil {
+ public static void addScopesForModules(Collection<Module> modules, List<TargetTypeBuildScope> scopes) {
+ if (!modules.isEmpty()) {
+ for (JavaModuleBuildTargetType type : JavaModuleBuildTargetType.ALL_TYPES) {
+ TargetTypeBuildScope.Builder builder = TargetTypeBuildScope.newBuilder().setTypeId(type.getTypeId());
+ for (Module module : modules) {
+ builder.addTargetId(module.getName());
+ }
+ scopes.add(builder.build());
+ }
+ }
+ }
+
+ public static List<TargetTypeBuildScope> mergeScopes(List<TargetTypeBuildScope> scopes1, List<TargetTypeBuildScope> scopes2) {
+ if (scopes2.isEmpty()) return scopes1;
+ if (scopes1.isEmpty()) return scopes2;
+
+ Map<String, TargetTypeBuildScope> scopeById = new HashMap<String, TargetTypeBuildScope>();
+ mergeScopes(scopeById, scopes1);
+ mergeScopes(scopeById, scopes2);
+ return new ArrayList<TargetTypeBuildScope>(scopeById.values());
+ }
+
+ private static void mergeScopes(Map<String, TargetTypeBuildScope> scopeById, List<TargetTypeBuildScope> scopes) {
+ for (TargetTypeBuildScope scope : scopes) {
+ String id = scope.getTypeId();
+ TargetTypeBuildScope old = scopeById.get(id);
+ if (old == null) {
+ scopeById.put(id, scope);
+ }
+ else {
+ scopeById.put(id, mergeScope(old, scope));
+ }
+ }
+ }
+
+ private static TargetTypeBuildScope mergeScope(TargetTypeBuildScope scope1, TargetTypeBuildScope scope2) {
+ if (scope1.getAllTargets()) return scope1;
+ if (scope2.getAllTargets()) return scope2;
+ return TargetTypeBuildScope.newBuilder()
+ .setTypeId(scope1.getTypeId())
+ .addAllTargetId(scope1.getTargetIdList())
+ .addAllTargetId(scope2.getTargetIdList())
+ .build();
+ }
+
+ public static boolean allProjectModulesAffected(CompileContextImpl compileContext) {
+ final Set<Module> allModules = new HashSet<Module>(Arrays.asList(compileContext.getProjectCompileScope().getAffectedModules()));
+ allModules.removeAll(Arrays.asList(compileContext.getCompileScope().getAffectedModules()));
+ return allModules.isEmpty();
+ }
+
+ public static List<String> fetchFiles(CompileContextImpl context) {
+ if (context.isRebuild()) {
+ return Collections.emptyList();
+ }
+ final CompileScope scope = context.getCompileScope();
+ if (shouldFetchFiles(scope)) {
+ final List<String> paths = new ArrayList<String>();
+ for (VirtualFile file : scope.getFiles(null, true)) {
+ paths.add(file.getPath());
+ }
+ return paths;
+ }
+ return Collections.emptyList();
+ }
+
+ private static boolean shouldFetchFiles(CompileScope scope) {
+ if (scope instanceof CompositeScope) {
+ for (CompileScope compileScope : ((CompositeScope)scope).getScopes()) {
+ if (shouldFetchFiles(compileScope)) {
+ return true;
+ }
+ }
+ }
+ return scope instanceof OneProjectItemCompileScope || scope instanceof FileSetCompileScope;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompilerCacheManager.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompilerCacheManager.java
new file mode 100644
index 0000000..5c147ee
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompilerCacheManager.java
@@ -0,0 +1,187 @@
+/*
+ * 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 com.intellij.compiler.impl;
+
+import com.intellij.compiler.impl.generic.GenericCompilerCache;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.compiler.*;
+import com.intellij.openapi.compiler.Compiler;
+import com.intellij.openapi.compiler.generic.GenericCompiler;
+import com.intellij.openapi.components.ProjectComponent;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: May 4, 2008
+ */
+public class CompilerCacheManager implements ProjectComponent {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.CompilerCacheManager");
+ private final Map<Compiler, Object> myCompilerToCacheMap = new HashMap<Compiler, Object>();
+ private final Map<GenericCompiler<?,?,?>, GenericCompilerCache<?,?,?>> myGenericCachesMap = new HashMap<GenericCompiler<?,?,?>, GenericCompilerCache<?,?,?>>();
+ private final List<Disposable> myCacheDisposables = new ArrayList<Disposable>();
+ private final File myCachesRoot;
+ private final Project myProject;
+
+ public CompilerCacheManager(Project project) {
+ myProject = project;
+ myCachesRoot = CompilerPaths.getCacheStoreDirectory(project);
+ }
+
+ public static CompilerCacheManager getInstance(Project project) {
+ return project.getComponent(CompilerCacheManager.class);
+ }
+
+ public void projectOpened() {
+ }
+
+ public void projectClosed() {
+ }
+
+ @NotNull
+ public String getComponentName() {
+ return "CompilerCacheManager";
+ }
+
+ public void initComponent() {
+ }
+
+ public void disposeComponent() {
+ flushCaches();
+ }
+
+ private File getCompilerRootDir(final Compiler compiler) {
+ final File dir = new File(myCachesRoot, getCompilerIdString(compiler));
+ dir.mkdirs();
+ return dir;
+ }
+
+ public synchronized <Key, SourceState, OutputState> GenericCompilerCache<Key, SourceState, OutputState>
+ getGenericCompilerCache(final GenericCompiler<Key, SourceState, OutputState> compiler) throws IOException {
+ GenericCompilerCache<?,?,?> cache = myGenericCachesMap.get(compiler);
+ if (cache == null) {
+ final GenericCompilerCache<?,?,?> genericCache = new GenericCompilerCache<Key, SourceState, OutputState>(compiler, GenericCompilerRunner
+ .getGenericCompilerCacheDir(myProject, compiler));
+ myGenericCachesMap.put(compiler, genericCache);
+ myCacheDisposables.add(new Disposable() {
+ public void dispose() {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Closing cache for feneric compiler " + compiler.getId());
+ }
+ genericCache.close();
+ }
+ });
+ cache = genericCache;
+ }
+ //noinspection unchecked
+ return (GenericCompilerCache<Key, SourceState, OutputState>)cache;
+ }
+
+ public synchronized FileProcessingCompilerStateCache getFileProcessingCompilerCache(final FileProcessingCompiler compiler) throws IOException {
+ Object cache = myCompilerToCacheMap.get(compiler);
+ if (cache == null) {
+ final File compilerRootDir = getCompilerRootDir(compiler);
+ final FileProcessingCompilerStateCache stateCache = new FileProcessingCompilerStateCache(compilerRootDir, compiler);
+ myCompilerToCacheMap.put(compiler, stateCache);
+ myCacheDisposables.add(new Disposable() {
+ public void dispose() {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Closing cache for compiler " + compiler.getDescription() + "; cache root dir: " + compilerRootDir);
+ }
+ stateCache.close();
+ }
+ });
+ cache = stateCache;
+ }
+ else {
+ LOG.assertTrue(cache instanceof FileProcessingCompilerStateCache);
+ }
+ return (FileProcessingCompilerStateCache)cache;
+ }
+
+ public synchronized StateCache<ValidityState> getGeneratingCompilerCache(final GeneratingCompiler compiler) throws IOException {
+ Object cache = myCompilerToCacheMap.get(compiler);
+ if (cache == null) {
+ final File cacheDir = getCompilerRootDir(compiler);
+ final StateCache<ValidityState> stateCache = new StateCache<ValidityState>(new File(cacheDir, "timestamps")) {
+ public ValidityState read(DataInput stream) throws IOException {
+ return compiler.createValidityState(stream);
+ }
+
+ public void write(ValidityState validityState, DataOutput out) throws IOException {
+ validityState.save(out);
+ }
+ };
+ myCompilerToCacheMap.put(compiler, stateCache);
+ myCacheDisposables.add(new Disposable() {
+ public void dispose() {
+ try {
+ stateCache.close();
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ }
+ }
+ });
+ cache = stateCache;
+ }
+ return (StateCache<ValidityState>)cache;
+ }
+
+ public static String getCompilerIdString(Compiler compiler) {
+ @NonNls String description = compiler.getDescription();
+ return description.replaceAll("\\s+", "_").replaceAll("[\\.\\?]", "_").toLowerCase();
+ }
+
+ public synchronized void flushCaches() {
+ for (Disposable disposable : myCacheDisposables) {
+ try {
+ disposable.dispose();
+ }
+ catch (Throwable e) {
+ LOG.info(e);
+ }
+ }
+ myCacheDisposables.clear();
+ myGenericCachesMap.clear();
+ myCompilerToCacheMap.clear();
+ }
+
+ public void clearCaches(final CompileContext context) {
+ flushCaches();
+ final File[] children = myCachesRoot.listFiles();
+ if (children != null) {
+ for (final File child : children) {
+ final boolean deleteOk = FileUtil.delete(child);
+ if (!deleteOk) {
+ context.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("compiler.error.failed.to.delete", child.getPath()), null, -1, -1);
+ }
+ }
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompilerContentIterator.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompilerContentIterator.java
new file mode 100644
index 0000000..c763480
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompilerContentIterator.java
@@ -0,0 +1,49 @@
+/*
+ * 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 com.intellij.compiler.impl;
+
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.FileTypeManager;
+import com.intellij.openapi.roots.ContentIterator;
+import com.intellij.openapi.roots.FileIndex;
+import com.intellij.openapi.vfs.VirtualFile;
+
+import java.util.Collection;
+
+public class CompilerContentIterator implements ContentIterator {
+ final FileTypeManager fileTypeManager = FileTypeManager.getInstance();
+ private final FileType myFileType;
+ private final FileIndex myFileIndex;
+ private final boolean myInSourceOnly;
+ private final Collection<VirtualFile> myFiles;
+
+ public CompilerContentIterator(FileType fileType, FileIndex fileIndex, boolean inSourceOnly, Collection<VirtualFile> files) {
+ myFileType = fileType;
+ myFileIndex = fileIndex;
+ myInSourceOnly = inSourceOnly;
+ myFiles = files;
+ }
+
+ public boolean processFile(VirtualFile fileOrDir) {
+ if (fileOrDir.isDirectory()) return true;
+ if (!fileOrDir.isInLocalFileSystem()) return true;
+ if (myInSourceOnly && !myFileIndex.isInSourceContent(fileOrDir)) return true;
+ if (myFileType == null || myFileType == fileOrDir.getFileType()) {
+ myFiles.add(fileOrDir);
+ }
+ return true;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompilerEncodingServiceImpl.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompilerEncodingServiceImpl.java
new file mode 100644
index 0000000..d50063d
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompilerEncodingServiceImpl.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl;
+
+import com.intellij.compiler.CompilerEncodingService;
+import com.intellij.openapi.compiler.CompilerManager;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ModuleRootManager;
+import com.intellij.openapi.roots.ProjectFileIndex;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.encoding.EncodingProjectManager;
+import com.intellij.openapi.vfs.encoding.EncodingProjectManagerImpl;
+import com.intellij.psi.util.CachedValue;
+import com.intellij.psi.util.CachedValueProvider;
+import com.intellij.psi.util.CachedValuesManager;
+import com.intellij.util.containers.ContainerUtil;
+import gnu.trove.THashMap;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.nio.charset.Charset;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author nik
+ */
+public class CompilerEncodingServiceImpl extends CompilerEncodingService {
+ @NotNull private final Project myProject;
+ private final CachedValue<Map<Module, Set<Charset>>> myModuleFileEncodings;
+
+ public CompilerEncodingServiceImpl(@NotNull Project project) {
+ myProject = project;
+ myModuleFileEncodings = CachedValuesManager.getManager(project).createCachedValue(new CachedValueProvider<Map<Module, Set<Charset>>>() {
+ @Override
+ public Result<Map<Module, Set<Charset>>> compute() {
+ Map<Module, Set<Charset>> result = computeModuleCharsetMap();
+ return Result.create(result, ProjectRootManager.getInstance(myProject),
+ ((EncodingProjectManagerImpl)EncodingProjectManager.getInstance(myProject)).getModificationTracker());
+ }
+ }, false);
+ }
+
+ private Map<Module, Set<Charset>> computeModuleCharsetMap() {
+ final Map<Module, Set<Charset>> map = new THashMap<Module, Set<Charset>>();
+ final Map<VirtualFile, Charset> mappings = EncodingProjectManager.getInstance(myProject).getAllMappings();
+ ProjectFileIndex index = ProjectRootManager.getInstance(myProject).getFileIndex();
+ final CompilerManager compilerManager = CompilerManager.getInstance(myProject);
+ for (Map.Entry<VirtualFile, Charset> entry : mappings.entrySet()) {
+ final VirtualFile file = entry.getKey();
+ final Charset charset = entry.getValue();
+ if (file == null || charset == null || (!file.isDirectory() && !compilerManager.isCompilableFileType(file.getFileType()))
+ || !index.isInSourceContent(file)) continue;
+
+ final Module module = index.getModuleForFile(file);
+ if (module == null) continue;
+
+ Set<Charset> set = map.get(module);
+ if (set == null) {
+ set = new LinkedHashSet<Charset>();
+ map.put(module, set);
+
+ final VirtualFile sourceRoot = index.getSourceRootForFile(file);
+ VirtualFile current = file.getParent();
+ Charset parentCharset = null;
+ while (current != null) {
+ final Charset currentCharset = mappings.get(current);
+ if (currentCharset != null) {
+ parentCharset = currentCharset;
+ }
+ if (current.equals(sourceRoot)) {
+ break;
+ }
+ current = current.getParent();
+ }
+ if (parentCharset != null) {
+ set.add(parentCharset);
+ }
+ }
+ set.add(charset);
+ }
+ //todo[nik,jeka] perhaps we should take into account encodings of source roots only not individual files
+ for (Module module : ModuleManager.getInstance(myProject).getModules()) {
+ for (VirtualFile file : ModuleRootManager.getInstance(module).getSourceRoots(true)) {
+ Charset encoding = EncodingProjectManager.getInstance(myProject).getEncoding(file, true);
+ if (encoding != null) {
+ Set<Charset> charsets = map.get(module);
+ if (charsets == null) {
+ charsets = new LinkedHashSet<Charset>();
+ map.put(module, charsets);
+ }
+ charsets.add(encoding);
+ }
+ }
+ }
+
+ return map;
+ }
+
+ @Override
+ @Nullable
+ public Charset getPreferredModuleEncoding(@NotNull Module module) {
+ final Set<Charset> encodings = myModuleFileEncodings.getValue().get(module);
+ return ContainerUtil.getFirstItem(encodings, EncodingProjectManager.getInstance(myProject).getDefaultCharset());
+ }
+
+ @NotNull
+ @Override
+ public Collection<Charset> getAllModuleEncodings(@NotNull Module module) {
+ final Set<Charset> encodings = myModuleFileEncodings.getValue().get(module);
+ if (encodings != null) {
+ return encodings;
+ }
+ return ContainerUtil.createMaybeSingletonList(EncodingProjectManager.getInstance(myProject).getDefaultCharset());
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompilerErrorTreeView.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompilerErrorTreeView.java
new file mode 100644
index 0000000..d9fea53
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompilerErrorTreeView.java
@@ -0,0 +1,182 @@
+/*
+ * 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 com.intellij.compiler.impl;
+
+import com.intellij.codeInsight.daemon.impl.actions.SuppressFix;
+import com.intellij.codeInsight.daemon.impl.actions.SuppressForClassFix;
+import com.intellij.compiler.CompilerWorkspaceConfiguration;
+import com.intellij.compiler.HelpID;
+import com.intellij.ide.errorTreeView.ErrorTreeElement;
+import com.intellij.ide.errorTreeView.NavigatableMessageElement;
+import com.intellij.ide.errorTreeView.NewErrorTreeViewPanel;
+import com.intellij.openapi.actionSystem.*;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.command.CommandProcessor;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.fileEditor.OpenFileDescriptor;
+import com.intellij.openapi.module.LanguageLevelUtil;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleUtilCore;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.JavaSdk;
+import com.intellij.openapi.projectRoots.JavaSdkVersion;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.roots.ModuleRootManager;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.pom.Navigatable;
+import com.intellij.pom.java.LanguageLevel;
+import com.intellij.psi.*;
+import com.intellij.util.IncorrectOperationException;
+import org.jetbrains.annotations.NotNull;
+
+public class CompilerErrorTreeView extends NewErrorTreeViewPanel {
+ public CompilerErrorTreeView(Project project, Runnable rerunAction) {
+ super(project, HelpID.COMPILER, true, true, rerunAction);
+ }
+
+ protected void fillRightToolbarGroup(DefaultActionGroup group) {
+ super.fillRightToolbarGroup(group);
+ group.add(new CompilerPropertiesAction());
+ }
+
+ protected void addExtraPopupMenuActions(DefaultActionGroup group) {
+ group.add(new ExcludeFromCompileAction(myProject, this));
+ group.addSeparator();
+ group.add(new SuppressJavacWarningsAction());
+ group.add(new SuppressJavacWarningForClassAction());
+ group.addSeparator();
+ ActionGroup popupGroup = (ActionGroup)ActionManager.getInstance().getAction(IdeActions.GROUP_COMPILER_ERROR_VIEW_POPUP);
+ if (popupGroup != null) {
+ for (AnAction action : popupGroup.getChildren(null)) {
+ group.add(action);
+ }
+ }
+ }
+
+ protected boolean shouldShowFirstErrorInEditor() {
+ return CompilerWorkspaceConfiguration.getInstance(myProject).AUTO_SHOW_ERRORS_IN_EDITOR;
+ }
+
+ private class SuppressJavacWarningsAction extends AnAction {
+ public void actionPerformed(final AnActionEvent e) {
+ final NavigatableMessageElement messageElement = (NavigatableMessageElement)getSelectedErrorTreeElement();
+ final String[] text = messageElement.getText();
+ final String id = text[0].substring(1, text[0].indexOf("]"));
+ final SuppressFix suppressInspectionFix = getSuppressAction(id);
+ final Project project = PlatformDataKeys.PROJECT.getData(e.getDataContext());
+ assert project != null;
+ final OpenFileDescriptor navigatable = (OpenFileDescriptor)messageElement.getNavigatable();
+ final PsiFile file = PsiManager.getInstance(project).findFile(navigatable.getFile());
+ assert file != null;
+ CommandProcessor.getInstance().executeCommand(project, new Runnable() {
+ public void run() {
+ ApplicationManager.getApplication().runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ suppressInspectionFix.invoke(project, null, file.findElementAt(navigatable.getOffset()));
+ }
+ catch (IncorrectOperationException e1) {
+ LOG.error(e1);
+ }
+ }
+ });
+ }
+ }, suppressInspectionFix.getText(), null);
+ }
+
+ @Override
+ public void update(final AnActionEvent e) {
+ final Presentation presentation = e.getPresentation();
+ presentation.setVisible(false);
+ presentation.setEnabled(false);
+ final Project project = PlatformDataKeys.PROJECT.getData(e.getDataContext());
+ if (project == null) {
+ return;
+ }
+ final ErrorTreeElement errorTreeElement = getSelectedErrorTreeElement();
+ if (errorTreeElement instanceof NavigatableMessageElement) {
+ final NavigatableMessageElement messageElement = (NavigatableMessageElement)errorTreeElement;
+ final String[] text = messageElement.getText();
+ if (text.length > 0) {
+ if (text[0].startsWith("[") && text[0].indexOf("]") != -1) {
+ final Navigatable navigatable = messageElement.getNavigatable();
+ if (navigatable instanceof OpenFileDescriptor) {
+ final OpenFileDescriptor fileDescriptor = (OpenFileDescriptor)navigatable;
+ final VirtualFile virtualFile = fileDescriptor.getFile();
+ final Module module = ModuleUtilCore.findModuleForFile(virtualFile, project);
+ if (module == null) {
+ return;
+ }
+ final Sdk jdk = ModuleRootManager.getInstance(module).getSdk();
+ if (jdk == null) {
+ return;
+ }
+ final boolean is_1_5 = JavaSdk.getInstance().isOfVersionOrHigher(jdk, JavaSdkVersion.JDK_1_5);
+ if (!is_1_5) {
+ return;
+ }
+ final PsiFile psiFile = PsiManager.getInstance(project).findFile(virtualFile);
+ if (psiFile == null) {
+ return;
+ }
+ if (LanguageLevelUtil.getEffectiveLanguageLevel(module).compareTo(LanguageLevel.JDK_1_5) < 0) return;
+ final PsiElement context = psiFile.findElementAt(fileDescriptor.getOffset());
+ if (context == null) {
+ return;
+ }
+ final String id = text[0].substring(1, text[0].indexOf("]"));
+ final SuppressFix suppressInspectionFix = getSuppressAction(id);
+ final boolean available = suppressInspectionFix.isAvailable(project, null, context);
+ presentation.setEnabled(available);
+ presentation.setVisible(available);
+ if (available) {
+ presentation.setText(suppressInspectionFix.getText());
+ }
+ }
+ }
+ }
+ }
+ }
+
+ protected SuppressFix getSuppressAction(final String id) {
+ return new SuppressFix(id) {
+ @Override
+ @SuppressWarnings({"SimplifiableIfStatement"})
+ public boolean isAvailable(@NotNull final Project project, final Editor editor, @NotNull final PsiElement context) {
+ if (getContainer(context) instanceof PsiClass) return false;
+ return super.isAvailable(project, editor, context);
+ }
+
+ @Override
+ protected boolean use15Suppressions(final PsiDocCommentOwner container) {
+ return true;
+ }
+ };
+ }
+ }
+
+ private class SuppressJavacWarningForClassAction extends SuppressJavacWarningsAction {
+ @Override
+ protected SuppressFix getSuppressAction(final String id) {
+ return new SuppressForClassFix(id){
+ @Override
+ protected boolean use15Suppressions(final PsiDocCommentOwner container) {
+ return true;
+ }
+ };
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompilerPropertiesAction.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompilerPropertiesAction.java
new file mode 100644
index 0000000..5f1bf29
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompilerPropertiesAction.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl;
+
+import com.intellij.compiler.options.CompilerConfigurable;
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.PlatformDataKeys;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.options.ShowSettingsUtil;
+import com.intellij.openapi.project.Project;
+
+/**
+* @author Eugene Zhuravlev
+* Date: 9/12/12
+*/
+class CompilerPropertiesAction extends AnAction {
+ public CompilerPropertiesAction() {
+ super(CompilerBundle.message("action.compiler.properties.text"), null, AllIcons.General.Settings);
+ }
+
+ public void actionPerformed(AnActionEvent e) {
+ Project project = PlatformDataKeys.PROJECT.getData(e.getDataContext());
+ if (project != null) {
+ ShowSettingsUtil.getInstance().editConfigurable(project, new CompilerConfigurable(project));
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompilerUtil.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompilerUtil.java
new file mode 100644
index 0000000..1417fa6
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompilerUtil.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright 2000-2012 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.
+ */
+
+/**
+ * created at Jan 3, 2002
+ * @author Jeka
+ */
+package com.intellij.compiler.impl;
+
+import com.intellij.CommonBundle;
+import com.intellij.compiler.CompilerConfiguration;
+import com.intellij.compiler.impl.javaCompiler.ModuleChunk;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.roots.ModuleRootManager;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.*;
+import com.intellij.openapi.vfs.newvfs.RefreshQueue;
+import com.intellij.pom.java.LanguageLevel;
+import com.intellij.util.ThrowableRunnable;
+import com.intellij.util.containers.ContainerUtil;
+import gnu.trove.THashMap;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.util.*;
+
+public class CompilerUtil {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.CompilerUtil");
+
+ public static String quotePath(String path) {
+ if(path != null && path.indexOf(' ') != -1) {
+ path = path.replaceAll("\\\\", "\\\\\\\\");
+ path = '"' + path + '"';
+ }
+ return path;
+ }
+
+ public static void collectFiles(Collection<File> container, File rootDir, FileFilter fileFilter) {
+ final File[] files = rootDir.listFiles(fileFilter);
+ if (files == null) {
+ return;
+ }
+ for (File file : files) {
+ if (file.isDirectory()) {
+ collectFiles(container, file, fileFilter);
+ }
+ else {
+ container.add(file);
+ }
+ }
+ }
+
+ public static Map<Module, List<VirtualFile>> buildModuleToFilesMap(CompileContext context, VirtualFile[] files) {
+ return buildModuleToFilesMap(context, Arrays.asList(files));
+ }
+
+
+ public static Map<Module, List<VirtualFile>> buildModuleToFilesMap(final CompileContext context, final List<VirtualFile> files) {
+ //assertion: all files are different
+ final Map<Module, List<VirtualFile>> map = new THashMap<Module, List<VirtualFile>>();
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ for (VirtualFile file : files) {
+ final Module module = context.getModuleByFile(file);
+
+ if (module == null) {
+ continue; // looks like file invalidated
+ }
+
+ List<VirtualFile> moduleFiles = map.get(module);
+ if (moduleFiles == null) {
+ moduleFiles = new ArrayList<VirtualFile>();
+ map.put(module, moduleFiles);
+ }
+ moduleFiles.add(file);
+ }
+ }
+ });
+ return map;
+ }
+
+
+ /**
+ * must not be called inside ReadAction
+ * @param files
+ */
+ public static void refreshIOFiles(@NotNull final Collection<File> files) {
+ if (!files.isEmpty()) {
+ LocalFileSystem.getInstance().refreshIoFiles(files);
+ }
+ }
+
+ public static void refreshIODirectories(@NotNull final Collection<File> files) {
+ final LocalFileSystem lfs = LocalFileSystem.getInstance();
+ final List<VirtualFile> filesToRefresh = new ArrayList<VirtualFile>();
+ for (File file : files) {
+ final VirtualFile virtualFile = lfs.refreshAndFindFileByIoFile(file);
+ if (virtualFile != null) {
+ filesToRefresh.add(virtualFile);
+ }
+ }
+ if (!filesToRefresh.isEmpty()) {
+ RefreshQueue.getInstance().refresh(false, true, null, filesToRefresh);
+ }
+ }
+
+ public static void refreshIOFile(final File file) {
+ final VirtualFile vFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file);
+ if (vFile != null) {
+ vFile.refresh(false, false);
+ }
+ }
+
+ public static void addLocaleOptions(final List<String> commandLine, final boolean launcherUsed) {
+ // need to specify default encoding so that javac outputs messages in 'correct' language
+ //noinspection HardCodedStringLiteral
+ commandLine.add((launcherUsed? "-J" : "") + "-D" + CharsetToolkit.FILE_ENCODING_PROPERTY + "=" + CharsetToolkit.getDefaultSystemCharset().name());
+ // javac's VM should use the same default locale that IDEA uses in order for javac to print messages in 'correct' language
+ //noinspection HardCodedStringLiteral
+ final String lang = System.getProperty("user.language");
+ if (lang != null) {
+ //noinspection HardCodedStringLiteral
+ commandLine.add((launcherUsed? "-J" : "") + "-Duser.language=" + lang);
+ }
+ //noinspection HardCodedStringLiteral
+ final String country = System.getProperty("user.country");
+ if (country != null) {
+ //noinspection HardCodedStringLiteral
+ commandLine.add((launcherUsed? "-J" : "") + "-Duser.country=" + country);
+ }
+ //noinspection HardCodedStringLiteral
+ final String region = System.getProperty("user.region");
+ if (region != null) {
+ //noinspection HardCodedStringLiteral
+ commandLine.add((launcherUsed? "-J" : "") + "-Duser.region=" + region);
+ }
+ }
+
+ public static void addTargetCommandLineSwitch(final ModuleChunk chunk, final List<String> commandLine) {
+ String optionValue = null;
+ CompilerConfiguration config = null;
+ final Module[] modules = chunk.getModules();
+ for (Module module : modules) {
+ if (config == null) {
+ config = CompilerConfiguration.getInstance(module.getProject());
+ }
+ final String moduleTarget = config.getBytecodeTargetLevel(module);
+ if (moduleTarget == null) {
+ continue;
+ }
+ if (optionValue == null) {
+ optionValue = moduleTarget;
+ }
+ else {
+ if (moduleTarget.compareTo(optionValue) < 0) {
+ optionValue = moduleTarget; // use the lower possible target among modules that form the chunk
+ }
+ }
+ }
+ if (optionValue != null) {
+ commandLine.add("-target");
+ commandLine.add(optionValue);
+ }
+ }
+
+ public static void addSourceCommandLineSwitch(final Sdk jdk, LanguageLevel chunkLanguageLevel, @NonNls final List<String> commandLine) {
+ final String versionString = jdk.getVersionString();
+ if (StringUtil.isEmpty(versionString)) {
+ throw new IllegalArgumentException(CompilerBundle.message("javac.error.unknown.jdk.version", jdk.getName()));
+ }
+
+ final LanguageLevel applicableLanguageLevel = getApplicableLanguageLevel(versionString, chunkLanguageLevel);
+ if (applicableLanguageLevel.equals(LanguageLevel.JDK_1_8)) {
+ commandLine.add("-source");
+ commandLine.add("8");
+ }
+ else if (applicableLanguageLevel.equals(LanguageLevel.JDK_1_7)) {
+ commandLine.add("-source");
+ commandLine.add("1.7");
+ }
+ else if (applicableLanguageLevel.equals(LanguageLevel.JDK_1_6)) {
+ commandLine.add("-source");
+ commandLine.add("1.6");
+ }
+ else if (applicableLanguageLevel.equals(LanguageLevel.JDK_1_5)) {
+ commandLine.add("-source");
+ commandLine.add("1.5");
+ }
+ else if (applicableLanguageLevel.equals(LanguageLevel.JDK_1_4)) {
+ commandLine.add("-source");
+ commandLine.add("1.4");
+ }
+ else if (applicableLanguageLevel.equals(LanguageLevel.JDK_1_3)) {
+ if (!(isOfVersion(versionString, "1.3") || isOfVersion(versionString, "1.2") || isOfVersion(versionString, "1.1"))) {
+ //noinspection HardCodedStringLiteral
+ commandLine.add("-source");
+ commandLine.add("1.3");
+ }
+ }
+ }
+
+ //todo[nik] rewrite using JavaSdkVersion#getMaxLanguageLevel
+ @NotNull
+ public static LanguageLevel getApplicableLanguageLevel(String versionString, @NotNull LanguageLevel languageLevel) {
+ final boolean is8OrNewer = isOfVersion(versionString, "1.8") || isOfVersion(versionString, "8.0");
+ final boolean is7OrNewer = is8OrNewer || isOfVersion(versionString, "1.7") || isOfVersion(versionString, "7.0");
+ final boolean is6OrNewer = is7OrNewer || isOfVersion(versionString, "1.6") || isOfVersion(versionString, "6.0");
+ final boolean is5OrNewer = is6OrNewer || isOfVersion(versionString, "1.5") || isOfVersion(versionString, "5.0");
+ final boolean is4OrNewer = is5OrNewer || isOfVersion(versionString, "1.4");
+ final boolean is3OrNewer = is4OrNewer || isOfVersion(versionString, "1.3");
+ final boolean is2OrNewer = is3OrNewer || isOfVersion(versionString, "1.2");
+ final boolean is1OrNewer = is2OrNewer || isOfVersion(versionString, "1.0") || isOfVersion(versionString, "1.1");
+
+ if (!is1OrNewer) {
+ // unknown jdk version, cannot say anything about the corresponding language level, so leave it unchanged
+ return languageLevel;
+ }
+ // now correct the language level to be not higher than jdk used to compile
+ if (LanguageLevel.JDK_1_8.equals(languageLevel) && !is8OrNewer) {
+ languageLevel = LanguageLevel.JDK_1_7;
+ }
+ if (LanguageLevel.JDK_1_7.equals(languageLevel) && !is7OrNewer) {
+ languageLevel = LanguageLevel.JDK_1_6;
+ }
+ if (LanguageLevel.JDK_1_6.equals(languageLevel) && !is6OrNewer) {
+ languageLevel = LanguageLevel.JDK_1_5;
+ }
+ if (LanguageLevel.JDK_1_5.equals(languageLevel) && !is5OrNewer) {
+ languageLevel = LanguageLevel.JDK_1_4;
+ }
+ if (LanguageLevel.JDK_1_4.equals(languageLevel) && !is4OrNewer) {
+ languageLevel = LanguageLevel.JDK_1_3;
+ }
+ return languageLevel;
+ }
+
+ public static boolean isOfVersion(String versionString, String checkedVersion) {
+ return versionString.contains(checkedVersion);
+ }
+
+ public static <T extends Throwable> void runInContext(CompileContext context, String title, ThrowableRunnable<T> action) throws T {
+ if (title != null) {
+ context.getProgressIndicator().pushState();
+ context.getProgressIndicator().setText(title);
+ }
+ try {
+ action.run();
+ }
+ finally {
+ if (title != null) {
+ context.getProgressIndicator().popState();
+ }
+ }
+ }
+
+ public static void logDuration(final String activityName, long duration) {
+ LOG.info(activityName + " took " + duration + " ms: " + duration /60000 + " min " +(duration %60000)/1000 + "sec");
+ }
+
+ public static void clearOutputDirectories(final Collection<File> outputDirectories) {
+ final long start = System.currentTimeMillis();
+ // do not delete directories themselves, or we'll get rootsChanged() otherwise
+ final Collection<File> filesToDelete = new ArrayList<File>(outputDirectories.size() * 2);
+ for (File outputDirectory : outputDirectories) {
+ File[] files = outputDirectory.listFiles();
+ if (files != null) {
+ ContainerUtil.addAll(filesToDelete, files);
+ }
+ }
+ if (filesToDelete.size() > 0) {
+ FileUtil.asyncDelete(filesToDelete);
+
+ // ensure output directories exist
+ for (final File file : outputDirectories) {
+ file.mkdirs();
+ }
+ final long clearStop = System.currentTimeMillis();
+
+ refreshIODirectories(outputDirectories);
+
+ final long refreshStop = System.currentTimeMillis();
+
+ logDuration("Clearing output dirs", clearStop - start);
+ logDuration("Refreshing output directories", refreshStop - clearStop);
+ }
+ }
+
+ public static void computeIntersectingPaths(final Project project,
+ final Collection<VirtualFile> outputPaths,
+ final Collection<VirtualFile> result) {
+ for (Module module : ModuleManager.getInstance(project).getModules()) {
+ final ModuleRootManager rootManager = ModuleRootManager.getInstance(module);
+ final VirtualFile[] sourceRoots = rootManager.getSourceRoots();
+ for (final VirtualFile outputPath : outputPaths) {
+ for (VirtualFile sourceRoot : sourceRoots) {
+ if (VfsUtilCore.isAncestor(outputPath, sourceRoot, true) || VfsUtilCore.isAncestor(sourceRoot, outputPath, false)) {
+ result.add(outputPath);
+ }
+ }
+ }
+ }
+ }
+
+ public static boolean askUserToContinueWithNoClearing(Project project, Collection<VirtualFile> affectedOutputPaths) {
+ final StringBuilder paths = new StringBuilder();
+ for (final VirtualFile affectedOutputPath : affectedOutputPaths) {
+ if (paths.length() > 0) {
+ paths.append(",\n");
+ }
+ paths.append(affectedOutputPath.getPath().replace('/', File.separatorChar));
+ }
+ final int answer = Messages.showOkCancelDialog(project,
+ CompilerBundle.message("warning.sources.under.output.paths", paths.toString()),
+ CommonBundle.getErrorTitle(), Messages.getWarningIcon());
+ if (answer == Messages.OK) { // ok
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/CompositeScope.java b/java/compiler/impl/src/com/intellij/compiler/impl/CompositeScope.java
new file mode 100644
index 0000000..2d18e44
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/CompositeScope.java
@@ -0,0 +1,106 @@
+/*
+ * 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.
+ */
+
+/*
+ * @author: Eugene Zhuravlev
+ * Date: Feb 5, 2003
+ * Time: 4:17:58 PM
+ */
+package com.intellij.compiler.impl;
+
+import com.intellij.openapi.compiler.CompileScope;
+import com.intellij.openapi.compiler.ExportableUserDataHolderBase;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.containers.ContainerUtil;
+import gnu.trove.THashSet;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+
+public class CompositeScope extends ExportableUserDataHolderBase implements CompileScope{
+ private final List<CompileScope> myScopes = new ArrayList<CompileScope>();
+
+ public CompositeScope(CompileScope scope1, CompileScope scope2) {
+ addScope(scope1);
+ addScope(scope2);
+ }
+
+ public CompositeScope(CompileScope[] scopes) {
+ for (CompileScope scope : scopes) {
+ addScope(scope);
+ }
+ }
+
+ private void addScope(CompileScope scope) {
+ if (scope instanceof CompositeScope) {
+ final CompositeScope compositeScope = (CompositeScope)scope;
+ for (CompileScope childScope : compositeScope.myScopes) {
+ addScope(childScope);
+ }
+ }
+ else {
+ myScopes.add(scope);
+ }
+ }
+
+ @NotNull
+ public VirtualFile[] getFiles(FileType fileType, boolean inSourceOnly) {
+ Set<VirtualFile> allFiles = new THashSet<VirtualFile>();
+ for (CompileScope scope : myScopes) {
+ final VirtualFile[] files = scope.getFiles(fileType, inSourceOnly);
+ if (files.length > 0) {
+ ContainerUtil.addAll(allFiles, files);
+ }
+ }
+ return VfsUtil.toVirtualFileArray(allFiles);
+ }
+
+ public boolean belongs(String url) {
+ for (CompileScope scope : myScopes) {
+ if (scope.belongs(url)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @NotNull
+ public Module[] getAffectedModules() {
+ Set<Module> modules = new HashSet<Module>();
+ for (final CompileScope compileScope : myScopes) {
+ ContainerUtil.addAll(modules, compileScope.getAffectedModules());
+ }
+ return modules.toArray(new Module[modules.size()]);
+ }
+
+ public <T> T getUserData(@NotNull Key<T> key) {
+ for (CompileScope compileScope : myScopes) {
+ T userData = compileScope.getUserData(key);
+ if (userData != null) {
+ return userData;
+ }
+ }
+ return super.getUserData(key);
+ }
+
+ public Collection<CompileScope> getScopes() {
+ return Collections.unmodifiableList(myScopes);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/ExcludeFromCompileAction.java b/java/compiler/impl/src/com/intellij/compiler/impl/ExcludeFromCompileAction.java
new file mode 100644
index 0000000..91055db
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/ExcludeFromCompileAction.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl;
+
+import com.intellij.compiler.CompilerConfiguration;
+import com.intellij.compiler.CompilerConfigurationImpl;
+import com.intellij.ide.errorTreeView.ErrorTreeElement;
+import com.intellij.ide.errorTreeView.ErrorTreeNodeDescriptor;
+import com.intellij.ide.errorTreeView.GroupingElement;
+import com.intellij.ide.errorTreeView.NewErrorTreeViewPanel;
+import com.intellij.ide.util.treeView.NodeDescriptor;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.Presentation;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.compiler.options.ExcludeEntryDescription;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vcs.FileStatusManager;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.Nullable;
+
+/**
+* @author Eugene Zhuravlev
+* Date: 9/12/12
+*/
+class ExcludeFromCompileAction extends AnAction {
+ private final Project myProject;
+ private final NewErrorTreeViewPanel myErrorTreeView;
+
+ public ExcludeFromCompileAction(Project project, NewErrorTreeViewPanel errorTreeView) {
+ super(CompilerBundle.message("actions.exclude.from.compile.text"));
+ myProject = project;
+ myErrorTreeView = errorTreeView;
+ }
+
+ public void actionPerformed(AnActionEvent e) {
+ VirtualFile file = getSelectedFile();
+
+ if (file != null && file.isValid()) {
+ ExcludeEntryDescription description = new ExcludeEntryDescription(file, false, true, myProject);
+ ((CompilerConfigurationImpl) CompilerConfiguration.getInstance(myProject)).getExcludedEntriesConfiguration().addExcludeEntryDescription(description);
+ FileStatusManager.getInstance(myProject).fileStatusesChanged();
+ }
+ }
+
+ @Nullable
+ private VirtualFile getSelectedFile() {
+ final ErrorTreeNodeDescriptor descriptor = myErrorTreeView.getSelectedNodeDescriptor();
+ ErrorTreeElement element = descriptor != null? descriptor.getElement() : null;
+ if (element != null && !(element instanceof GroupingElement)) {
+ NodeDescriptor parent = descriptor.getParentDescriptor();
+ if (parent instanceof ErrorTreeNodeDescriptor) {
+ element = ((ErrorTreeNodeDescriptor)parent).getElement();
+ }
+ }
+ return element instanceof GroupingElement? ((GroupingElement)element).getFile() : null;
+ }
+
+ public void update(AnActionEvent e) {
+ final Presentation presentation = e.getPresentation();
+ final boolean isApplicable = getSelectedFile() != null;
+ presentation.setEnabled(isApplicable);
+ presentation.setVisible(isApplicable);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/ExitException.java b/java/compiler/impl/src/com/intellij/compiler/impl/ExitException.java
new file mode 100644
index 0000000..b40d6f4
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/ExitException.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2000-2011 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 com.intellij.compiler.impl;
+
+/**
+* @author Eugene Zhuravlev
+* Date: 11/24/11
+*/
+public class ExitException extends Exception {
+ private final ExitStatus myStatus;
+
+ public ExitException(ExitStatus status) {
+ myStatus = status;
+ }
+
+ public ExitStatus getExitStatus() {
+ return myStatus;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/ExitStatus.java b/java/compiler/impl/src/com/intellij/compiler/impl/ExitStatus.java
new file mode 100644
index 0000000..d44156b
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/ExitStatus.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2000-2011 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 com.intellij.compiler.impl;
+
+import org.jetbrains.annotations.NonNls;
+
+/**
+* @author Eugene Zhuravlev
+* Date: 11/24/11
+*/
+public class ExitStatus {
+ private final String myName;
+
+ ExitStatus(@NonNls String name) {
+ myName = name;
+ }
+
+ public String toString() {
+ return myName;
+ }
+
+ public static final ExitStatus CANCELLED = new ExitStatus("CANCELLED");
+ public static final ExitStatus ERRORS = new ExitStatus("ERRORS");
+ public static final ExitStatus SUCCESS = new ExitStatus("SUCCESS");
+ public static final ExitStatus UP_TO_DATE = new ExitStatus("UP_TO_DATE");
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/FileIndexCompileScope.java b/java/compiler/impl/src/com/intellij/compiler/impl/FileIndexCompileScope.java
new file mode 100644
index 0000000..8f1f2dd
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/FileIndexCompileScope.java
@@ -0,0 +1,46 @@
+/*
+ * 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 com.intellij.compiler.impl;
+
+import com.intellij.openapi.compiler.CompileScope;
+import com.intellij.openapi.compiler.ExportableUserDataHolderBase;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.roots.FileIndex;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Dec 18, 2003
+ */
+public abstract class FileIndexCompileScope extends ExportableUserDataHolderBase implements CompileScope {
+
+ protected abstract FileIndex[] getFileIndices();
+
+ @NotNull
+ public VirtualFile[] getFiles(final FileType fileType, final boolean inSourceOnly) {
+ final List<VirtualFile> files = new ArrayList<VirtualFile>();
+ final FileIndex[] fileIndices = getFileIndices();
+ for (final FileIndex fileIndex : fileIndices) {
+ fileIndex.iterateContent(new CompilerContentIterator(fileType, fileIndex, inSourceOnly, files));
+ }
+ return VfsUtil.toVirtualFileArray(files);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/FileProcessingCompilerAdapter.java b/java/compiler/impl/src/com/intellij/compiler/impl/FileProcessingCompilerAdapter.java
new file mode 100644
index 0000000..8087b42
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/FileProcessingCompilerAdapter.java
@@ -0,0 +1,54 @@
+/*
+ * 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 com.intellij.compiler.impl;
+
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.FileProcessingCompiler;
+import com.intellij.openapi.compiler.ValidityState;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Oct 16
+ * @author 2003
+ */
+public class FileProcessingCompilerAdapter {
+ private final CompileContext myCompileContext;
+ private final FileProcessingCompiler myCompiler;
+
+ protected FileProcessingCompilerAdapter(CompileContext compileContext, FileProcessingCompiler compiler) {
+ myCompileContext = compileContext;
+ myCompiler = compiler;
+ }
+
+ public CompileContext getCompileContext() {
+ return myCompileContext;
+ }
+
+ public FileProcessingCompiler.ProcessingItem[] getProcessingItems() {
+ return myCompiler.getProcessingItems(getCompileContext());
+ }
+
+ public FileProcessingCompiler.ProcessingItem[] process(FileProcessingCompiler.ProcessingItem[] items) {
+ return myCompiler.process(getCompileContext(), items);
+ }
+
+ public final FileProcessingCompiler getCompiler() {
+ return myCompiler;
+ }
+
+ public void processOutdatedItem(CompileContext context, String url, @Nullable ValidityState state){}
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/FileProcessingCompilerAdapterTask.java b/java/compiler/impl/src/com/intellij/compiler/impl/FileProcessingCompilerAdapterTask.java
new file mode 100644
index 0000000..f53e4df
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/FileProcessingCompilerAdapterTask.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl;
+
+import com.intellij.compiler.CompilerWorkspaceConfiguration;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.*;
+import com.intellij.openapi.project.DumbService;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.ThrowableRunnable;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This is an adapter for running any FileProcessingCompiler as a compiler task
+ *
+ *
+ * @author Eugene Zhuravlev
+ * Date: 9/5/12
+ */
+public class FileProcessingCompilerAdapterTask implements CompileTask{
+ private final FileProcessingCompiler myCompiler;
+
+ public FileProcessingCompilerAdapterTask(FileProcessingCompiler compiler) {
+ myCompiler = compiler;
+ }
+
+ public FileProcessingCompiler getCompiler() {
+ return myCompiler;
+ }
+
+ @Override
+ public boolean execute(CompileContext context) {
+ final Project project = context.getProject();
+ if (!CompilerWorkspaceConfiguration.getInstance(project).useOutOfProcessBuild()) {
+ return true;
+ }
+ try {
+ final FileProcessingCompiler.ProcessingItem[] items = myCompiler.getProcessingItems(context);
+ if (items.length == 0) {
+ return true;
+ }
+ final List<FileProcessingCompiler.ProcessingItem> toProcess = new ArrayList<FileProcessingCompiler.ProcessingItem>();
+ final Ref<IOException> ex = new Ref<IOException>(null);
+
+ DumbService.getInstance(project).waitForSmartMode();
+
+ final FileProcessingCompilerStateCache cache = CompilerCacheManager.getInstance(project).getFileProcessingCompilerCache(myCompiler);
+ final boolean isMake = context.isMake();
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ try {
+ for (FileProcessingCompiler.ProcessingItem item : items) {
+ final VirtualFile file = item.getFile();
+ final String url = file.getUrl();
+ if (isMake && cache.getTimestamp(url) == file.getTimeStamp()) {
+ final ValidityState state = cache.getExtState(url);
+ final ValidityState itemState = item.getValidityState();
+ if (state != null ? state.equalsTo(itemState) : itemState == null) {
+ continue;
+ }
+ }
+ toProcess.add(item);
+ }
+ }
+ catch (IOException e) {
+ ex.set(e);
+ }
+ }
+ });
+
+ if (ex.get() != null) {
+ throw ex.get();
+ }
+
+ if (toProcess.isEmpty()) {
+ return true;
+ }
+
+ final FileProcessingCompiler.ProcessingItem[] processed = myCompiler.process(context, toProcess.toArray(new FileProcessingCompiler.ProcessingItem[toProcess.size()]));
+
+ if (processed.length == 0) {
+ return true;
+ }
+
+ CompilerUtil.runInContext(context, CompilerBundle.message("progress.updating.caches"), new ThrowableRunnable<IOException>() {
+ public void run() throws IOException{
+ final List<VirtualFile> vFiles = new ArrayList<VirtualFile>(processed.length);
+ for (FileProcessingCompiler.ProcessingItem item : processed) {
+ vFiles.add(item.getFile());
+ }
+ LocalFileSystem.getInstance().refreshFiles(vFiles);
+ for (FileProcessingCompiler.ProcessingItem item : processed) {
+ cache.update(item.getFile(), item.getValidityState());
+ }
+ }
+ });
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ return true;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/FileProcessingCompilerStateCache.java b/java/compiler/impl/src/com/intellij/compiler/impl/FileProcessingCompilerStateCache.java
new file mode 100644
index 0000000..050002d
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/FileProcessingCompilerStateCache.java
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ */
+
+/*
+ * @author: Eugene Zhuravlev
+ * Date: Apr 1, 2003
+ * Time: 1:17:50 PM
+ */
+package com.intellij.compiler.impl;
+
+import com.intellij.openapi.compiler.ValidityState;
+import com.intellij.openapi.compiler.ValidityStateFactory;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.*;
+import java.util.Collection;
+
+public class FileProcessingCompilerStateCache {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.FileProcessingCompilerStateCache");
+ private final StateCache<MyState> myCache;
+
+ public FileProcessingCompilerStateCache(File storeDirectory, final ValidityStateFactory stateFactory) throws IOException {
+ myCache = new StateCache<MyState>(new File(storeDirectory, "timestamps")) {
+ public MyState read(DataInput stream) throws IOException {
+ return new MyState(stream.readLong(), stateFactory.createValidityState(stream));
+ }
+
+ public void write(MyState state, DataOutput out) throws IOException {
+ out.writeLong(state.getTimestamp());
+ final ValidityState extState = state.getExtState();
+ if (extState != null) {
+ extState.save(out);
+ }
+ }
+ };
+ }
+
+ public void update(VirtualFile sourceFile, ValidityState extState) throws IOException {
+ if (sourceFile.isValid()) {
+ // only mark as up-to-date if the file did not become invalid during make
+ myCache.update(sourceFile.getUrl(), new MyState(sourceFile.getTimeStamp(), extState));
+ }
+ }
+
+ public void remove(String url) throws IOException {
+ myCache.remove(url);
+ }
+
+ public long getTimestamp(String url) throws IOException {
+ final Serializable savedState = myCache.getState(url);
+ if (savedState != null) {
+ LOG.assertTrue(savedState instanceof MyState);
+ }
+ MyState state = (MyState)savedState;
+ return (state != null)? state.getTimestamp() : -1L;
+ }
+
+ public ValidityState getExtState(String url) throws IOException {
+ MyState state = myCache.getState(url);
+ return (state != null)? state.getExtState() : null;
+ }
+
+ public void force() {
+ myCache.force();
+ }
+
+ public Collection<String> getUrls() throws IOException {
+ return myCache.getUrls();
+ }
+
+ public boolean wipe() {
+ return myCache.wipe();
+ }
+
+ public void close() {
+ try {
+ myCache.close();
+ }
+ catch (IOException ignored) {
+ LOG.info(ignored);
+ }
+ }
+
+ private static class MyState implements Serializable {
+ private final long myTimestamp;
+ private final ValidityState myExtState;
+
+ public MyState(long timestamp, @Nullable ValidityState extState) {
+ myTimestamp = timestamp;
+ myExtState = extState;
+ }
+
+ public long getTimestamp() {
+ return myTimestamp;
+ }
+
+ public @Nullable ValidityState getExtState() {
+ return myExtState;
+ }
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/FileSetCompileScope.java b/java/compiler/impl/src/com/intellij/compiler/impl/FileSetCompileScope.java
new file mode 100644
index 0000000..4a10ad3
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/FileSetCompileScope.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.CompileScope;
+import com.intellij.openapi.compiler.ExportableUserDataHolderBase;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileVisitor;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+
+/**
+ * @author Eugene Zhuravlev
+ * @since Jan 20, 2003
+ */
+public class FileSetCompileScope extends ExportableUserDataHolderBase implements CompileScope {
+ private final Set<VirtualFile> myRootFiles = new HashSet<VirtualFile>();
+ private final Set<String> myDirectoryUrls = new HashSet<String>();
+ private Set<String> myUrls = null; // urls caching
+ private final Module[] myAffectedModules;
+
+ public FileSetCompileScope(final Collection<VirtualFile> files, Module[] modules) {
+ myAffectedModules = modules;
+ ApplicationManager.getApplication().runReadAction(
+ new Runnable() {
+ public void run() {
+ for (VirtualFile file : files) {
+ assert file != null;
+ addFile(file);
+ }
+ }
+ }
+ );
+ }
+
+ @NotNull
+ public Module[] getAffectedModules() {
+ return myAffectedModules;
+ }
+
+ public Collection<VirtualFile> getRootFiles() {
+ return Collections.unmodifiableCollection(myRootFiles);
+ }
+
+ @NotNull
+ public VirtualFile[] getFiles(final FileType fileType, boolean inSourceOnly) {
+ final List<VirtualFile> files = new ArrayList<VirtualFile>();
+ for (Iterator<VirtualFile> it = myRootFiles.iterator(); it.hasNext();) {
+ VirtualFile file = it.next();
+ if (!file.isValid()) {
+ it.remove();
+ continue;
+ }
+ if (file.isDirectory()) {
+ addRecursively(files, file, fileType);
+ }
+ else {
+ if (fileType == null || fileType.equals(file.getFileType())) {
+ files.add(file);
+ }
+ }
+ }
+ return VfsUtilCore.toVirtualFileArray(files);
+ }
+
+ public boolean belongs(String url) {
+ //url = CompilerUtil.normalizePath(url, '/');
+ if (getUrls().contains(url)) {
+ return true;
+ }
+ for (String directoryUrl : myDirectoryUrls) {
+ if (FileUtil.startsWith(url, directoryUrl)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private Set<String> getUrls() {
+ if (myUrls == null) {
+ myUrls = new HashSet<String>();
+ for (VirtualFile file : myRootFiles) {
+ String url = file.getUrl();
+ myUrls.add(url);
+ }
+ }
+ return myUrls;
+ }
+
+ private void addFile(VirtualFile file) {
+ if (file.isDirectory()) {
+ myDirectoryUrls.add(file.getUrl() + "/");
+ }
+ myRootFiles.add(file);
+ myUrls = null;
+ }
+
+ private static void addRecursively(final Collection<VirtualFile> container, VirtualFile fromDirectory, final FileType fileType) {
+ VfsUtilCore.visitChildrenRecursively(fromDirectory, new VirtualFileVisitor(VirtualFileVisitor.SKIP_ROOT) {
+ @Override
+ public boolean visitFile(@NotNull VirtualFile child) {
+ if (!child.isDirectory() && (fileType == null || fileType.equals(child.getFileType()))) {
+ container.add(child);
+ }
+ return true;
+ }
+ });
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/GenericCompilerRunner.java b/java/compiler/impl/src/com/intellij/compiler/impl/GenericCompilerRunner.java
new file mode 100644
index 0000000..3cba065
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/GenericCompilerRunner.java
@@ -0,0 +1,338 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.compiler.impl;
+
+import com.intellij.compiler.impl.generic.GenericCompilerCache;
+import com.intellij.compiler.impl.generic.GenericCompilerPersistentData;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ReadAction;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.application.RunResult;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.compiler.CompilerMessageCategory;
+import com.intellij.openapi.compiler.CompilerPaths;
+import com.intellij.openapi.compiler.generic.*;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.openapi.project.DumbService;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Ref;
+import com.intellij.util.CommonProcessors;
+import com.intellij.util.Processor;
+import com.intellij.util.ThrowableRunnable;
+import com.intellij.util.io.KeyDescriptor;
+import gnu.trove.THashSet;
+import gnu.trove.TObjectHashingStrategy;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * @author nik
+ */
+public class GenericCompilerRunner {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.GenericCompilerRunner");
+ private static final Logger FULL_LOG = Logger.getInstance("#com.intellij.full-generic-compiler-log");
+ private CompileContext myContext;
+ private final boolean myForceCompile;
+ private final boolean myOnlyCheckStatus;
+ private final GenericCompiler<?,?,?>[] myCompilers;
+ private final Project myProject;
+
+ public GenericCompilerRunner(CompileContext context,
+ boolean forceCompile,
+ boolean onlyCheckStatus, final GenericCompiler[] compilers) {
+ myContext = context;
+ myForceCompile = forceCompile;
+ myOnlyCheckStatus = onlyCheckStatus;
+ myCompilers = compilers;
+ myProject = myContext.getProject();
+ }
+
+ public boolean invokeCompilers(GenericCompiler.CompileOrderPlace place) throws ExitException {
+ boolean didSomething = false;
+ try {
+ for (GenericCompiler<?,?,?> compiler : myCompilers) {
+ if (compiler.getOrderPlace().equals(place)) {
+ didSomething |= invokeCompiler(compiler);
+ }
+ }
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ myContext.requestRebuildNextTime(e.getMessage());
+ throw new ExitException(ExitStatus.ERRORS);
+ }
+ catch (ExitException e) {
+ throw e;
+ }
+ catch (ProcessCanceledException e) {
+ throw e;
+ }
+ catch (Exception e) {
+ LOG.info(e);
+ myContext.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("compiler.error.exception", e.getMessage()), null, -1, -1);
+ }
+ return didSomething;
+ }
+
+ private <Key, SourceState, OutputState> boolean invokeCompiler(GenericCompiler<Key, SourceState, OutputState> compiler) throws IOException,
+ ExitException {
+ return invokeCompiler(compiler, compiler.createInstance(myContext));
+ }
+
+ private <T extends BuildTarget, Item extends CompileItem<Key, SourceState, OutputState>, Key, SourceState, OutputState>
+ boolean invokeCompiler(GenericCompiler<Key, SourceState, OutputState> compiler, final GenericCompilerInstance<T, Item, Key, SourceState, OutputState> instance) throws IOException,
+ ExitException {
+ final GenericCompilerCache<Key, SourceState, OutputState> cache = CompilerCacheManager.getInstance(myProject).getGenericCompilerCache(compiler);
+ GenericCompilerPersistentData
+ data = new GenericCompilerPersistentData(getGenericCompilerCacheDir(myProject, compiler), compiler.getVersion());
+ if (data.isVersionChanged()) {
+ LOG.info("Clearing cache for " + compiler.getDescription());
+ cache.wipe();
+ data.save();
+ }
+
+ final Set<String> targetsToRemove = new HashSet<String>(data.getAllTargets());
+ new ReadAction() {
+ protected void run(final Result result) {
+ for (T target : instance.getAllTargets()) {
+ targetsToRemove.remove(target.getId());
+ }
+ }
+ }.execute();
+
+ if (!myOnlyCheckStatus) {
+ for (final String target : targetsToRemove) {
+ final int id = data.removeId(target);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Removing obsolete target '" + target + "' (id=" + id + ")");
+ }
+
+ final List<Key> keys = new ArrayList<Key>();
+ CompilerUtil.runInContext(myContext, "Processing obsolete targets...", new ThrowableRunnable<IOException>() {
+ @Override
+ public void run() throws IOException {
+ cache.processSources(id, new CommonProcessors.CollectProcessor<Key>(keys));
+ List<GenericCompilerCacheState<Key, SourceState, OutputState>> obsoleteSources = new ArrayList<GenericCompilerCacheState<Key,SourceState,OutputState>>();
+ for (Key key : keys) {
+ final GenericCompilerCache.PersistentStateData<SourceState, OutputState> state = cache.getState(id, key);
+ obsoleteSources.add(new GenericCompilerCacheState<Key,SourceState,OutputState>(key, state.mySourceState, state.myOutputState));
+ }
+ instance.processObsoleteTarget(target, obsoleteSources);
+ }
+ });
+ checkForErrorsOrCanceled();
+ for (Key key : keys) {
+ cache.remove(id, key);
+ }
+ }
+ }
+
+ final List<T> selectedTargets = new ReadAction<List<T>>() {
+ protected void run(final Result<List<T>> result) {
+ result.setResult(instance.getSelectedTargets());
+ }
+ }.execute().getResultObject();
+
+ boolean didSomething = false;
+ for (T target : selectedTargets) {
+ int id = data.getId(target.getId());
+ didSomething |= processTarget(target, id, compiler, instance, cache);
+ }
+
+ data.save();
+ return didSomething;
+ }
+
+ private void checkForErrorsOrCanceled() throws ExitException {
+ if (myContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
+ throw new ExitException(ExitStatus.ERRORS);
+ }
+ if (myContext.getProgressIndicator().isCanceled()) {
+ throw new ExitException(ExitStatus.CANCELLED);
+ }
+ }
+
+ public static File getGenericCompilerCacheDir(Project project, GenericCompiler<?,?,?> compiler) {
+ return new File(CompilerPaths.getCacheStoreDirectory(project), compiler.getId());
+ }
+
+ private <T extends BuildTarget, Item extends CompileItem<Key, SourceState, OutputState>, Key, SourceState, OutputState>
+ boolean processTarget(T target, final int targetId, final GenericCompiler<Key, SourceState, OutputState> compiler, final GenericCompilerInstance<T, Item, Key, SourceState, OutputState> instance,
+ final GenericCompilerCache<Key, SourceState, OutputState> cache) throws IOException, ExitException {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Processing target '" + target + "' (id=" + targetId + ") by " + compiler);
+ }
+ final List<Item> items = instance.getItems(target);
+ checkForErrorsOrCanceled();
+
+ final List<GenericCompilerProcessingItem<Item, SourceState, OutputState>> toProcess = new ArrayList<GenericCompilerProcessingItem<Item,SourceState,OutputState>>();
+ final THashSet<Key> keySet = new THashSet<Key>(new SourceItemHashingStrategy<Key>(compiler));
+ final Ref<IOException> exception = Ref.create(null);
+ DumbService.getInstance(myProject).waitForSmartMode();
+ final Map<Item, SourceState> sourceStates = new HashMap<Item,SourceState>();
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ for (Item item : items) {
+ final Key key = item.getKey();
+ keySet.add(key);
+ if (item.isExcluded()) continue;
+
+ final GenericCompilerCache.PersistentStateData<SourceState, OutputState> data = cache.getState(targetId, key);
+ SourceState sourceState = data != null ? data.mySourceState : null;
+ final OutputState outputState = data != null ? data.myOutputState : null;
+ if (myForceCompile || sourceState == null || !item.isSourceUpToDate(sourceState)
+ || outputState == null || !item.isOutputUpToDate(outputState)) {
+ sourceStates.put(item, item.computeSourceState());
+ toProcess.add(new GenericCompilerProcessingItem<Item,SourceState,OutputState>(item, sourceState, outputState));
+ }
+ }
+ }
+ catch (IOException e) {
+ exception.set(e);
+ }
+ }
+ });
+ if (!exception.isNull()) {
+ throw exception.get();
+ }
+
+ final List<Key> toRemove = new ArrayList<Key>();
+ cache.processSources(targetId, new Processor<Key>() {
+ @Override
+ public boolean process(Key key) {
+ if (!keySet.contains(key)) {
+ toRemove.add(key);
+ }
+ return true;
+ }
+ });
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(toProcess.size() + " items will be processed, " + toRemove.size() + " items will be removed");
+ for (int i = 0; i < getItemsCountToShowInLog(toProcess.size()); i++) {
+ LOG.debug("to process:" + toProcess.get(i).getItem().getKey());
+ }
+ for (int i = 0; i < getItemsCountToShowInLog(toRemove.size()); i++) {
+ LOG.debug("to delete:" + toRemove.get(i));
+ }
+ }
+
+ if (toProcess.isEmpty() && toRemove.isEmpty()) {
+ return false;
+ }
+
+ if (myOnlyCheckStatus) {
+ throw new ExitException(ExitStatus.CANCELLED);
+ }
+
+ List<GenericCompilerCacheState<Key, SourceState, OutputState>> obsoleteItems = new ArrayList<GenericCompilerCacheState<Key,SourceState,OutputState>>();
+ for (Key key : toRemove) {
+ final GenericCompilerCache.PersistentStateData<SourceState, OutputState> data = cache.getState(targetId, key);
+ obsoleteItems.add(new GenericCompilerCacheState<Key,SourceState,OutputState>(key, data.mySourceState, data.myOutputState));
+ }
+
+ final List<Item> processedItems = new ArrayList<Item>();
+ final List<File> filesToRefresh = new ArrayList<File>();
+ final List<File> dirsToRefresh = new ArrayList<File>();
+ instance.processItems(target, toProcess, obsoleteItems, new GenericCompilerInstance.OutputConsumer<Item>() {
+ @Override
+ public void addFileToRefresh(@NotNull File file) {
+ filesToRefresh.add(file);
+ }
+
+ @Override
+ public void addDirectoryToRefresh(@NotNull File dir) {
+ dirsToRefresh.add(dir);
+ }
+
+ @Override
+ public void addProcessedItem(@NotNull Item sourceItem) {
+ processedItems.add(sourceItem);
+ }
+ });
+ checkForErrorsOrCanceled();
+
+ CompilerUtil.runInContext(myContext, CompilerBundle.message("progress.updating.caches"), new ThrowableRunnable<IOException>() {
+ @Override
+ public void run() throws IOException {
+ for (Key key : toRemove) {
+ cache.remove(targetId, key);
+ }
+ CompilerUtil.refreshIOFiles(filesToRefresh);
+ CompilerUtil.refreshIODirectories(dirsToRefresh);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("refreshed " + filesToRefresh.size() + " files and " + dirsToRefresh.size() + " dirs");
+ for (int i = 0; i < getItemsCountToShowInLog(filesToRefresh.size()); i++) {
+ LOG.debug("file: " + filesToRefresh.get(i));
+ }
+ for (int i = 0; i < getItemsCountToShowInLog(dirsToRefresh.size()); i++) {
+ LOG.debug("dir: " + dirsToRefresh.get(i));
+ }
+ }
+
+ final RunResult runResult = new ReadAction() {
+ protected void run(final Result result) throws Throwable {
+ for (Item item : processedItems) {
+ SourceState sourceState = sourceStates.get(item);
+ if (sourceState == null) {
+ sourceState = item.computeSourceState();
+ }
+ cache.putState(targetId, item.getKey(), sourceState, item.computeOutputState());
+ }
+ }
+ }.executeSilently();
+ Throwable throwable = runResult.getThrowable();
+ if (throwable instanceof IOException) throw (IOException) throwable;
+ else if (throwable != null) throw new RuntimeException(throwable);
+ }
+ });
+
+ return true;
+
+ }
+
+ private static int getItemsCountToShowInLog(final int size) {
+ if (size > 100 && !FULL_LOG.isDebugEnabled()) {
+ return 100;
+ }
+ return size;
+ }
+
+ private class SourceItemHashingStrategy<S> implements TObjectHashingStrategy<S> {
+ private KeyDescriptor<S> myKeyDescriptor;
+
+ public SourceItemHashingStrategy(GenericCompiler<S, ?, ?> compiler) {
+ myKeyDescriptor = compiler.getItemKeyDescriptor();
+ }
+
+ @Override
+ public int computeHashCode(S object) {
+ return myKeyDescriptor.getHashCode(object);
+ }
+
+ @Override
+ public boolean equals(S o1, S o2) {
+ return myKeyDescriptor.isEqual(o1, o2);
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/InternedPath.java b/java/compiler/impl/src/com/intellij/compiler/impl/InternedPath.java
new file mode 100644
index 0000000..7914dc2
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/InternedPath.java
@@ -0,0 +1,79 @@
+/*
+ * 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 com.intellij.compiler.impl;
+
+import com.intellij.util.StringBuilderSpinAllocator;
+import com.intellij.util.containers.StringInterner;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Jun 21, 2006
+ */
+public class InternedPath {
+ private final @NotNull List<String> myValue;
+
+ public InternedPath(StringInterner interner, String url, final char separator) {
+ myValue = convert(interner, url, separator);
+ }
+
+ public String toString() {
+ return join(myValue, '/');
+ }
+
+ public static List<String> convert(StringInterner interner, String value, char delim) {
+ final List<String> result = new ArrayList<String>();
+ int start = 0;
+ final int len = value.length();
+ for (int idx = 0; idx < len; idx++) {
+ if (value.charAt(idx) == delim) {
+ result.add(interner.intern(value.substring(start, idx)));
+ start = idx + 1;
+ }
+ }
+ if (start < value.length()) {
+ result.add(interner.intern(value.substring(start)));
+ }
+ if (len > 0 && value.charAt(len-1) == delim) { // ends with delimiter
+ result.add("");
+ }
+ return result;
+ }
+
+ public static String join(List<String> value, char separator) {
+ final int size = value.size();
+ if (size > 1) {
+ final StringBuilder builder = StringBuilderSpinAllocator.alloc();
+ try {
+ builder.append(value.get(0));
+ for (int idx = 1; idx < size; idx++) {
+ builder.append(separator).append(value.get(idx));
+ }
+ return builder.toString();
+ }
+ finally {
+ StringBuilderSpinAllocator.dispose(builder);
+ }
+ }
+ else if (size == 1){
+ return value.get(0);
+ }
+ return "";
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/ModuleCompileScope.java b/java/compiler/impl/src/com/intellij/compiler/impl/ModuleCompileScope.java
new file mode 100644
index 0000000..3be5efc
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/ModuleCompileScope.java
@@ -0,0 +1,177 @@
+/*
+ * 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.
+ */
+
+/*
+ * @author: Eugene Zhuravlev
+ * Date: Jan 20, 2003
+ * Time: 5:34:19 PM
+ */
+package com.intellij.compiler.impl;
+
+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.FileIndex;
+import com.intellij.openapi.roots.ModuleRootManager;
+import com.intellij.openapi.roots.ProjectFileIndex;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class ModuleCompileScope extends FileIndexCompileScope {
+ private final Project myProject;
+ private final Set<Module> myScopeModules;
+ private final Module[] myModules;
+
+ public ModuleCompileScope(final Module module, boolean includeDependentModules) {
+ myProject = module.getProject();
+ myScopeModules = new HashSet<Module>();
+ if (includeDependentModules) {
+ buildScopeModulesSet(module);
+ }
+ else {
+ myScopeModules.add(module);
+ }
+ myModules = ModuleManager.getInstance(myProject).getModules();
+ }
+
+ public ModuleCompileScope(Project project, final Module[] modules, boolean includeDependentModules) {
+ myProject = project;
+ myScopeModules = new HashSet<Module>();
+ for (Module module : modules) {
+ if (module == null) {
+ continue; // prevent NPE
+ }
+ if (includeDependentModules) {
+ buildScopeModulesSet(module);
+ }
+ else {
+ myScopeModules.add(module);
+ }
+ }
+ myModules = ModuleManager.getInstance(myProject).getModules();
+ }
+
+ private void buildScopeModulesSet(Module module) {
+ myScopeModules.add(module);
+ final Module[] dependencies = ModuleRootManager.getInstance(module).getDependencies();
+ for (Module dependency : dependencies) {
+ if (!myScopeModules.contains(dependency)) { // may be in case of module circular dependencies
+ buildScopeModulesSet(dependency);
+ }
+ }
+ }
+
+ @NotNull
+ public Module[] getAffectedModules() {
+ return myScopeModules.toArray(new Module[myScopeModules.size()]);
+ }
+
+ protected FileIndex[] getFileIndices() {
+ final FileIndex[] indices = new FileIndex[myScopeModules.size()];
+ int idx = 0;
+ for (final Module module : myScopeModules) {
+ indices[idx++] = ModuleRootManager.getInstance(module).getFileIndex();
+ }
+ return indices;
+ }
+
+ public boolean belongs(final String url) {
+ if (myScopeModules.isEmpty()) {
+ return false; // optimization
+ }
+ Module candidateModule = null;
+ int maxUrlLength = 0;
+ final ProjectFileIndex projectFileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
+ for (final Module module : myModules) {
+ final String[] contentRootUrls = getModuleContentUrls(module);
+ for (final String contentRootUrl : contentRootUrls) {
+ if (contentRootUrl.length() < maxUrlLength) {
+ continue;
+ }
+ if (!isUrlUnderRoot(url, contentRootUrl)) {
+ continue;
+ }
+ if (contentRootUrl.length() == maxUrlLength) {
+ if (candidateModule == null) {
+ candidateModule = module;
+ }
+ else {
+ // the same content root exists in several modules
+ if (!candidateModule.equals(module)) {
+ candidateModule = ApplicationManager.getApplication().runReadAction(new Computable<Module>() {
+ public Module compute() {
+ final VirtualFile contentRootFile = VirtualFileManager.getInstance().findFileByUrl(contentRootUrl);
+ if (contentRootFile != null) {
+ return projectFileIndex.getModuleForFile(contentRootFile);
+ }
+ return null;
+ }
+ });
+ }
+ }
+ }
+ else {
+ maxUrlLength = contentRootUrl.length();
+ candidateModule = module;
+ }
+ }
+ }
+
+ if (candidateModule != null && myScopeModules.contains(candidateModule)) {
+ final ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(candidateModule);
+ final String[] excludeRootUrls = moduleRootManager.getExcludeRootUrls();
+ for (String excludeRootUrl : excludeRootUrls) {
+ if (isUrlUnderRoot(url, excludeRootUrl)) {
+ return false;
+ }
+ }
+ final String[] sourceRootUrls = moduleRootManager.getSourceRootUrls();
+ for (String sourceRootUrl : sourceRootUrls) {
+ if (isUrlUnderRoot(url, sourceRootUrl)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean isUrlUnderRoot(final String url, final String root) {
+ return (url.length() > root.length()) && url.charAt(root.length()) == '/' && FileUtil.startsWith(url, root);
+ }
+
+ private final Map<Module, String[]> myContentUrlsCache = new HashMap<Module, String[]>();
+
+ private String[] getModuleContentUrls(final Module module) {
+ String[] contentRootUrls = myContentUrlsCache.get(module);
+ if (contentRootUrls == null) {
+ contentRootUrls = ModuleRootManager.getInstance(module).getContentRootUrls();
+ myContentUrlsCache.put(module, contentRootUrls);
+ }
+ return contentRootUrls;
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/OneProjectItemCompileScope.java b/java/compiler/impl/src/com/intellij/compiler/impl/OneProjectItemCompileScope.java
new file mode 100644
index 0000000..4b0b653
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/OneProjectItemCompileScope.java
@@ -0,0 +1,80 @@
+/*
+ * 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 com.intellij.compiler.impl;
+
+import com.intellij.openapi.compiler.CompileScope;
+import com.intellij.openapi.compiler.ExportableUserDataHolderBase;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleUtil;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ContentIterator;
+import com.intellij.openapi.roots.FileIndex;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class OneProjectItemCompileScope extends ExportableUserDataHolderBase implements CompileScope{
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.OneProjectItemCompileScope");
+ private final Project myProject;
+ private final VirtualFile myFile;
+ private final String myUrl;
+
+ public OneProjectItemCompileScope(Project project, VirtualFile file) {
+ myProject = project;
+ myFile = file;
+ final String url = file.getUrl();
+ myUrl = file.isDirectory()? url + "/" : url;
+ }
+
+ @NotNull
+ public VirtualFile[] getFiles(final FileType fileType, final boolean inSourceOnly) {
+ final List<VirtualFile> files = new ArrayList<VirtualFile>(1);
+ final FileIndex projectFileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
+ final ContentIterator iterator = new CompilerContentIterator(fileType, projectFileIndex, inSourceOnly, files);
+ if (myFile.isDirectory()){
+ projectFileIndex.iterateContentUnderDirectory(myFile, iterator);
+ }
+ else{
+ iterator.processFile(myFile);
+ }
+ return VfsUtil.toVirtualFileArray(files);
+ }
+
+ public boolean belongs(String url) {
+ if (myFile.isDirectory()){
+ return FileUtil.startsWith(url, myUrl);
+ }
+ return FileUtil.pathsEqual(url, myUrl);
+ }
+
+ @NotNull
+ public Module[] getAffectedModules() {
+ final Module module = ModuleUtil.findModuleForFile(myFile, myProject);
+ if (module == null) {
+ LOG.error("Module is null for file " + myFile.getPresentableUrl());
+ return Module.EMPTY_ARRAY;
+ }
+ return new Module[] {module};
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/OutputPathFinder.java b/java/compiler/impl/src/com/intellij/compiler/impl/OutputPathFinder.java
new file mode 100644
index 0000000..6a712d1
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/OutputPathFinder.java
@@ -0,0 +1,219 @@
+/*
+ * 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 com.intellij.compiler.impl;
+
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.io.FileUtil;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Apr 6, 2009
+ */
+public class OutputPathFinder {
+ private final Node myRoot = new Node("");
+
+ public OutputPathFinder(Set<File> outputDirs) {
+ for (File dir : outputDirs) {
+ final String path = FileUtil.toSystemIndependentName(dir.getPath());
+ Node node = myRoot;
+ int idx = path.startsWith("/")? 1 : 0;
+ int slashIndex = path.indexOf('/', idx);
+
+ for (; slashIndex >= 0; slashIndex = path.indexOf('/', idx)) {
+ final String name = path.substring(idx, slashIndex);
+ node = node.addChild(name);
+ idx = slashIndex + 1;
+ }
+
+ if (idx < path.length()) {
+ node = node.addChild(path.substring(idx));
+ }
+
+ node.setData(path);
+ }
+ }
+
+ @Nullable
+ public String lookupOutputPath(File outputFile) {
+ return lookupOutputPath(outputFile.getPath());
+ }
+
+ @Nullable
+ public String lookupOutputPath(String filePath) {
+ final String path = FileUtil.toSystemIndependentName(filePath);
+ Node node = myRoot;
+ int idx = path.startsWith("/")? 1 : 0;
+
+ return findOutputPath(path, idx, node);
+ }
+
+ private static @Nullable String findOutputPath(final String path, int idx, Node node) {
+ while (true) {
+ final int slashIndex = path.indexOf('/', idx);
+ final String name = slashIndex < idx? path.substring(idx) : path.substring(idx, slashIndex);
+ node = node.getChild(name);
+ if (node == null) {
+ return null;
+ }
+ if (node.isOutputRoot()) {
+ if (node.hasChildren() && slashIndex > idx) {
+ final String candidate = findOutputPath(path, slashIndex + 1, node);
+ if (candidate != null) {
+ return candidate;
+ }
+ }
+ return node.getData();
+ }
+ if (slashIndex < 0) {
+ return null; // end of path reached
+ }
+ idx = slashIndex + 1;
+ }
+ }
+
+ private static class Node {
+ private final String myName;
+ @Nullable
+ private Object myChildren; // either a Node or a Map<String, Node> or a String or a Pair<String, Node or NodeMap>
+
+ private Node(String name) {
+ myName = name;
+ }
+
+ public String getName() {
+ return myName;
+ }
+
+ public boolean isOutputRoot() {
+ return myChildren instanceof String || myChildren instanceof Pair;
+ }
+
+ public boolean hasChildren() {
+ return myChildren instanceof Map || myChildren instanceof Pair;
+ }
+
+ @Nullable
+ public String getData() {
+ if (myChildren instanceof String) {
+ return (String)myChildren;
+ }
+ if (myChildren instanceof Pair) {
+ //noinspection unchecked
+ return (String)((Pair)myChildren).first;
+ }
+ return null;
+ }
+
+ public void setData(String path) {
+ if (myChildren != null) {
+ if (myChildren instanceof String) {
+ myChildren = path;
+ }
+ else {
+ myChildren = new Pair(path, myChildren instanceof Pair? ((Pair)myChildren).second : myChildren);
+ }
+ }
+ else {
+ myChildren = path;
+ }
+ }
+
+ public Node addChild(String childName) {
+ if (myChildren == null) {
+ final Node node = new Node(childName);
+ myChildren = node;
+ return node;
+ }
+ if (myChildren instanceof String) {
+ final Node node = new Node(childName);
+ myChildren = new Pair(myChildren, node);
+ return node;
+ }
+
+ final Map<String, Node> map;
+ if (myChildren instanceof Map) {
+ map = (Map<String, Node>)myChildren;
+ }
+ else if (myChildren instanceof Node) {
+ final Node existingChild = (Node)myChildren;
+ myChildren = map = new HashMap<String, Node>();
+ map.put(existingChild.getName(), existingChild);
+ }
+ else { // myChildren is a Pair
+ Object children = ((Pair)myChildren).second;
+ if (children instanceof Map) {
+ map = (Map<String, Node>)children;
+ }
+ else {
+ final Node existingChild = (Node)children;
+ myChildren = new Pair(((Pair)myChildren).first, map = new HashMap<String, Node>());
+ map.put(existingChild.getName(), existingChild);
+ }
+ }
+
+ Node node = map.get(childName);
+ if (node == null) {
+ map.put(childName, node = new Node(childName));
+ }
+ return node;
+ }
+
+ @Nullable
+ public Node getChild(String childName) {
+ final Object children = myChildren instanceof Pair? ((Pair)myChildren).second : myChildren;
+ if (children instanceof Node) {
+ final Node childNode = (Node)children;
+ return childName.equals(childNode.getName())? childNode : null;
+ }
+ if (children instanceof Map) {
+ return ((Map<String, Node>)myChildren).get(childName);
+ }
+ return null;
+ }
+ }
+
+
+ public static void main(String[] args) {
+ final Set<File> set = new HashSet<File>();
+ set.add(new File("/a/b/c"));
+ set.add(new File("a/b/d"));
+ set.add(new File("a/b/e"));
+ set.add(new File("/a/b/f/g"));
+ set.add(new File("/a/b/f/g/zzz"));
+
+ final OutputPathFinder finder = new OutputPathFinder(set);
+
+ System.out.println(finder.lookupOutputPath(new File("a/b")));
+ System.out.println(finder.lookupOutputPath(new File("a/b/c/dir1/dir2/File.class")));
+ System.out.println(finder.lookupOutputPath(new File("a/b/d/dir1/dir2/File.class")));
+ System.out.println(finder.lookupOutputPath(new File("a/b/jjjjj/dir1/dir2/File.class")));
+ System.out.println(finder.lookupOutputPath(new File("a/b/e/File.class")));
+ System.out.println(finder.lookupOutputPath(new File("a/b/File.class")));
+
+ System.out.println(finder.lookupOutputPath(new File("/a/b/f/g/File.class")));
+ System.out.println(finder.lookupOutputPath(new File("/a/b/f/g/ttt/yy/File.class")));
+ System.out.println(finder.lookupOutputPath(new File("/a/b/f/g/zzz/File.class")));
+ System.out.println(finder.lookupOutputPath(new File("/a/b/f/g/zzz/mmm/ttt/File.class")));
+
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/PackagingCompilerAdapter.java b/java/compiler/impl/src/com/intellij/compiler/impl/PackagingCompilerAdapter.java
new file mode 100644
index 0000000..d2f7826
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/PackagingCompilerAdapter.java
@@ -0,0 +1,34 @@
+/*
+ * 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 com.intellij.compiler.impl;
+
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.PackagingCompiler;
+import com.intellij.openapi.compiler.ValidityState;
+import org.jetbrains.annotations.Nullable;
+
+public class PackagingCompilerAdapter extends FileProcessingCompilerAdapter{
+ private final PackagingCompiler myCompiler;
+
+ public PackagingCompilerAdapter(CompileContext compileContext, PackagingCompiler compiler) {
+ super(compileContext, compiler);
+ myCompiler = compiler;
+ }
+
+ public void processOutdatedItem(CompileContext context, String url, @Nullable ValidityState state) {
+ myCompiler.processOutdatedItem(context, url, state);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/ProblemsViewImpl.java b/java/compiler/impl/src/com/intellij/compiler/impl/ProblemsViewImpl.java
new file mode 100644
index 0000000..320898a
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/ProblemsViewImpl.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl;
+
+import com.intellij.compiler.ProblemsView;
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.errorTreeView.ErrorTreeElement;
+import com.intellij.ide.errorTreeView.ErrorViewStructure;
+import com.intellij.ide.errorTreeView.GroupingElement;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.CompileScope;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.wm.ToolWindow;
+import com.intellij.openapi.wm.ToolWindowAnchor;
+import com.intellij.openapi.wm.ToolWindowManager;
+import com.intellij.pom.Navigatable;
+import com.intellij.ui.content.Content;
+import com.intellij.ui.content.ContentFactory;
+import com.intellij.util.concurrency.SequentialTaskExecutor;
+import com.intellij.util.ui.UIUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.UUID;
+import java.util.concurrent.Executor;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 9/18/12
+ */
+public class ProblemsViewImpl extends ProblemsView{
+ private static final String PROBLEMS_TOOLWINDOW_ID = "Problems";
+
+ private final ProblemsViewPanel myPanel;
+ private final SequentialTaskExecutor myViewUpdater = new SequentialTaskExecutor(new Executor() {
+ @Override
+ public void execute(Runnable command) {
+ ApplicationManager.getApplication().executeOnPooledThread(command);
+ }
+ });
+
+ public ProblemsViewImpl(final Project project, final ToolWindowManager wm) {
+ super(project);
+ myPanel = new ProblemsViewPanel(project);
+ Disposer.register(project, new Disposable() {
+ @Override
+ public void dispose() {
+ Disposer.dispose(myPanel);
+ }
+ });
+ UIUtil.invokeLaterIfNeeded(new Runnable() {
+ @Override
+ public void run() {
+ if (project.isDisposed()) {
+ return;
+ }
+ final ToolWindow tw = wm.registerToolWindow(PROBLEMS_TOOLWINDOW_ID, false, ToolWindowAnchor.BOTTOM, project);
+ tw.setIcon(AllIcons.Toolwindows.Problems);
+ final Content content = ContentFactory.SERVICE.getInstance().createContent(myPanel, "", false);
+ // todo: setup content?
+ tw.getContentManager().addContent(content);
+ Disposer.register(project, new Disposable() {
+ @Override
+ public void dispose() {
+ tw.getContentManager().removeAllContents(true);
+ }
+ });
+ }
+ });
+ }
+
+ @Override
+ public void clearOldMessages(@Nullable final CompileScope scope, @NotNull final UUID currentSessionId) {
+ myViewUpdater.execute(new Runnable() {
+ @Override
+ public void run() {
+ cleanupChildrenRecursively(myPanel.getErrorViewStructure().getRootElement(), scope, currentSessionId);
+ myPanel.reload();
+ }
+ });
+ }
+
+ private void cleanupChildrenRecursively(@NotNull final Object fromElement, final @Nullable CompileScope scope, @NotNull UUID currentSessionId) {
+ final ErrorViewStructure structure = myPanel.getErrorViewStructure();
+ for (ErrorTreeElement element : structure.getChildElements(fromElement)) {
+ if (element instanceof GroupingElement) {
+ if (scope != null) {
+ final VirtualFile file = ((GroupingElement)element).getFile();
+ if (file != null && !scope.belongs(file.getUrl())) {
+ continue;
+ }
+ }
+ if (!currentSessionId.equals(element.getData())) {
+ structure.removeElement(element);
+ }
+ else {
+ cleanupChildrenRecursively(element, scope, currentSessionId);
+ }
+ }
+ else {
+ if (!currentSessionId.equals(element.getData())) {
+ structure.removeElement(element);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void addMessage(final int type,
+ @NotNull final String[] text,
+ @Nullable final String groupName,
+ @Nullable final Navigatable navigatable,
+ @Nullable final String exportTextPrefix, @Nullable final String rendererTextPrefix, @Nullable final UUID sessionId) {
+
+ myViewUpdater.execute(new Runnable() {
+ @Override
+ public void run() {
+ final ErrorViewStructure structure = myPanel.getErrorViewStructure();
+ final GroupingElement group = structure.lookupGroupingElement(groupName);
+ if (group != null && !sessionId.equals(group.getData())) {
+ structure.removeElement(group);
+ }
+ if (navigatable != null) {
+ myPanel.addMessage(type, text, groupName, navigatable, exportTextPrefix, rendererTextPrefix, sessionId);
+ }
+ else {
+ myPanel.addMessage(type, text, null, -1, -1, sessionId);
+ }
+ }
+ });
+ }
+
+ @Override
+ public void setProgress(String text, float fraction) {
+ myPanel.setProgress(text, fraction);
+ }
+
+ @Override
+ public void setProgress(String text) {
+ myPanel.setProgressText(text);
+ }
+
+ @Override
+ public void clearProgress() {
+ myPanel.clearProgressData();
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/ProblemsViewPanel.java b/java/compiler/impl/src/com/intellij/compiler/impl/ProblemsViewPanel.java
new file mode 100644
index 0000000..d65451e
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/ProblemsViewPanel.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl;
+
+import com.intellij.ide.errorTreeView.NewErrorTreeViewPanel;
+import com.intellij.openapi.actionSystem.DefaultActionGroup;
+import com.intellij.openapi.project.Project;
+
+public class ProblemsViewPanel extends NewErrorTreeViewPanel {
+ public ProblemsViewPanel(Project project) {
+ super(project, null, false, true, null);
+ myTree.getEmptyText().setText("No compilation problems found");
+ }
+
+
+ protected void fillRightToolbarGroup(DefaultActionGroup group) {
+ super.fillRightToolbarGroup(group);
+ group.add(new CompilerPropertiesAction());
+ }
+
+ protected void addExtraPopupMenuActions(DefaultActionGroup group) {
+ group.add(new ExcludeFromCompileAction(myProject, this));
+ // todo: do we need compiler's popup actions here?
+ //ActionGroup popupGroup = (ActionGroup)ActionManager.getInstance().getAction(IdeActions.GROUP_COMPILER_ERROR_VIEW_POPUP);
+ //if (popupGroup != null) {
+ // for (AnAction action : popupGroup.getChildren(null)) {
+ // group.add(action);
+ // }
+ //}
+ }
+
+ @Override
+ protected boolean shouldShowFirstErrorInEditor() {
+ return false;
+ }
+
+ @Override
+ protected boolean canHideWarnings() {
+ return false;
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/ProjectCompileScope.java b/java/compiler/impl/src/com/intellij/compiler/impl/ProjectCompileScope.java
new file mode 100644
index 0000000..ed41606
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/ProjectCompileScope.java
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+/*
+ * @author: Eugene Zhuravlev
+ * Date: Jan 20, 2003
+ * Time: 5:34:19 PM
+ */
+package com.intellij.compiler.impl;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.FileIndex;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import org.jetbrains.annotations.NotNull;
+
+public class ProjectCompileScope extends FileIndexCompileScope {
+ private final Project myProject;
+
+ public ProjectCompileScope(final Project project) {
+ myProject = project;
+ }
+
+ protected FileIndex[] getFileIndices() {
+ return new FileIndex[] {ProjectRootManager.getInstance(myProject).getFileIndex()};
+ }
+
+ public boolean belongs(String url) {
+ final VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(url);
+ if (file != null) {
+ for (FileIndex index : getFileIndices()) {
+ if (index.isInSourceContent(file)) {
+ return true;
+ }
+ }
+ }
+ else {
+ // the file might be deleted
+ for (VirtualFile root : ProjectRootManager.getInstance(myProject).getContentSourceRoots()) {
+ final String rootUrl = root.getUrl();
+ if (FileUtil.startsWith(url, rootUrl.endsWith("/")? rootUrl : rootUrl + "/")) {
+ return true;
+ }
+ }
+ }
+ return false;
+ //return !FileUtil.startsWith(url, myTempDirUrl);
+ }
+
+ @NotNull
+ public Module[] getAffectedModules() {
+ return ModuleManager.getInstance(myProject).getModules();
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/SourceUrlClassNamePair.java b/java/compiler/impl/src/com/intellij/compiler/impl/SourceUrlClassNamePair.java
new file mode 100644
index 0000000..6d9a60d
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/SourceUrlClassNamePair.java
@@ -0,0 +1,40 @@
+/*
+ * 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 com.intellij.compiler.impl;
+
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Jun 21, 2006
+ */
+public class SourceUrlClassNamePair {
+ private final String mySourceUrl;
+ private final @Nullable String myClassName;
+
+ public SourceUrlClassNamePair(String url, @Nullable String className) {
+ mySourceUrl = url;
+ myClassName = className;
+ }
+
+ public String getSourceUrl() {
+ return mySourceUrl;
+ }
+
+ public @Nullable String getClassName() {
+ return myClassName;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/StateCache.java b/java/compiler/impl/src/com/intellij/compiler/impl/StateCache.java
new file mode 100644
index 0000000..bd56b66
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/StateCache.java
@@ -0,0 +1,105 @@
+/*
+ * 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 com.intellij.compiler.impl;
+
+import com.intellij.util.io.DataExternalizer;
+import com.intellij.util.io.EnumeratorStringDescriptor;
+import com.intellij.util.io.PersistentHashMap;
+import org.jetbrains.annotations.NonNls;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Iterator;
+
+public abstract class StateCache<T> {
+ private PersistentHashMap<String, T> myMap;
+ private final File myBaseFile;
+
+ public StateCache(@NonNls File storePath) throws IOException {
+ myBaseFile = storePath;
+ myMap = createMap(storePath);
+ }
+
+ protected abstract T read(DataInput stream) throws IOException;
+
+ protected abstract void write(T t, DataOutput out) throws IOException;
+
+ public void force() {
+ myMap.force();
+ }
+
+ public void close() throws IOException {
+ myMap.close();
+ }
+
+ public boolean wipe() {
+ try {
+ myMap.close();
+ }
+ catch (IOException ignored) {
+ }
+ PersistentHashMap.deleteFilesStartingWith(myBaseFile);
+ try {
+ myMap = createMap(myBaseFile);
+ }
+ catch (IOException ignored) {
+ return false;
+ }
+ return true;
+ }
+
+ public void update(@NonNls String url, T state) throws IOException {
+ if (state != null) {
+ myMap.put(url, state);
+ }
+ else {
+ remove(url);
+ }
+ }
+
+ public void remove(String url) throws IOException {
+ myMap.remove(url);
+ }
+
+ public T getState(String url) throws IOException {
+ return myMap.get(url);
+ }
+
+ public Collection<String> getUrls() throws IOException {
+ return myMap.getAllKeysWithExistingMapping();
+ }
+
+ public Iterator<String> getUrlsIterator() throws IOException {
+ return myMap.getAllKeysWithExistingMapping().iterator();
+ }
+
+
+ private PersistentHashMap<String, T> createMap(final File file) throws IOException {
+ return new PersistentHashMap<String,T>(file, new EnumeratorStringDescriptor(), new DataExternalizer<T>() {
+ public void save(final DataOutput out, final T value) throws IOException {
+ StateCache.this.write(value, out);
+ }
+
+ public T read(final DataInput in) throws IOException {
+ return StateCache.this.read(in);
+ }
+ });
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/TranslatingCompilerFilesMonitor.java b/java/compiler/impl/src/com/intellij/compiler/impl/TranslatingCompilerFilesMonitor.java
new file mode 100644
index 0000000..8ba9de9
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/TranslatingCompilerFilesMonitor.java
@@ -0,0 +1,1920 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl;
+
+import com.intellij.ProjectTopics;
+import com.intellij.compiler.CompilerConfiguration;
+import com.intellij.compiler.CompilerIOUtil;
+import com.intellij.compiler.CompilerWorkspaceConfiguration;
+import com.intellij.compiler.make.MakeUtil;
+import com.intellij.compiler.server.BuildManager;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.application.Application;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ModalityState;
+import com.intellij.openapi.compiler.*;
+import com.intellij.openapi.compiler.ex.CompileContextEx;
+import com.intellij.openapi.components.ApplicationComponent;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.fileTypes.FileTypeManager;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.progress.Task;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
+import com.intellij.openapi.project.ProjectManagerAdapter;
+import com.intellij.openapi.roots.*;
+import com.intellij.openapi.startup.StartupManager;
+import com.intellij.openapi.util.*;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.*;
+import com.intellij.openapi.vfs.newvfs.FileAttribute;
+import com.intellij.openapi.vfs.newvfs.ManagingFS;
+import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
+import com.intellij.openapi.vfs.newvfs.persistent.FSRecords;
+import com.intellij.openapi.vfs.newvfs.persistent.PersistentFS;
+import com.intellij.util.Alarm;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.SLRUCache;
+import com.intellij.util.indexing.FileBasedIndex;
+import com.intellij.util.indexing.IndexInfrastructure;
+import com.intellij.util.io.*;
+import com.intellij.util.io.DataOutputStream;
+import com.intellij.util.messages.MessageBusConnection;
+import gnu.trove.*;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.*;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * @author Eugene Zhuravlev
+ * @since Jun 3, 2008
+ *
+ * A source file is scheduled for recompilation if
+ * 1. its timestamp has changed
+ * 2. one of its corresponding output files was deleted
+ * 3. output root of containing module has changed
+ *
+ * An output file is scheduled for deletion if:
+ * 1. corresponding source file has been scheduled for recompilation (see above)
+ * 2. corresponding source file has been deleted
+ */
+public class TranslatingCompilerFilesMonitor implements ApplicationComponent {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.TranslatingCompilerFilesMonitor");
+ private static final boolean ourDebugMode = false;
+
+ private static final FileAttribute ourSourceFileAttribute = new FileAttribute("_make_source_file_info_", 3);
+ private static final FileAttribute ourOutputFileAttribute = new FileAttribute("_make_output_file_info_", 3);
+ private static final Key<Map<String, VirtualFile>> SOURCE_FILES_CACHE = Key.create("_source_url_to_vfile_cache_");
+
+ private final Object myDataLock = new Object();
+
+ private final TIntHashSet mySuspendedProjects = new TIntHashSet(); // projectId for all projects that should not be monitored
+
+ private final TIntObjectHashMap<TIntHashSet> mySourcesToRecompile = new TIntObjectHashMap<TIntHashSet>(); // ProjectId->set of source file paths
+ private PersistentHashMap<Integer, TIntObjectHashMap<Pair<Integer, Integer>>> myOutputRootsStorage; // ProjectId->map[moduleId->Pair(outputDirId, testOutputDirId)]
+
+ // Map: projectId -> Map{output path -> [sourceUrl; className]}
+ private final SLRUCache<Integer, Outputs> myOutputsToDelete = new SLRUCache<Integer, Outputs>(3, 3) {
+ @Override
+ public Outputs getIfCached(Integer key) {
+ final Outputs value = super.getIfCached(key);
+ if (value != null) {
+ value.allocate();
+ }
+ return value;
+ }
+
+ @NotNull
+ @Override
+ public Outputs get(Integer key) {
+ final Outputs value = super.get(key);
+ value.allocate();
+ return value;
+ }
+
+ @NotNull
+ @Override
+ public Outputs createValue(Integer key) {
+ try {
+ final String dirName = FSRecords.getNames().valueOf(key);
+ final File storeFile;
+ if (StringUtil.isEmpty(dirName)) {
+ storeFile = null;
+ }
+ else {
+ final File compilerCacheDir = CompilerPaths.getCacheStoreDirectory(dirName);
+ storeFile = compilerCacheDir.exists()? new File(compilerCacheDir, "paths_to_delete.dat") : null;
+ }
+ return new Outputs(storeFile, loadPathsToDelete(storeFile));
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ return new Outputs(null, new HashMap<String, SourceUrlClassNamePair>());
+ }
+ }
+
+ @Override
+ protected void onDropFromCache(Integer key, Outputs value) {
+ value.release();
+ }
+ };
+ private final SLRUCache<Project, File> myGeneratedDataPaths = new SLRUCache<Project, File>(8, 8) {
+ @NotNull
+ public File createValue(final Project project) {
+ Disposer.register(project, new Disposable() {
+ public void dispose() {
+ myGeneratedDataPaths.remove(project);
+ }
+ });
+ return CompilerPaths.getGeneratedDataDirectory(project);
+ }
+ };
+ private final SLRUCache<Integer, TIntObjectHashMap<Pair<Integer, Integer>>> myProjectOutputRoots = new SLRUCache<Integer, TIntObjectHashMap<Pair<Integer, Integer>>>(2, 2) {
+ protected void onDropFromCache(Integer key, TIntObjectHashMap<Pair<Integer, Integer>> value) {
+ try {
+ myOutputRootsStorage.put(key, value);
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ }
+ }
+
+ @NotNull
+ public TIntObjectHashMap<Pair<Integer, Integer>> createValue(Integer key) {
+ TIntObjectHashMap<Pair<Integer, Integer>> map = null;
+ try {
+ ensureOutputStorageInitialized();
+ map = myOutputRootsStorage.get(key);
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ }
+ return map != null? map : new TIntObjectHashMap<Pair<Integer, Integer>>();
+ }
+ };
+ private final ProjectManager myProjectManager;
+ private final TIntIntHashMap myInitInProgress = new TIntIntHashMap(); // projectId for successfully initialized projects
+ private final Object myAsyncScanLock = new Object();
+
+ public TranslatingCompilerFilesMonitor(VirtualFileManager vfsManager, ProjectManager projectManager, Application application) {
+ myProjectManager = projectManager;
+
+ projectManager.addProjectManagerListener(new MyProjectManagerListener());
+ vfsManager.addVirtualFileListener(new MyVfsListener(), application);
+ }
+
+ public static TranslatingCompilerFilesMonitor getInstance() {
+ return ApplicationManager.getApplication().getComponent(TranslatingCompilerFilesMonitor.class);
+ }
+
+ public void suspendProject(Project project) {
+ final int projectId = getProjectId(project);
+
+ synchronized (myDataLock) {
+ if (!mySuspendedProjects.add(projectId)) {
+ return;
+ }
+ FileUtil.createIfDoesntExist(CompilerPaths.getRebuildMarkerFile(project));
+ // cleanup internal structures to free memory
+ mySourcesToRecompile.remove(projectId);
+ myOutputsToDelete.remove(projectId);
+ myGeneratedDataPaths.remove(project);
+ }
+
+ synchronized (myProjectOutputRoots) {
+ ensureOutputStorageInitialized();
+ myProjectOutputRoots.remove(projectId);
+ try {
+ myOutputRootsStorage.remove(projectId);
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ }
+ }
+ }
+
+ public void watchProject(Project project) {
+ synchronized (myDataLock) {
+ mySuspendedProjects.remove(getProjectId(project));
+ }
+ }
+
+ public boolean isSuspended(Project project) {
+ return isSuspended(getProjectId(project));
+ }
+
+ public boolean isSuspended(int projectId) {
+ synchronized (myDataLock) {
+ return mySuspendedProjects.contains(projectId);
+ }
+ }
+
+ @Nullable
+ public static VirtualFile getSourceFileByOutput(VirtualFile outputFile) {
+ final OutputFileInfo outputFileInfo = loadOutputInfo(outputFile);
+ if (outputFileInfo != null) {
+ final String path = outputFileInfo.getSourceFilePath();
+ if (path != null) {
+ return LocalFileSystem.getInstance().findFileByPath(path);
+ }
+ }
+ return null;
+ }
+
+ public void collectFiles(CompileContext context, final TranslatingCompiler compiler, Iterator<VirtualFile> scopeSrcIterator, boolean forceCompile,
+ final boolean isRebuild,
+ Collection<VirtualFile> toCompile,
+ Collection<Trinity<File, String, Boolean>> toDelete) {
+ final Project project = context.getProject();
+ final int projectId = getProjectId(project);
+ final CompilerConfiguration configuration = CompilerConfiguration.getInstance(project);
+ final boolean _forceCompile = forceCompile || isRebuild;
+ final Set<VirtualFile> selectedForRecompilation = new HashSet<VirtualFile>();
+ synchronized (myDataLock) {
+ final TIntHashSet pathsToRecompile = mySourcesToRecompile.get(projectId);
+ if (_forceCompile || pathsToRecompile != null && !pathsToRecompile.isEmpty()) {
+ if (ourDebugMode) {
+ System.out.println("Analysing potentially recompilable files for " + compiler.getDescription());
+ }
+ while (scopeSrcIterator.hasNext()) {
+ final VirtualFile file = scopeSrcIterator.next();
+ if (!file.isValid()) {
+ if (LOG.isDebugEnabled() || ourDebugMode) {
+ LOG.debug("Skipping invalid file " + file.getPresentableUrl());
+ if (ourDebugMode) {
+ System.out.println("\t SKIPPED(INVALID) " + file.getPresentableUrl());
+ }
+ }
+ continue;
+ }
+ final int fileId = getFileId(file);
+ if (_forceCompile) {
+ if (compiler.isCompilableFile(file, context) && !configuration.isExcludedFromCompilation(file)) {
+ toCompile.add(file);
+ if (ourDebugMode) {
+ System.out.println("\t INCLUDED " + file.getPresentableUrl());
+ }
+ selectedForRecompilation.add(file);
+ if (pathsToRecompile == null || !pathsToRecompile.contains(fileId)) {
+ loadInfoAndAddSourceForRecompilation(projectId, file);
+ }
+ }
+ else {
+ if (ourDebugMode) {
+ System.out.println("\t NOT COMPILABLE OR EXCLUDED " + file.getPresentableUrl());
+ }
+ }
+ }
+ else if (pathsToRecompile.contains(fileId)) {
+ if (compiler.isCompilableFile(file, context) && !configuration.isExcludedFromCompilation(file)) {
+ toCompile.add(file);
+ if (ourDebugMode) {
+ System.out.println("\t INCLUDED " + file.getPresentableUrl());
+ }
+ selectedForRecompilation.add(file);
+ }
+ else {
+ if (ourDebugMode) {
+ System.out.println("\t NOT COMPILABLE OR EXCLUDED " + file.getPresentableUrl());
+ }
+ }
+ }
+ else {
+ if (ourDebugMode) {
+ System.out.println("\t NOT INCLUDED " + file.getPresentableUrl());
+ }
+ }
+ }
+ }
+ // it is important that files to delete are collected after the files to compile (see what happens if forceCompile == true)
+ if (!isRebuild) {
+ final Outputs outputs = myOutputsToDelete.get(projectId);
+ try {
+ final VirtualFileManager vfm = VirtualFileManager.getInstance();
+ final LocalFileSystem lfs = LocalFileSystem.getInstance();
+ final List<String> zombieEntries = new ArrayList<String>();
+ final Map<String, VirtualFile> srcFileCache = getFileCache(context);
+ for (Map.Entry<String, SourceUrlClassNamePair> entry : outputs.getEntries()) {
+ final String outputPath = entry.getKey();
+ final SourceUrlClassNamePair classNamePair = entry.getValue();
+ final String sourceUrl = classNamePair.getSourceUrl();
+
+ final VirtualFile srcFile;
+ if (srcFileCache.containsKey(sourceUrl)) {
+ srcFile = srcFileCache.get(sourceUrl);
+ }
+ else {
+ srcFile = vfm.findFileByUrl(sourceUrl);
+ srcFileCache.put(sourceUrl, srcFile);
+ }
+
+ final boolean sourcePresent = srcFile != null;
+ if (sourcePresent) {
+ if (!compiler.isCompilableFile(srcFile, context)) {
+ continue; // do not collect files that were compiled by another compiler
+ }
+ if (!selectedForRecompilation.contains(srcFile)) {
+ if (!isMarkedForRecompilation(projectId, getFileId(srcFile))) {
+ if (LOG.isDebugEnabled() || ourDebugMode) {
+ final String message = "Found zombie entry (output is marked, but source is present and up-to-date): " + outputPath;
+ LOG.debug(message);
+ if (ourDebugMode) {
+ System.out.println(message);
+ }
+ }
+ zombieEntries.add(outputPath);
+ }
+ continue;
+ }
+ }
+ if (lfs.findFileByPath(outputPath) != null) {
+ //noinspection UnnecessaryBoxing
+ final File file = new File(outputPath);
+ toDelete.add(new Trinity<File, String, Boolean>(file, classNamePair.getClassName(), Boolean.valueOf(sourcePresent)));
+ if (LOG.isDebugEnabled() || ourDebugMode) {
+ final String message = "Found file to delete: " + file;
+ LOG.debug(message);
+ if (ourDebugMode) {
+ System.out.println(message);
+ }
+ }
+ }
+ else {
+ if (LOG.isDebugEnabled() || ourDebugMode) {
+ final String message = "Found zombie entry marked for deletion: " + outputPath;
+ LOG.debug(message);
+ if (ourDebugMode) {
+ System.out.println(message);
+ }
+ }
+ // must be gagbage entry, should cleanup
+ zombieEntries.add(outputPath);
+ }
+ }
+ for (String path : zombieEntries) {
+ unmarkOutputPathForDeletion(projectId, path);
+ }
+ }
+ finally {
+ outputs.release();
+ }
+ }
+ }
+ }
+
+ private static Map<String, VirtualFile> getFileCache(CompileContext context) {
+ Map<String, VirtualFile> cache = context.getUserData(SOURCE_FILES_CACHE);
+ if (cache == null) {
+ context.putUserData(SOURCE_FILES_CACHE, cache = new HashMap<String, VirtualFile>());
+ }
+ return cache;
+ }
+
+ private static int getFileId(final VirtualFile file) {
+ return FileBasedIndex.getFileId(file);
+ }
+
+ private static VirtualFile findFileById(int id) {
+ return IndexInfrastructure.findFileById((PersistentFS)ManagingFS.getInstance(), id);
+ }
+
+ public void update(final CompileContext context, @Nullable final String outputRoot, final Collection<TranslatingCompiler.OutputItem> successfullyCompiled, final VirtualFile[] filesToRecompile)
+ throws IOException {
+ final Project project = context.getProject();
+ final int projectId = getProjectId(project);
+ if (!successfullyCompiled.isEmpty()) {
+ final LocalFileSystem lfs = LocalFileSystem.getInstance();
+ final IOException[] exceptions = {null};
+ // need read action here to ensure that no modifications were made to VFS while updating file attributes
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ try {
+ final Map<VirtualFile, SourceFileInfo> compiledSources = new HashMap<VirtualFile, SourceFileInfo>();
+ final Set<VirtualFile> forceRecompile = new HashSet<VirtualFile>();
+
+ for (TranslatingCompiler.OutputItem item : successfullyCompiled) {
+ final VirtualFile sourceFile = item.getSourceFile();
+ final boolean isSourceValid = sourceFile.isValid();
+ SourceFileInfo srcInfo = compiledSources.get(sourceFile);
+ if (isSourceValid && srcInfo == null) {
+ srcInfo = loadSourceInfo(sourceFile);
+ if (srcInfo != null) {
+ srcInfo.clearPaths(projectId);
+ }
+ else {
+ srcInfo = new SourceFileInfo();
+ }
+ compiledSources.put(sourceFile, srcInfo);
+ }
+
+ final String outputPath = item.getOutputPath();
+ if (outputPath != null) { // can be null for packageinfo
+ final VirtualFile outputFile = lfs.findFileByPath(outputPath);
+
+ //assert outputFile != null : "Virtual file was not found for \"" + outputPath + "\"";
+
+ if (outputFile != null) {
+ if (!sourceFile.equals(outputFile)) {
+ final String className = outputRoot == null? null : MakeUtil.relativeClassPathToQName(outputPath.substring(outputRoot.length()), '/');
+ if (isSourceValid) {
+ srcInfo.addOutputPath(projectId, outputPath);
+ saveOutputInfo(outputFile, new OutputFileInfo(sourceFile.getPath(), className));
+ }
+ else {
+ markOutputPathForDeletion(projectId, outputPath, className, sourceFile.getUrl());
+ }
+ }
+ }
+ else { // output file was not found
+ LOG.warn("TranslatingCompilerFilesMonitor.update(): Virtual file was not found for \"" + outputPath + "\"");
+ if (isSourceValid) {
+ forceRecompile.add(sourceFile);
+ }
+ }
+ }
+ }
+ final long compilationStartStamp = ((CompileContextEx)context).getStartCompilationStamp();
+ for (Map.Entry<VirtualFile, SourceFileInfo> entry : compiledSources.entrySet()) {
+ final SourceFileInfo info = entry.getValue();
+ final VirtualFile file = entry.getKey();
+
+ final long fileStamp = file.getTimeStamp();
+ info.updateTimestamp(projectId, fileStamp);
+ saveSourceInfo(file, info);
+ if (LOG.isDebugEnabled() || ourDebugMode) {
+ final String message = "Unschedule recompilation (successfully compiled) " + file.getPresentableUrl();
+ LOG.debug(message);
+ if (ourDebugMode) {
+ System.out.println(message);
+ }
+ }
+ removeSourceForRecompilation(projectId, Math.abs(getFileId(file)));
+ if (fileStamp > compilationStartStamp && !((CompileContextEx)context).isGenerated(file) || forceRecompile.contains(file)) {
+ // changes were made during compilation, need to re-schedule compilation
+ // it is important to invoke removeSourceForRecompilation() before this call to make sure
+ // the corresponding output paths will be scheduled for deletion
+ addSourceForRecompilation(projectId, file, info);
+ }
+ }
+ }
+ catch (IOException e) {
+ exceptions[0] = e;
+ }
+ }
+ });
+ if (exceptions[0] != null) {
+ throw exceptions[0];
+ }
+ }
+
+ if (filesToRecompile.length > 0) {
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ for (VirtualFile file : filesToRecompile) {
+ if (file.isValid()) {
+ loadInfoAndAddSourceForRecompilation(projectId, file);
+ }
+ }
+ }
+ });
+ }
+ }
+
+ public void updateOutputRootsLayout(Project project) {
+ final TIntObjectHashMap<Pair<Integer, Integer>> map = buildOutputRootsLayout(new ProjectRef(project));
+ final int projectId = getProjectId(project);
+ synchronized (myProjectOutputRoots) {
+ myProjectOutputRoots.put(projectId, map);
+ }
+ }
+
+ @NotNull
+ public String getComponentName() {
+ return "TranslatingCompilerFilesMonitor";
+ }
+
+ public void initComponent() {
+ ensureOutputStorageInitialized();
+ }
+
+ private static File getOutputRootsFile() {
+ return new File(CompilerPaths.getCompilerSystemDirectory(), "output_roots.dat");
+ }
+
+ private static void deleteStorageFiles(File tableFile) {
+ final File[] files = tableFile.getParentFile().listFiles();
+ if (files != null) {
+ final String name = tableFile.getName();
+ for (File file : files) {
+ if (file.getName().startsWith(name)) {
+ FileUtil.delete(file);
+ }
+ }
+ }
+ }
+
+ private static Map<String, SourceUrlClassNamePair> loadPathsToDelete(@Nullable final File file) {
+ final Map<String, SourceUrlClassNamePair> map = new HashMap<String, SourceUrlClassNamePair>();
+ try {
+ if (file != null && file.length() > 0) {
+ final DataInputStream is = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
+ try {
+ final int size = is.readInt();
+ for (int i = 0; i < size; i++) {
+ final String _outputPath = CompilerIOUtil.readString(is);
+ final String srcUrl = CompilerIOUtil.readString(is);
+ final String className = CompilerIOUtil.readString(is);
+ map.put(FileUtil.toSystemIndependentName(_outputPath), new SourceUrlClassNamePair(srcUrl, className));
+ }
+ }
+ finally {
+ is.close();
+ }
+ }
+ }
+ catch (FileNotFoundException ignored) {
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ }
+ return map;
+ }
+
+ private void ensureOutputStorageInitialized() {
+ if (myOutputRootsStorage != null) {
+ return;
+ }
+ final File rootsFile = getOutputRootsFile();
+ try {
+ initOutputRootsFile(rootsFile);
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ deleteStorageFiles(rootsFile);
+ try {
+ initOutputRootsFile(rootsFile);
+ }
+ catch (IOException e1) {
+ LOG.error(e1);
+ }
+ }
+ }
+
+ private TIntObjectHashMap<Pair<Integer, Integer>> buildOutputRootsLayout(ProjectRef projRef) {
+ final TIntObjectHashMap<Pair<Integer, Integer>> map = new TIntObjectHashMap<Pair<Integer, Integer>>();
+ for (Module module : ModuleManager.getInstance(projRef.get()).getModules()) {
+ final CompilerModuleExtension manager = CompilerModuleExtension.getInstance(module);
+ if (manager != null) {
+ final VirtualFile output = manager.getCompilerOutputPath();
+ final int first = output != null? Math.abs(getFileId(output)) : -1;
+ final VirtualFile testsOutput = manager.getCompilerOutputPathForTests();
+ final int second = testsOutput != null? Math.abs(getFileId(testsOutput)) : -1;
+ map.put(getModuleId(module), new Pair<Integer, Integer>(first, second));
+ }
+ }
+ return map;
+ }
+
+ private void initOutputRootsFile(File rootsFile) throws IOException {
+ myOutputRootsStorage = new PersistentHashMap<Integer, TIntObjectHashMap<Pair<Integer, Integer>>>(rootsFile, EnumeratorIntegerDescriptor.INSTANCE, new DataExternalizer<TIntObjectHashMap<Pair<Integer, Integer>>>() {
+ public void save(DataOutput out, TIntObjectHashMap<Pair<Integer, Integer>> value) throws IOException {
+ for (final TIntObjectIterator<Pair<Integer, Integer>> it = value.iterator(); it.hasNext();) {
+ it.advance();
+ DataInputOutputUtil.writeINT(out, it.key());
+ final Pair<Integer, Integer> pair = it.value();
+ DataInputOutputUtil.writeINT(out, pair.first);
+ DataInputOutputUtil.writeINT(out, pair.second);
+ }
+ }
+
+ public TIntObjectHashMap<Pair<Integer, Integer>> read(DataInput in) throws IOException {
+ final DataInputStream _in = (DataInputStream)in;
+ final TIntObjectHashMap<Pair<Integer, Integer>> map = new TIntObjectHashMap<Pair<Integer, Integer>>();
+ while (_in.available() > 0) {
+ final int key = DataInputOutputUtil.readINT(_in);
+ final int first = DataInputOutputUtil.readINT(_in);
+ final int second = DataInputOutputUtil.readINT(_in);
+ map.put(key, new Pair<Integer, Integer>(first, second));
+ }
+ return map;
+ }
+ });
+ }
+
+ public void disposeComponent() {
+ try {
+ synchronized (myProjectOutputRoots) {
+ myProjectOutputRoots.clear();
+ }
+ }
+ finally {
+ synchronized (myDataLock) {
+ myOutputsToDelete.clear();
+ }
+ }
+
+ try {
+ final PersistentHashMap<Integer, TIntObjectHashMap<Pair<Integer, Integer>>> storage = myOutputRootsStorage;
+ if (storage != null) {
+ storage.close();
+ }
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ deleteStorageFiles(getOutputRootsFile());
+ }
+ }
+
+ private static void savePathsToDelete(final File file, final Map<String, SourceUrlClassNamePair> outputs) {
+ try {
+ FileUtil.createParentDirs(file);
+ final DataOutputStream os = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(file)));
+ try {
+ if (outputs != null) {
+ os.writeInt(outputs.size());
+ for (Map.Entry<String, SourceUrlClassNamePair> entry : outputs.entrySet()) {
+ CompilerIOUtil.writeString(entry.getKey(), os);
+ final SourceUrlClassNamePair pair = entry.getValue();
+ CompilerIOUtil.writeString(pair.getSourceUrl(), os);
+ CompilerIOUtil.writeString(pair.getClassName(), os);
+ }
+ }
+ else {
+ os.writeInt(0);
+ }
+ }
+ finally {
+ os.close();
+ }
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
+
+ @Nullable
+ private static SourceFileInfo loadSourceInfo(final VirtualFile file) {
+ try {
+ final DataInputStream is = ourSourceFileAttribute.readAttribute(file);
+ if (is != null) {
+ try {
+ return new SourceFileInfo(is);
+ }
+ finally {
+ is.close();
+ }
+ }
+ }
+ catch (RuntimeException e) {
+ final Throwable cause = e.getCause();
+ if (cause instanceof IOException) {
+ LOG.info(e); // ignore IOExceptions
+ }
+ else {
+ throw e;
+ }
+ }
+ catch (IOException ignored) {
+ LOG.info(ignored);
+ }
+ return null;
+ }
+
+ public static void removeSourceInfo(VirtualFile file) {
+ saveSourceInfo(file, new SourceFileInfo());
+ }
+
+ private static void saveSourceInfo(VirtualFile file, SourceFileInfo descriptor) {
+ final java.io.DataOutputStream out = ourSourceFileAttribute.writeAttribute(file);
+ try {
+ try {
+ descriptor.save(out);
+ }
+ finally {
+ out.close();
+ }
+ }
+ catch (IOException ignored) {
+ LOG.info(ignored);
+ }
+ }
+
+ @Nullable
+ private static OutputFileInfo loadOutputInfo(final VirtualFile file) {
+ try {
+ final DataInputStream is = ourOutputFileAttribute.readAttribute(file);
+ if (is != null) {
+ try {
+ return new OutputFileInfo(is);
+ }
+ finally {
+ is.close();
+ }
+ }
+ }
+ catch (RuntimeException e) {
+ final Throwable cause = e.getCause();
+ if (cause instanceof IOException) {
+ LOG.info(e); // ignore IO exceptions
+ }
+ else {
+ throw e;
+ }
+ }
+ catch (IOException ignored) {
+ LOG.info(ignored);
+ }
+ return null;
+ }
+
+ private static void saveOutputInfo(VirtualFile file, OutputFileInfo descriptor) {
+ final java.io.DataOutputStream out = ourOutputFileAttribute.writeAttribute(file);
+ try {
+ try {
+ descriptor.save(out);
+ }
+ finally {
+ out.close();
+ }
+ }
+ catch (IOException ignored) {
+ LOG.info(ignored);
+ }
+ }
+
+ private int getProjectId(Project project) {
+ try {
+ return FSRecords.getNames().enumerate(CompilerPaths.getCompilerSystemDirectoryName(project));
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ }
+ return -1;
+ }
+
+ private int getModuleId(Module module) {
+ try {
+ return FSRecords.getNames().enumerate(module.getName().toLowerCase(Locale.US));
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ }
+ return -1;
+ }
+
+ private static class OutputFileInfo {
+ private final int mySourcePath;
+
+ private final int myClassName;
+
+ OutputFileInfo(final String sourcePath, @Nullable String className) throws IOException {
+ final PersistentStringEnumerator symtable = FSRecords.getNames();
+ mySourcePath = symtable.enumerate(sourcePath);
+ myClassName = className != null? symtable.enumerate(className) : -1;
+ }
+
+ OutputFileInfo(final DataInput in) throws IOException {
+ mySourcePath = in.readInt();
+ myClassName = in.readInt();
+ }
+
+ String getSourceFilePath() {
+ try {
+ return FSRecords.getNames().valueOf(mySourcePath);
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ }
+ return null;
+ }
+
+ @Nullable
+ public String getClassName() {
+ try {
+ return myClassName < 0? null : FSRecords.getNames().valueOf(myClassName);
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ }
+ return null;
+ }
+
+ public void save(final DataOutput out) throws IOException {
+ out.writeInt(mySourcePath);
+ out.writeInt(myClassName);
+ }
+ }
+
+ private static class SourceFileInfo {
+ private TIntLongHashMap myTimestamps; // ProjectId -> last compiled stamp
+ private TIntObjectHashMap<Serializable> myProjectToOutputPathMap; // ProjectId -> either a single output path or a set of output paths
+
+ private SourceFileInfo() {
+ }
+
+ private SourceFileInfo(@NotNull DataInput in) throws IOException {
+ final int projCount = DataInputOutputUtil.readINT(in);
+ for (int idx = 0; idx < projCount; idx++) {
+ final int projectId = DataInputOutputUtil.readINT(in);
+ final long stamp = DataInputOutputUtil.readTIME(in);
+ updateTimestamp(projectId, stamp);
+
+ final int pathsCount = DataInputOutputUtil.readINT(in);
+ for (int i = 0; i < pathsCount; i++) {
+ final int path = in.readInt();
+ addOutputPath(projectId, path);
+ }
+ }
+ }
+
+ public void save(@NotNull final DataOutput out) throws IOException {
+ final int[] projects = getProjectIds().toArray();
+ DataInputOutputUtil.writeINT(out, projects.length);
+ for (int projectId : projects) {
+ DataInputOutputUtil.writeINT(out, projectId);
+ DataInputOutputUtil.writeTIME(out, getTimestamp(projectId));
+ final Object value = myProjectToOutputPathMap != null? myProjectToOutputPathMap.get(projectId) : null;
+ if (value instanceof Integer) {
+ DataInputOutputUtil.writeINT(out, 1);
+ out.writeInt(((Integer)value).intValue());
+ }
+ else if (value instanceof TIntHashSet) {
+ final TIntHashSet set = (TIntHashSet)value;
+ DataInputOutputUtil.writeINT(out, set.size());
+ final IOException[] ex = new IOException[] {null};
+ set.forEach(new TIntProcedure() {
+ public boolean execute(final int value) {
+ try {
+ out.writeInt(value);
+ return true;
+ }
+ catch (IOException e) {
+ ex[0] = e;
+ return false;
+ }
+ }
+ });
+ if (ex[0] != null) {
+ throw ex[0];
+ }
+ }
+ else {
+ DataInputOutputUtil.writeINT(out, 0);
+ }
+ }
+ }
+
+ private void updateTimestamp(final int projectId, final long stamp) {
+ if (stamp > 0L) {
+ if (myTimestamps == null) {
+ myTimestamps = new TIntLongHashMap(1, 0.98f);
+ }
+ myTimestamps.put(projectId, stamp);
+ }
+ else {
+ if (myTimestamps != null) {
+ myTimestamps.remove(projectId);
+ }
+ }
+ }
+
+ TIntHashSet getProjectIds() {
+ final TIntHashSet result = new TIntHashSet();
+ if (myTimestamps != null) {
+ result.addAll(myTimestamps.keys());
+ }
+ if (myProjectToOutputPathMap != null) {
+ result.addAll(myProjectToOutputPathMap.keys());
+ }
+ return result;
+ }
+
+ private void addOutputPath(final int projectId, String outputPath) {
+ try {
+ addOutputPath(projectId, FSRecords.getNames().enumerate(outputPath));
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ }
+ }
+
+ private void addOutputPath(final int projectId, final int outputPath) {
+ if (myProjectToOutputPathMap == null) {
+ myProjectToOutputPathMap = new TIntObjectHashMap<Serializable>(1, 0.98f);
+ myProjectToOutputPathMap.put(projectId, outputPath);
+ }
+ else {
+ final Object val = myProjectToOutputPathMap.get(projectId);
+ if (val == null) {
+ myProjectToOutputPathMap.put(projectId, outputPath);
+ }
+ else {
+ TIntHashSet set;
+ if (val instanceof Integer) {
+ set = new TIntHashSet();
+ set.add(((Integer)val).intValue());
+ myProjectToOutputPathMap.put(projectId, set);
+ }
+ else {
+ assert val instanceof TIntHashSet;
+ set = (TIntHashSet)val;
+ }
+ set.add(outputPath);
+ }
+ }
+ }
+
+ public boolean clearPaths(final int projectId){
+ if (myProjectToOutputPathMap != null) {
+ final Serializable removed = myProjectToOutputPathMap.remove(projectId);
+ return removed != null;
+ }
+ return false;
+ }
+
+ long getTimestamp(final int projectId) {
+ return myTimestamps == null? -1L : myTimestamps.get(projectId);
+ }
+
+ void processOutputPaths(final int projectId, final Proc proc){
+ if (myProjectToOutputPathMap != null) {
+ try {
+ final PersistentStringEnumerator symtable = FSRecords.getNames();
+ final Object val = myProjectToOutputPathMap.get(projectId);
+ if (val instanceof Integer) {
+ proc.execute(projectId, symtable.valueOf(((Integer)val).intValue()));
+ }
+ else if (val instanceof TIntHashSet) {
+ ((TIntHashSet)val).forEach(new TIntProcedure() {
+ public boolean execute(final int value) {
+ try {
+ proc.execute(projectId, symtable.valueOf(value));
+ return true;
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ return false;
+ }
+ }
+ });
+ }
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ }
+ }
+ }
+
+ boolean isAssociated(int projectId, String outputPath) {
+ if (myProjectToOutputPathMap != null) {
+ try {
+ final Object val = myProjectToOutputPathMap.get(projectId);
+ if (val instanceof Integer) {
+ return FileUtil.pathsEqual(outputPath, FSRecords.getNames().valueOf(((Integer)val).intValue()));
+ }
+ if (val instanceof TIntHashSet) {
+ final int _outputPath = FSRecords.getNames().enumerate(outputPath);
+ return ((TIntHashSet)val).contains(_outputPath);
+ }
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ }
+ }
+ return false;
+ }
+ }
+
+ public List<String> getCompiledClassNames(VirtualFile srcFile, Project project) {
+ final SourceFileInfo info = loadSourceInfo(srcFile);
+ if (info == null) {
+ return Collections.emptyList();
+ }
+
+ final ArrayList<String> result = new ArrayList<String>();
+
+ info.processOutputPaths(getProjectId(project), new Proc() {
+ @Override
+ public boolean execute(int projectId, String outputPath) {
+ VirtualFile clsFile = LocalFileSystem.getInstance().findFileByPath(outputPath);
+ if (clsFile != null) {
+ OutputFileInfo outputInfo = loadOutputInfo(clsFile);
+ if (outputInfo != null) {
+ ContainerUtil.addIfNotNull(result, outputInfo.getClassName());
+ }
+ }
+ return true;
+ }
+ });
+ return result;
+ }
+
+
+ private interface FileProcessor {
+ void execute(VirtualFile file);
+ }
+
+ private static void processRecursively(VirtualFile file, final boolean dbOnly, final FileProcessor processor) {
+ if (!(file.getFileSystem() instanceof LocalFileSystem)) {
+ return;
+ }
+
+ final FileTypeManager fileTypeManager = FileTypeManager.getInstance();
+ VfsUtilCore.visitChildrenRecursively(file, new VirtualFileVisitor() {
+ @NotNull @Override
+ public Result visitFileEx(@NotNull VirtualFile file) {
+ if (fileTypeManager.isFileIgnored(file)) {
+ return SKIP_CHILDREN;
+ }
+
+ if (!file.isDirectory()) {
+ processor.execute(file);
+ }
+ return CONTINUE;
+ }
+
+ @Nullable
+ @Override
+ public Iterable<VirtualFile> getChildrenIterable(@NotNull VirtualFile file) {
+ return file.isDirectory() && dbOnly ? ((NewVirtualFile)file).iterInDbChildren() : null;
+ }
+ });
+ }
+
+ // made public for tests
+ public void scanSourceContent(final ProjectRef projRef, final Collection<VirtualFile> roots, final int totalRootCount, final boolean isNewRoots) {
+ if (roots.isEmpty()) {
+ return;
+ }
+ final int projectId = getProjectId(projRef.get());
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Scanning source content for project projectId=" + projectId + "; url=" + projRef.get().getPresentableUrl());
+ }
+
+ final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(projRef.get()).getFileIndex();
+ final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
+ int processed = 0;
+ for (VirtualFile srcRoot : roots) {
+ if (indicator != null) {
+ projRef.get();
+ indicator.setText2(srcRoot.getPresentableUrl());
+ indicator.setFraction(++processed / (double)totalRootCount);
+ }
+ if (isNewRoots) {
+ fileIndex.iterateContentUnderDirectory(srcRoot, new ContentIterator() {
+ public boolean processFile(final VirtualFile file) {
+ if (!file.isDirectory()) {
+ if (!isMarkedForRecompilation(projectId, Math.abs(getFileId(file)))) {
+ final SourceFileInfo srcInfo = loadSourceInfo(file);
+ if (srcInfo == null || srcInfo.getTimestamp(projectId) != file.getTimeStamp()) {
+ addSourceForRecompilation(projectId, file, srcInfo);
+ }
+ }
+ }
+ else {
+ projRef.get();
+ }
+ return true;
+ }
+ });
+ }
+ else {
+ final FileTypeManager fileTypeManager = FileTypeManager.getInstance();
+ VfsUtilCore.visitChildrenRecursively(srcRoot, new VirtualFileVisitor() {
+ @Override
+ public boolean visitFile(@NotNull VirtualFile file) {
+ if (fileTypeManager.isFileIgnored(file)) {
+ return false;
+ }
+ final int fileId = getFileId(file);
+ if (fileId > 0 /*file is valid*/) {
+ if (file.isDirectory()) {
+ projRef.get();
+ }
+ else if (!isMarkedForRecompilation(projectId, fileId)) {
+ final SourceFileInfo srcInfo = loadSourceInfo(file);
+ if (srcInfo != null) {
+ addSourceForRecompilation(projectId, file, srcInfo);
+ }
+ }
+ }
+ return true;
+ }
+ });
+ }
+ }
+ }
+
+ public void ensureInitializationCompleted(Project project, ProgressIndicator indicator) {
+ final int id = getProjectId(project);
+ synchronized (myAsyncScanLock) {
+ while (myInitInProgress.containsKey(id)) {
+ if (!project.isOpen() || project.isDisposed() || (indicator != null && indicator.isCanceled())) {
+ // makes no sense to continue waiting
+ break;
+ }
+ try {
+ myAsyncScanLock.wait(500);
+ }
+ catch (InterruptedException ignored) {
+ break;
+ }
+ }
+ }
+ }
+
+ private void markOldOutputRoots(final ProjectRef projRef, final TIntObjectHashMap<Pair<Integer, Integer>> currentLayout) {
+ final int projectId = getProjectId(projRef.get());
+
+ final TIntHashSet rootsToMark = new TIntHashSet();
+ synchronized (myProjectOutputRoots) {
+ final TIntObjectHashMap<Pair<Integer, Integer>> oldLayout = myProjectOutputRoots.get(projectId);
+ for (final TIntObjectIterator<Pair<Integer, Integer>> it = oldLayout.iterator(); it.hasNext();) {
+ it.advance();
+ final Pair<Integer, Integer> currentRoots = currentLayout.get(it.key());
+ final Pair<Integer, Integer> oldRoots = it.value();
+ if (shouldMark(oldRoots.first, currentRoots != null? currentRoots.first : -1)) {
+ rootsToMark.add(oldRoots.first);
+ }
+ if (shouldMark(oldRoots.second, currentRoots != null? currentRoots.second : -1)) {
+ rootsToMark.add(oldRoots.second);
+ }
+ }
+ }
+
+ for (TIntIterator it = rootsToMark.iterator(); it.hasNext();) {
+ final int id = it.next();
+ final VirtualFile outputRoot = findFileById(id);
+ if (outputRoot != null) {
+ processOldOutputRoot(projectId, outputRoot);
+ }
+ }
+ }
+
+ private static boolean shouldMark(Integer oldOutputRoot, Integer currentOutputRoot) {
+ return oldOutputRoot != null && oldOutputRoot.intValue() > 0 && !Comparing.equal(oldOutputRoot, currentOutputRoot);
+ }
+
+ private void processOldOutputRoot(final int projectId, VirtualFile outputRoot) {
+ // recursively mark all corresponding sources for recompilation
+ VfsUtilCore.visitChildrenRecursively(outputRoot, new VirtualFileVisitor() {
+ @Override
+ public boolean visitFile(@NotNull VirtualFile file) {
+ if (!file.isDirectory()) {
+ // todo: possible optimization - process only those outputs that are not marked for deletion yet
+ final OutputFileInfo outputInfo = loadOutputInfo(file);
+ if (outputInfo != null) {
+ final String srcPath = outputInfo.getSourceFilePath();
+ final VirtualFile srcFile = srcPath != null? LocalFileSystem.getInstance().findFileByPath(srcPath) : null;
+ if (srcFile != null) {
+ loadInfoAndAddSourceForRecompilation(projectId, srcFile);
+ }
+ }
+ }
+ return true;
+ }
+ });
+ }
+
+ public void scanSourcesForCompilableFiles(final Project project) {
+ final int projectId = getProjectId(project);
+ if (isSuspended(projectId)) {
+ return;
+ }
+ startAsyncScan(projectId);
+ StartupManager.getInstance(project).runWhenProjectIsInitialized(new Runnable() {
+ public void run() {
+ new Task.Backgroundable(project, CompilerBundle.message("compiler.initial.scanning.progress.text"), false) {
+ public void run(@NotNull final ProgressIndicator indicator) {
+ final ProjectRef projRef = new ProjectRef(project);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Initial sources scan for project hash=" + projectId + "; url="+ projRef.get().getPresentableUrl());
+ }
+ try {
+ final IntermediateOutputCompiler[] compilers =
+ CompilerManager.getInstance(projRef.get()).getCompilers(IntermediateOutputCompiler.class);
+
+ final Set<VirtualFile> intermediateRoots = new HashSet<VirtualFile>();
+ if (compilers.length > 0) {
+ final Module[] modules = ModuleManager.getInstance(projRef.get()).getModules();
+ for (IntermediateOutputCompiler compiler : compilers) {
+ for (Module module : modules) {
+ if (module.isDisposed()) {
+ continue;
+ }
+ final VirtualFile outputRoot = LocalFileSystem.getInstance().refreshAndFindFileByPath(CompilerPaths.getGenerationOutputPath(compiler, module, false));
+ if (outputRoot != null) {
+ intermediateRoots.add(outputRoot);
+ }
+ final VirtualFile testsOutputRoot = LocalFileSystem.getInstance().refreshAndFindFileByPath(CompilerPaths.getGenerationOutputPath(compiler, module, true));
+ if (testsOutputRoot != null) {
+ intermediateRoots.add(testsOutputRoot);
+ }
+ }
+ }
+ }
+
+ final List<VirtualFile> projectRoots = Arrays.asList(ProjectRootManager.getInstance(projRef.get()).getContentSourceRoots());
+ final int totalRootsCount = projectRoots.size() + intermediateRoots.size();
+ scanSourceContent(projRef, projectRoots, totalRootsCount, true);
+
+ if (!intermediateRoots.isEmpty()) {
+ final FileProcessor processor = new FileProcessor() {
+ public void execute(final VirtualFile file) {
+ if (!isMarkedForRecompilation(projectId, Math.abs(getFileId(file)))) {
+ final SourceFileInfo srcInfo = loadSourceInfo(file);
+ if (srcInfo == null || srcInfo.getTimestamp(projectId) != file.getTimeStamp()) {
+ addSourceForRecompilation(projectId, file, srcInfo);
+ }
+ }
+ }
+ };
+ int processed = projectRoots.size();
+ for (VirtualFile root : intermediateRoots) {
+ projRef.get();
+ indicator.setText2(root.getPresentableUrl());
+ indicator.setFraction(++processed / (double)totalRootsCount);
+ processRecursively(root, false, processor);
+ }
+ }
+
+ markOldOutputRoots(projRef, buildOutputRootsLayout(projRef));
+ }
+ catch (ProjectRef.ProjectClosedException swallowed) {
+ }
+ finally {
+ terminateAsyncScan(projectId, false);
+ }
+ }
+ }.queue();
+ }
+ });
+ }
+
+ private void terminateAsyncScan(int projectId, final boolean clearCounter) {
+ synchronized (myAsyncScanLock) {
+ int counter = myInitInProgress.remove(projectId);
+ if (clearCounter) {
+ myAsyncScanLock.notifyAll();
+ }
+ else {
+ if (--counter > 0) {
+ myInitInProgress.put(projectId, counter);
+ }
+ else {
+ myAsyncScanLock.notifyAll();
+ }
+ }
+ }
+ }
+
+ private void startAsyncScan(final int projectId) {
+ synchronized (myAsyncScanLock) {
+ int counter = myInitInProgress.get(projectId);
+ counter = (counter > 0)? counter + 1 : 1;
+ myInitInProgress.put(projectId, counter);
+ myAsyncScanLock.notifyAll();
+ }
+ }
+
+ private class MyProjectManagerListener extends ProjectManagerAdapter {
+
+ final Map<Project, MessageBusConnection> myConnections = new HashMap<Project, MessageBusConnection>();
+
+ public void projectOpened(final Project project) {
+ final MessageBusConnection conn = project.getMessageBus().connect();
+ myConnections.put(project, conn);
+ final ProjectRef projRef = new ProjectRef(project);
+ final int projectId = getProjectId(project);
+
+ if (CompilerWorkspaceConfiguration.getInstance(project).useOutOfProcessBuild()) {
+ suspendProject(project);
+ }
+ else {
+ watchProject(project);
+ }
+
+ conn.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() {
+ private VirtualFile[] myRootsBefore;
+ private Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD, project);
+
+ public void beforeRootsChange(final ModuleRootEvent event) {
+ if (isSuspended(projectId)) {
+ return;
+ }
+ try {
+ myRootsBefore = ProjectRootManager.getInstance(projRef.get()).getContentSourceRoots();
+ }
+ catch (ProjectRef.ProjectClosedException e) {
+ myRootsBefore = null;
+ }
+ }
+
+ public void rootsChanged(final ModuleRootEvent event) {
+ if (isSuspended(projectId)) {
+ return;
+ }
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Before roots changed for projectId=" + projectId + "; url="+ project.getPresentableUrl());
+ }
+ try {
+ final VirtualFile[] rootsBefore = myRootsBefore;
+ myRootsBefore = null;
+ final VirtualFile[] rootsAfter = ProjectRootManager.getInstance(projRef.get()).getContentSourceRoots();
+ final Set<VirtualFile> newRoots = new HashSet<VirtualFile>();
+ final Set<VirtualFile> oldRoots = new HashSet<VirtualFile>();
+ {
+ if (rootsAfter.length > 0) {
+ ContainerUtil.addAll(newRoots, rootsAfter);
+ }
+ if (rootsBefore != null) {
+ newRoots.removeAll(Arrays.asList(rootsBefore));
+ }
+ }
+ {
+ if (rootsBefore != null) {
+ ContainerUtil.addAll(oldRoots, rootsBefore);
+ }
+ if (!oldRoots.isEmpty() && rootsAfter.length > 0) {
+ oldRoots.removeAll(Arrays.asList(rootsAfter));
+ }
+ }
+
+ myAlarm.cancelAllRequests(); // need alarm to deal with multiple rootsChanged events
+ myAlarm.addRequest(new Runnable() {
+ public void run() {
+ startAsyncScan(projectId);
+ new Task.Backgroundable(project, CompilerBundle.message("compiler.initial.scanning.progress.text"), false) {
+ public void run(@NotNull final ProgressIndicator indicator) {
+ try {
+ if (newRoots.size() > 0) {
+ scanSourceContent(projRef, newRoots, newRoots.size(), true);
+ }
+ if (oldRoots.size() > 0) {
+ scanSourceContent(projRef, oldRoots, oldRoots.size(), false);
+ }
+ markOldOutputRoots(projRef, buildOutputRootsLayout(projRef));
+ }
+ catch (ProjectRef.ProjectClosedException swallowed) {
+ // ignored
+ }
+ finally {
+ terminateAsyncScan(projectId, false);
+ }
+ }
+ }.queue();
+ }
+ }, 500, ModalityState.NON_MODAL);
+ }
+ catch (ProjectRef.ProjectClosedException e) {
+ LOG.info(e);
+ }
+ }
+ });
+
+ scanSourcesForCompilableFiles(project);
+ }
+
+ public void projectClosed(final Project project) {
+ final int projectId = getProjectId(project);
+ terminateAsyncScan(projectId, true);
+ myConnections.remove(project).disconnect();
+ synchronized (myDataLock) {
+ mySourcesToRecompile.remove(projectId);
+ myOutputsToDelete.remove(projectId); // drop cache to save memory
+ }
+ }
+ }
+
+ private class MyVfsListener extends VirtualFileAdapter {
+ public void propertyChanged(final VirtualFilePropertyEvent event) {
+ if (VirtualFile.PROP_NAME.equals(event.getPropertyName())) {
+ final VirtualFile eventFile = event.getFile();
+ final VirtualFile parent = event.getParent();
+ if (parent != null) {
+ final String oldName = (String)event.getOldValue();
+ final String root = parent.getPath() + "/" + oldName;
+ final Set<File> toMark = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY);
+ if (eventFile.isDirectory()) {
+ VfsUtilCore.visitChildrenRecursively(eventFile, new VirtualFileVisitor() {
+ private StringBuilder filePath = new StringBuilder(root);
+
+ @Override
+ public boolean visitFile(@NotNull VirtualFile child) {
+ if (child.isDirectory()) {
+ if (!Comparing.equal(child, eventFile)) {
+ filePath.append("/").append(child.getName());
+ }
+ }
+ else {
+ String childPath = filePath.toString();
+ if (!Comparing.equal(child, eventFile)) {
+ childPath += "/" + child.getName();
+ }
+ toMark.add(new File(childPath));
+ }
+ return true;
+ }
+
+ @Override
+ public void afterChildrenVisited(@NotNull VirtualFile file) {
+ if (file.isDirectory() && !Comparing.equal(file, eventFile)) {
+ filePath.delete(filePath.length() - file.getName().length() - 1, filePath.length());
+ }
+ }
+ });
+ }
+ else {
+ toMark.add(new File(root));
+ }
+ notifyFilesDeleted(toMark);
+ }
+ markDirtyIfSource(eventFile, false);
+ }
+ }
+
+ public void contentsChanged(final VirtualFileEvent event) {
+ markDirtyIfSource(event.getFile(), false);
+ }
+
+ public void fileCreated(final VirtualFileEvent event) {
+ processNewFile(event.getFile(), true);
+ }
+
+ public void fileCopied(final VirtualFileCopyEvent event) {
+ processNewFile(event.getFile(), true);
+ }
+
+ public void fileMoved(VirtualFileMoveEvent event) {
+ processNewFile(event.getFile(), true);
+ }
+
+ public void beforeFileDeletion(final VirtualFileEvent event) {
+ final VirtualFile eventFile = event.getFile();
+ if ((LOG.isDebugEnabled() && eventFile.isDirectory()) || ourDebugMode) {
+ final String message = "Processing file deletion: " + eventFile.getPresentableUrl();
+ LOG.debug(message);
+ if (ourDebugMode) {
+ System.out.println(message);
+ }
+ }
+
+ final Set<File> pathsToMark = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY);
+
+ processRecursively(eventFile, true, new FileProcessor() {
+ private final TIntArrayList myAssociatedProjectIds = new TIntArrayList();
+
+ public void execute(final VirtualFile file) {
+ final String filePath = file.getPath();
+ pathsToMark.add(new File(filePath));
+ myAssociatedProjectIds.clear();
+ try {
+ final OutputFileInfo outputInfo = loadOutputInfo(file);
+ if (outputInfo != null) {
+ final String srcPath = outputInfo.getSourceFilePath();
+ final VirtualFile srcFile = srcPath != null? LocalFileSystem.getInstance().findFileByPath(srcPath) : null;
+ if (srcFile != null) {
+ final SourceFileInfo srcInfo = loadSourceInfo(srcFile);
+ if (srcInfo != null) {
+ final boolean srcWillBeDeleted = VfsUtil.isAncestor(eventFile, srcFile, false);
+ for (int projectId : srcInfo.getProjectIds().toArray()) {
+ if (isSuspended(projectId)) {
+ continue;
+ }
+ if (srcInfo.isAssociated(projectId, filePath)) {
+ myAssociatedProjectIds.add(projectId);
+ if (srcWillBeDeleted) {
+ if (LOG.isDebugEnabled() || ourDebugMode) {
+ final String message = "Unschedule recompilation because of deletion " + srcFile.getPresentableUrl();
+ LOG.debug(message);
+ if (ourDebugMode) {
+ System.out.println(message);
+ }
+ }
+ removeSourceForRecompilation(projectId, Math.abs(getFileId(srcFile)));
+ }
+ else {
+ addSourceForRecompilation(projectId, srcFile, srcInfo);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ final SourceFileInfo srcInfo = loadSourceInfo(file);
+ if (srcInfo != null) {
+ final TIntHashSet projects = srcInfo.getProjectIds();
+ if (!projects.isEmpty()) {
+ final ScheduleOutputsForDeletionProc deletionProc = new ScheduleOutputsForDeletionProc(file.getUrl());
+ deletionProc.setRootBeingDeleted(eventFile);
+ final int sourceFileId = Math.abs(getFileId(file));
+ for (int projectId : projects.toArray()) {
+ if (isSuspended(projectId)) {
+ continue;
+ }
+ if (srcInfo.isAssociated(projectId, filePath)) {
+ myAssociatedProjectIds.add(projectId);
+ }
+ // mark associated outputs for deletion
+ srcInfo.processOutputPaths(projectId, deletionProc);
+ if (LOG.isDebugEnabled() || ourDebugMode) {
+ final String message = "Unschedule recompilation because of deletion " + file.getPresentableUrl();
+ LOG.debug(message);
+ if (ourDebugMode) {
+ System.out.println(message);
+ }
+ }
+ removeSourceForRecompilation(projectId, sourceFileId);
+ }
+ }
+ }
+ }
+ finally {
+ // it is important that update of myOutputsToDelete is done at the end
+ // otherwise the filePath of the file that is about to be deleted may be re-scheduled for deletion in addSourceForRecompilation()
+ myAssociatedProjectIds.forEach(new TIntProcedure() {
+ public boolean execute(int projectId) {
+ unmarkOutputPathForDeletion(projectId, filePath);
+ return true;
+ }
+ });
+ }
+ }
+ });
+
+ notifyFilesDeleted(pathsToMark);
+ }
+
+ public void beforeFileMovement(final VirtualFileMoveEvent event) {
+ markDirtyIfSource(event.getFile(), true);
+ }
+
+ private void markDirtyIfSource(final VirtualFile file, final boolean fromMove) {
+ final Set<File> pathsToMark = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY);
+ processRecursively(file, false, new FileProcessor() {
+ public void execute(final VirtualFile file) {
+ pathsToMark.add(new File(file.getPath()));
+ final SourceFileInfo srcInfo = file.isValid()? loadSourceInfo(file) : null;
+ if (srcInfo != null) {
+ for (int projectId : srcInfo.getProjectIds().toArray()) {
+ if (isSuspended(projectId)) {
+ if (srcInfo.clearPaths(projectId)) {
+ srcInfo.updateTimestamp(projectId, -1L);
+ saveSourceInfo(file, srcInfo);
+ }
+ }
+ else {
+ addSourceForRecompilation(projectId, file, srcInfo);
+ // when the file is moved to a new location, we should 'forget' previous associations
+ if (fromMove) {
+ if (srcInfo.clearPaths(projectId)) {
+ saveSourceInfo(file, srcInfo);
+ }
+ }
+ }
+ }
+ }
+ else {
+ processNewFile(file, false);
+ }
+ }
+ });
+ if (fromMove) {
+ notifyFilesDeleted(pathsToMark);
+ }
+ else if (!isIgnoredOrUnderIgnoredDirectory(file)) {
+ notifyFilesChanged(pathsToMark);
+ }
+ }
+
+ private void processNewFile(final VirtualFile file, final boolean notifyServer) {
+ final Ref<Boolean> isInContent = Ref.create(false);
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ // need read action to ensure that the project was not disposed during the iteration over the project list
+ public void run() {
+ for (final Project project : myProjectManager.getOpenProjects()) {
+ if (!project.isInitialized()) {
+ continue; // the content of this project will be scanned during its post-startup activities
+ }
+ final int projectId = getProjectId(project);
+ final boolean projectSuspended = isSuspended(projectId);
+ final ProjectRootManager rootManager = ProjectRootManager.getInstance(project);
+ ProjectFileIndex fileIndex = rootManager.getFileIndex();
+ if (fileIndex.isInContent(file)) {
+ isInContent.set(true);
+ }
+
+ if (fileIndex.isInSourceContent(file)) {
+ final TranslatingCompiler[] translators = CompilerManager.getInstance(project).getCompilers(TranslatingCompiler.class);
+ processRecursively(file, false, new FileProcessor() {
+ public void execute(final VirtualFile file) {
+ if (!projectSuspended && isCompilable(file)) {
+ loadInfoAndAddSourceForRecompilation(projectId, file);
+ }
+ }
+
+ boolean isCompilable(VirtualFile file) {
+ for (TranslatingCompiler translator : translators) {
+ if (translator.isCompilableFile(file, DummyCompileContext.getInstance())) {
+ return true;
+ }
+ }
+ return false;
+ }
+ });
+ }
+ else {
+ if (!projectSuspended && belongsToIntermediateSources(file, project)) {
+ processRecursively(file, false, new FileProcessor() {
+ public void execute(final VirtualFile file) {
+ loadInfoAndAddSourceForRecompilation(projectId, file);
+ }
+ });
+ }
+ }
+ }
+ }
+ });
+ if (notifyServer && !isIgnoredOrUnderIgnoredDirectory(file)) {
+ final Set<File> pathsToMark = new THashSet<File>(FileUtil.FILE_HASHING_STRATEGY);
+ boolean dbOnly = !isInContent.get();
+ processRecursively(file, dbOnly, new FileProcessor() {
+ @Override
+ public void execute(VirtualFile file) {
+ pathsToMark.add(new File(file.getPath()));
+ }
+ });
+ notifyFilesChanged(pathsToMark);
+ }
+ }
+ }
+
+ private boolean isIgnoredOrUnderIgnoredDirectory(final VirtualFile file) {
+ FileTypeManager fileTypeManager = FileTypeManager.getInstance();
+ if (fileTypeManager.isFileIgnored(file)) {
+ return true;
+ }
+
+ //optimization: if file is in content of some project it's definitely not ignored
+ boolean isInContent = ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
+ @Override
+ public Boolean compute() {
+ for (Project project : myProjectManager.getOpenProjects()) {
+ if (project.isInitialized() && ProjectRootManager.getInstance(project).getFileIndex().isInContent(file)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ });
+ if (isInContent) {
+ return false;
+ }
+
+ VirtualFile current = file.getParent();
+ while (current != null) {
+ if (fileTypeManager.isFileIgnored(current)) {
+ return true;
+ }
+ current = current.getParent();
+ }
+ return false;
+ }
+
+ private static void notifyFilesChanged(Collection<File> paths) {
+ if (!paths.isEmpty()) {
+ BuildManager.getInstance().notifyFilesChanged(paths);
+ }
+ }
+
+ private static void notifyFilesDeleted(Collection<File> paths) {
+ if (!paths.isEmpty()) {
+ BuildManager.getInstance().notifyFilesDeleted(paths);
+ }
+ }
+
+ private boolean belongsToIntermediateSources(VirtualFile file, Project project) {
+ return FileUtil.isAncestor(myGeneratedDataPaths.get(project), new File(file.getPath()), true);
+ }
+
+ private void loadInfoAndAddSourceForRecompilation(final int projectId, final VirtualFile srcFile) {
+ addSourceForRecompilation(projectId, srcFile, loadSourceInfo(srcFile));
+ }
+ private void addSourceForRecompilation(final int projectId, final VirtualFile srcFile, @Nullable final SourceFileInfo srcInfo) {
+ final boolean alreadyMarked;
+ synchronized (myDataLock) {
+ TIntHashSet set = mySourcesToRecompile.get(projectId);
+ if (set == null) {
+ set = new TIntHashSet();
+ mySourcesToRecompile.put(projectId, set);
+ }
+ alreadyMarked = !set.add(Math.abs(getFileId(srcFile)));
+ if (!alreadyMarked && (LOG.isDebugEnabled() || ourDebugMode)) {
+ final String message = "Scheduled recompilation " + srcFile.getPresentableUrl();
+ LOG.debug(message);
+ if (ourDebugMode) {
+ System.out.println(message);
+ }
+ }
+ }
+
+ if (!alreadyMarked && srcInfo != null) {
+ srcInfo.updateTimestamp(projectId, -1L);
+ srcInfo.processOutputPaths(projectId, new ScheduleOutputsForDeletionProc(srcFile.getUrl()));
+ saveSourceInfo(srcFile, srcInfo);
+ }
+ }
+
+ private void removeSourceForRecompilation(final int projectId, final int srcId) {
+ synchronized (myDataLock) {
+ TIntHashSet set = mySourcesToRecompile.get(projectId);
+ if (set != null) {
+ set.remove(srcId);
+ if (set.isEmpty()) {
+ mySourcesToRecompile.remove(projectId);
+ }
+ }
+ }
+ }
+
+ public boolean isMarkedForCompilation(Project project, VirtualFile file) {
+ if (CompilerWorkspaceConfiguration.getInstance(project).USE_COMPILE_SERVER) {
+ final CompilerManager compilerManager = CompilerManager.getInstance(project);
+ return !compilerManager.isUpToDate(compilerManager.createFilesCompileScope(new VirtualFile[]{file}));
+ }
+ return isMarkedForRecompilation(getProjectId(project), getFileId(file));
+ }
+
+ private boolean isMarkedForRecompilation(int projectId, final int srcId) {
+ synchronized (myDataLock) {
+ final TIntHashSet set = mySourcesToRecompile.get(projectId);
+ return set != null && set.contains(srcId);
+ }
+ }
+
+ private interface Proc {
+ boolean execute(final int projectId, String outputPath);
+ }
+
+ private class ScheduleOutputsForDeletionProc implements Proc {
+ private final String mySrcUrl;
+ private final LocalFileSystem myFileSystem;
+ @Nullable
+ private VirtualFile myRootBeingDeleted;
+
+ private ScheduleOutputsForDeletionProc(final String srcUrl) {
+ mySrcUrl = srcUrl;
+ myFileSystem = LocalFileSystem.getInstance();
+ }
+
+ public void setRootBeingDeleted(@Nullable VirtualFile rootBeingDeleted) {
+ myRootBeingDeleted = rootBeingDeleted;
+ }
+
+ public boolean execute(final int projectId, String outputPath) {
+ final VirtualFile outFile = myFileSystem.findFileByPath(outputPath);
+ if (outFile != null) { // not deleted yet
+ if (myRootBeingDeleted != null && VfsUtil.isAncestor(myRootBeingDeleted, outFile, false)) {
+ unmarkOutputPathForDeletion(projectId, outputPath);
+ }
+ else {
+ final OutputFileInfo outputInfo = loadOutputInfo(outFile);
+ final String classname = outputInfo != null? outputInfo.getClassName() : null;
+ markOutputPathForDeletion(projectId, outputPath, classname, mySrcUrl);
+ }
+ }
+ return true;
+ }
+ }
+
+ private void markOutputPathForDeletion(final int projectId, final String outputPath, final String classname, final String srcUrl) {
+ final SourceUrlClassNamePair pair = new SourceUrlClassNamePair(srcUrl, classname);
+ synchronized (myDataLock) {
+ final Outputs outputs = myOutputsToDelete.get(projectId);
+ try {
+ outputs.put(outputPath, pair);
+ if (LOG.isDebugEnabled() || ourDebugMode) {
+ final String message = "ADD path to delete: " + outputPath + "; source: " + srcUrl;
+ LOG.debug(message);
+ if (ourDebugMode) {
+ System.out.println(message);
+ }
+ }
+ }
+ finally {
+ outputs.release();
+ }
+ }
+ }
+
+ private void unmarkOutputPathForDeletion(final int projectId, String outputPath) {
+ synchronized (myDataLock) {
+ final Outputs outputs = myOutputsToDelete.get(projectId);
+ try {
+ final SourceUrlClassNamePair val = outputs.remove(outputPath);
+ if (val != null) {
+ if (LOG.isDebugEnabled() || ourDebugMode) {
+ final String message = "REMOVE path to delete: " + outputPath;
+ LOG.debug(message);
+ if (ourDebugMode) {
+ System.out.println(message);
+ }
+ }
+ }
+ }
+ finally {
+ outputs.release();
+ }
+ }
+ }
+
+ public static final class ProjectRef extends Ref<Project> {
+ static class ProjectClosedException extends RuntimeException {
+ }
+
+ public ProjectRef(Project project) {
+ super(project);
+ }
+
+ public Project get() {
+ final Project project = super.get();
+ if (project != null && project.isDisposed()) {
+ throw new ProjectClosedException();
+ }
+ return project;
+ }
+ }
+
+ private static class Outputs {
+ private boolean myIsDirty = false;
+ @Nullable
+ private final File myStoreFile;
+ private final Map<String, SourceUrlClassNamePair> myMap;
+ private final AtomicInteger myRefCount = new AtomicInteger(1);
+
+ Outputs(@Nullable File storeFile, Map<String, SourceUrlClassNamePair> map) {
+ myStoreFile = storeFile;
+ myMap = map;
+ }
+
+ public Set<Map.Entry<String, SourceUrlClassNamePair>> getEntries() {
+ return Collections.unmodifiableSet(myMap.entrySet());
+ }
+
+ public void put(String outputPath, SourceUrlClassNamePair pair) {
+ if (myStoreFile == null) {
+ return;
+ }
+ if (pair == null) {
+ remove(outputPath);
+ }
+ else {
+ myMap.put(outputPath, pair);
+ myIsDirty = true;
+ }
+ }
+
+ public SourceUrlClassNamePair remove(String outputPath) {
+ if (myStoreFile == null) {
+ return null;
+ }
+ final SourceUrlClassNamePair removed = myMap.remove(outputPath);
+ myIsDirty |= removed != null;
+ return removed;
+ }
+
+ void allocate() {
+ myRefCount.incrementAndGet();
+ }
+
+ public void release() {
+ if (myRefCount.decrementAndGet() == 0) {
+ if (myIsDirty && myStoreFile != null) {
+ savePathsToDelete(myStoreFile, myMap);
+ }
+ }
+ }
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/TreeBasedMap.java b/java/compiler/impl/src/com/intellij/compiler/impl/TreeBasedMap.java
new file mode 100644
index 0000000..0031966
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/TreeBasedMap.java
@@ -0,0 +1,239 @@
+/*
+ * 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 com.intellij.compiler.impl;
+
+import com.intellij.util.containers.StringInterner;
+import com.intellij.util.containers.EmptyIterator;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Stack;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Jun 18, 2006
+ */
+public class TreeBasedMap<T> {
+ private Node<T> myRoot = new Node<T>();
+ private final StringInterner myInterner;
+ private final char mySeparator;
+ private int mySize = 0;
+
+ public TreeBasedMap(StringInterner table, final char separator) {
+ myInterner = table;
+ mySeparator = separator;
+ }
+
+ private class Node<T> {
+ private boolean myMappingExists = false;
+ private T myValue = null;
+ private @Nullable HashMap<String, Node<T>> myChildren = null;
+
+ public void setValue(T value) {
+ myValue = value;
+ myMappingExists = true;
+ }
+
+ public T getValue() {
+ return myValue;
+ }
+
+ public void removeValue() {
+ myValue = null;
+ myMappingExists = false;
+ }
+
+ public boolean mappingExists() {
+ return myMappingExists;
+ }
+
+ @Nullable
+ public Node<T> findRelative(String text, boolean create, final StringInterner table) {
+ return findRelative(text, 0, create, table);
+ }
+
+ @Nullable
+ private Node<T> findRelative(final String text, final int nameStartIndex, final boolean create, final StringInterner table) {
+ if (myChildren == null && !create) {
+ return null;
+ }
+
+ final int textLen = text.length();
+ final int separatorIdx = text.indexOf(mySeparator, nameStartIndex);
+ final int childNameEnd = separatorIdx >= 0 ? separatorIdx : textLen;
+
+ if (myChildren != null) {
+ final Node<T> child = myChildren.get(text.substring(nameStartIndex, childNameEnd));
+ if (child != null) {
+ if (separatorIdx < 0) {
+ return child;
+ }
+ return child.findRelative(text, childNameEnd + 1, create, table);
+ }
+ }
+
+ if (create) {
+ return addChild(table, text, nameStartIndex, childNameEnd);
+ }
+
+ return null;
+ }
+
+ @NotNull
+ private Node<T> addChild(final StringInterner table, final String text, final int nameStartIndex, final int nameEndIndex) {
+ if (myChildren == null) {
+ myChildren = new HashMap<String, Node<T>>(3, 0.95f);
+ }
+
+ Node<T> newChild = new Node<T>();
+ final String key = table.intern(text.substring(nameStartIndex, nameEndIndex));
+ myChildren.put(key, newChild);
+
+ if (nameEndIndex == text.length()) {
+ return newChild;
+ }
+
+ Node<T> restNodes = newChild.findRelative(text, nameEndIndex + 1, true, table);
+ assert restNodes != null;
+ return restNodes;
+ }
+ }
+
+ public void put(String key, T value) {
+ final Node<T> node = myRoot.findRelative(key, true, myInterner);
+ assert node != null;
+ final boolean mappingExisted = node.mappingExists();
+ node.setValue(value);
+ if (!mappingExisted) {
+ mySize++;
+ }
+ }
+
+ public void remove(String key) {
+ final Node node = myRoot.findRelative(key, false, myInterner);
+ if (node != null && node.mappingExists()) {
+ node.removeValue();
+ mySize--;
+ }
+ }
+
+ public int size() {
+ return mySize;
+ }
+
+ public T get(String key) {
+ final Node<T> node = myRoot.findRelative(key, false, myInterner);
+ return (node != null && node.mappingExists()) ? node.getValue() : null;
+ }
+
+ public boolean containsKey(String key) {
+ final Node<T> node = myRoot.findRelative(key, false, myInterner);
+ return node != null && node.mappingExists();
+ }
+
+ public void removeAll() {
+ myRoot = new Node<T>();
+ }
+
+ public Iterator<String> getKeysIterator() {
+ return new KeysIterator();
+ }
+
+
+ private class KeysIterator implements Iterator<String> {
+ private final Stack<PathElement<T>> myCurrentNodePath = new Stack<PathElement<T>>();
+ private final StringBuilder myCurrentName = new StringBuilder();
+
+ public KeysIterator() {
+ pushNode("", myRoot);
+ findNextNode();
+ }
+
+ public boolean hasNext() {
+ return myCurrentNodePath.size() > 0;
+ }
+
+ public String next() {
+ final String key = myCurrentName.toString();
+ popNode();
+ findNextNode();
+ return key;
+ }
+
+ public void remove() {
+ throw new UnsupportedOperationException("Remove not supported");
+ }
+
+ private boolean pushNode(final @NotNull String name, @NotNull Node<T> node) {
+ final HashMap<String, Node<T>> childrenMap = node.myChildren;
+ final boolean hasChildren = childrenMap != null && childrenMap.size() > 0;
+ if (hasChildren || node.mappingExists()) {
+ myCurrentNodePath.push(new PathElement<T>(node, hasChildren? childrenMap.keySet().iterator() : EmptyIterator.<String>getInstance()));
+ if (myCurrentNodePath.size() > 2) {
+ // do not add separator before the Root and its direct child nodes
+ myCurrentName.append(mySeparator);
+ }
+ myCurrentName.append(name);
+ return true;
+ }
+ return false;
+ }
+
+ private void popNode() {
+ myCurrentNodePath.pop();
+ final int separatorIndex = myCurrentName.lastIndexOf(String.valueOf(mySeparator));
+ if (separatorIndex >= 0) {
+ myCurrentName.replace(separatorIndex, myCurrentName.length(), "");
+ }
+ else {
+ myCurrentName.setLength(0);
+ }
+ }
+
+ private void findNextNode() {
+ MAIN_LOOP: while (!myCurrentNodePath.isEmpty()) {
+ final PathElement<T> element = myCurrentNodePath.peek();
+ final Iterator<String> childrenIterator = element.iterator;
+ final Node<T> currentNode = element.node;
+ while (childrenIterator.hasNext()) {
+ final String name = childrenIterator.next();
+ final Node<T> childNode = currentNode.myChildren.get(name);
+ if (pushNode(name, childNode)) {
+ continue MAIN_LOOP;
+ }
+ }
+ if (!currentNode.mappingExists()) {
+ popNode();
+ }
+ else {
+ break;
+ }
+ }
+ }
+ }
+
+ private class PathElement<T> {
+ final @NotNull Iterator<String> iterator;
+ final @NotNull Node<T> node;
+ public PathElement(@NotNull final Node<T> node, Iterator<String> iterator) {
+ this.node = node;
+ this.iterator = iterator;
+ }
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/TreeBasedPathsSet.java b/java/compiler/impl/src/com/intellij/compiler/impl/TreeBasedPathsSet.java
new file mode 100644
index 0000000..4fd3ee8
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/TreeBasedPathsSet.java
@@ -0,0 +1,42 @@
+/*
+ * 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 com.intellij.compiler.impl;
+
+import com.intellij.util.containers.StringInterner;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Jun 19, 2006
+ */
+public class TreeBasedPathsSet {
+ private final TreeBasedMap<Object> myMap;
+
+ public TreeBasedPathsSet(StringInterner interner, char separator) {
+ myMap = new TreeBasedMap<Object>(interner, separator);
+ }
+
+ public void add(String path) {
+ myMap.put(path, null);
+ }
+
+ public void remove(String path) {
+ myMap.remove(path);
+ }
+
+ public boolean contains(String path) {
+ return myMap.containsKey(path);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/generic/GenericCompilerCache.java b/java/compiler/impl/src/com/intellij/compiler/impl/generic/GenericCompilerCache.java
new file mode 100644
index 0000000..5741b9c
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/generic/GenericCompilerCache.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.compiler.impl.generic;
+
+import com.intellij.openapi.compiler.generic.GenericCompiler;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.util.Processor;
+import com.intellij.util.io.DataExternalizer;
+import com.intellij.util.io.KeyDescriptor;
+import com.intellij.util.io.PersistentEnumerator;
+import com.intellij.util.io.PersistentHashMap;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author nik
+ */
+public class GenericCompilerCache<Key, SourceState, OutputState> {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.generic.GenericCompilerCache");
+ private PersistentHashMap<KeyAndTargetData<Key>, PersistentStateData<SourceState, OutputState>> myPersistentMap;
+ private File myCacheFile;
+ private final GenericCompiler<Key, SourceState, OutputState> myCompiler;
+
+ public GenericCompilerCache(GenericCompiler<Key, SourceState, OutputState> compiler, final File compilerCacheDir) throws IOException {
+ myCompiler = compiler;
+ myCacheFile = new File(compilerCacheDir, "timestamps");
+ createMap();
+ }
+
+ private void createMap() throws IOException {
+ try {
+ myPersistentMap = new PersistentHashMap<KeyAndTargetData<Key>, PersistentStateData<SourceState,OutputState>>(myCacheFile, new SourceItemDataDescriptor(myCompiler.getItemKeyDescriptor()),
+ new PersistentStateDataExternalizer(myCompiler));
+ }
+ catch (PersistentEnumerator.CorruptedException e) {
+ FileUtil.delete(myCacheFile);
+ throw e;
+ }
+ }
+
+ private KeyAndTargetData<Key> getKeyAndTargetData(Key key, int target) {
+ return new KeyAndTargetData<Key>(target, key);
+ }
+
+ public void wipe() throws IOException {
+ try {
+ myPersistentMap.close();
+ }
+ catch (IOException ignored) {
+ }
+ PersistentHashMap.deleteFilesStartingWith(myCacheFile);
+ createMap();
+ }
+
+ public void close() {
+ try {
+ myPersistentMap.close();
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ }
+ }
+
+ public void remove(int targetId, Key key) throws IOException {
+ myPersistentMap.remove(getKeyAndTargetData(key, targetId));
+ }
+
+ public PersistentStateData<SourceState, OutputState> getState(int targetId, Key key) throws IOException {
+ return myPersistentMap.get(getKeyAndTargetData(key, targetId));
+ }
+
+ public void processSources(final int targetId, final Processor<Key> processor) throws IOException {
+ myPersistentMap.processKeysWithExistingMapping(new Processor<KeyAndTargetData<Key>>() {
+ @Override
+ public boolean process(KeyAndTargetData<Key> data) {
+ return targetId == data.myTarget ? processor.process(data.myKey) : true;
+ }
+ });
+ }
+
+ public void putState(int targetId, @NotNull Key key, @NotNull SourceState sourceState, @NotNull OutputState outputState) throws IOException {
+ myPersistentMap.put(getKeyAndTargetData(key, targetId), new PersistentStateData<SourceState,OutputState>(sourceState, outputState));
+ }
+
+
+ private static class KeyAndTargetData<Key> {
+ public final int myTarget;
+ public final Key myKey;
+
+ private KeyAndTargetData(int target, Key key) {
+ myTarget = target;
+ myKey = key;
+ }
+ }
+
+ public static class PersistentStateData<SourceState, OutputState> {
+ public final SourceState mySourceState;
+ public final OutputState myOutputState;
+
+ private PersistentStateData(@NotNull SourceState sourceState, @NotNull OutputState outputState) {
+ mySourceState = sourceState;
+ myOutputState = outputState;
+ }
+ }
+
+ private class SourceItemDataDescriptor implements KeyDescriptor<KeyAndTargetData<Key>> {
+ private final KeyDescriptor<Key> myKeyDescriptor;
+
+ public SourceItemDataDescriptor(KeyDescriptor<Key> keyDescriptor) {
+ myKeyDescriptor = keyDescriptor;
+ }
+
+ @Override
+ public boolean isEqual(KeyAndTargetData<Key> val1, KeyAndTargetData<Key> val2) {
+ return val1.myTarget == val2.myTarget;
+ }
+
+ @Override
+ public int getHashCode(KeyAndTargetData<Key> value) {
+ return value.myTarget + 239 * myKeyDescriptor.getHashCode(value.myKey);
+ }
+
+ @Override
+ public void save(DataOutput out, KeyAndTargetData<Key> value) throws IOException {
+ out.writeInt(value.myTarget);
+ myKeyDescriptor.save(out, value.myKey);
+ }
+
+
+ @Override
+ public KeyAndTargetData<Key> read(DataInput in) throws IOException {
+ int target = in.readInt();
+ final Key item = myKeyDescriptor.read(in);
+ return getKeyAndTargetData(item, target);
+ }
+ }
+
+ private class PersistentStateDataExternalizer implements DataExternalizer<PersistentStateData<SourceState, OutputState>> {
+ private DataExternalizer<SourceState> mySourceStateExternalizer;
+ private DataExternalizer<OutputState> myOutputStateExternalizer;
+
+ public PersistentStateDataExternalizer(GenericCompiler<Key,SourceState,OutputState> compiler) {
+ mySourceStateExternalizer = compiler.getSourceStateExternalizer();
+ myOutputStateExternalizer = compiler.getOutputStateExternalizer();
+ }
+
+ @Override
+ public void save(DataOutput out, PersistentStateData<SourceState, OutputState> value) throws IOException {
+ mySourceStateExternalizer.save(out, value.mySourceState);
+ myOutputStateExternalizer.save(out, value.myOutputState);
+ }
+
+ @Override
+ public PersistentStateData<SourceState, OutputState> read(DataInput in) throws IOException {
+ SourceState sourceState = mySourceStateExternalizer.read(in);
+ OutputState outputState = myOutputStateExternalizer.read(in);
+ return new PersistentStateData<SourceState,OutputState>(sourceState, outputState);
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/generic/GenericCompilerPersistentData.java b/java/compiler/impl/src/com/intellij/compiler/impl/generic/GenericCompilerPersistentData.java
new file mode 100644
index 0000000..513be6c
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/generic/GenericCompilerPersistentData.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.compiler.impl.generic;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.util.io.IOUtil;
+import gnu.trove.TIntHashSet;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.*;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author nik
+ */
+public class GenericCompilerPersistentData {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.generic.GenericCompilerPersistentData");
+ private static final int VERSION = 1;
+ private File myFile;
+ private Map<String, Integer> myTarget2Id = new HashMap<String, Integer>();
+ private TIntHashSet myUsedIds = new TIntHashSet();
+ private boolean myVersionChanged;
+ private final int myCompilerVersion;
+
+ public GenericCompilerPersistentData(File cacheStoreDirectory, int compilerVersion) throws IOException {
+ myCompilerVersion = compilerVersion;
+ myFile = new File(cacheStoreDirectory, "info");
+ if (!myFile.exists()) {
+ LOG.info("Compiler info file doesn't exists: " + myFile.getAbsolutePath());
+ myVersionChanged = true;
+ return;
+ }
+
+ DataInputStream input = new DataInputStream(new BufferedInputStream(new FileInputStream(myFile)));
+ try {
+ final int dataVersion = input.readInt();
+ if (dataVersion != VERSION) {
+ LOG.info("Version of compiler info file (" + myFile.getAbsolutePath() + ") changed: " + dataVersion + " -> " + VERSION);
+ myVersionChanged = true;
+ return;
+ }
+
+ final int savedCompilerVersion = input.readInt();
+ if (savedCompilerVersion != compilerVersion) {
+ LOG.info("Compiler caches version changed (" + myFile.getAbsolutePath() + "): " + savedCompilerVersion + " -> " + compilerVersion);
+ myVersionChanged = true;
+ return;
+ }
+
+ int size = input.readInt();
+ while (size-- > 0) {
+ final String target = IOUtil.readString(input);
+ final int id = input.readInt();
+ myTarget2Id.put(target, id);
+ myUsedIds.add(id);
+ }
+ }
+ finally {
+ input.close();
+ }
+ }
+
+ public boolean isVersionChanged() {
+ return myVersionChanged;
+ }
+
+ public void save() throws IOException {
+ final DataOutputStream output = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(myFile)));
+ try {
+ output.writeInt(VERSION);
+ output.writeInt(myCompilerVersion);
+ output.writeInt(myTarget2Id.size());
+
+ for (Map.Entry<String, Integer> entry : myTarget2Id.entrySet()) {
+ IOUtil.writeString(entry.getKey(), output);
+ output.writeInt(entry.getValue());
+ }
+ }
+ finally {
+ output.close();
+ }
+ }
+
+ public int getId(@NotNull String target) {
+ if (myTarget2Id.containsKey(target)) {
+ return myTarget2Id.get(target);
+ }
+ int id = 0;
+ while (myUsedIds.contains(id)) {
+ id++;
+ }
+ myTarget2Id.put(target, id);
+ myUsedIds.add(id);
+ return id;
+ }
+
+ public Set<String> getAllTargets() {
+ return myTarget2Id.keySet();
+ }
+
+ public int removeId(String target) {
+ return myTarget2Id.remove(target);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/AnnotationProcessingCompiler.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/AnnotationProcessingCompiler.java
new file mode 100644
index 0000000..c7269a4
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/AnnotationProcessingCompiler.java
@@ -0,0 +1,151 @@
+/*
+ * 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.
+ */
+
+/*
+ * @author: Eugene Zhuravlev
+ * Date: Jan 17, 2003
+ * Time: 3:22:59 PM
+ */
+package com.intellij.compiler.impl.javaCompiler;
+
+import com.intellij.compiler.CompilerConfiguration;
+import com.intellij.compiler.CompilerConfigurationImpl;
+import com.intellij.compiler.CompilerException;
+import com.intellij.compiler.impl.CompileContextExProxy;
+import com.intellij.compiler.impl.javaCompiler.javac.JavacCompiler;
+import com.intellij.compiler.make.CacheCorruptedException;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.*;
+import com.intellij.openapi.compiler.ex.CompileContextEx;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.fileTypes.StdFileTypes;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.Chunk;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+public class AnnotationProcessingCompiler implements TranslatingCompiler{
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.javaCompiler.JavaCompiler");
+ private final Project myProject;
+ private final CompilerConfiguration myConfig;
+
+ public AnnotationProcessingCompiler(Project project) {
+ myProject = project;
+ myConfig = CompilerConfiguration.getInstance(project);
+ }
+
+ @NotNull
+ public String getDescription() {
+ return CompilerBundle.message("annotation.processing.compiler.description");
+ }
+
+ public boolean isCompilableFile(VirtualFile file, CompileContext context) {
+ if (!context.isAnnotationProcessorsEnabled()) {
+ return false;
+ }
+ return file.getFileType() == StdFileTypes.JAVA && !isExcludedFromAnnotationProcessing(file, context);
+ }
+
+ public void compile(final CompileContext context, final Chunk<Module> moduleChunk, final VirtualFile[] files, OutputSink sink) {
+ if (!context.isAnnotationProcessorsEnabled()) {
+ return;
+ }
+ final LocalFileSystem lfs = LocalFileSystem.getInstance();
+ final CompileContextEx _context = new CompileContextExProxy((CompileContextEx)context) {
+ public VirtualFile getModuleOutputDirectory(Module module) {
+ final String path = CompilerPaths.getAnnotationProcessorsGenerationPath(module);
+ return path != null? lfs.findFileByPath(path) : null;
+ }
+
+ public VirtualFile getModuleOutputDirectoryForTests(Module module) {
+ return getModuleOutputDirectory(module);
+ }
+ };
+ final JavacCompiler javacCompiler = getBackEndCompiler();
+ final boolean processorMode = javacCompiler.setAnnotationProcessorMode(true);
+ final BackendCompilerWrapper wrapper = new BackendCompilerWrapper(moduleChunk, myProject, Arrays.asList(files), _context, javacCompiler, sink);
+ wrapper.setForceCompileTestsSeparately(true);
+ try {
+ wrapper.compile();
+ }
+ catch (CompilerException e) {
+ _context.addMessage(CompilerMessageCategory.ERROR, e.getMessage(), null, -1, -1);
+ }
+ catch (CacheCorruptedException e) {
+ LOG.info(e);
+ _context.requestRebuildNextTime(e.getMessage());
+ }
+ finally {
+ javacCompiler.setAnnotationProcessorMode(processorMode);
+ final Set<VirtualFile> dirsToRefresh = new HashSet<VirtualFile>();
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ for (Module module : moduleChunk.getNodes()) {
+ final VirtualFile out = _context.getModuleOutputDirectory(module);
+ if (out != null) {
+ dirsToRefresh.add(out);
+ }
+ }
+ }
+ });
+ for (VirtualFile root : dirsToRefresh) {
+ root.refresh(false, true);
+ }
+ }
+ }
+
+ private boolean isExcludedFromAnnotationProcessing(VirtualFile file, CompileContext context) {
+ if (!context.isAnnotationProcessorsEnabled()) {
+ return true;
+ }
+ final Module module = context.getModuleByFile(file);
+ if (module != null) {
+ if (!myConfig.getAnnotationProcessingConfiguration(module).isEnabled()) {
+ return true;
+ }
+ final String path = CompilerPaths.getAnnotationProcessorsGenerationPath(module);
+ final VirtualFile generationDir = path != null? LocalFileSystem.getInstance().findFileByPath(path) : null;
+ if (generationDir != null && VfsUtil.isAncestor(generationDir, file, false)) {
+ return true;
+ }
+ }
+ return myConfig.isExcludedFromCompilation(file);
+ }
+
+ public boolean validateConfiguration(CompileScope scope) {
+ final JavacCompiler compiler = getBackEndCompiler();
+ final boolean previousValue = compiler.setAnnotationProcessorMode(true);
+ try {
+ return compiler.checkCompiler(scope);
+ }
+ finally {
+ compiler.setAnnotationProcessorMode(previousValue);
+ }
+ }
+
+ private JavacCompiler getBackEndCompiler() {
+ CompilerConfigurationImpl configuration = (CompilerConfigurationImpl)myConfig;
+ return configuration.getJavacCompiler();
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/BackendCompiler.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/BackendCompiler.java
new file mode 100644
index 0000000..9da4a39
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/BackendCompiler.java
@@ -0,0 +1,50 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler;
+
+import com.intellij.compiler.OutputParser;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.CompileScope;
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.options.Configurable;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.util.Set;
+
+public interface BackendCompiler {
+ ExtensionPointName<BackendCompiler> EP_NAME = ExtensionPointName.create("com.intellij.java.compiler");
+
+ @NotNull @NonNls String getId(); // used for externalization
+ @NotNull String getPresentableName();
+ @NotNull Configurable createConfigurable();
+ @NotNull Set<FileType> getCompilableFileTypes();
+ @Nullable OutputParser createErrorParser(@NotNull String outputDir, Process process);
+ @Nullable OutputParser createOutputParser(@NotNull String outputDir);
+
+ boolean checkCompiler(final CompileScope scope);
+
+ @NotNull Process launchProcess(
+ @NotNull ModuleChunk chunk,
+ @NotNull String outputDir,
+ @NotNull CompileContext compileContext) throws IOException;
+
+ void compileFinished();
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/BackendCompilerWrapper.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/BackendCompilerWrapper.java
new file mode 100644
index 0000000..a6567a5
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/BackendCompilerWrapper.java
@@ -0,0 +1,1014 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl.javaCompiler;
+
+import com.intellij.codeInsight.NullableNotNullManager;
+import com.intellij.compiler.*;
+import com.intellij.compiler.classParsing.AnnotationConstantValue;
+import com.intellij.compiler.classParsing.MethodInfo;
+import com.intellij.compiler.impl.CompileDriver;
+import com.intellij.compiler.impl.CompilerUtil;
+import com.intellij.compiler.make.Cache;
+import com.intellij.compiler.make.CacheCorruptedException;
+import com.intellij.compiler.make.DependencyCache;
+import com.intellij.compiler.make.MakeUtil;
+import com.intellij.compiler.notNullVerification.NotNullVerifyingInstrumenter;
+import com.intellij.ide.util.projectWizard.JavaModuleBuilder;
+import com.intellij.openapi.application.Application;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.*;
+import com.intellij.openapi.compiler.ex.CompileContextEx;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.fileTypes.FileTypeManager;
+import com.intellij.openapi.module.JavaModuleType;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleType;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.JavaSdk;
+import com.intellij.openapi.projectRoots.JavaSdkType;
+import com.intellij.openapi.projectRoots.JavaSdkVersion;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.roots.*;
+import com.intellij.openapi.util.*;
+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.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileVisitor;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.search.GlobalSearchScopes;
+import com.intellij.util.Chunk;
+import com.intellij.util.cls.ClsFormatException;
+import gnu.trove.THashMap;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.asm4.ClassReader;
+import org.jetbrains.asm4.ClassWriter;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.*;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+/**
+ * @author Eugene Zhuravlev
+ * @since Jan 24, 2003
+ */
+public class BackendCompilerWrapper {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.javaCompiler.BackendCompilerWrapper");
+
+ private final BackendCompiler myCompiler;
+
+ private final CompileContextEx myCompileContext;
+ private final List<VirtualFile> myFilesToCompile;
+ private final TranslatingCompiler.OutputSink mySink;
+ private final Chunk<Module> myChunk;
+ private final Project myProject;
+ private final Map<Module, VirtualFile> myModuleToTempDirMap = new THashMap<Module, VirtualFile>();
+ private final ProjectFileIndex myProjectFileIndex;
+ @NonNls private static final String PACKAGE_ANNOTATION_FILE_NAME = "package-info.java";
+ private static final FileObject myStopThreadToken = new FileObject(new File(""), new byte[0]);
+ public final Map<String, Set<CompiledClass>> myFileNameToSourceMap= new THashMap<String, Set<CompiledClass>>();
+ private final Set<VirtualFile> myProcessedPackageInfos = new HashSet<VirtualFile>();
+ private final CompileStatistics myStatistics;
+ private volatile String myModuleName = null;
+ private boolean myForceCompileTestsSeparately = false;
+
+ public BackendCompilerWrapper(Chunk<Module> chunk, @NotNull final Project project,
+ @NotNull List<VirtualFile> filesToCompile,
+ @NotNull CompileContextEx compileContext,
+ @NotNull BackendCompiler compiler, TranslatingCompiler.OutputSink sink) {
+ myChunk = chunk;
+ myProject = project;
+ myCompiler = compiler;
+ myCompileContext = compileContext;
+ myFilesToCompile = filesToCompile;
+ mySink = sink;
+ myProjectFileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
+ CompileStatistics stat = compileContext.getUserData(CompileStatistics.KEY);
+ if (stat == null) {
+ stat = new CompileStatistics();
+ compileContext.putUserData(CompileStatistics.KEY, stat);
+ }
+ myStatistics = stat;
+ }
+
+ public void compile() throws CompilerException, CacheCorruptedException {
+ Application application = ApplicationManager.getApplication();
+ try {
+ if (!myFilesToCompile.isEmpty()) {
+ if (application.isUnitTestMode()) {
+ saveTestData();
+ }
+ compileModules(buildModuleToFilesMap(myFilesToCompile));
+ }
+ }
+ catch (SecurityException e) {
+ throw new CompilerException(CompilerBundle.message("error.compiler.process.not.started", e.getMessage()), e);
+ }
+ catch (IllegalArgumentException e) {
+ throw new CompilerException(e.getMessage(), e);
+ }
+ finally {
+ for (final VirtualFile file : myModuleToTempDirMap.values()) {
+ if (file != null) {
+ final File ioFile = new File(file.getPath());
+ FileUtil.asyncDelete(ioFile);
+ }
+ }
+ myModuleToTempDirMap.clear();
+ }
+
+ if (!myFilesToCompile.isEmpty() && myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) == 0) {
+ // package-info.java hack
+ final List<TranslatingCompiler.OutputItem> outputs = new ArrayList<TranslatingCompiler.OutputItem>();
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ for (final VirtualFile file : myFilesToCompile) {
+ if (PACKAGE_ANNOTATION_FILE_NAME.equals(file.getName()) && !myProcessedPackageInfos.contains(file)) {
+ outputs.add(new OutputItemImpl(file));
+ }
+ }
+ }
+ });
+ if (!outputs.isEmpty()) {
+ mySink.add(null, outputs, VirtualFile.EMPTY_ARRAY);
+ }
+ }
+ }
+
+ public boolean isForceCompileTestsSeparately() {
+ return myForceCompileTestsSeparately;
+ }
+
+ public void setForceCompileTestsSeparately(boolean forceCompileTestsSeparately) {
+ myForceCompileTestsSeparately = forceCompileTestsSeparately;
+ }
+
+ private Map<Module, List<VirtualFile>> buildModuleToFilesMap(final List<VirtualFile> filesToCompile) {
+ if (myChunk.getNodes().size() == 1) {
+ return Collections.singletonMap(myChunk.getNodes().iterator().next(), Collections.unmodifiableList(filesToCompile));
+ }
+ return CompilerUtil.buildModuleToFilesMap(myCompileContext, filesToCompile);
+ }
+
+ private void compileModules(final Map<Module, List<VirtualFile>> moduleToFilesMap) throws CompilerException {
+ try {
+ compileChunk(new ModuleChunk(myCompileContext, myChunk, moduleToFilesMap));
+ }
+ catch (IOException e) {
+ throw new CompilerException(e.getMessage(), e);
+ }
+ }
+
+ private void compileChunk(ModuleChunk chunk) throws IOException {
+ final String chunkPresentableName = getPresentableNameFor(chunk);
+ myModuleName = chunkPresentableName;
+
+ // validate encodings
+ if (chunk.getModuleCount() > 1) {
+ validateEncoding(chunk, chunkPresentableName);
+ // todo: validation for bytecode target?
+ }
+
+ runTransformingCompilers(chunk);
+
+
+ final List<OutputDir> outs = new ArrayList<OutputDir>();
+ File fileToDelete = getOutputDirsToCompileTo(chunk, outs);
+
+ try {
+ for (final OutputDir outputDir : outs) {
+ chunk.setSourcesFilter(outputDir.getKind());
+ doCompile(chunk, outputDir.getPath());
+ }
+ }
+ finally {
+ if (fileToDelete != null) {
+ FileUtil.asyncDelete(fileToDelete);
+ }
+ }
+ }
+
+ private void validateEncoding(ModuleChunk chunk, String chunkPresentableName) {
+ final CompilerEncodingService es = CompilerEncodingService.getInstance(myProject);
+ Charset charset = null;
+ for (Module module : chunk.getModules()) {
+ final Charset moduleCharset = es.getPreferredModuleEncoding(module);
+ if (charset == null) {
+ charset = moduleCharset;
+ }
+ else {
+ if (!Comparing.equal(charset, moduleCharset)) {
+ // warn user
+ final Charset chunkEncoding = CompilerEncodingService.getPreferredModuleEncoding(chunk);
+ final StringBuilder message = new StringBuilder();
+ message.append("Modules in chunk [");
+ message.append(chunkPresentableName);
+ message.append("] configured to use different encodings.\n");
+ if (chunkEncoding != null) {
+ message.append("\"").append(chunkEncoding.name()).append("\" encoding will be used to compile the chunk");
+ }
+ else {
+ message.append("Default compiler encoding will be used to compile the chunk");
+ }
+ myCompileContext.addMessage(CompilerMessageCategory.INFORMATION, message.toString(), null, -1, -1);
+ break;
+ }
+ }
+ }
+ }
+
+
+ private static String getPresentableNameFor(final ModuleChunk chunk) {
+ return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
+ public String compute() {
+ final Module[] modules = chunk.getModules();
+ StringBuilder moduleName = new StringBuilder(Math.min(128, modules.length * 8));
+ for (int idx = 0; idx < modules.length; idx++) {
+ final Module module = modules[idx];
+ if (idx > 0) {
+ moduleName.append(", ");
+ }
+ moduleName.append(module.getName());
+ if (moduleName.length() > 128 && idx + 1 < modules.length /*name is already too long and seems to grow longer*/) {
+ moduleName.append("...");
+ break;
+ }
+ }
+ return moduleName.toString();
+ }
+ });
+ }
+
+ @Nullable
+ private File getOutputDirsToCompileTo(ModuleChunk chunk, final List<OutputDir> dirs) throws IOException {
+ File fileToDelete = null;
+ if (chunk.getModuleCount() == 1) { // optimization
+ final Module module = chunk.getModules()[0];
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ final String sourcesOutputDir = getOutputDir(module);
+ if (shouldCompileTestsSeparately(module)) {
+ if (sourcesOutputDir != null) {
+ dirs.add(new OutputDir(sourcesOutputDir, ModuleChunk.SOURCES));
+ }
+ final String testsOutputDir = getTestsOutputDir(module);
+ if (testsOutputDir == null) {
+ LOG.error("Tests output dir is null for module \"" + module.getName() + "\"");
+ }
+ else {
+ dirs.add(new OutputDir(testsOutputDir, ModuleChunk.TEST_SOURCES));
+ }
+ }
+ else { // both sources and test sources go into the same output
+ if (sourcesOutputDir == null) {
+ LOG.error("Sources output dir is null for module \"" + module.getName() + "\"");
+ }
+ else {
+ dirs.add(new OutputDir(sourcesOutputDir, ModuleChunk.ALL_SOURCES));
+ }
+ }
+ }
+ });
+ }
+ else { // chunk has several modules
+ final File outputDir = FileUtil.createTempDirectory("compile", "output");
+ fileToDelete = outputDir;
+ dirs.add(new OutputDir(outputDir.getPath(), ModuleChunk.ALL_SOURCES));
+ }
+ return fileToDelete;
+ }
+
+
+ private boolean shouldCompileTestsSeparately(Module module) {
+ if (myForceCompileTestsSeparately) {
+ return true;
+ }
+ final String moduleTestOutputDirectory = getTestsOutputDir(module);
+ if (moduleTestOutputDirectory == null) {
+ return false;
+ }
+ // here we have test output specified
+ final String moduleOutputDirectory = getOutputDir(module);
+ if (moduleOutputDirectory == null) {
+ // only test output is specified, so should return true
+ return true;
+ }
+ return !FileUtil.pathsEqual(moduleTestOutputDirectory, moduleOutputDirectory);
+ }
+
+ private void saveTestData() {
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ for (VirtualFile file : myFilesToCompile) {
+ CompilerManagerImpl.addCompiledPath(file.getPath());
+ }
+ }
+ });
+ }
+
+ private final Object lock = new Object();
+
+ private class SynchedCompilerParsing extends CompilerParsingThread {
+ private final ClassParsingThread myClassParsingThread;
+
+ private SynchedCompilerParsing(Process process,
+ final CompileContext context,
+ OutputParser outputParser,
+ ClassParsingThread classParsingThread,
+ boolean readErrorStream,
+ boolean trimLines) {
+ super(process, outputParser, readErrorStream, trimLines,context);
+ myClassParsingThread = classParsingThread;
+ }
+
+ public void setProgressText(String text) {
+ synchronized (lock) {
+ super.setProgressText(text);
+ }
+ }
+
+ public void message(CompilerMessageCategory category, String message, String url, int lineNum, int columnNum) {
+ synchronized (lock) {
+ super.message(category, message, url, lineNum, columnNum);
+ }
+ }
+
+ public void fileProcessed(String path) {
+ synchronized (lock) {
+ sourceFileProcessed();
+ }
+ }
+
+ protected void processCompiledClass(final FileObject classFileToProcess) throws CacheCorruptedException {
+ synchronized (lock) {
+ myClassParsingThread.addPath(classFileToProcess);
+ }
+ }
+ }
+
+ private void doCompile(@NotNull final ModuleChunk chunk, @NotNull String outputDir) throws IOException {
+ myCompileContext.getProgressIndicator().checkCanceled();
+
+ if (ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
+ public Boolean compute() {
+ return chunk.getFilesToCompile().isEmpty() ? Boolean.TRUE : Boolean.FALSE;
+ }
+ }).booleanValue()) {
+ return; // should not invoke javac with empty sources list
+ }
+
+ ModuleType moduleType = ModuleType.get(chunk.getModules()[0]);
+ if ((chunk.getJdk() == null || !(chunk.getJdk().getSdkType() instanceof JavaSdkType)) &&
+ !(moduleType instanceof JavaModuleType || moduleType.createModuleBuilder() instanceof JavaModuleBuilder)) {
+ // TODO
+ // don't try to compile non-java type module
+ return;
+ }
+
+ int exitValue = 0;
+ try {
+ final Process process = myCompiler.launchProcess(chunk, outputDir, myCompileContext);
+ final long compilationStart = System.currentTimeMillis();
+ final ClassParsingThread classParsingThread = new ClassParsingThread(isJdk6(chunk.getJdk()), outputDir);
+ final Future<?> classParsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(classParsingThread);
+
+ OutputParser errorParser = myCompiler.createErrorParser(outputDir, process);
+ CompilerParsingThread errorParsingThread = errorParser == null
+ ? null
+ : new SynchedCompilerParsing(process, myCompileContext, errorParser, classParsingThread,
+ true, errorParser.isTrimLines());
+ Future<?> errorParsingThreadFuture = null;
+ if (errorParsingThread != null) {
+ errorParsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(errorParsingThread);
+ }
+
+ OutputParser outputParser = myCompiler.createOutputParser(outputDir);
+ CompilerParsingThread outputParsingThread = outputParser == null
+ ? null
+ : new SynchedCompilerParsing(process, myCompileContext, outputParser, classParsingThread,
+ false, outputParser.isTrimLines());
+ Future<?> outputParsingThreadFuture = null;
+ if (outputParsingThread != null) {
+ outputParsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(outputParsingThread);
+ }
+
+ try {
+ exitValue = process.waitFor();
+ }
+ catch (InterruptedException e) {
+ process.destroy();
+ exitValue = process.exitValue();
+ }
+ catch (Error e) {
+ process.destroy();
+ exitValue = process.exitValue();
+ throw e;
+ }
+ finally {
+ if (CompileDriver.ourDebugMode) {
+ System.out.println("Compiler exit code is " + exitValue);
+ }
+ if (errorParsingThread != null) {
+ errorParsingThread.setProcessTerminated(true);
+ }
+ if (outputParsingThread != null) {
+ outputParsingThread.setProcessTerminated(true);
+ }
+ joinThread(errorParsingThreadFuture);
+ joinThread(outputParsingThreadFuture);
+ classParsingThread.stopParsing();
+ joinThread(classParsingThreadFuture);
+
+ registerParsingException(outputParsingThread);
+ registerParsingException(errorParsingThread);
+ assert outputParsingThread == null || !outputParsingThread.processing;
+ assert errorParsingThread == null || !errorParsingThread.processing;
+ assert classParsingThread == null || !classParsingThread.processing;
+ }
+ }
+ finally {
+ compileFinished(exitValue, chunk, outputDir);
+ myModuleName = null;
+ }
+ }
+
+ private static void joinThread(final Future<?> threadFuture) {
+ if (threadFuture != null) {
+ try {
+ threadFuture.get();
+ }
+ catch (InterruptedException ignored) {
+ LOG.info("Thread interrupted", ignored);
+ }
+ catch (ExecutionException ignored) {
+ LOG.info("Thread interrupted", ignored);
+ }
+ }
+ }
+
+ private void registerParsingException(final CompilerParsingThread outputParsingThread) {
+ Throwable error = outputParsingThread == null ? null : outputParsingThread.getError();
+ if (error != null) {
+ String message = error.getMessage();
+ if (error instanceof CacheCorruptedException) {
+ myCompileContext.requestRebuildNextTime(message);
+ }
+ else {
+ myCompileContext.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1);
+ }
+ }
+ }
+
+ private void runTransformingCompilers(final ModuleChunk chunk) {
+ final JavaSourceTransformingCompiler[] transformers =
+ CompilerManager.getInstance(myProject).getCompilers(JavaSourceTransformingCompiler.class);
+ if (transformers.length == 0) {
+ return;
+ }
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Running transforming compilers...");
+ }
+ final Module[] modules = chunk.getModules();
+ for (final JavaSourceTransformingCompiler transformer : transformers) {
+ final Map<VirtualFile, VirtualFile> originalToCopyFileMap = new HashMap<VirtualFile, VirtualFile>();
+ final Application application = ApplicationManager.getApplication();
+ application.invokeAndWait(new Runnable() {
+ public void run() {
+ for (final Module module : modules) {
+ for (final VirtualFile file : chunk.getFilesToCompile(module)) {
+ final VirtualFile untransformed = chunk.getOriginalFile(file);
+ if (transformer.isTransformable(untransformed)) {
+ application.runWriteAction(new Runnable() {
+ public void run() {
+ try {
+ // if untransformed != file, the file is already a (possibly transformed) copy of the original 'untransformed' file.
+ // If this is the case, just use already created copy and do not copy file content once again
+ final VirtualFile fileCopy = untransformed.equals(file)? createFileCopy(getTempDir(module), file) : file;
+ originalToCopyFileMap.put(file, fileCopy);
+ }
+ catch (IOException e) {
+ // skip it
+ }
+ }
+ });
+ }
+ }
+ }
+ }
+ }, myCompileContext.getProgressIndicator().getModalityState());
+
+ // do actual transform
+ for (final Module module : modules) {
+ final List<VirtualFile> filesToCompile = chunk.getFilesToCompile(module);
+ for (int j = 0; j < filesToCompile.size(); j++) {
+ final VirtualFile file = filesToCompile.get(j);
+ final VirtualFile fileCopy = originalToCopyFileMap.get(file);
+ if (fileCopy != null) {
+ final boolean ok = transformer.transform(myCompileContext, fileCopy, chunk.getOriginalFile(file));
+ if (ok) {
+ chunk.substituteWithTransformedVersion(module, j, fileCopy);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private VirtualFile createFileCopy(VirtualFile tempDir, final VirtualFile file) throws IOException {
+ final String fileName = file.getName();
+ if (tempDir.findChild(fileName) != null) {
+ int idx = 0;
+ while (true) {
+ //noinspection HardCodedStringLiteral
+ final String dirName = "dir" + idx++;
+ final VirtualFile dir = tempDir.findChild(dirName);
+ if (dir == null) {
+ tempDir = tempDir.createChildDirectory(this, dirName);
+ break;
+ }
+ if (dir.findChild(fileName) == null) {
+ tempDir = dir;
+ break;
+ }
+ }
+ }
+ return VfsUtilCore.copyFile(this, file, tempDir);
+ }
+
+ private VirtualFile getTempDir(Module module) throws IOException {
+ VirtualFile tempDir = myModuleToTempDirMap.get(module);
+ if (tempDir == null) {
+ final String projectName = myProject.getName();
+ final String moduleName = module.getName();
+ File tempDirectory = FileUtil.createTempDirectory(projectName, moduleName);
+ tempDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(tempDirectory);
+ if (tempDir == null) {
+ LOG.error("Cannot locate temp directory " + tempDirectory.getPath());
+ }
+ myModuleToTempDirMap.put(module, tempDir);
+ }
+ return tempDir;
+ }
+
+ private void compileFinished(int exitValue, final ModuleChunk chunk, final String outputDir) {
+ if (exitValue != 0 && !myCompileContext.getProgressIndicator().isCanceled() && myCompileContext.getMessageCount(CompilerMessageCategory.ERROR) == 0) {
+ myCompileContext.addMessage(CompilerMessageCategory.ERROR, CompilerBundle.message("error.compiler.internal.error", exitValue), null, -1, -1);
+ }
+
+ myCompiler.compileFinished();
+ final List<File> toRefresh = new ArrayList<File>();
+ final Map<String, Collection<TranslatingCompiler.OutputItem>> results = new HashMap<String, Collection<TranslatingCompiler.OutputItem>>();
+ try {
+ final FileTypeManager typeManager = FileTypeManager.getInstance();
+ final String outputDirPath = outputDir.replace(File.separatorChar, '/');
+ try {
+ for (final Module module : chunk.getModules()) {
+ for (final VirtualFile root : chunk.getSourceRoots(module)) {
+ final String packagePrefix = myProjectFileIndex.getPackageNameByDirectory(root);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Building output items for " + root.getPresentableUrl() + "; output dir = " + outputDirPath + "; packagePrefix = \"" + packagePrefix + "\"");
+ }
+ buildOutputItemsList(outputDirPath, module, root, typeManager, root, packagePrefix, toRefresh, results);
+ }
+ }
+ }
+ catch (CacheCorruptedException e) {
+ myCompileContext.requestRebuildNextTime(CompilerBundle.message("error.compiler.caches.corrupted"));
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(e);
+ }
+ }
+ }
+ finally {
+ CompilerUtil.refreshIOFiles(toRefresh);
+ for (Iterator<Map.Entry<String, Collection<TranslatingCompiler.OutputItem>>> it = results.entrySet().iterator(); it.hasNext();) {
+ Map.Entry<String, Collection<TranslatingCompiler.OutputItem>> entry = it.next();
+ mySink.add(entry.getKey(), entry.getValue(), VirtualFile.EMPTY_ARRAY);
+ it.remove(); // to free memory
+ }
+ }
+ myFileNameToSourceMap.clear(); // clear the map before the next use
+ }
+
+ private void buildOutputItemsList(final String outputDir, final Module module, VirtualFile from,
+ final FileTypeManager typeManager,
+ final VirtualFile sourceRoot,
+ final String packagePrefix, final List<File> filesToRefresh, final Map<String, Collection<TranslatingCompiler.OutputItem>> results) throws CacheCorruptedException {
+ final Ref<CacheCorruptedException> exRef = new Ref<CacheCorruptedException>(null);
+ final ModuleFileIndex fileIndex = ModuleRootManager.getInstance(module).getFileIndex();
+ final GlobalSearchScope srcRootScope = GlobalSearchScope.moduleScope(module).intersectWith(
+ GlobalSearchScopes.directoryScope(myProject, sourceRoot, true));
+
+ final ContentIterator contentIterator = new ContentIterator() {
+ public boolean processFile(final VirtualFile child) {
+ try {
+ if (child.isValid()) {
+ if (!child.isDirectory() && myCompiler.getCompilableFileTypes().contains(child.getFileType())) {
+ updateOutputItemsList(outputDir, child, sourceRoot, packagePrefix, filesToRefresh, results, srcRootScope);
+ }
+ }
+ return true;
+ }
+ catch (CacheCorruptedException e) {
+ exRef.set(e);
+ return false;
+ }
+ }
+ };
+ if (fileIndex.isInContent(from)) {
+ // use file index for iteration to handle 'inner modules' and excludes properly
+ fileIndex.iterateContentUnderDirectory(from, contentIterator);
+ }
+ else {
+ // seems to be a root for generated sources
+ VfsUtilCore.visitChildrenRecursively(from, new VirtualFileVisitor() {
+ @Override
+ public boolean visitFile(@NotNull VirtualFile file) {
+ if (!file.isDirectory()) {
+ contentIterator.processFile(file);
+ }
+ return true;
+ }
+ });
+ }
+ final CacheCorruptedException exc = exRef.get();
+ if (exc != null) {
+ throw exc;
+ }
+ }
+
+ private void putName(String sourceFileName, int classQName, String relativePathToSource, String pathToClass) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Registering [sourceFileName, relativePathToSource, pathToClass] = [" + sourceFileName + "; " + relativePathToSource +
+ "; " + pathToClass + "]");
+ }
+ Set<CompiledClass> paths = myFileNameToSourceMap.get(sourceFileName);
+
+ if (paths == null) {
+ paths = new HashSet<CompiledClass>();
+ myFileNameToSourceMap.put(sourceFileName, paths);
+ }
+ paths.add(new CompiledClass(classQName, relativePathToSource, pathToClass));
+ }
+
+ private void updateOutputItemsList(final String outputDir, final VirtualFile srcFile,
+ VirtualFile sourceRoot,
+ final String packagePrefix, final List<File> filesToRefresh,
+ Map<String, Collection<TranslatingCompiler.OutputItem>> results,
+ final GlobalSearchScope srcRootScope) throws CacheCorruptedException {
+ final Cache newCache = myCompileContext.getDependencyCache().getNewClassesCache();
+ final Set<CompiledClass> paths = myFileNameToSourceMap.get(srcFile.getName());
+ if (paths == null || paths.isEmpty()) {
+ return;
+ }
+ final String filePath = "/" + calcPackagePath(srcFile, sourceRoot, packagePrefix);
+ for (final CompiledClass cc : paths) {
+ myCompileContext.getProgressIndicator().checkCanceled();
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Checking [pathToClass; relPathToSource] = " + cc);
+ }
+
+ boolean pathsEquals = FileUtil.pathsEqual(filePath, cc.relativePathToSource);
+ if (!pathsEquals) {
+ final String qName = myCompileContext.getDependencyCache().resolve(cc.qName);
+ if (qName != null) {
+ pathsEquals = ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
+ public Boolean compute() {
+ final JavaPsiFacade facade = JavaPsiFacade.getInstance(myProject);
+ PsiClass psiClass = facade.findClass(qName, srcRootScope);
+ if (psiClass == null) {
+ final int dollarIndex = qName.indexOf("$");
+ if (dollarIndex >= 0) {
+ final String topLevelClassName = qName.substring(0, dollarIndex);
+ psiClass = facade.findClass(topLevelClassName, srcRootScope);
+ }
+ }
+ if (psiClass != null) {
+ final VirtualFile vFile = psiClass.getContainingFile().getVirtualFile();
+ return vFile != null && vFile.equals(srcFile);
+ }
+ return false;
+ }
+ });
+ }
+ }
+
+ if (pathsEquals) {
+ final String outputPath = cc.pathToClass.replace(File.separatorChar, '/');
+ final Pair<String, String> realLocation = moveToRealLocation(outputDir, outputPath, srcFile, filesToRefresh);
+ if (realLocation != null) {
+ Collection<TranslatingCompiler.OutputItem> outputs = results.get(realLocation.getFirst());
+ if (outputs == null) {
+ outputs = new ArrayList<TranslatingCompiler.OutputItem>();
+ results.put(realLocation.getFirst(), outputs);
+ }
+ outputs.add(new OutputItemImpl(realLocation.getSecond(), srcFile));
+ if (PACKAGE_ANNOTATION_FILE_NAME.equals(srcFile.getName())) {
+ myProcessedPackageInfos.add(srcFile);
+ }
+ if (CompilerConfiguration.MAKE_ENABLED) {
+ newCache.setPath(cc.qName, realLocation.getSecond());
+ }
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Added output item: [outputDir; outputPath; sourceFile] = [" + realLocation.getFirst() + "; " +
+ realLocation.getSecond() + "; " + srcFile.getPresentableUrl() + "]");
+ }
+ }
+ else {
+ myCompileContext.addMessage(CompilerMessageCategory.ERROR, "Failed to copy from temporary location to output directory: " + outputPath + " (see idea.log for details)", null, -1, -1);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Failed to move to real location: " + outputPath + "; from " + outputDir);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ *
+ * @param srcFile
+ * @param sourceRoot
+ * @param packagePrefix
+ * @return A 'package'-path to a given src file relative to a specified root. "/" slashes must be used
+ */
+ protected static String calcPackagePath(VirtualFile srcFile, VirtualFile sourceRoot, String packagePrefix) {
+ final String prefix = packagePrefix != null && packagePrefix.length() > 0 ? packagePrefix.replace('.', '/') + "/" : "";
+ return prefix + VfsUtilCore.getRelativePath(srcFile, sourceRoot, '/');
+ }
+
+ @Nullable
+ private Pair<String, String> moveToRealLocation(String tempOutputDir, String pathToClass, VirtualFile sourceFile, final List<File> filesToRefresh) {
+ final Module module = myCompileContext.getModuleByFile(sourceFile);
+ if (module == null) {
+ final String message =
+ "Cannot determine module for source file: " + sourceFile.getPresentableUrl() + ";\nCorresponding output file: " + pathToClass;
+ LOG.info(message);
+ myCompileContext.addMessage(CompilerMessageCategory.WARNING, message, sourceFile.getUrl(), -1, -1);
+ // do not move: looks like source file has been invalidated, need recompilation
+ return new Pair<String, String>(tempOutputDir, pathToClass);
+ }
+ final String realOutputDir;
+ if (myCompileContext.isInTestSourceContent(sourceFile)) {
+ realOutputDir = getTestsOutputDir(module);
+ LOG.assertTrue(realOutputDir != null);
+ }
+ else {
+ realOutputDir = getOutputDir(module);
+ LOG.assertTrue(realOutputDir != null);
+ }
+
+ if (FileUtil.pathsEqual(tempOutputDir, realOutputDir)) { // no need to move
+ filesToRefresh.add(new File(pathToClass));
+ return new Pair<String, String>(realOutputDir, pathToClass);
+ }
+
+ final String realPathToClass = realOutputDir + pathToClass.substring(tempOutputDir.length());
+ final File fromFile = new File(pathToClass);
+ final File toFile = new File(realPathToClass);
+
+ boolean success = fromFile.renameTo(toFile);
+ if (!success) {
+ // assuming cause of the fail: intermediate dirs do not exist
+ FileUtil.createParentDirs(toFile);
+ // retry after making non-existent dirs
+ success = fromFile.renameTo(toFile);
+ }
+ if (!success) { // failed to move the file: e.g. because source and destination reside on different mountpoints.
+ try {
+ FileUtil.copy(fromFile, toFile);
+ FileUtil.delete(fromFile);
+ success = true;
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ success = false;
+ }
+ }
+ if (success) {
+ filesToRefresh.add(toFile);
+ return new Pair<String, String>(realOutputDir, realPathToClass);
+ }
+ return null;
+ }
+
+ private final Map<Module, String> myModuleToTestsOutput = new HashMap<Module, String>();
+
+ private String getTestsOutputDir(final Module module) {
+ if (myModuleToTestsOutput.containsKey(module)) {
+ return myModuleToTestsOutput.get(module);
+ }
+ final VirtualFile outputDirectory = myCompileContext.getModuleOutputDirectoryForTests(module);
+ final String out = outputDirectory != null? outputDirectory.getPath() : null;
+ myModuleToTestsOutput.put(module, out);
+ return out;
+ }
+
+ private final Map<Module, String> myModuleToOutput = new HashMap<Module, String>();
+
+ private String getOutputDir(final Module module) {
+ if (myModuleToOutput.containsKey(module)) {
+ return myModuleToOutput.get(module);
+ }
+ final VirtualFile outputDirectory = myCompileContext.getModuleOutputDirectory(module);
+ final String out = outputDirectory != null? outputDirectory.getPath() : null;
+ myModuleToOutput.put(module, out);
+ return out;
+ }
+
+ private void sourceFileProcessed() {
+ myStatistics.incFilesCount();
+ updateStatistics();
+ }
+
+ private void updateStatistics() {
+ final String msg;
+ String moduleName = myModuleName;
+ if (moduleName != null) {
+ msg = CompilerBundle.message("statistics.files.classes.module", myStatistics.getFilesCount(), myStatistics.getClassesCount(), moduleName);
+ }
+ else {
+ msg = CompilerBundle.message("statistics.files.classes", myStatistics.getFilesCount(), myStatistics.getClassesCount());
+ }
+ myCompileContext.getProgressIndicator().setText2(msg);
+ //myCompileContext.getProgressIndicator().setFraction(1.0* myProcessedFilesCount /myTotalFilesToCompile);
+ }
+
+ private class ClassParsingThread implements Runnable {
+ private final BlockingQueue<FileObject> myPaths = new ArrayBlockingQueue<FileObject>(50000);
+ private CacheCorruptedException myError = null;
+ private final boolean myAddNotNullAssertions;
+ private final boolean myIsJdk16;
+ private final String myOutputDir;
+
+ private ClassParsingThread(final boolean isJdk16, String outputDir) {
+ myIsJdk16 = isJdk16;
+ myOutputDir = FileUtil.toSystemIndependentName(outputDir);
+ myAddNotNullAssertions = CompilerConfiguration.getInstance(myProject).isAddNotNullAssertions();
+ }
+
+ private volatile boolean processing;
+ public void run() {
+ processing = true;
+ try {
+ while (true) {
+ FileObject path = myPaths.take();
+
+ if (path == myStopThreadToken) {
+ break;
+ }
+ processPath(path, myProject);
+ }
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ }
+ catch (CacheCorruptedException e) {
+ myError = e;
+ }
+ finally {
+ processing = false;
+ }
+ }
+
+ public void addPath(FileObject path) throws CacheCorruptedException {
+ if (myError != null) {
+ throw myError;
+ }
+ myPaths.offer(path);
+ }
+
+ public void stopParsing() {
+ myPaths.offer(myStopThreadToken);
+ }
+
+ private void processPath(FileObject fileObject, Project project) throws CacheCorruptedException {
+ File file = fileObject.getFile();
+ final String path = file.getPath();
+ try {
+ if (CompilerConfiguration.MAKE_ENABLED) {
+ byte[] fileContent = fileObject.getContent();
+ // the file is assumed to exist!
+ final DependencyCache dependencyCache = myCompileContext.getDependencyCache();
+ final int newClassQName = dependencyCache.reparseClassFile(file, fileContent);
+ final Cache newClassesCache = dependencyCache.getNewClassesCache();
+ final String sourceFileName = newClassesCache.getSourceFileName(newClassQName);
+ final String qName = dependencyCache.resolve(newClassQName);
+ String relativePathToSource = "/" + MakeUtil.createRelativePathToSource(qName, sourceFileName);
+ putName(sourceFileName, newClassQName, relativePathToSource, path);
+ boolean haveToInstrument = myAddNotNullAssertions && hasNotNullAnnotations(newClassesCache, dependencyCache.getSymbolTable(), newClassQName, project);
+
+ if (haveToInstrument) {
+ try {
+ ClassReader reader = new ClassReader(fileContent, 0, fileContent.length);
+ ClassWriter writer = new PsiClassWriter(myProject, myIsJdk16);
+
+ final NotNullVerifyingInstrumenter instrumenter = new NotNullVerifyingInstrumenter(writer);
+ reader.accept(instrumenter, 0);
+ if (instrumenter.isModification()) {
+ fileObject = new FileObject(file, writer.toByteArray());
+ }
+ }
+ catch (Exception ignored) {
+ LOG.info(ignored);
+ }
+ }
+
+ fileObject.save();
+ }
+ else {
+ final String _path = FileUtil.toSystemIndependentName(path);
+ final int dollarIndex = _path.indexOf('$');
+ final int tailIndex = dollarIndex >=0 ? dollarIndex : _path.length() - ".class".length();
+ final int slashIndex = _path.lastIndexOf('/');
+ final String sourceFileName = _path.substring(slashIndex + 1, tailIndex) + ".java";
+ String relativePathToSource = _path.substring(myOutputDir.length(), tailIndex) + ".java";
+ putName(sourceFileName, 0 /*doesn't matter here*/ , relativePathToSource.startsWith("/")? relativePathToSource : "/" + relativePathToSource, path);
+ }
+ }
+ catch (ClsFormatException e) {
+ final String m = e.getMessage();
+ String message = CompilerBundle.message("error.bad.class.file.format", StringUtil.isEmpty(m) ? path : m + "\n" + path);
+ myCompileContext.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1);
+ LOG.info(e);
+ }
+ catch (IOException e) {
+ myCompileContext.addMessage(CompilerMessageCategory.ERROR, e.getMessage(), null, -1, -1);
+ LOG.info(e);
+ }
+ finally {
+ myStatistics.incClassesCount();
+ updateStatistics();
+ }
+ }
+ }
+
+ private static boolean hasNotNullAnnotations(final Cache cache, final SymbolTable symbolTable, final int className, Project project) throws CacheCorruptedException {
+ final NullableNotNullManager manager = NullableNotNullManager.getInstance(project);
+ final List<String> notNulls = manager.getNotNulls();
+ for (MethodInfo methodId : cache.getMethods(className)) {
+ for (AnnotationConstantValue annotation : methodId.getRuntimeInvisibleAnnotations()) {
+ if (notNulls.contains(symbolTable.getSymbol(annotation.getAnnotationQName()))) {
+ return true;
+ }
+ }
+ final AnnotationConstantValue[][] paramAnnotations = methodId.getRuntimeInvisibleParameterAnnotations();
+ for (AnnotationConstantValue[] _singleParamAnnotations : paramAnnotations) {
+ for (AnnotationConstantValue annotation : _singleParamAnnotations) {
+ if (notNulls.contains(symbolTable.getSymbol(annotation.getAnnotationQName()))) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private static boolean isJdk6(final Sdk jdk) {
+ return jdk != null && JavaSdk.getInstance().isOfVersionOrHigher(jdk, JavaSdkVersion.JDK_1_6);
+ }
+
+ private static final class CompileStatistics {
+ private static final Key<CompileStatistics> KEY = Key.create("_Compile_Statistics_");
+ private int myClassesCount;
+ private int myFilesCount;
+
+ public int getClassesCount() {
+ return myClassesCount;
+ }
+
+ public int incClassesCount() {
+ return ++myClassesCount;
+ }
+
+ public int getFilesCount() {
+ return myFilesCount;
+ }
+
+ public int incFilesCount() {
+ return ++myFilesCount;
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/CompiledClass.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/CompiledClass.java
new file mode 100644
index 0000000..4bfa519
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/CompiledClass.java
@@ -0,0 +1,56 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler;
+
+/**
+* @author cdr
+*/
+final class CompiledClass {
+ public final int qName;
+ public final String relativePathToSource;
+ public final String pathToClass;
+
+ CompiledClass(final int qName, final String relativePathToSource, final String pathToClass) {
+ this.qName = qName;
+ this.relativePathToSource = relativePathToSource;
+ this.pathToClass = pathToClass;
+ }
+
+ public boolean equals(final Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final CompiledClass that = (CompiledClass)o;
+
+ if (qName != that.qName) return false;
+ if (!pathToClass.equals(that.pathToClass)) return false;
+ if (!relativePathToSource.equals(that.relativePathToSource)) return false;
+
+ return true;
+ }
+
+ public int hashCode() {
+ int result;
+ result = qName;
+ result = 31 * result + relativePathToSource.hashCode();
+ result = 31 * result + pathToClass.hashCode();
+ return result;
+ }
+
+ public String toString() {
+ return "[" + pathToClass + ";" + relativePathToSource + "]";
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/CompilerParsingThread.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/CompilerParsingThread.java
new file mode 100644
index 0000000..38b6b2f
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/CompilerParsingThread.java
@@ -0,0 +1,263 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler;
+
+import com.intellij.compiler.OutputParser;
+import com.intellij.compiler.impl.CompileDriver;
+import com.intellij.compiler.make.CacheCorruptedException;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.CompilerMessageCategory;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.util.SpinAllocator;
+import com.intellij.util.StringBuilderSpinAllocator;
+import org.jetbrains.annotations.NonNls;
+
+import java.io.*;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 30, 2004
+ */
+public class CompilerParsingThread implements Runnable, OutputParser.Callback {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.javaCompiler.JavaCompilerParsingThread");
+ @NonNls public static final String TERMINATION_STRING = "__terminate_read__";
+ private final Reader myCompilerOutStreamReader;
+ private Process myProcess;
+ private final OutputParser myOutputParser;
+ private final boolean myTrimLines;
+ private boolean mySkipLF = false;
+ private Throwable myError = null;
+ private final boolean myIsUnitTestMode;
+ private FileObject myClassFileToProcess = null;
+ private String myLastReadLine = null;
+ private String myPushBackLine = null;
+ private volatile boolean myProcessExited = false;
+ private final CompileContext myContext;
+
+ public CompilerParsingThread(Process process, OutputParser outputParser, final boolean readErrorStream, boolean trimLines, CompileContext context) {
+ myProcess = process;
+ myOutputParser = outputParser;
+ myTrimLines = trimLines;
+ InputStream stream = readErrorStream ? process.getErrorStream() : process.getInputStream();
+ myCompilerOutStreamReader = stream == null ? null : new BufferedReader(new InputStreamReader(stream), 16384);
+ myIsUnitTestMode = ApplicationManager.getApplication().isUnitTestMode();
+ myContext = context;
+ }
+
+ volatile boolean processing;
+ public void run() {
+ processing = true;
+ try {
+ while (true) {
+ if (!myIsUnitTestMode && myProcess == null) {
+ break;
+ }
+ if (isCanceled()) {
+ break;
+ }
+ if (!myOutputParser.processMessageLine(this)) {
+ break;
+ }
+ }
+ if (myClassFileToProcess != null) {
+ processCompiledClass(myClassFileToProcess);
+ myClassFileToProcess = null;
+ }
+ }
+ catch (Throwable e) {
+ e.printStackTrace();
+ myError = e;
+ LOG.info(e);
+ }
+ finally {
+ killProcess();
+ processing = false;
+ }
+ }
+
+ private void killProcess() {
+ if (myProcess != null) {
+ myProcess.destroy();
+ myProcess = null;
+ }
+ }
+
+ public Throwable getError() {
+ return myError;
+ }
+
+ public String getCurrentLine() {
+ return myLastReadLine;
+ }
+
+ public final String getNextLine() {
+ final String pushBack = myPushBackLine;
+ if (pushBack != null) {
+ myPushBackLine = null;
+ myLastReadLine = pushBack;
+ return pushBack;
+ }
+ final String line = readLine(myCompilerOutStreamReader);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("LIne read: #" + line + "#");
+ }
+ if (CompileDriver.ourDebugMode) {
+ System.out.println("LIne read: #" + line + "#");
+ }
+ if (TERMINATION_STRING.equals(line)) {
+ myLastReadLine = null;
+ }
+ else {
+ myLastReadLine = line == null ? null : myTrimLines ? line.trim() : line;
+ }
+ return myLastReadLine;
+ }
+
+ @Override
+ public void pushBack(String line) {
+ myLastReadLine = null;
+ myPushBackLine = line;
+ }
+
+ public final void fileGenerated(FileObject path) {
+ // javac first logs file generated, then starts to write the file to disk,
+ // so this thread sometimes can stumble on not yet existing file,
+ // hence this complex logic
+ FileObject previousPath = myClassFileToProcess;
+ myClassFileToProcess = path;
+ if (previousPath != null) {
+ try {
+ processCompiledClass(previousPath);
+ }
+ catch (CacheCorruptedException e) {
+ if (CompileDriver.ourDebugMode) {
+ e.printStackTrace();
+ }
+ myError = e;
+ LOG.info(e);
+ killProcess();
+ }
+ }
+ }
+
+ protected boolean isCanceled() {
+ return myContext.getProgressIndicator().isCanceled();
+ }
+
+ public void setProgressText(String text) {
+ myContext.getProgressIndicator().setText(text);
+ }
+
+ public void fileProcessed(String path) {
+ }
+
+ public void message(CompilerMessageCategory category, String message, String url, int lineNum, int columnNum) {
+ myContext.addMessage(category, message, url, lineNum, columnNum);
+ }
+
+ protected void processCompiledClass(final FileObject classFileToProcess) throws CacheCorruptedException {
+ }
+
+
+ private String readLine(final Reader reader) {
+ StringBuilder buffer;
+ boolean releaseBuffer = true;
+ try {
+ buffer = StringBuilderSpinAllocator.alloc();
+ }
+ catch (SpinAllocator.AllocatorExhaustedException e) {
+ if (CompileDriver.ourDebugMode) {
+ e.printStackTrace();
+ }
+ LOG.info(e);
+ buffer = new StringBuilder();
+ releaseBuffer = false;
+ }
+
+ try {
+ boolean first = true;
+ while (true) {
+ int c = readNextByte(reader);
+ if (c == -1) break;
+ first = false;
+ if (c == '\n') {
+ if (mySkipLF) {
+ mySkipLF = false;
+ continue;
+ }
+ break;
+ }
+ else if (c == '\r') {
+ mySkipLF = true;
+ break;
+ }
+ else {
+ mySkipLF = false;
+ buffer.append((char)c);
+ }
+ }
+ if (first) {
+ return null;
+ }
+ return buffer.toString();
+ }
+ finally {
+ if (releaseBuffer) {
+ StringBuilderSpinAllocator.dispose(buffer);
+ }
+ }
+ }
+
+ private int readNextByte(final Reader reader) {
+ try {
+ while(!reader.ready()) {
+ if (isProcessTerminated()) {
+ if (reader.ready()) {
+ break;
+ }
+ return -1;
+ }
+ try {
+ Thread.sleep(1L);
+ }
+ catch (InterruptedException ignore) {
+ }
+ }
+ return reader.read();
+ }
+ catch (IOException e) {
+ if (CompileDriver.ourDebugMode) {
+ e.printStackTrace();
+ }
+ return -1; // When process terminated Process.getInputStream()'s underlying stream becomes closed on Linux.
+ }
+ catch (Throwable t) {
+ if (CompileDriver.ourDebugMode) {
+ t.printStackTrace();
+ }
+ return -1;
+ }
+ }
+
+ private boolean isProcessTerminated() {
+ return myProcessExited;
+ }
+
+ public void setProcessTerminated(final boolean procesExited) {
+ myProcessExited = procesExited;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/DummySourceGeneratingCompiler.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/DummySourceGeneratingCompiler.java
new file mode 100644
index 0000000..5fd91b0
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/DummySourceGeneratingCompiler.java
@@ -0,0 +1,137 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.CompileScope;
+import com.intellij.openapi.compiler.SourceGeneratingCompiler;
+import com.intellij.openapi.compiler.ValidityState;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.DataInput;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Oct 9, 2004
+ */
+@SuppressWarnings({"HardCodedStringLiteral"})
+public class DummySourceGeneratingCompiler implements SourceGeneratingCompiler{
+ public static final String MODULE_NAME = "generated";
+ private final Project myProject;
+
+ public DummySourceGeneratingCompiler(Project project) {
+ myProject = project;
+ }
+
+ @Override
+ public VirtualFile getPresentableFile(CompileContext context, Module module, VirtualFile outputRoot, VirtualFile generatedFile) {
+ return null;
+ }
+
+ public GenerationItem[] getGenerationItems(CompileContext context) {
+ final Module module = findMyModule();
+ return new GenerationItem[] {
+ new MyGenerationItem("aaa/p1.properties", module, false),
+ new MyGenerationItem("bbb/p2.properties", module, false),
+ new MyGenerationItem("bbb/ccc/p3.properties", module, false),
+ new MyGenerationItem("aaa/p1.properties", module, true),
+ new MyGenerationItem("bbb/p2-t.properties", module, true),
+ new MyGenerationItem("bbb/ccc/p3.properties", module, true)
+ };
+ }
+
+ private Module findMyModule() {
+ return ApplicationManager.getApplication().runReadAction(new Computable<Module>() {
+ public Module compute() {
+ Module[] modules = ModuleManager.getInstance(myProject).getModules();
+ for (Module module : modules) {
+ if (MODULE_NAME.equals(module.getName())) {
+ return module;
+ }
+ }
+ return null;
+ }
+ });
+ }
+
+ public GenerationItem[] generate(CompileContext context, GenerationItem[] items, final VirtualFile outputRootDirectory) {
+ final String rootPath = ApplicationManager.getApplication().runReadAction(new Computable<String>() {
+ public String compute() {
+ return outputRootDirectory.getPath();
+ }
+ });
+ final List<GenerationItem> success = new ArrayList<GenerationItem>();
+ for (GenerationItem item1 : items) {
+ GenerationItem item = item1;
+ File file = new File(rootPath + File.separator + item.getPath());
+ FileUtil.createIfDoesntExist(file);
+ success.add(item);
+ }
+ return success.toArray(new GenerationItem[success.size()]);
+ }
+
+ @NotNull
+ public String getDescription() {
+ return "Dummy Source Generator";
+ }
+
+ public boolean validateConfiguration(CompileScope scope) {
+ return findMyModule() != null;
+ }
+
+ public ValidityState createValidityState(DataInput in) throws IOException {
+ return null;
+ }
+
+ private static class MyGenerationItem implements GenerationItem {
+ private final String myRelPath;
+ private final Module myModule;
+ private final boolean myIsTestSource;
+
+ public MyGenerationItem(String relPath, Module module, final boolean isTestSource) {
+ myRelPath = relPath;
+ myModule = module;
+ myIsTestSource = isTestSource;
+ }
+
+ public String getPath() {
+ return myRelPath;
+ }
+
+ public ValidityState getValidityState() {
+ return null;
+ }
+
+ public Module getModule() {
+ return myModule;
+ }
+
+ public boolean isTestSource() {
+ return myIsTestSource;
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/DummyTransformingCompiler.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/DummyTransformingCompiler.java
new file mode 100644
index 0000000..2931190
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/DummyTransformingCompiler.java
@@ -0,0 +1,78 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.CompileScope;
+import com.intellij.openapi.compiler.CompilerMessageCategory;
+import com.intellij.openapi.compiler.JavaSourceTransformingCompiler;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.ui.UIUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.*;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Jul 10, 2004
+ */
+@SuppressWarnings({"HardCodedStringLiteral"})
+public class DummyTransformingCompiler implements JavaSourceTransformingCompiler{
+ public boolean isTransformable(VirtualFile file) {
+ return "A.java".equals(file.getName());
+ }
+
+ public boolean transform(CompileContext context, final VirtualFile file, VirtualFile originalFile) {
+ System.out.println("DummyTransformingCompiler.transform");
+ final String url = ApplicationManager.getApplication().runReadAction(new Computable<String>() {
+ public String compute() {
+ return file.getPresentableUrl();
+ }
+ });
+ context.getProgressIndicator().setText("Transforming file: " + url);
+ try {
+ OutputStream os = new BufferedOutputStream(new FileOutputStream(new File(url)));
+ DataOutput out = new DataOutputStream(os);
+ out.writeBytes("package a; ");
+ out.writeBytes("public class A { public static void main(String[] args) { System.out.println(\"Hello from modified class\");} }");
+ os.close();
+ UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+ public void run() {
+ file.refresh(false, false);
+ }
+ });
+ return true;
+ }
+ catch (FileNotFoundException e) {
+ context.addMessage(CompilerMessageCategory.ERROR, e.getMessage(), null, -1, -1);
+ }
+ catch (IOException e) {
+ context.addMessage(CompilerMessageCategory.ERROR, e.getMessage(), null, -1, -1);
+ }
+ return false;
+ }
+
+ @NotNull
+ public String getDescription() {
+ return "a dummy compiler for testing";
+ }
+
+ public boolean validateConfiguration(CompileScope scope) {
+ return true;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/DummyTranslatingCompiler.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/DummyTranslatingCompiler.java
new file mode 100644
index 0000000..573b7e0
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/DummyTranslatingCompiler.java
@@ -0,0 +1,93 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler;
+
+import com.intellij.compiler.impl.CompilerUtil;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.*;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.Chunk;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Dec 4, 2007
+ */
+public class DummyTranslatingCompiler implements TranslatingCompiler, IntermediateOutputCompiler{
+ @NonNls private static final String DESCRIPTION = "DUMMY TRANSLATOR";
+ @NonNls private static final String FILETYPE_EXTENSION = ".dummy";
+
+ public boolean isCompilableFile(final VirtualFile file, final CompileContext context) {
+ return file.getName().endsWith(FILETYPE_EXTENSION);
+ }
+
+ public void compile(final CompileContext context, Chunk<Module> moduleChunk, final VirtualFile[] files, OutputSink sink) {
+ final List<File> filesToRefresh = new ArrayList<File>();
+ final Map<String, Collection<OutputItem>> outputs = new HashMap<String, Collection<OutputItem>>();
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ for (final VirtualFile file : files) {
+ final Module module = context.getModuleByFile(file);
+ try {
+ final VirtualFile outputDir = context.getModuleOutputDirectory(module);
+ if (outputDir != null) {
+ final String outputDirPath = outputDir.getPath();
+ final File compiledFile = doCompile(outputDir, file);
+ filesToRefresh.add(compiledFile);
+ Collection<OutputItem> collection = outputs.get(outputDirPath);
+ if (collection == null) {
+ collection = new ArrayList<OutputItem>();
+ outputs.put(outputDirPath, collection);
+ }
+ collection.add(new OutputItemImpl(FileUtil.toSystemIndependentName(compiledFile.getPath()), file));
+ }
+ }
+ catch (IOException e) {
+ context.addMessage(CompilerMessageCategory.ERROR, e.getMessage(), null, 0, 0);
+ }
+ }
+ }
+ });
+ CompilerUtil.refreshIOFiles(filesToRefresh);
+ for (Map.Entry<String, Collection<OutputItem>> entry : outputs.entrySet()) {
+ sink.add(entry.getKey(), entry.getValue(), VirtualFile.EMPTY_ARRAY);
+ }
+ }
+
+ private static File doCompile(VirtualFile out, VirtualFile src) throws IOException {
+ final String originalName = src.getName();
+ String compiledName = originalName.substring(0, originalName.length() - FILETYPE_EXTENSION.length());
+ final File destFile = new File(out.getPath(), compiledName + ".java");
+ FileUtil.copy(new File(src.getPath()), destFile);
+ return destFile;
+ }
+
+ @NotNull
+ public String getDescription() {
+ return DESCRIPTION;
+ }
+
+ public boolean validateConfiguration(final CompileScope scope) {
+ return true;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/ExternalCompiler.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/ExternalCompiler.java
new file mode 100644
index 0000000..9223cbd
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/ExternalCompiler.java
@@ -0,0 +1,83 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler;
+
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.StdFileTypes;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.EnvironmentUtil;
+import com.intellij.util.StringBuilderSpinAllocator;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Set;
+
+public abstract class ExternalCompiler implements BackendCompiler {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.javaCompiler.ExternalCompiler");
+ private static final Set<FileType> COMPILABLE_TYPES = Collections.<FileType>singleton(StdFileTypes.JAVA);
+
+ @NotNull
+ public abstract String[] createStartupCommand(ModuleChunk chunk, CompileContext context, String outputPath)
+ throws IOException, IllegalArgumentException;
+
+ @NotNull
+ public Set<FileType> getCompilableFileTypes() {
+ return COMPILABLE_TYPES;
+ }
+
+ /**
+ * Checks if the compiler can compile the specified file.
+ *
+ * @param file the file to check.
+ * @param context the context for the current compile operation.
+ * @return true if can compile the file, false otherwise. If the method returns false, <code>file</code>
+ * will not be included in the list of files passed to {@link #compile(CompileContext, com.intellij.util.Chunk < com.intellij.openapi.module.Module >,com.intellij.openapi.vfs.VirtualFile[], com.intellij.openapi.compiler.TranslatingCompiler.OutputSink)}.
+ */
+ public boolean isCompilableFile(VirtualFile file, CompileContext context) {
+ return getCompilableFileTypes().contains(file.getFileType());
+ }
+
+ @NotNull
+ public Process launchProcess(@NotNull final ModuleChunk chunk, @NotNull final String outputDir, @NotNull final CompileContext compileContext) throws IOException {
+ final String[] commands = createStartupCommand(chunk, compileContext, outputDir);
+
+ if (LOG.isDebugEnabled()) {
+ @NonNls final StringBuilder buf = StringBuilderSpinAllocator.alloc();
+ try {
+ buf.append("\n===================================Environment:===========================\n");
+ for (String pair : EnvironmentUtil.getEnvironment()) {
+ buf.append("\t").append(pair).append("\n");
+ }
+ buf.append("=============================================================================\n");
+ buf.append("Running compiler: ");
+ for (final String command : commands) {
+ buf.append(" ").append(command);
+ }
+
+ LOG.debug(buf.toString());
+ }
+ finally {
+ StringBuilderSpinAllocator.dispose(buf);
+ }
+ }
+
+ return Runtime.getRuntime().exec(commands);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/FileObject.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/FileObject.java
new file mode 100644
index 0000000..4ce53a5
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/FileObject.java
@@ -0,0 +1,73 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler;
+
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.util.ArrayUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+/**
+ * @author cdr
+ */
+public class FileObject {
+ private static final byte[] NOT_LOADED = ArrayUtil.EMPTY_BYTE_ARRAY;
+
+ private final File myFile;
+ private final byte[] myContent;
+
+ public FileObject(@NotNull File file, @NotNull byte[] content) {
+ myFile = file;
+ myContent = content;
+ }
+
+ public FileObject(File file) {
+ myFile = file;
+ myContent = NOT_LOADED;
+ }
+
+ public File getFile() {
+ return myFile;
+ }
+
+ public byte[] getContent() throws IOException {
+ if (myContent == NOT_LOADED) {
+ return FileUtil.loadFileBytes(myFile);
+ }
+ return myContent;
+ }
+
+ public void save() throws IOException {
+ if (myContent == NOT_LOADED) {
+ return; // already on disk
+ }
+ try {
+ FileUtil.writeToFile(myFile, myContent);
+ }
+ catch (FileNotFoundException e) {
+ FileUtil.createParentDirs(myFile);
+ FileUtil.writeToFile(myFile, myContent);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getFile().toString();
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/JavaCompiler.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/JavaCompiler.java
new file mode 100644
index 0000000..827c813
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/JavaCompiler.java
@@ -0,0 +1,94 @@
+/*
+ * 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.
+ */
+
+/*
+ * @author: Eugene Zhuravlev
+ * Date: Jan 17, 2003
+ * Time: 3:22:59 PM
+ */
+package com.intellij.compiler.impl.javaCompiler;
+
+import com.intellij.compiler.CompilerConfiguration;
+import com.intellij.compiler.CompilerConfigurationImpl;
+import com.intellij.compiler.CompilerException;
+import com.intellij.compiler.impl.CompileDriver;
+import com.intellij.compiler.make.CacheCorruptedException;
+import com.intellij.openapi.compiler.*;
+import com.intellij.openapi.compiler.ex.CompileContextEx;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.Chunk;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+
+public class JavaCompiler implements TranslatingCompiler {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.javaCompiler.JavaCompiler");
+ private final Project myProject;
+
+ public JavaCompiler(Project project) {
+ myProject = project;
+ }
+
+ @NotNull
+ public String getDescription() {
+ return CompilerBundle.message("java.compiler.description");
+ }
+
+ public boolean isCompilableFile(VirtualFile file, CompileContext context) {
+ final BackendCompiler backEndCompiler = getBackEndCompiler();
+ if (backEndCompiler instanceof ExternalCompiler) {
+ return ((ExternalCompiler)backEndCompiler).isCompilableFile(file, context);
+ }
+ return backEndCompiler.getCompilableFileTypes().contains(file.getFileType());
+ }
+
+ public void compile(CompileContext context, Chunk<Module> moduleChunk, VirtualFile[] files, OutputSink sink) {
+ final BackendCompiler backEndCompiler = getBackEndCompiler();
+ final BackendCompilerWrapper wrapper = new BackendCompilerWrapper(moduleChunk, myProject, Arrays.asList(files), (CompileContextEx)context, backEndCompiler, sink);
+ try {
+ if (CompileDriver.ourDebugMode) {
+ System.out.println("Starting java compiler; with backend compiler: " + backEndCompiler.getClass().getName());
+ }
+ wrapper.compile();
+ }
+ catch (CompilerException e) {
+ if (CompileDriver.ourDebugMode) {
+ e.printStackTrace();
+ }
+ context.addMessage(CompilerMessageCategory.ERROR, e.getMessage(), null, -1, -1);
+ LOG.info(e);
+ }
+ catch (CacheCorruptedException e) {
+ if (CompileDriver.ourDebugMode) {
+ e.printStackTrace();
+ }
+ LOG.info(e);
+ context.requestRebuildNextTime(e.getMessage());
+ }
+ }
+
+ public boolean validateConfiguration(CompileScope scope) {
+ return getBackEndCompiler().checkCompiler(scope);
+ }
+
+ private BackendCompiler getBackEndCompiler() {
+ CompilerConfigurationImpl configuration = (CompilerConfigurationImpl)CompilerConfiguration.getInstance(myProject);
+ return configuration.getDefaultCompiler();
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/ModuleChunk.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/ModuleChunk.java
new file mode 100644
index 0000000..864e4f8
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/ModuleChunk.java
@@ -0,0 +1,343 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler;
+
+import com.intellij.compiler.CompilerConfiguration;
+import com.intellij.compiler.CompilerConfigurationImpl;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.ex.CompileContextEx;
+import com.intellij.openapi.module.LanguageLevelUtil;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.roots.JdkOrderEntry;
+import com.intellij.openapi.roots.ModuleRootManager;
+import com.intellij.openapi.roots.OrderEntry;
+import com.intellij.openapi.roots.OrderEnumerator;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.util.Condition;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.pom.java.LanguageLevel;
+import com.intellij.util.Chunk;
+import com.intellij.util.PathsList;
+import com.intellij.util.StringBuilderSpinAllocator;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.OrderedSet;
+import gnu.trove.THashMap;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.*;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Sep 29, 2004
+ */
+public class ModuleChunk extends Chunk<Module> {
+ private final CompileContextEx myContext;
+ private final Map<Module, List<VirtualFile>> myModuleToFilesMap = new THashMap<Module, List<VirtualFile>>();
+ private final Map<VirtualFile, VirtualFile> myTransformedToOriginalMap = new THashMap<VirtualFile, VirtualFile>();
+ private int mySourcesFilter = ALL_SOURCES;
+
+ public ModuleChunk(CompileContextEx context, Chunk<Module> chunk, Map<Module, List<VirtualFile>> moduleToFilesMap) {
+ super(chunk.getNodes());
+ myContext = context;
+ for (final Module module : chunk.getNodes()) {
+ final List<VirtualFile> files = moduleToFilesMap.get(module);
+ // Important!!! Collections in the myModuleToFilesMap must be modifiable copies of the corresponding collections
+ // from the moduleToFilesMap. This is needed to support SourceTransforming compilers
+ myModuleToFilesMap.put(module, files == null ? Collections.<VirtualFile>emptyList() : new ArrayList<VirtualFile>(files));
+ }
+ }
+
+ public static final int SOURCES = 0x1;
+ public static final int TEST_SOURCES = 0x2;
+ public static final int ALL_SOURCES = SOURCES | TEST_SOURCES;
+
+ public void setSourcesFilter(int filter) {
+ mySourcesFilter = filter;
+ }
+
+ public int getSourcesFilter() {
+ return mySourcesFilter;
+ }
+
+ public void substituteWithTransformedVersion(Module module, int fileIndex, VirtualFile transformedFile) {
+ final List<VirtualFile> moduleFiles = getFilesToCompile(module);
+ final VirtualFile currentFile = moduleFiles.get(fileIndex);
+ moduleFiles.set(fileIndex, transformedFile);
+ VirtualFile originalFile = myTransformedToOriginalMap.remove(currentFile);
+ if (originalFile == null) {
+ originalFile = currentFile;
+ }
+ myTransformedToOriginalMap.put(transformedFile, originalFile);
+ }
+
+ public VirtualFile getOriginalFile(VirtualFile file) {
+ final VirtualFile original = myTransformedToOriginalMap.get(file);
+ return original != null? original : file;
+ }
+
+ @NotNull
+ public List<VirtualFile> getFilesToCompile(Module forModule) {
+ return myModuleToFilesMap.get(forModule);
+ }
+
+ @NotNull
+ public List<VirtualFile> getFilesToCompile() {
+ if (getModuleCount() == 0) {
+ return Collections.emptyList();
+ }
+ final Set<Module> modules = getNodes();
+
+ final List<VirtualFile> filesToCompile = new ArrayList<VirtualFile>();
+ for (final Module module : modules) {
+ final List<VirtualFile> moduleCompilableFiles = getFilesToCompile(module);
+ if (mySourcesFilter == ALL_SOURCES) {
+ filesToCompile.addAll(moduleCompilableFiles);
+ }
+ else {
+ for (final VirtualFile file : moduleCompilableFiles) {
+ VirtualFile originalFile = myTransformedToOriginalMap.get(file);
+ if (originalFile == null) {
+ originalFile = file;
+ }
+ if (mySourcesFilter == TEST_SOURCES) {
+ if (myContext.isInTestSourceContent(originalFile)) {
+ filesToCompile.add(file);
+ }
+ }
+ else {
+ if (!myContext.isInTestSourceContent(originalFile)) {
+ filesToCompile.add(file);
+ }
+ }
+ }
+ }
+ }
+ return filesToCompile;
+ }
+
+ /**
+ * @return the jdk. Assumes that the jdk is the same for all modules
+ */
+ public Sdk getJdk() {
+ final Module module = getNodes().iterator().next();
+ return ModuleRootManager.getInstance(module).getSdk();
+ }
+
+ public VirtualFile[] getSourceRoots() {
+ return getSourceRoots(mySourcesFilter);
+ }
+
+ private VirtualFile[] getSourceRoots(final int sourcesFilter) {
+ return ApplicationManager.getApplication().runReadAction(new Computable<VirtualFile[]>() {
+ public VirtualFile[] compute() {
+ return filterRoots(getAllSourceRoots(), getNodes().iterator().next().getProject(), sourcesFilter);
+ }
+ });
+ }
+
+ public VirtualFile[] getSourceRoots(final Module module) {
+ if (!getNodes().contains(module)) {
+ return VirtualFile.EMPTY_ARRAY;
+ }
+ return ApplicationManager.getApplication().runReadAction(new Computable<VirtualFile[]>() {
+ public VirtualFile[] compute() {
+ return filterRoots(myContext.getSourceRoots(module), module.getProject(), mySourcesFilter);
+ }
+ });
+ }
+
+ private VirtualFile[] filterRoots(VirtualFile[] roots, Project project, final int sourcesFilter) {
+ final List<VirtualFile> filteredRoots = new ArrayList<VirtualFile>(roots.length);
+ final CompilerConfigurationImpl compilerConfiguration = (CompilerConfigurationImpl)CompilerConfiguration.getInstance(project);
+ for (final VirtualFile root : roots) {
+ if (sourcesFilter != ALL_SOURCES) {
+ if (myContext.isInTestSourceContent(root)) {
+ if ((sourcesFilter & TEST_SOURCES) == 0) {
+ continue;
+ }
+ }
+ else {
+ if ((sourcesFilter & SOURCES) == 0) {
+ continue;
+ }
+ }
+ }
+ if (compilerConfiguration.isExcludedFromCompilation(root)) {
+ continue;
+ }
+ filteredRoots.add(root);
+ }
+ return VfsUtil.toVirtualFileArray(filteredRoots);
+ }
+
+ private VirtualFile[] getAllSourceRoots() {
+ final Set<Module> modules = getNodes();
+ Set<VirtualFile> roots = new HashSet<VirtualFile>();
+ for (final Module module : modules) {
+ ContainerUtil.addAll(roots, myContext.getSourceRoots(module));
+ }
+ return VfsUtil.toVirtualFileArray(roots);
+ }
+
+ public String getCompilationClasspath() {
+ final OrderedSet<VirtualFile> cpFiles = getCompilationClasspathFiles();
+ return convertToStringPath(cpFiles);
+
+ }
+
+ public OrderedSet<VirtualFile> getCompilationClasspathFiles() {
+ return getCompilationClasspathFiles(true);
+ }
+
+ public OrderedSet<VirtualFile> getCompilationClasspathFiles(final boolean exportedOnly) {
+ final Set<Module> modules = getNodes();
+
+ OrderedSet<VirtualFile> cpFiles = new OrderedSet<VirtualFile>();
+ for (final Module module : modules) {
+ Collections.addAll(cpFiles, orderEnumerator(module, exportedOnly, new AfterJdkOrderEntryCondition()).getClassesRoots());
+ }
+ return cpFiles;
+ }
+
+ private OrderEnumerator orderEnumerator(Module module, boolean exportedOnly, Condition<OrderEntry> condition) {
+ OrderEnumerator enumerator = OrderEnumerator.orderEntries(module).compileOnly().satisfying(condition);
+ if ((mySourcesFilter & TEST_SOURCES) == 0) {
+ enumerator = enumerator.productionOnly();
+ }
+ enumerator = enumerator.recursively();
+ return exportedOnly ? enumerator.exportedOnly() : enumerator;
+ }
+
+ public String getCompilationBootClasspath() {
+ return convertToStringPath(getCompilationBootClasspathFiles());
+ }
+
+ public OrderedSet<VirtualFile> getCompilationBootClasspathFiles() {
+ return getCompilationBootClasspathFiles(true);
+ }
+
+ public OrderedSet<VirtualFile> getCompilationBootClasspathFiles(final boolean exportedOnly) {
+ final Set<Module> modules = getNodes();
+ final OrderedSet<VirtualFile> cpFiles = new OrderedSet<VirtualFile>();
+ final OrderedSet<VirtualFile> jdkFiles = new OrderedSet<VirtualFile>();
+ for (final Module module : modules) {
+ Collections.addAll(cpFiles, orderEnumerator(module, exportedOnly, new BeforeJdkOrderEntryCondition(module)).getClassesRoots());
+ Collections.addAll(jdkFiles, OrderEnumerator.orderEntries(module).sdkOnly().getClassesRoots());
+ }
+ cpFiles.addAll(jdkFiles);
+ return cpFiles;
+ }
+
+ private static String convertToStringPath(final OrderedSet<VirtualFile> cpFiles) {
+ PathsList classpath = new PathsList();
+ classpath.addVirtualFiles(cpFiles);
+ return classpath.getPathsString();
+ }
+
+ //private String tryZipFor(String outputDir) {
+ // final File zip = CompilerPathsEx.getZippedOutputPath(myContext.getProject(), outputDir);
+ // if (zip.exists()) {
+ // try {
+ // myContext.commitZip(outputDir); // flush unsaved data if any
+ // }
+ // catch (IOException e) {
+ // LOG.info(e);
+ // }
+ // return zip.getPath();
+ // }
+ // return outputDir;
+ //}
+
+ public int getModuleCount() {
+ return getNodes().size();
+ }
+
+ public Module[] getModules() {
+ final Set<Module> nodes = getNodes();
+ return nodes.toArray(new Module[nodes.size()]);
+ }
+
+ public String getSourcePath() {
+ return getSourcePath(mySourcesFilter);
+ }
+
+ public String getSourcePath(final int sourcesFilter) {
+ if (getModuleCount() == 0) {
+ return "";
+ }
+ final VirtualFile[] filteredRoots = getSourceRoots(sourcesFilter);
+ final StringBuilder buffer = StringBuilderSpinAllocator.alloc();
+ try {
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ for (VirtualFile root : filteredRoots) {
+ if (buffer.length() > 0) {
+ buffer.append(File.pathSeparatorChar);
+ }
+ buffer.append(root.getPath().replace('/', File.separatorChar));
+ }
+ }
+ });
+ return buffer.toString();
+ }
+ finally {
+ StringBuilderSpinAllocator.dispose(buffer);
+ }
+ }
+
+ //the check for equal language levels is done elsewhere
+ public LanguageLevel getLanguageLevel() {
+ return LanguageLevelUtil.getEffectiveLanguageLevel(getNodes().iterator().next());
+ }
+
+ public Project getProject() {
+ return myContext.getProject();
+ }
+
+ private static class BeforeJdkOrderEntryCondition implements Condition<OrderEntry> {
+ private boolean myJdkFound;
+ private final Module myOwnerModule;
+
+ private BeforeJdkOrderEntryCondition(Module ownerModule) {
+ myOwnerModule = ownerModule;
+ }
+
+ @Override
+ public boolean value(OrderEntry orderEntry) {
+ if (orderEntry instanceof JdkOrderEntry && myOwnerModule.equals(orderEntry.getOwnerModule())) {
+ myJdkFound = true;
+ }
+ return !myJdkFound;
+ }
+ }
+
+ private static class AfterJdkOrderEntryCondition implements Condition<OrderEntry> {
+ private boolean myJdkFound;
+
+ @Override
+ public boolean value(OrderEntry orderEntry) {
+ if (orderEntry instanceof JdkOrderEntry) {
+ myJdkFound = true;
+ return false;
+ }
+ return myJdkFound;
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/OutputDir.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/OutputDir.java
new file mode 100644
index 0000000..7d7b12d
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/OutputDir.java
@@ -0,0 +1,60 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+* @author cdr
+*/
+class OutputDir {
+ private final String myPath;
+ private final int myKind;
+
+ OutputDir(@NotNull String path, int kind) {
+ myPath = path;
+ myKind = kind;
+ }
+
+ @NotNull
+ public String getPath() {
+ return myPath;
+ }
+
+ public int getKind() {
+ return myKind;
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof OutputDir)) {
+ return false;
+ }
+
+ final OutputDir outputDir = (OutputDir)o;
+
+ return myKind == outputDir.myKind && myPath.equals(outputDir.myPath);
+
+ }
+
+ public int hashCode() {
+ int result = myPath.hashCode();
+ result = 29 * result + myKind;
+ return result;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/OutputItemImpl.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/OutputItemImpl.java
new file mode 100644
index 0000000..eb5720c
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/OutputItemImpl.java
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+/*
+ * @author: Eugene Zhuravlev
+ * Date: Apr 1, 2003
+ * Time: 5:58:41 PM
+ */
+package com.intellij.compiler.impl.javaCompiler;
+
+import com.intellij.openapi.compiler.TranslatingCompiler;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.Nullable;
+
+public class OutputItemImpl implements TranslatingCompiler.OutputItem{
+
+ private final String myOutputPath;
+ private final VirtualFile mySourceFile;
+
+ public OutputItemImpl(VirtualFile packageInfoFile) {
+ this(null, packageInfoFile);
+ }
+
+ /**
+ * @param outputPath absolute path of the output file ('/' slashes used)
+ * @param sourceFile corresponding source file
+ */
+ public OutputItemImpl(@Nullable String outputPath, VirtualFile sourceFile) {
+ myOutputPath = outputPath;
+ mySourceFile = sourceFile;
+ }
+
+ public String getOutputPath() {
+ return myOutputPath;
+ }
+
+ public VirtualFile getSourceFile() {
+ return mySourceFile;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/CompAPIDriver.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/CompAPIDriver.java
new file mode 100644
index 0000000..d75fe63
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/CompAPIDriver.java
@@ -0,0 +1,180 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler.api;
+
+import com.intellij.compiler.OutputParser;
+import com.intellij.openapi.compiler.CompilerMessageCategory;
+import com.sun.source.util.JavacTask;
+import com.sun.source.util.TaskEvent;
+import com.sun.source.util.TaskListener;
+import com.sun.tools.javac.api.JavacTool;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.tools.*;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.Field;
+import java.net.URI;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+/**
+ * @author cdr
+ */
+@SuppressWarnings({"Since15"})
+class CompAPIDriver {
+ private final BlockingQueue<CompilationEvent> myCompilationResults = new LinkedBlockingQueue<CompilationEvent>();
+ private static final CompilationEvent GUARD = new CompilationEvent() {
+ @Override
+ protected void process(OutputParser.Callback callback) {
+ }
+
+ @Override
+ public String toString() {
+ return "FINISH";
+ }
+ };
+ private String myOutputDir;
+
+ private volatile boolean compiling;
+ private static final PrintWriter COMPILER_ERRORS = new PrintWriter(System.err);
+ @Nullable
+ private final String mySourcesEncoding;
+
+ CompAPIDriver(@Nullable String sourcesEncoding) {
+ mySourcesEncoding = sourcesEncoding;
+ }
+
+ public void compile(List<String> commandLine, List<File> paths, final String outputDir) {
+ myOutputDir = outputDir;
+ compiling = true;
+
+ assert myCompilationResults.isEmpty();
+ JavaCompiler compiler = JavacTool.create(); //use current classloader
+ StandardJavaFileManager standardFileManager = compiler.getStandardFileManager(null, null, null);
+ MyFileManager manager = new MyFileManager(this, outputDir, standardFileManager, mySourcesEncoding);
+
+ Iterable<? extends JavaFileObject> input = manager.getJavaFileObjectsFromFiles(paths);
+
+ DiagnosticListener<JavaFileObject> listener = new DiagnosticListener<JavaFileObject>() {
+ public void report(Diagnostic<? extends JavaFileObject> diagnostic) {
+ CompilationEvent event = CompilationEvent.diagnostic(diagnostic);
+ myCompilationResults.offer(event);
+ }
+ };
+ try {
+ JavaCompiler.CompilationTask task = compiler.getTask(COMPILER_ERRORS, manager, listener, commandLine, null, input);
+ ((JavacTask)task).setTaskListener(new TaskListener() {
+ public void started(TaskEvent taskEvent) {
+ JavaFileObject sourceFile = taskEvent.getSourceFile();
+ CompilationEvent event;
+ switch (taskEvent.getKind()) {
+ case ANALYZE:
+ event = CompilationEvent.progress("Analyzing ",sourceFile);
+ break;
+ case PARSE:
+ event = CompilationEvent.progress("Parsing ", sourceFile);
+ break;
+ default:
+ event = null;
+ }
+ if (event != null) {
+ myCompilationResults.offer(event);
+ }
+ }
+ public void finished(TaskEvent taskEvent) {
+ CompilationEvent event;
+ switch (taskEvent.getKind()) {
+ case ENTER:
+ event = CompilationEvent.fileProcessed();
+ break;
+ default:
+ event = null;
+ }
+ if (event != null) {
+ myCompilationResults.offer(event);
+ }
+ }
+ });
+ task.call();
+ }
+ catch (IllegalArgumentException e) {
+ final String message = "Javac in-process compiler error: " + e.getMessage();
+ myCompilationResults.offer(new CompilationEvent() {
+ @Override
+ protected void process(OutputParser.Callback callback) {
+ callback.message(CompilerMessageCategory.ERROR, message, null, -1, -1);
+ }
+ });
+ }
+ finally {
+ compiling = false;
+ myCompilationResults.offer(GUARD);
+ try {
+ manager.close();
+ }
+ catch (IOException ignored) {
+ }
+ }
+ }
+
+ private volatile boolean processing;
+ public void processAll(@NotNull OutputParser.Callback callback) {
+ try {
+ processing = true;
+ while (true) {
+ CompilationEvent event = myCompilationResults.take();
+ if (event == GUARD) break;
+ event.process(callback);
+ }
+ }
+ catch (InterruptedException ignored) {
+ }
+ finally {
+ processing = false;
+ }
+ }
+
+ public void finish() {
+ assert !compiling : "still compiling to "+myOutputDir;
+ assert !processing;
+ //assert myCompilationResults.isEmpty() : myCompilationResults;
+ myCompilationResults.clear();
+ cleanupInternalFields();
+ }
+
+ private static void cleanupInternalFields() {
+ try {
+ Field freelist = Class.forName("com.sun.tools.javac.util.SharedNameTable").getDeclaredField("freelist");
+ freelist.setAccessible(true);
+ freelist.set(null, com.sun.tools.javac.util.List.nil());
+ }
+ catch (Exception ignored) {
+
+ }
+ }
+
+ public void offerClassFile(URI uri, byte[] bytes) {
+ CompilationEvent event = CompilationEvent.generateClass(uri, bytes);
+ myCompilationResults.offer(event);
+ }
+
+}
+
+
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/CompilationEvent.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/CompilationEvent.java
new file mode 100644
index 0000000..0ca1e2b
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/CompilationEvent.java
@@ -0,0 +1,127 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler.api;
+
+import com.intellij.compiler.OutputParser;
+import com.intellij.compiler.impl.javaCompiler.FileObject;
+import com.intellij.openapi.compiler.CompilerMessageCategory;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VfsUtil;
+import org.jetbrains.annotations.NonNls;
+
+import javax.tools.*;
+import java.io.File;
+import java.net.URI;
+
+/**
+ * @author cdr
+ */
+abstract class CompilationEvent {
+ protected abstract void process(OutputParser.Callback callback);
+ static CompilationEvent progress(final String title, final JavaFileObject fileObject) {
+ return new CompilationEvent() {
+ @Override
+ protected void process(OutputParser.Callback callback) {
+ showProgressFor(title, fileObject.toUri(), callback);
+ }
+
+ @NonNls
+ @Override
+ public String toString() {
+ return "Progress: "+title+" "+fileObject.toUri();
+ }
+ };
+ }
+
+ private static void showProgressFor(String title, URI uri, OutputParser.Callback callback) {
+ String normalizedPath;
+ try {
+ normalizedPath = new File(uri).getPath();
+ }
+ catch (IllegalArgumentException e) {
+ normalizedPath = uri.toString();
+ }
+ callback.setProgressText(title + StringUtil.last(normalizedPath, 100, true));
+ }
+
+ static CompilationEvent generateClass(final URI uri, final byte[] bytes) {
+ return new CompilationEvent() {
+ @Override
+ protected void process(OutputParser.Callback callback) {
+ showProgressFor("Writing ", uri, callback);
+ File file = new File(uri);
+ callback.fileGenerated(new FileObject(file,bytes));
+ }
+ @NonNls
+ @Override
+ public String toString() {
+ return "Write: "+uri;
+ }
+ };
+ }
+ static CompilationEvent diagnostic(final Diagnostic<? extends JavaFileObject> diagnostic) {
+ return new CompilationEvent() {
+ @Override
+ protected void process(OutputParser.Callback callback) {
+ JavaFileObject fileObject = diagnostic.getSource();
+ String message = diagnostic.getMessage(null);
+ String url;
+ if (fileObject == null) {
+ url = null;
+ }
+ else {
+ URI uri = fileObject.toUri();
+ if (uri.getScheme().equals("file")) {
+ url = VfsUtil.pathToUrl(FileUtil.toSystemIndependentName(uri.getPath()));
+ }
+ else {
+ url = fileObject.toString();
+ }
+ }
+
+ CompilerMessageCategory category = diagnostic.getKind() == Diagnostic.Kind.ERROR
+ ? CompilerMessageCategory.ERROR
+ : diagnostic.getKind() == Diagnostic.Kind.WARNING ||
+ diagnostic.getKind() == Diagnostic.Kind.MANDATORY_WARNING
+ ? CompilerMessageCategory.WARNING
+ : CompilerMessageCategory.INFORMATION;
+ callback.message(category, message, url, (int)diagnostic.getLineNumber(), (int)diagnostic.getColumnNumber());
+ }
+ @NonNls
+ @Override
+ public String toString() {
+ return "Diagnostic: "+diagnostic;
+ }
+ };
+ }
+
+ public static CompilationEvent fileProcessed() {
+ return FILE_PROCESSED;
+ }
+ private static final CompilationEvent FILE_PROCESSED = new CompilationEvent() {
+ @Override
+ protected void process(OutputParser.Callback callback) {
+ callback.fileProcessed(null);
+ }
+
+ @NonNls
+ @Override
+ public String toString() {
+ return "Processed";
+ }
+ };
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/CompilerAPICompiler.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/CompilerAPICompiler.java
new file mode 100644
index 0000000..c29b5c5
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/CompilerAPICompiler.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl.javaCompiler.api;
+
+import com.intellij.compiler.OutputParser;
+import com.intellij.compiler.impl.javaCompiler.BackendCompiler;
+import com.intellij.compiler.impl.javaCompiler.ModuleChunk;
+import com.intellij.compiler.impl.javaCompiler.javac.JavacCompiler;
+import com.intellij.compiler.impl.javaCompiler.javac.JavacConfigurable;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.CompileScope;
+import com.intellij.openapi.compiler.CompilerMessageCategory;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.StdFileTypes;
+import com.intellij.openapi.options.Configurable;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.JavaSdkVersion;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.jps.model.java.compiler.JavaCompilers;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+
+public class CompilerAPICompiler implements BackendCompiler {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.javaCompiler.api.CompilerAPICompiler");
+ private final Project myProject;
+ private static final Set<FileType> COMPILABLE_TYPES = Collections.<FileType>singleton(StdFileTypes.JAVA);
+
+ public CompilerAPICompiler(Project project) {
+ myProject = project;
+ }
+
+ public boolean checkCompiler(final CompileScope scope) {
+ return true;
+ }
+
+ @NotNull
+ @NonNls
+ public String getId() { // used for externalization
+ return JavaCompilers.JAVAC_API_ID;
+ }
+
+ @NotNull
+ public String getPresentableName() {
+ return "Javac in-process (Java6+ only)";
+ }
+
+ @NotNull
+ public Configurable createConfigurable() {
+ return new JavacConfigurable(CompilerAPIConfiguration.getOptions(myProject, CompilerAPIConfiguration.class));
+ }
+
+ @NotNull
+ public Set<FileType> getCompilableFileTypes() {
+ return COMPILABLE_TYPES;
+ }
+
+ @Nullable
+ public OutputParser createErrorParser(@NotNull final String outputDir, final Process process) {
+ return new OutputParser() {
+ public boolean processMessageLine(Callback callback) {
+ ((MyProcess)process).myCompAPIDriver.processAll(callback);
+ return false;
+ }
+ };
+ }
+
+ @Nullable
+ public OutputParser createOutputParser(@NotNull final String outputDir) {
+ return null;
+ }
+
+ public void compileFinished() {
+ }
+
+ @NotNull
+ public Process launchProcess(@NotNull final ModuleChunk chunk, @NotNull final String outputDir, @NotNull final CompileContext compileContext) throws IOException {
+ final IOException[] ex = {null};
+ @NonNls final List<String> commandLine = ApplicationManager.getApplication().runReadAction(new Computable<List<String>>() {
+ public List<String> compute() {
+ try {
+ List<String> commandLine = new ArrayList<String>();
+ final List<String> additionalOptions =
+ JavacCompiler.addAdditionalSettings(commandLine, CompilerAPIConfiguration.getOptions(myProject, CompilerAPIConfiguration.class), false, JavaSdkVersion.JDK_1_6, chunk, compileContext.isAnnotationProcessorsEnabled());
+
+ JavacCompiler.addCommandLineOptions(chunk, commandLine, outputDir, chunk.getJdk(), false,false, null, false, false, false);
+ commandLine.addAll(additionalOptions);
+ return commandLine;
+ }
+ catch (IOException e) {
+ ex[0] = e;
+ }
+ return null;
+ }
+ });
+ if (ex[0] != null) {
+ throw ex[0];
+ }
+ return new MyProcess(commandLine, chunk, outputDir, compileContext);
+ }
+
+ private static void compile(List<String> commandLine, ModuleChunk chunk, String outputDir, CompAPIDriver myCompAPIDriver) {
+ List<VirtualFile> filesToCompile = chunk.getFilesToCompile();
+ List<File> paths = new ArrayList<File>(filesToCompile.size());
+ for (VirtualFile file : filesToCompile) {
+ paths.add(new File(file.getPresentableUrl()));
+ }
+ myCompAPIDriver.compile(commandLine, paths, outputDir);
+ }
+
+ private static class MyProcess extends Process {
+ private final List<String> myCommandLine;
+ private final ModuleChunk myChunk;
+ private final String myOutputDir;
+ private final CompileContext myCompileContext;
+ private final CompAPIDriver myCompAPIDriver;
+
+ private MyProcess(List<String> commandLine, ModuleChunk chunk, String outputDir, CompileContext compileContext) {
+ myCommandLine = commandLine;
+ myChunk = chunk;
+ myOutputDir = outputDir;
+ myCompileContext = compileContext;
+ myCompAPIDriver = new CompAPIDriver(findEncodingValue(commandLine));
+ }
+
+ private static String findEncodingValue(List<String> commandLine) {
+ boolean found = false;
+ for (String param : commandLine) {
+ if (found) {
+ return param;
+ }
+ if ("-encoding".equalsIgnoreCase(param)) {
+ found = true;
+ }
+ }
+ return null;
+ }
+
+ public OutputStream getOutputStream() {
+ throw new UnsupportedOperationException();
+ }
+
+ public InputStream getInputStream() {
+ return null;
+ }
+
+ public InputStream getErrorStream() {
+ return null;
+ }
+
+ public void destroy() {
+ myCompAPIDriver.finish();
+ }
+
+ private int myExitCode;
+ public int waitFor() {
+ try {
+ myCommandLine.remove("-verbose");
+ compile(myCommandLine, myChunk, myOutputDir, myCompAPIDriver);
+ myExitCode = 0;
+ return myExitCode;
+ }
+ catch (Exception e) {
+ myCompileContext.addMessage(CompilerMessageCategory.ERROR, e.getMessage(), null, -1, -1);
+ LOG.info(e);
+ myExitCode = -1;
+ return -1;
+ }
+ }
+
+ public int exitValue() {
+ return myExitCode;
+ }
+
+ @Override
+ public String toString() {
+ return myChunk.toString();
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/CompilerAPIConfiguration.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/CompilerAPIConfiguration.java
new file mode 100755
index 0000000..0784a67
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/CompilerAPIConfiguration.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl.javaCompiler.api;
+
+import com.intellij.compiler.impl.javaCompiler.javac.JavacConfiguration;
+import com.intellij.openapi.components.State;
+import com.intellij.openapi.components.Storage;
+import com.intellij.openapi.components.StoragePathMacros;
+import com.intellij.openapi.components.StorageScheme;
+import com.intellij.openapi.project.Project;
+
+@State(
+ name = "CompilerAPISettings",
+ storages = {
+ @Storage( file = StoragePathMacros.PROJECT_FILE)
+ ,@Storage( file = StoragePathMacros.PROJECT_CONFIG_DIR + "/compiler.xml", scheme = StorageScheme.DIRECTORY_BASED)
+ }
+)
+public class CompilerAPIConfiguration extends JavacConfiguration {
+ public CompilerAPIConfiguration(Project project) {
+ super(project);
+ }
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/CompilerPerfTestAction.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/CompilerPerfTestAction.java
new file mode 100644
index 0000000..1b6b355
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/CompilerPerfTestAction.java
@@ -0,0 +1,98 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler.api;
+
+import com.intellij.compiler.CompilerConfiguration;
+import com.intellij.compiler.CompilerConfigurationImpl;
+import com.intellij.compiler.impl.javaCompiler.BackendCompiler;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.PlatformDataKeys;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.CompileStatusNotification;
+import com.intellij.openapi.compiler.CompilerManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.util.containers.ContainerUtil;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author cdr
+ */
+public class CompilerPerfTestAction extends AnAction {
+ @Override
+ public void update(AnActionEvent e) {
+ Project project = e.getData(PlatformDataKeys.PROJECT);
+ e.getPresentation().setEnabled(project != null);
+ }
+
+ public void actionPerformed(AnActionEvent e) {
+ final Project project = e.getData(PlatformDataKeys.PROJECT);
+
+ final CompilerManager compilerManager = CompilerManager.getInstance(project);
+
+ final CompilerConfigurationImpl configuration = (CompilerConfigurationImpl)CompilerConfiguration.getInstance(project);
+ List<BackendCompiler> compilers = (List<BackendCompiler>)configuration.getRegisteredJavaCompilers();
+ final List<BackendCompiler> allCompilers = ContainerUtil.concat(compilers, compilers, compilers, compilers, compilers, compilers, compilers);
+ final int[] i = new int[1];
+
+ CompileStatusNotification callback = new CompileStatusNotification() {
+ volatile long start;
+ BackendCompiler compiler;
+
+ public void finished(boolean aborted, int errors, int warnings, CompileContext compileContext) {
+ if (compiler == null) {
+ next();
+ return;
+ }
+ final long finish = System.currentTimeMillis();
+ System.out.println("Compiled with '" +
+ compiler.getPresentableName() + "' " +
+ " in " +
+ TimeUnit.MILLISECONDS.toMinutes(finish - start) + "m" +
+ TimeUnit.MILLISECONDS.toSeconds((finish - start)%60000) + "s" +
+ " with " +
+ errors +
+ " errors, " +
+ warnings +
+ " warnings, aborted=" +
+ aborted+"; free memory="+Runtime.getRuntime().freeMemory()+" bytes");
+ //ProfilingUtil.forceCaptureMemorySnapshot();
+ next();
+ }
+
+ void next() {
+ if (i[0] >= allCompilers.size()) return;
+ compiler = allCompilers.get(i[0]++);
+ if (compiler.getId().equals("Jikes")|| compiler.getId().contains("Eclipse")) {
+ next();
+ return;
+ }
+ boolean success = compiler.checkCompiler(compilerManager.createProjectCompileScope(project));
+ if (!success) {
+ next();
+ return;
+ }
+ configuration.setDefaultCompiler(compiler);
+ start = System.currentTimeMillis();
+ compilerManager.rebuild(this);
+ }
+ };
+
+ callback.finished(false,0,0,null);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/FileVirtualObject.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/FileVirtualObject.java
new file mode 100644
index 0000000..b6866f0
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/FileVirtualObject.java
@@ -0,0 +1,70 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler.api;
+
+import com.intellij.openapi.fileEditor.impl.LoadTextUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+
+import javax.tools.*;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.URI;
+
+/**
+ * @author cdr
+ */
+@SuppressWarnings({"Since15"})
+public abstract class FileVirtualObject extends SimpleJavaFileObject {
+ public FileVirtualObject(URI uri, Kind kind) {
+ super(uri, kind);
+ }
+
+ protected abstract VirtualFile getVirtualFile();
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+ VirtualFile virtualFile = getVirtualFile();
+ if (virtualFile == null) return null;
+ return LoadTextUtil.loadText(virtualFile);
+ }
+
+ @Override
+ public InputStream openInputStream() throws IOException {
+ // in-process compiler does not work well with zipped stream
+ byte[] bytes = getVirtualFile().contentsToByteArray();
+ return new ByteArrayInputStream(bytes);
+ }
+
+ @Override
+ public OutputStream openOutputStream() throws IOException {
+ return getVirtualFile().getOutputStream(this);
+ }
+
+ @Override
+ public String toString() {
+ return toUri().toString();
+ }
+ @Override
+ public int hashCode() {
+ return toUri().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof JavaFileObject && toUri().equals(((JavaFileObject)obj).toUri());
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/JavaIoFile.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/JavaIoFile.java
new file mode 100644
index 0000000..801fc75
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/JavaIoFile.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.compiler.impl.javaCompiler.api;
+
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.tools.JavaFileObject;
+import javax.tools.SimpleJavaFileObject;
+import java.io.*;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+/**
+* User: cdr
+*/
+@SuppressWarnings({"Since15"})
+class JavaIoFile extends SimpleJavaFileObject {
+ private final File myFile;
+ @Nullable
+ private final String myEncoding;
+
+ JavaIoFile(@NotNull File file, @NotNull Kind kind, @Nullable String encoding) {
+ super(convertToURI(file.getPath()), kind);
+ myFile = file;
+ myEncoding = encoding;
+ }
+
+ // File.toURI() asks for File.isDirectory which is too expensive
+ @NotNull
+ private static URI convertToURI(@NotNull String path) {
+ if (File.separatorChar != '/') {
+ path = path.replace(File.separatorChar, '/');
+ }
+ if (!StringUtil.startsWithChar(path, '/')) {
+ path = "/" + path;
+ }
+
+ if (path.startsWith("//")) {
+ path = "//" + path;
+ }
+ try {
+ return new URI("file", null, path, null);
+ }
+ catch (URISyntaxException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+ return FileUtil.loadFile(myFile, myEncoding);
+ }
+
+ @Override
+ public InputStream openInputStream() throws IOException {
+ return new BufferedInputStream(new FileInputStream(myFile));
+ }
+
+ @Override
+ public OutputStream openOutputStream() throws IOException {
+ return new BufferedOutputStream(new FileOutputStream(myFile));
+ }
+
+ @Override
+ public String toString() {
+ return toUri().toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return toUri().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof JavaFileObject && toUri().equals(((JavaFileObject)obj).toUri());
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/JavaVirtualByIoFile.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/JavaVirtualByIoFile.java
new file mode 100644
index 0000000..6fd1065
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/JavaVirtualByIoFile.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.compiler.impl.javaCompiler.api;
+
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+
+import java.io.File;
+
+/**
+ * User: cdr
+ */
+@SuppressWarnings({"Since15"})
+class JavaVirtualByIoFile extends FileVirtualObject {
+ private final File myFile;
+
+ protected JavaVirtualByIoFile(File file, Kind kind) {
+ super(file.toURI(), kind);
+ myFile = file;
+ }
+
+ protected VirtualFile getVirtualFile() {
+ return LocalFileSystem.getInstance().findFileByIoFile(myFile);
+ }
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/JavaVirtualFile.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/JavaVirtualFile.java
new file mode 100644
index 0000000..abe8f24
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/JavaVirtualFile.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.compiler.impl.javaCompiler.api;
+
+import com.intellij.openapi.vfs.VirtualFile;
+
+/**
+* User: cdr
+*/
+@SuppressWarnings({"Since15"})
+class JavaVirtualFile extends FileVirtualObject {
+ private final VirtualFile myFile;
+
+ JavaVirtualFile(VirtualFile file, Kind kind) {
+ super(MyFileManager.createUri(file.getUrl()), kind);
+ myFile = file;
+ }
+
+ protected VirtualFile getVirtualFile() {
+ return myFile;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/MyFileManager.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/MyFileManager.java
new file mode 100644
index 0000000..d46d504
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/MyFileManager.java
@@ -0,0 +1,260 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler.api;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.JarFileSystem;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.SmartList;
+import com.sun.tools.javac.util.ListBuffer;
+import gnu.trove.THashSet;
+import gnu.trove.TObjectHashingStrategy;
+
+import javax.tools.FileObject;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.util.*;
+
+/**
+* @author cdr
+*/
+class MyFileManager implements StandardJavaFileManager {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.javaCompiler.api.MyFileManager");
+
+ private final String myOutputDir;
+ private final StandardJavaFileManager myStandardFileManager;
+ private final String myEncoding;
+ private final CompAPIDriver myCompAPIDriver;
+
+ MyFileManager(CompAPIDriver compAPIDriver, String outputDir, StandardJavaFileManager standardFileManager, String encoding) {
+ myCompAPIDriver = compAPIDriver;
+ myOutputDir = outputDir;
+ myStandardFileManager = standardFileManager;
+ myEncoding = encoding;
+ }
+
+ @Override
+ public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(Iterable<? extends File> files) {
+ int size = ((Collection)files).size();
+ List<JavaFileObject> result = new ArrayList<JavaFileObject>(size);
+
+ for (File file : files) {
+ JavaFileObject fileObject = new JavaIoFile(file, JavaFileObject.Kind.SOURCE, myEncoding);
+ result.add(fileObject);
+ }
+
+ return result;
+ }
+
+ @Override
+ public JavaFileObject getJavaFileForOutput(Location location, String name, JavaFileObject.Kind kind, FileObject fileObject) {
+ URI uri = toURI(myOutputDir, name, kind);
+ return new Output(uri, myCompAPIDriver, kind);
+ }
+
+ static URI createUri(String url) {
+ return URI.create(url.replaceAll(" ","%20"));
+ }
+
+ private static URI toURI(String outputDir, String name, JavaFileObject.Kind kind) {
+ return createUri("file:///" + outputDir.replace('\\','/') + "/" + name.replace('.', '/') + kind.extension);
+ }
+
+
+ @Override
+ public Iterable<JavaFileObject> list(Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean recurse) throws IOException {
+ if (recurse) {
+ throw new IllegalArgumentException();
+ }
+ return findInside(location, packageName, kinds, false);
+ }
+
+ private List<JavaFileObject> findInside(Location location, String packageName, Set<JavaFileObject.Kind> kinds, boolean lookForFile)
+ throws IOException {
+ Iterable<? extends File> path = getLocation(location);
+ if (path == null) return Collections.emptyList();
+
+ String subdirectory = packageName.replace('.', '/');
+ List<JavaFileObject> results = null;
+
+ for (File directory : path) {
+ VirtualFile dir = LocalFileSystem.getInstance().findFileByIoFile(directory);
+ if (dir == null) continue;
+ if (!dir.isDirectory()) {
+ dir = JarFileSystem.getInstance().getJarRootForLocalFile(dir);
+ if (dir == null) continue;
+ }
+ VirtualFile virtualFile = StringUtil.isEmptyOrSpaces(subdirectory) ? dir : dir.findFileByRelativePath(subdirectory);
+ if (virtualFile == null) continue;
+
+ VirtualFile[] children;
+ if (lookForFile) {
+ if (!virtualFile.isDirectory()) {
+ children = new VirtualFile[]{virtualFile};
+ }
+ else continue;
+ }
+ else {
+ children = virtualFile.getChildren();
+ }
+ for (VirtualFile child : children) {
+ JavaFileObject.Kind kind = getKind("." + child.getExtension());
+ if (kinds == null || kinds.contains(kind)) {
+ if (results == null) results = new SmartList<JavaFileObject>();
+ if (kind == JavaFileObject.Kind.SOURCE && child.getFileSystem() instanceof JarFileSystem) continue; //for some reasdon javac looks for java files inside jar
+
+ // use VFS to read content inside .jar
+ String childPath = child.getPath();
+ JavaFileObject fileObject = !childPath.contains("!/") ? new JavaIoFile(new File(childPath), kind, myEncoding) : new JavaVirtualFile(child, kind);
+ results.add(fileObject);
+ }
+ }
+ }
+
+ List<JavaFileObject> ret = results == null ? Collections.<JavaFileObject>emptyList() : results;
+
+ if (LOG.isDebugEnabled()) {
+ // for testing consistency
+ Collection c = (Collection)myStandardFileManager.list(location, packageName, kinds, false);
+ Collection<JavaFileObject> sup = new HashSet(c);
+ assert sup.size() == c.size();
+ assert new HashSet(c).equals(sup);
+
+ THashSet<JavaFileObject> s = new THashSet<JavaFileObject>(new TObjectHashingStrategy<JavaFileObject>() {
+ public int computeHashCode(JavaFileObject object) {
+ return object.getName().hashCode();
+ }
+
+ public boolean equals(JavaFileObject o1, JavaFileObject o2) {
+ return o1.getKind() == o2.getKind() && o1.toUri().equals(o2.toUri());
+ }
+ });
+ s.addAll(ret);
+
+ s.removeAll(sup);
+ if (ret.size() != sup.size()) {
+ assert false : "our implementation differs from javac'";
+ }
+ }
+
+ return ret;
+ }
+
+ private static JavaFileObject.Kind getKind(String name) {
+ if (name.endsWith(JavaFileObject.Kind.CLASS.extension))
+ return JavaFileObject.Kind.CLASS;
+ else if (name.endsWith(JavaFileObject.Kind.SOURCE.extension))
+ return JavaFileObject.Kind.SOURCE;
+ else if (name.endsWith(JavaFileObject.Kind.HTML.extension))
+ return JavaFileObject.Kind.HTML;
+ else
+ return JavaFileObject.Kind.OTHER;
+ }
+
+
+ @Override
+ public String inferBinaryName(Location location, JavaFileObject file) {
+ return FileUtil.getNameWithoutExtension(new File(file.getName()).getName());
+ }
+
+ ////////// delegates
+ @Override
+ public int isSupportedOption(String option) {
+ return myStandardFileManager.isSupportedOption(option);
+ }
+
+ @Override
+ public void close() throws IOException {
+ myStandardFileManager.close();
+ }
+
+ @Override
+ public void flush() throws IOException {
+ myStandardFileManager.flush();
+ }
+
+ @Override
+ public boolean handleOption(String current, Iterator<String> remaining) {
+ return myStandardFileManager.handleOption(current, remaining);
+ }
+
+ @Override
+ public ClassLoader getClassLoader(Location location) {
+ return myStandardFileManager.getClassLoader(location);
+ }
+
+ @Override
+ public boolean isSameFile(FileObject a, FileObject b) {
+ if (a instanceof FileVirtualObject && b instanceof FileVirtualObject || a instanceof Output && b instanceof Output) {
+ return a.equals(b);
+ }
+ return myStandardFileManager.isSameFile(a, b);
+ }
+
+ public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) {
+ return getJavaFileObjectsFromFiles(Arrays.asList(files));
+ }
+
+ public Iterable<? extends JavaFileObject> getJavaFileObjects(String... names) {
+ return getJavaFileObjectsFromStrings(Arrays.asList(names));
+ }
+ public Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable<String> names) {
+ ListBuffer<File> files = new ListBuffer<File>();
+ for (String name : names) {
+ files.append(new File(name));
+ }
+ return getJavaFileObjectsFromFiles(files.toList());
+ }
+
+ public JavaFileObject getJavaFileForInput(Location location, String className, JavaFileObject.Kind kind) throws IOException {
+ List<JavaFileObject> result = findInside(location, className, kind == null ? null : Collections.singleton(kind), true);
+ if (!result.isEmpty()) {
+ return result.get(0);
+ }
+ return null;
+ }
+
+ @Override
+ public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {
+ return getJavaFileForInput(location, packageName + "/" + relativeName, getKind(relativeName));
+ }
+
+ @Override
+ public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling) throws IOException {
+ return getJavaFileForOutput(location, packageName + "/" + relativeName, getKind(relativeName), null);
+ }
+
+ @Override
+ public Iterable<? extends File> getLocation(Location location) {
+ return myStandardFileManager.getLocation(location);
+ }
+
+ @Override
+ public void setLocation(Location location, Iterable<? extends File> path) throws IOException {
+ myStandardFileManager.setLocation(location, path);
+ }
+
+ @Override
+ public boolean hasLocation(Location location) {
+ return myStandardFileManager.hasLocation(location);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/Output.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/Output.java
new file mode 100644
index 0000000..8cdf09c
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/api/Output.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.compiler.impl.javaCompiler.api;
+
+import org.jetbrains.annotations.Nullable;
+
+import javax.tools.*;
+import java.io.*;
+import java.net.URI;
+
+/**
+* User: cdr
+*/
+@SuppressWarnings({"ALL"})
+class Output extends SimpleJavaFileObject {
+ private final CompAPIDriver myCompAPIDriver;
+ @Nullable
+ private volatile byte[] myFileBytes;
+
+ Output(URI uri, CompAPIDriver compAPIDriver, final Kind kind) {
+ super(uri, kind);
+ myCompAPIDriver = compAPIDriver;
+ }
+
+ @Override
+ public ByteArrayOutputStream openOutputStream() {
+ return new ByteArrayOutputStream() {
+ @Override
+ public void close() throws IOException {
+ super.close();
+ final byte[] bytes = toByteArray();
+ myFileBytes = bytes;
+ if (Kind.CLASS.equals(kind)) {
+ myCompAPIDriver.offerClassFile(toUri(), bytes);
+ }
+ }
+ };
+ }
+
+ @Override
+ public InputStream openInputStream() throws IOException {
+ final byte[] bytes = myFileBytes;
+ if (bytes == null) {
+ throw new FileNotFoundException(toUri().getPath());
+ }
+ return new ByteArrayInputStream(bytes);
+ }
+
+ @Override
+ public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
+ final byte[] bytes = myFileBytes;
+ if (bytes == null) {
+ throw null;
+ }
+ return new String(bytes);
+ }
+
+ @Override
+ public int hashCode() {
+ return toUri().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof JavaFileObject && toUri().equals(((JavaFileObject)obj).toUri());
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseCompiler.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseCompiler.java
new file mode 100644
index 0000000..615315d
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseCompiler.java
@@ -0,0 +1,238 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl.javaCompiler.eclipse;
+
+import com.intellij.compiler.OutputParser;
+import com.intellij.compiler.impl.CompilerUtil;
+import com.intellij.compiler.impl.javaCompiler.ExternalCompiler;
+import com.intellij.compiler.impl.javaCompiler.ModuleChunk;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.PathManager;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.CompileScope;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.options.Configurable;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.JavaSdkType;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.ArrayUtil;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.jps.model.java.compiler.EclipseCompilerOptions;
+import org.jetbrains.jps.model.java.compiler.JavaCompilers;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+public class EclipseCompiler extends ExternalCompiler {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.javaCompiler.eclipse.EclipseCompiler");
+
+ private final Project myProject;
+ private final List<File> myTempFiles = new ArrayList<File>();
+ private static final String COMPILER_CLASS_NAME = "org.eclipse.jdt.core.compiler.batch.BatchCompiler";
+ @NonNls private static final String PATH_TO_COMPILER_JAR = findJarPah();
+
+ private static String findJarPah() {
+ try {
+ final Class<?> aClass = Class.forName(COMPILER_CLASS_NAME);
+ final String path = PathManager.getResourceRoot(aClass, "/" + aClass.getName().replace('.', '/') + ".class");
+ if (path != null) {
+ return path;
+ }
+ }
+ catch (ClassNotFoundException ignored) {
+ }
+
+ File dir = new File(PathManager.getLibPath());
+ File[] jars = dir.listFiles(new FilenameFilter() {
+ public boolean accept(File dir, String name) {
+ return name.startsWith("ecj-") && name.endsWith(".jar");
+ }
+ });
+ return jars.length == 0 ? dir + "/ecj-*.jar" : jars[0].getPath();
+ }
+
+ public EclipseCompiler(Project project) {
+ myProject = project;
+ }
+
+ public static boolean isInitialized() {
+ File file = new File(PATH_TO_COMPILER_JAR);
+ return file.exists();
+ }
+
+ public boolean checkCompiler(final CompileScope scope) {
+ if (!isInitialized()) {
+ Messages.showMessageDialog(
+ myProject,
+ CompilerBundle.message("eclipse.compiler.error.jar.not.found", PATH_TO_COMPILER_JAR),
+ CompilerBundle.message("compiler.eclipse.name"),
+ Messages.getErrorIcon()
+ );
+ return false;
+ }
+ return true;
+ }
+
+ @NonNls
+ public static String getCompilerClass() {
+ return "org.eclipse.jdt.internal.compiler.batch.Main";
+ }
+
+ @NotNull
+ public String getId() { // used for externalization
+ return JavaCompilers.ECLIPSE_ID;
+ }
+
+ @NotNull
+ public String getPresentableName() {
+ return CompilerBundle.message("compiler.eclipse.name");
+ }
+
+ @NotNull
+ public Configurable createConfigurable() {
+ return new EclipseCompilerConfigurable(EclipseCompilerConfiguration.getOptions(myProject, EclipseCompilerConfiguration.class));
+ }
+
+ public OutputParser createErrorParser(@NotNull final String outputDir, Process process) {
+ return new EclipseCompilerErrorParser();
+ }
+
+ @Nullable
+ public OutputParser createOutputParser(@NotNull final String outputDir) {
+ return new EclipseCompilerOutputParser(outputDir);
+ }
+
+ @NotNull
+ public String[] createStartupCommand(final ModuleChunk chunk, final CompileContext context, final String outputPath)
+ throws IOException {
+
+ final ArrayList<String> commandLine = new ArrayList<String>();
+ final IOException[] ex = {null};
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ try {
+ createStartupCommand(chunk, commandLine, outputPath, true);
+ }
+ catch (IOException e) {
+ ex[0] = e;
+ }
+ }
+ });
+ if (ex[0] != null) {
+ throw ex[0];
+ }
+ return ArrayUtil.toStringArray(commandLine);
+ }
+
+ private void createStartupCommand(final ModuleChunk chunk,
+ @NonNls final ArrayList<String> commandLine,
+ final String outputPath,
+ final boolean useTempFile) throws IOException {
+ final EclipseCompilerOptions options = EclipseCompilerConfiguration.getOptions(myProject, EclipseCompilerConfiguration.class);
+
+ final Sdk projectJdk = JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk();
+ final String vmExePath = ((JavaSdkType)projectJdk.getSdkType()).getVMExecutablePath(projectJdk);
+ commandLine.add(vmExePath);
+ commandLine.add("-Xmx" + options.MAXIMUM_HEAP_SIZE + "m");
+
+ CompilerUtil.addLocaleOptions(commandLine, false);
+
+ commandLine.add("-classpath");
+ commandLine.add(PATH_TO_COMPILER_JAR);
+ commandLine.add(getCompilerClass());
+
+ addCommandLineOptions(commandLine, chunk, outputPath, options, useTempFile, true);
+ }
+
+ public void addCommandLineOptions(@NotNull @NonNls final List<String> commandLine,
+ @NotNull final ModuleChunk chunk,
+ @NotNull final String outputPath,
+ @NotNull final EclipseCompilerOptions options,
+ final boolean useTempFile,
+ boolean quoteBootClasspath) throws IOException {
+ final Sdk jdk = chunk.getJdk();
+ CompilerUtil.addSourceCommandLineSwitch(jdk, chunk.getLanguageLevel(), commandLine);
+ CompilerUtil.addTargetCommandLineSwitch(chunk, commandLine);
+
+ final String bootCp = chunk.getCompilationBootClasspath();
+
+ final String classPath = chunk.getCompilationClasspath();
+
+ if (!StringUtil.isEmpty(bootCp)) {
+ commandLine.add("-bootclasspath");
+ // important: need to quote boot classpath if path to jdk contain spaces
+ commandLine.add(quoteBootClasspath ? CompilerUtil.quotePath(bootCp) : bootCp);
+ }
+
+ if (!StringUtil.isEmpty(classPath)) {
+ commandLine.add("-classpath");
+ commandLine.add(classPath);
+ }
+
+ commandLine.add("-d");
+ commandLine.add(outputPath.replace('/', File.separatorChar));
+
+ commandLine.add("-verbose");
+ StringTokenizer tokenizer = new StringTokenizer(new EclipseSettingsBuilder(options).getOptionsString(chunk), " ");
+ while (tokenizer.hasMoreTokens()) {
+ commandLine.add(tokenizer.nextToken());
+ }
+
+ final List<VirtualFile> files = chunk.getFilesToCompile();
+
+ if (useTempFile) {
+ File sourcesFile = FileUtil.createTempFile("javac", ".tmp");
+ sourcesFile.deleteOnExit();
+ myTempFiles.add(sourcesFile);
+ final PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(sourcesFile)));
+ try {
+ for (final VirtualFile file : files) {
+ // Important: should use "/" slashes!
+ // but not for JDK 1.5 - see SCR 36673
+ final String path = file.getPath().replace('/', File.separatorChar);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Adding path for compilation " + path);
+ }
+ writer.println(CompilerUtil.quotePath(path));
+ }
+ }
+ finally {
+ writer.close();
+ }
+ commandLine.add("@" + sourcesFile.getAbsolutePath());
+ }
+ else {
+ for (VirtualFile file : files) {
+ commandLine.add(file.getPath());
+ }
+ }
+ }
+
+ public void compileFinished() {
+ FileUtil.asyncDelete(myTempFiles);
+ myTempFiles.clear();
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseCompilerConfigurable.form b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseCompilerConfigurable.form
new file mode 100644
index 0000000..d8a50af
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseCompilerConfigurable.form
@@ -0,0 +1,118 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.compiler.impl.javaCompiler.eclipse.EclipseCompilerConfigurable">
+ <grid id="280f7" binding="myPanel" layout-manager="GridLayoutManager" row-count="6" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <xy x="79" y="160" width="584" height="236"/>
+ </constraints>
+ <properties/>
+ <clientProperties>
+ <BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithIndent"/>
+ </clientProperties>
+ <border type="etched" title-resource-bundle="messages/CompilerBundle" title-key="eclipse.options.group.title"/>
+ <children>
+ <component id="8538e" class="javax.swing.JCheckBox" binding="myCbDeprecation">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <selected value="false"/>
+ <text resource-bundle="messages/CompilerBundle" key="java.compiler.option.report.deprecated"/>
+ </properties>
+ </component>
+ <component id="3d38f" class="javax.swing.JCheckBox" binding="myCbDebuggingInfo">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <selected value="false"/>
+ <text resource-bundle="messages/CompilerBundle" key="java.compiler.option.generate.debugging.info"/>
+ </properties>
+ </component>
+ <component id="90803" class="javax.swing.JCheckBox" binding="myCbGenerateNoWarnings">
+ <constraints>
+ <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <selected value="false"/>
+ <text resource-bundle="messages/CompilerBundle" key="java.compiler.option.generate.no.warnings"/>
+ </properties>
+ </component>
+ <grid id="d7ddf" layout-manager="GridLayoutManager" row-count="2" column-count="4" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="10" left="8" bottom="0" right="0"/>
+ <constraints>
+ <grid row="4" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="f8a80" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="4" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="java.compiler.option.additional.command.line.parameters"/>
+ </properties>
+ </component>
+ <component id="479c2" class="com.intellij.ui.RawCommandLineEditor" binding="myAdditionalOptionsField">
+ <constraints>
+ <grid row="0" column="1" row-span="1" col-span="3" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
+ <preferred-size width="150" height="-1"/>
+ </grid>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="327dc" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="4" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="javac.option.max.heap.size"/>
+ </properties>
+ </component>
+ <component id="15c16" class="javax.swing.JTextField" binding="myJavacMaximumHeapField">
+ <constraints>
+ <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false">
+ <preferred-size width="60" height="-1"/>
+ </grid>
+ </constraints>
+ <properties>
+ <margin top="0" left="2" bottom="0" right="0"/>
+ <text value="128"/>
+ </properties>
+ <clientProperties>
+ <caretAspectRatio class="java.lang.Float" value="0.04"/>
+ </clientProperties>
+ </component>
+ <component id="6f8c2" class="com.intellij.ui.components.JBLabel">
+ <constraints>
+ <grid row="1" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <enabled value="false"/>
+ <text value="(ineffective when "Use external build" is on)"/>
+ </properties>
+ </component>
+ <hspacer id="a1325">
+ <constraints>
+ <grid row="1" column="3" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ </hspacer>
+ </children>
+ </grid>
+ <vspacer id="af30">
+ <constraints>
+ <grid row="5" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
+ </constraints>
+ </vspacer>
+ <component id="4eacb" class="javax.swing.JCheckBox" binding="myCbProceedOnErrors">
+ <constraints>
+ <grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="Proceed on errors"/>
+ </properties>
+ </component>
+ </children>
+ </grid>
+</form>
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseCompilerConfigurable.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseCompilerConfigurable.java
new file mode 100644
index 0000000..ad9cb1d
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseCompilerConfigurable.java
@@ -0,0 +1,103 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler.eclipse;
+
+import com.intellij.compiler.options.ComparingUtils;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.options.Configurable;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.ui.RawCommandLineEditor;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.jps.model.java.compiler.EclipseCompilerOptions;
+
+import javax.swing.*;
+
+/**
+ * @author cdr
+ */
+public class EclipseCompilerConfigurable implements Configurable {
+ private JPanel myPanel;
+ private JCheckBox myCbDeprecation;
+ private JCheckBox myCbDebuggingInfo;
+ private JCheckBox myCbGenerateNoWarnings;
+ private RawCommandLineEditor myAdditionalOptionsField;
+ private JTextField myJavacMaximumHeapField;
+ private JCheckBox myCbProceedOnErrors;
+ private final EclipseCompilerOptions myCompilerSettings;
+
+ public EclipseCompilerConfigurable(EclipseCompilerOptions options) {
+ myCompilerSettings = options;
+ myAdditionalOptionsField.setDialogCaption(CompilerBundle.message("java.compiler.option.additional.command.line.parameters"));
+ }
+
+ public String getDisplayName() {
+ return null;
+ }
+
+ @Nullable
+ @NonNls
+ public String getHelpTopic() {
+ return null;
+ }
+
+ public JComponent createComponent() {
+ return myPanel;
+ }
+
+ public boolean isModified() {
+ boolean isModified = ComparingUtils.isModified(myJavacMaximumHeapField, myCompilerSettings.MAXIMUM_HEAP_SIZE);
+
+ isModified |= ComparingUtils.isModified(myCbDeprecation, myCompilerSettings.DEPRECATION);
+ isModified |= ComparingUtils.isModified(myCbDebuggingInfo, myCompilerSettings.DEBUGGING_INFO);
+ isModified |= ComparingUtils.isModified(myCbGenerateNoWarnings, myCompilerSettings.GENERATE_NO_WARNINGS);
+ isModified |= ComparingUtils.isModified(myCbProceedOnErrors, myCompilerSettings.PROCEED_ON_ERROR);
+ isModified |= ComparingUtils.isModified(myAdditionalOptionsField, myCompilerSettings.ADDITIONAL_OPTIONS_STRING);
+ return isModified;
+ }
+
+ public void apply() throws ConfigurationException {
+
+ try {
+ myCompilerSettings.MAXIMUM_HEAP_SIZE = Integer.parseInt(myJavacMaximumHeapField.getText());
+ if(myCompilerSettings.MAXIMUM_HEAP_SIZE < 1) {
+ myCompilerSettings.MAXIMUM_HEAP_SIZE = 128;
+ }
+ }
+ catch(NumberFormatException exception) {
+ myCompilerSettings.MAXIMUM_HEAP_SIZE = 128;
+ }
+
+ myCompilerSettings.DEPRECATION = myCbDeprecation.isSelected();
+ myCompilerSettings.DEBUGGING_INFO = myCbDebuggingInfo.isSelected();
+ myCompilerSettings.GENERATE_NO_WARNINGS = myCbGenerateNoWarnings.isSelected();
+ myCompilerSettings.PROCEED_ON_ERROR = myCbProceedOnErrors.isSelected();
+ myCompilerSettings.ADDITIONAL_OPTIONS_STRING = myAdditionalOptionsField.getText();
+ }
+
+ public void reset() {
+ myJavacMaximumHeapField.setText(Integer.toString(myCompilerSettings.MAXIMUM_HEAP_SIZE));
+ myCbDeprecation.setSelected(myCompilerSettings.DEPRECATION);
+ myCbDebuggingInfo.setSelected(myCompilerSettings.DEBUGGING_INFO);
+ myCbGenerateNoWarnings.setSelected(myCompilerSettings.GENERATE_NO_WARNINGS);
+ myCbProceedOnErrors.setSelected(myCompilerSettings.PROCEED_ON_ERROR);
+ myAdditionalOptionsField.setText(myCompilerSettings.ADDITIONAL_OPTIONS_STRING);
+ }
+
+ public void disposeUIResources() {
+
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseCompilerConfiguration.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseCompilerConfiguration.java
new file mode 100755
index 0000000..4efbdcd
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseCompilerConfiguration.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl.javaCompiler.eclipse;
+
+import com.intellij.openapi.components.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.util.xmlb.XmlSerializerUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.jps.model.java.compiler.EclipseCompilerOptions;
+
+@State(
+ name = "EclipseCompilerSettings",
+ storages = {
+ @Storage( file = StoragePathMacros.PROJECT_FILE)
+ ,@Storage( file = StoragePathMacros.PROJECT_CONFIG_DIR + "/compiler.xml", scheme = StorageScheme.DIRECTORY_BASED)
+ }
+)
+public class EclipseCompilerConfiguration implements PersistentStateComponent<EclipseCompilerOptions> {
+ private final EclipseCompilerOptions mySettings = new EclipseCompilerOptions();
+
+ @NotNull
+ public EclipseCompilerOptions getState() {
+ return mySettings;
+ }
+
+ public void loadState(EclipseCompilerOptions state) {
+ XmlSerializerUtil.copyBean(state, mySettings);
+ }
+
+ public static EclipseCompilerOptions getOptions(Project project, Class<? extends EclipseCompilerConfiguration> aClass) {
+ return ServiceManager.getService(project, aClass).getState();
+ }}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseCompilerDriver.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseCompilerDriver.java
new file mode 100644
index 0000000..9538c16
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseCompilerDriver.java
@@ -0,0 +1,255 @@
+/*
+ * 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.
+ */
+
+/**
+ * @author Alexey
+ */
+
+package com.intellij.compiler.impl.javaCompiler.eclipse;
+
+import com.intellij.compiler.OutputParser;
+import com.intellij.compiler.impl.javaCompiler.FileObject;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.compiler.CompilerMessageCategory;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import org.eclipse.jdt.core.compiler.IProblem;
+import org.eclipse.jdt.core.compiler.InvalidInputException;
+import org.eclipse.jdt.internal.compiler.*;
+import org.eclipse.jdt.internal.compiler.Compiler;
+import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
+import org.eclipse.jdt.internal.compiler.batch.CompilationUnit;
+import org.eclipse.jdt.internal.compiler.batch.FileSystem;
+import org.eclipse.jdt.internal.compiler.batch.Main;
+import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
+import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public class EclipseCompilerDriver {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.javaCompiler.eclipse.EclipseCompilerDriver");
+
+ private String[] sourceFilePaths;
+ private Map compilerOptions;
+ private final BlockingQueue<CompilationResult> myCompilationResults = new LinkedBlockingQueue<CompilationResult>();
+ private FileSystem classPath;
+
+ private void parseCommandLine(String[] args) throws InvalidInputException {
+ StringWriter err = new StringWriter();
+ Main driver = new Main(null, new PrintWriter(err), false);
+ driver.configure(args);
+ StringBuffer buffer = err.getBuffer();
+ if (buffer.length() != 0) {
+ throw new InvalidInputException(buffer.toString());
+ }
+ sourceFilePaths = driver.filenames;
+ compilerOptions = driver.options;
+ classPath = driver.getLibraryAccess();
+ }
+
+ private CompilationUnit[] getCompilationUnits() {
+ int fileCount = sourceFilePaths.length;
+ CompilationUnit[] units = new CompilationUnit[fileCount];
+ final String defaultEncoding = null;
+
+ for (int i = 0; i < fileCount; i++) {
+ units[i] = new MyCompilationUnit(sourceFilePaths[i], defaultEncoding);
+ }
+ return units;
+ }
+
+ private ICompilerRequestor getBatchRequestor(final CompileContext compileContext) {
+ return new ICompilerRequestor() {
+ public void acceptResult(CompilationResult compilationResult) {
+ ProgressIndicator progress = compileContext.getProgressIndicator();
+ if (progress != null) {
+ progress.checkCanceled();
+ }
+ myCompilationResults.offer(compilationResult);
+ }
+ };
+ }
+
+ private static final CompilationResult END_OF_STREAM = new CompilationResult(new char[0], 0, 0, 0);
+
+ private INameEnvironment getEnvironment() {
+ return classPath;
+ }
+
+ private static IProblemFactory getProblemFactory() {
+ return new DefaultProblemFactory(Locale.getDefault());
+ }
+
+ private static IErrorHandlingPolicy getHandlingPolicy() {
+ return new IErrorHandlingPolicy() {
+ public boolean proceedOnErrors() {
+ return false; // stop if there are some errors
+ }
+
+ public boolean stopOnFirstError() {
+ return false;
+ }
+ };
+ }
+
+ private Map getCompilerOptions() {
+ return compilerOptions;
+ }
+
+
+ private void compile(final CompileContext compileContext) {
+ final INameEnvironment environment = getEnvironment();
+
+ Compiler compiler =
+ new Compiler(
+ environment,
+ getHandlingPolicy(),
+ getCompilerOptions(),
+ getBatchRequestor(compileContext),
+ getProblemFactory()){
+ protected void handleInternalException(Throwable internalException, CompilationUnitDeclaration unit, CompilationResult result) {
+ if (internalException instanceof ProcessCanceledException) throw (ProcessCanceledException)internalException;
+ super.handleInternalException(internalException, unit, result);
+ }
+ };
+ compiler.parseThreshold = 2500;
+ try {
+ compiler.compile(getCompilationUnits());
+ }
+ catch (ProcessCanceledException e) {
+ //compileContext.addMessage(CompilerMessageCategory.ERROR, "Canceled",null,-1,-1);
+ }
+ catch (Exception e) {
+ ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
+ if (indicator == null || !indicator.isCanceled()) {
+ compileContext.addMessage(CompilerMessageCategory.ERROR, "Internal Error: "+e.toString(),null,-1,-1);
+ }
+ }
+ finally {
+ myCompilationResults.offer(END_OF_STREAM);
+ environment.cleanup();
+ }
+ }
+
+ public boolean processMessageLine(final OutputParser.Callback callback, final String outputDir, Project project) {
+ ProgressManager.checkCanceled();
+ CompilationResult result;
+ try {
+ result = myCompilationResults.take();
+ }
+ catch (InterruptedException e) {
+ LOG.error(e);
+ return true;
+ }
+ if (result == END_OF_STREAM) {
+ return false;
+ }
+
+ String file = String.valueOf(result.getFileName());
+ callback.setProgressText(CompilerBundle.message("eclipse.compiler.parsing", file));
+ callback.fileProcessed(file);
+
+ ClassFile[] classFiles = result.getClassFiles();
+ for (ClassFile classFile : classFiles) {
+ String filePath = String.valueOf(classFile.fileName());
+ String relativePath = FileUtil.toSystemDependentName(filePath + ".class");
+ String path = FileUtil.toSystemDependentName(outputDir) + File.separatorChar + relativePath;
+
+ byte[] bytes = classFile.getBytes();
+ File out = new File(path);
+
+ callback.fileGenerated(new FileObject(out,bytes));
+ }
+ IProblem[] problems = result.getProblems();
+ if (problems != null) {
+ for (IProblem problem : problems) {
+ CompilerMessageCategory category = problem.isError() ? CompilerMessageCategory.ERROR
+ : problem.isWarning() ? CompilerMessageCategory.WARNING :
+ CompilerMessageCategory.INFORMATION;
+ String filePath = String.valueOf(problem.getOriginatingFileName());
+ String url = VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, FileUtil.toSystemIndependentName(filePath));
+ int lineNumber = problem.getSourceLineNumber();
+ int sourceStart = problem.getSourceStart();
+ int column = getColumn(url, lineNumber, sourceStart, project);
+ callback.message(category, problem.getMessage(), url, lineNumber, column);
+ }
+ }
+ return true;
+ }
+
+ private static int getColumn(final String url, final int lineNumber, final int sourceStart, final Project project) {
+ if (sourceStart == 0) return 0;
+ return ApplicationManager.getApplication().runReadAction(new Computable<Integer>() {
+ public Integer compute() {
+ VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(url);
+ if (file == null) return 0;
+ Document document = FileDocumentManager.getInstance().getDocument(file);
+ if (document == null) return 0;
+ int lineStartOffset = document.getLineStartOffset(lineNumber - 1);
+
+ String lineSeparator = FileDocumentManager.getInstance().getLineSeparator(file, project);
+ int offsetInVirtualFile = sourceStart - (lineNumber - 1) * (lineSeparator.length() - 1);
+ return offsetInVirtualFile - lineStartOffset + 1;
+ }
+ }).intValue();
+ }
+
+ public void parseCommandLineAndCompile(final String[] finalCmds, final CompileContext compileContext) throws Exception {
+ parseCommandLine(finalCmds);
+
+ compile(compileContext);
+ }
+
+ private static class MyCompilationUnit extends CompilationUnit {
+ private final String myDefaultEncoding;
+
+ private MyCompilationUnit(final String sourceFilePath, final String defaultEncoding) {
+ super(null, sourceFilePath, defaultEncoding);
+ myDefaultEncoding = defaultEncoding;
+ }
+
+ public char[] getContents() {
+ final String fileName = String.valueOf(getFileName());
+ try {
+ ProgressManager.checkCanceled();
+ return FileUtil.loadFileText(new File(fileName), myDefaultEncoding);
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ return null;
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseCompilerErrorParser.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseCompilerErrorParser.java
new file mode 100644
index 0000000..aa87dcb
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseCompilerErrorParser.java
@@ -0,0 +1,131 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler.eclipse;
+
+import com.intellij.compiler.OutputParser;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.CompilerMessageCategory;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import org.jetbrains.annotations.NonNls;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class EclipseCompilerErrorParser extends OutputParser {
+
+ private final StringBuilder problemText = new StringBuilder();
+
+ public boolean processMessageLine(Callback callback) {
+ @NonNls String line = callback.getNextLine();
+ if (line == null) {
+ spitOutProblem(callback);
+ return false;
+ }
+ if (line.trim().length() == 0) {
+ return true;
+ }
+ if (line.equals("----------")) {
+ spitOutProblem(callback);
+ problemText.setLength(0);
+ return true;
+ }
+ problemText.append(line);
+ problemText.append("\n");
+ return true;
+ }
+
+ private void spitOutProblem(final Callback callback) {
+ final String problem = problemText.toString();
+ if (problem.trim().length() == 0) return;
+
+ @NonNls final String problemTemplate = "(\\d*)\\. (\\w*) in (.*)" +
+ "\\s*\\(at line (\\d*)\\)\n" +
+ "\\s*(.*)\n" +
+ "(\\s*)\\^+\n" +
+ "(.*)\\s*";
+ final Pattern PATTERN = Pattern.compile(problemTemplate, Pattern.DOTALL);
+ Matcher matcher = PATTERN.matcher(problem);
+ if (matcher.matches()) {
+ //String seqN = matcher.group(1);
+ @NonNls String problemType = matcher.group(2);
+ String path = matcher.group(3).trim();
+ String lineNum = matcher.group(4);
+ String codeSnippet = matcher.group(5);
+ String indentWhiteSpace = matcher.group(6);
+ String message = matcher.group(7).trim();
+
+ CompilerMessageCategory messageCategory;
+ if ("WARNING".equals(problemType)) {
+ messageCategory = CompilerMessageCategory.WARNING;
+ }
+ else if ("ERROR".equals(problemType)) {
+ messageCategory = CompilerMessageCategory.ERROR;
+ }
+ else {
+ messageCategory = CompilerMessageCategory.INFORMATION;
+ }
+ final String url = VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, FileUtil.toSystemIndependentName(path));
+ final int line = Integer.parseInt(lineNum);
+ int col = indentWhiteSpace.length();
+ final String offendingCode = codeSnippet.substring(col-1);
+
+ // try to find similar text inside source file
+ int colFromFile = ApplicationManager.getApplication().runReadAction(new Computable<Integer>() {
+ public Integer compute() {
+ int index = -1;
+ VirtualFile file = VirtualFileManager.getInstance().findFileByUrl(url);
+ Document document = file == null ? null : FileDocumentManager.getInstance().getDocument(file);
+ if (document != null) {
+ // line is one-based
+ int docLine = line == 0 ? 0 : line-1;
+ int startOffset = document.getLineStartOffset(docLine);
+ int endOffset = document.getLineEndOffset(docLine);
+ String lineText = document.getText().substring(startOffset, endOffset);
+ index = lineText.indexOf(offendingCode);
+ if (index == -1) {
+ for (index = 0; index < lineText.length(); index++) {
+ if (!Character.isWhitespace(lineText.charAt(index))) break;
+ }
+ if (index == lineText.length()) index = -1;
+ }
+ // to one-based
+ if (index != -1) {
+ index++;
+ }
+ }
+ return index;
+ }
+ }).intValue();
+ if (colFromFile != -1) {
+ col = colFromFile;
+ }
+ callback.message(messageCategory, message, url, line, col);
+ }
+ else {
+ callback.message(CompilerMessageCategory.WARNING, problem, null, -1, -1);
+ }
+ }
+
+ public boolean isTrimLines() {
+ return false;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseCompilerOutputParser.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseCompilerOutputParser.java
new file mode 100644
index 0000000..12ceca3
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseCompilerOutputParser.java
@@ -0,0 +1,90 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler.eclipse;
+
+import com.intellij.compiler.OutputParser;
+import com.intellij.compiler.impl.javaCompiler.FileObject;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.compiler.CompilerMessageCategory;
+import com.intellij.openapi.util.io.FileUtil;
+import org.jetbrains.annotations.NonNls;
+
+import java.io.File;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class EclipseCompilerOutputParser extends OutputParser {
+ private final String myOutputDir;
+
+ public EclipseCompilerOutputParser(final String outputDir) {
+ myOutputDir = outputDir;
+ }
+
+ @NonNls private static final Pattern PATH_PATTERN = Pattern.compile("\\s*(.*) - #.*");
+ @NonNls private static final Pattern COMPILED_PATTERN = Pattern.compile("\\[\\d* unit(s)? compiled\\]");
+ @NonNls private static final Pattern GENERATED_PATTERN = Pattern.compile("\\[\\d* \\.class file(s)? generated\\]");
+ public boolean processMessageLine(Callback callback) {
+ @NonNls String line = callback.getNextLine();
+ if (line == null) {
+ return false;
+ }
+ if (line.trim().length() == 0) {
+ return true;
+ }
+ if (line.startsWith("[parsing ")) {
+ Matcher matcher = PATH_PATTERN.matcher(line.substring("[parsing ".length()));
+ matcher.matches();
+ String path = matcher.group(1);
+ callback.setProgressText(CompilerBundle.message("eclipse.compiler.parsing", path));
+ callback.fileProcessed(path);
+ return true;
+ }
+ if (line.startsWith("[reading ")) {
+ //StringTokenizer tokenizer = new StringTokenizer(line.substring("[reading ".length()), " ]");
+ //String fqn = tokenizer.nextToken();
+ callback.setProgressText(CompilerBundle.message("eclipse.compiler.reading"));
+ return true;
+ }
+ if (line.startsWith("[analyzing ")) {
+ Matcher matcher = PATH_PATTERN.matcher(line.substring("[analyzing ".length()));
+ matcher.matches();
+ String path = matcher.group(1);
+ callback.setProgressText(CompilerBundle.message("eclipse.compiler.analyzing", path));
+ return true;
+ }
+ if (line.startsWith("[completed ")) {
+ //Matcher matcher = PATH_PATTERN.matcher(line.substring("[completed ".length()));
+ //matcher.matches();
+ //String path = matcher.group(1);
+ //callback.setProgressText(CompilerBundle.message("eclipse.compiler.completed", path));
+ return true;
+ }
+ if (line.startsWith("[writing ")) {
+ Matcher matcher = PATH_PATTERN.matcher(line.substring("[writing ".length()));
+ matcher.matches();
+ String path = matcher.group(1);
+ String absPath = FileUtil.toSystemDependentName(myOutputDir + '/' + path);
+ //callback.setProgressText(CompilerBundle.message("eclipse.compiler.writing", absPath));
+ callback.fileGenerated(new FileObject(new File(absPath)));
+ return true;
+ }
+ if (COMPILED_PATTERN.matcher(line).matches() || GENERATED_PATTERN.matcher(line).matches()) {
+ return true;
+ }
+ callback.message(CompilerMessageCategory.INFORMATION, line, null, -1, -1);
+ return true;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseEmbeddedCompiler.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseEmbeddedCompiler.java
new file mode 100644
index 0000000..77aa2a1
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseEmbeddedCompiler.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl.javaCompiler.eclipse;
+
+import com.intellij.compiler.OutputParser;
+import com.intellij.compiler.impl.javaCompiler.BackendCompiler;
+import com.intellij.compiler.impl.javaCompiler.ModuleChunk;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.CompileScope;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.compiler.CompilerMessageCategory;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.StdFileTypes;
+import com.intellij.openapi.options.Configurable;
+import com.intellij.openapi.project.Project;
+import com.intellij.util.ArrayUtil;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.jps.model.java.compiler.EclipseCompilerOptions;
+import org.jetbrains.jps.model.java.compiler.JavaCompilers;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Set;
+
+
+public class EclipseEmbeddedCompiler implements BackendCompiler {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.javaCompiler.eclipse.EclipseEmbeddedCompiler");
+
+ private final Project myProject;
+ private final EclipseCompiler myEclipseExternalCompiler;
+ private int myExitCode;
+ private final EclipseCompilerDriver myEclipseCompilerDriver;
+ private static final Set<FileType> COMPILABLE_TYPES = Collections.<FileType>singleton(StdFileTypes.JAVA);
+
+ public EclipseEmbeddedCompiler(Project project) {
+ myProject = project;
+ myEclipseExternalCompiler = new EclipseCompiler(project);
+ myEclipseCompilerDriver = new EclipseCompilerDriver();
+ }
+
+ public boolean checkCompiler(final CompileScope scope) {
+ return myEclipseCompilerDriver != null && myEclipseExternalCompiler.checkCompiler(scope);
+ }
+
+ @NotNull
+ @NonNls
+ public String getId() { // used for externalization
+ return JavaCompilers.ECLIPSE_EMBEDDED_ID;
+ }
+
+ @NotNull
+ public String getPresentableName() {
+ return CompilerBundle.message("compiler.eclipse.embedded.name");
+ }
+
+ @NotNull
+ public Configurable createConfigurable() {
+ return new EclipseCompilerConfigurable(EclipseEmbeddedCompilerConfiguration.getOptions(myProject, EclipseEmbeddedCompilerConfiguration.class));
+ }
+
+ @NotNull
+ public Set<FileType> getCompilableFileTypes() {
+ return COMPILABLE_TYPES;
+ }
+
+ @Nullable
+ public OutputParser createErrorParser(@NotNull final String outputDir, Process process) {
+ return new OutputParser() {
+ public boolean processMessageLine(Callback callback) {
+ return myEclipseCompilerDriver.processMessageLine(callback, outputDir, myProject);
+ }
+ };
+ }
+
+ @Nullable
+ public OutputParser createOutputParser(@NotNull final String outputDir) {
+ return null;
+ }
+
+ public void compileFinished() {
+ }
+
+
+ @NotNull
+ public Process launchProcess(@NotNull final ModuleChunk chunk, @NotNull final String outputDir, @NotNull final CompileContext compileContext) throws IOException {
+ @NonNls final ArrayList<String> commandLine = new ArrayList<String>();
+ final IOException[] ex = {null};
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ try {
+ final EclipseCompilerOptions options = EclipseCompilerConfiguration.getOptions(myProject, EclipseEmbeddedCompilerConfiguration.class);
+ myEclipseExternalCompiler.addCommandLineOptions(commandLine, chunk, outputDir, options, false, false);
+ }
+ catch (IOException e) {
+ ex[0] = e;
+ }
+ }
+ });
+ if (ex[0] != null) {
+ throw ex[0];
+ }
+
+ return new Process() {
+ public OutputStream getOutputStream() {
+ throw new UnsupportedOperationException();
+ }
+
+ public InputStream getInputStream() {
+ return null;
+ }
+
+ public InputStream getErrorStream() {
+ return null;
+ }
+
+ public void destroy() {
+ }
+
+ public int waitFor() {
+ try {
+ commandLine.remove("-verbose");
+ String[] finalCmds = ArrayUtil.toStringArray(commandLine);
+ myEclipseCompilerDriver.parseCommandLineAndCompile(finalCmds,compileContext);
+ myExitCode = 0;
+ return myExitCode;
+ }
+ catch (Exception e) {
+ compileContext.addMessage(CompilerMessageCategory.ERROR, e.getMessage(), null, -1, -1);
+ LOG.info(e);
+ myExitCode = -1;
+ return -1;
+ }
+ }
+
+ public int exitValue() {
+ return myExitCode;
+ }
+ };
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseEmbeddedCompilerConfiguration.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseEmbeddedCompilerConfiguration.java
new file mode 100755
index 0000000..1c36e57
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseEmbeddedCompilerConfiguration.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl.javaCompiler.eclipse;
+
+import com.intellij.openapi.components.State;
+import com.intellij.openapi.components.Storage;
+import com.intellij.openapi.components.StoragePathMacros;
+import com.intellij.openapi.components.StorageScheme;
+
+@State(
+ name = "EclipseEmbeddedCompilerSettings",
+ storages = {
+ @Storage( file = StoragePathMacros.PROJECT_FILE)
+ ,@Storage( file = StoragePathMacros.PROJECT_CONFIG_DIR + "/compiler.xml", scheme = StorageScheme.DIRECTORY_BASED)
+ }
+)
+public class EclipseEmbeddedCompilerConfiguration extends EclipseCompilerConfiguration {
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseSettingsBuilder.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseSettingsBuilder.java
new file mode 100644
index 0000000..7748223
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/eclipse/EclipseSettingsBuilder.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl.javaCompiler.eclipse;
+
+import com.intellij.compiler.impl.javaCompiler.javac.JavacSettingsBuilder;
+import com.intellij.openapi.module.Module;
+import com.intellij.util.Chunk;
+import org.jetbrains.jps.model.java.compiler.EclipseCompilerOptions;
+
+import java.util.Collection;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 9/27/12
+ */
+public class EclipseSettingsBuilder extends JavacSettingsBuilder {
+ public EclipseSettingsBuilder(final EclipseCompilerOptions options) {
+ super(options);
+ }
+
+ @Override
+ public EclipseCompilerOptions getOptions() {
+ return (EclipseCompilerOptions)super.getOptions();
+ }
+
+ @Override
+ public Collection<String> getOptions(Chunk<Module> chunk) {
+ final Collection<String> options = super.getOptions(chunk);
+ if (getOptions().PROCEED_ON_ERROR) {
+ options.add("-proceedOnError");
+ }
+ return options;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/FilePathActionJavac.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/FilePathActionJavac.java
new file mode 100644
index 0000000..fc32b08
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/FilePathActionJavac.java
@@ -0,0 +1,65 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler.javac;
+
+import com.intellij.compiler.OutputParser;
+import com.intellij.compiler.impl.javaCompiler.FileObject;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.FileTypeManager;
+import com.intellij.openapi.fileTypes.StdFileTypes;
+
+import java.io.File;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Sep 14, 2005
+ */
+public class FilePathActionJavac extends JavacParserAction {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.javaCompiler.javac.FilePathActionJavac");
+ private final Matcher myJdk7FormatMatcher;
+
+ public FilePathActionJavac(final Matcher matcher) {
+ super(matcher);
+ myJdk7FormatMatcher = Pattern.compile("^\\w+\\[(.+)\\]$", Pattern.CASE_INSENSITIVE).matcher("");
+ }
+
+ protected void doExecute(final String line, String filePath, final OutputParser.Callback callback) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Process parsing message: " + filePath);
+ }
+
+ // for jdk7: cut off characters wrapping the path. e.g. "RegularFileObject[C:/tmp/bugs/src/a/Demo1.java]"
+ if (myJdk7FormatMatcher.reset(filePath).matches()) {
+ filePath = myJdk7FormatMatcher.group(1);
+ }
+
+ int index = filePath.lastIndexOf('/');
+ final String name = index >= 0 ? filePath.substring(index + 1) : filePath;
+
+ final FileType fileType = FileTypeManager.getInstance().getFileTypeByFileName(name);
+ if (StdFileTypes.JAVA.equals(fileType)) {
+ callback.fileProcessed(filePath);
+ callback.setProgressText(CompilerBundle.message("progress.parsing.file", name));
+ }
+ else if (StdFileTypes.CLASS.equals(fileType)) {
+ callback.fileGenerated(new FileObject(new File(filePath)));
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacCompiler.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacCompiler.java
new file mode 100644
index 0000000..e962a93
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacCompiler.java
@@ -0,0 +1,450 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl.javaCompiler.javac;
+
+import com.intellij.compiler.CompilerConfiguration;
+import com.intellij.compiler.CompilerConfigurationImpl;
+import com.intellij.compiler.CompilerIOUtil;
+import com.intellij.compiler.OutputParser;
+import com.intellij.compiler.impl.CompilerUtil;
+import com.intellij.compiler.impl.javaCompiler.ExternalCompiler;
+import com.intellij.compiler.impl.javaCompiler.ModuleChunk;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.CompileScope;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.compiler.CompilerPaths;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.options.Configurable;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.*;
+import com.intellij.openapi.projectRoots.ex.JavaSdkUtil;
+import com.intellij.openapi.projectRoots.impl.MockJdkWrapper;
+import com.intellij.openapi.roots.ModuleRootManager;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.pom.java.LanguageLevel;
+import com.intellij.rt.compiler.JavacRunner;
+import com.intellij.util.ArrayUtil;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.jps.model.java.compiler.AnnotationProcessingConfiguration;
+import org.jetbrains.jps.model.java.compiler.JavaCompilers;
+import org.jetbrains.jps.model.java.compiler.JpsJavaCompilerOptions;
+
+import java.io.*;
+import java.util.*;
+
+public class JavacCompiler extends ExternalCompiler {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.javaCompiler.javac.JavacCompiler");
+ private final Project myProject;
+ private final List<File> myTempFiles = new ArrayList<File>();
+ @NonNls private static final String JAVAC_MAIN_CLASS_OLD = "sun.tools.javac.Main";
+ @NonNls public static final String JAVAC_MAIN_CLASS = "com.sun.tools.javac.Main";
+ private boolean myAnnotationProcessorMode = false;
+
+ public JavacCompiler(Project project) {
+ myProject = project;
+ }
+
+ public boolean isAnnotationProcessorMode() {
+ return myAnnotationProcessorMode;
+ }
+
+ /**
+ * @param annotationProcessorMode
+ * @return previous value
+ */
+ public boolean setAnnotationProcessorMode(boolean annotationProcessorMode) {
+ final boolean oldValue = myAnnotationProcessorMode;
+ myAnnotationProcessorMode = annotationProcessorMode;
+ return oldValue;
+ }
+
+ public boolean checkCompiler(final CompileScope scope) {
+ final Module[] modules = scope.getAffectedModules();
+ final Set<Sdk> checkedJdks = new HashSet<Sdk>();
+ for (final Module module : modules) {
+ final Sdk jdk = ModuleRootManager.getInstance(module).getSdk();
+ if (jdk == null || checkedJdks.contains(jdk)) {
+ continue;
+ }
+ checkedJdks.add(jdk);
+ final SdkTypeId sdkType = jdk.getSdkType();
+ if (!(sdkType instanceof JavaSdkType)) {
+ continue;
+ }
+ final VirtualFile homeDirectory = jdk.getHomeDirectory();
+ if (homeDirectory == null) {
+ Messages.showMessageDialog(
+ myProject, CompilerBundle.jdkHomeNotFoundMessage(jdk), CompilerBundle.message("compiler.javac.name"), Messages.getErrorIcon()
+ );
+ return false;
+ }
+ final String vmExecutablePath = ((JavaSdkType)sdkType).getVMExecutablePath(jdk);
+ if (vmExecutablePath == null) {
+ Messages.showMessageDialog(
+ myProject, CompilerBundle.message("javac.error.vm.executable.missing", jdk.getName()), CompilerBundle.message("compiler.javac.name"), Messages.getErrorIcon()
+ );
+ return false;
+ }
+ final String toolsJarPath = ((JavaSdkType)sdkType).getToolsPath(jdk);
+ if (toolsJarPath == null) {
+ Messages.showMessageDialog(
+ myProject, CompilerBundle.message("javac.error.tools.jar.missing", jdk.getName()), CompilerBundle.message("compiler.javac.name"), Messages.getErrorIcon()
+ );
+ return false;
+ }
+ final String versionString = jdk.getVersionString();
+ if (versionString == null) {
+ Messages.showMessageDialog(
+ myProject, CompilerBundle.message("javac.error.unknown.jdk.version", jdk.getName()), CompilerBundle.message("compiler.javac.name"), Messages.getErrorIcon()
+ );
+ return false;
+ }
+
+ if (CompilerUtil.isOfVersion(versionString, "1.0")) {
+ Messages.showMessageDialog(
+ myProject, CompilerBundle.message("javac.error.1_0_compilation.not.supported"), CompilerBundle.message("compiler.javac.name"), Messages.getErrorIcon()
+ );
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @NotNull
+ @NonNls
+ public String getId() { // used for externalization
+ return JavaCompilers.JAVAC_ID;
+ }
+
+ @NotNull
+ public String getPresentableName() {
+ return CompilerBundle.message("compiler.javac.name");
+ }
+
+ @NotNull
+ public Configurable createConfigurable() {
+ return new JavacConfigurable(JavacConfiguration.getOptions(myProject, JavacConfiguration.class));
+ }
+
+ public OutputParser createErrorParser(@NotNull final String outputDir, Process process) {
+ return new JavacOutputParser(myProject);
+ }
+
+ public OutputParser createOutputParser(@NotNull final String outputDir) {
+ return null;
+ }
+
+ private static class MyException extends RuntimeException {
+ private MyException(Throwable cause) {
+ super(cause);
+ }
+ }
+
+ @NotNull
+ public String[] createStartupCommand(final ModuleChunk chunk, final CompileContext context, final String outputPath)
+ throws IOException, IllegalArgumentException {
+
+ try {
+ return ApplicationManager.getApplication().runReadAction(new Computable<String[]>() {
+ public String[] compute() {
+ try {
+ final List<String> commandLine = new ArrayList<String>();
+ createStartupCommand(chunk, commandLine, outputPath, JavacConfiguration.getOptions(myProject, JavacConfiguration.class), context.isAnnotationProcessorsEnabled());
+ return ArrayUtil.toStringArray(commandLine);
+ }
+ catch (IOException e) {
+ throw new MyException(e);
+ }
+ }
+ });
+ }
+ catch (MyException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof IOException) {
+ throw (IOException)cause;
+ }
+ throw e;
+ }
+ }
+
+ private void createStartupCommand(final ModuleChunk chunk, @NonNls final List<String> commandLine, final String outputPath,
+ JpsJavaCompilerOptions javacOptions, final boolean annotationProcessorsEnabled) throws IOException {
+ final Sdk jdk = getJdkForStartupCommand(chunk);
+ final String versionString = jdk.getVersionString();
+ JavaSdkVersion version = JavaSdk.getInstance().getVersion(jdk);
+ if (versionString == null || version == null || !(jdk.getSdkType() instanceof JavaSdkType)) {
+ throw new IllegalArgumentException(CompilerBundle.message("javac.error.unknown.jdk.version", jdk.getName()));
+ }
+ final boolean isVersion1_0 = version == JavaSdkVersion.JDK_1_0;
+ final boolean isVersion1_1 = version == JavaSdkVersion.JDK_1_1;
+
+ JavaSdkType sdkType = (JavaSdkType)jdk.getSdkType();
+
+ final String toolsJarPath = sdkType.getToolsPath(jdk);
+ if (toolsJarPath == null) {
+ throw new IllegalArgumentException(CompilerBundle.message("javac.error.tools.jar.missing", jdk.getName()));
+ }
+
+ final String vmExePath = sdkType.getVMExecutablePath(jdk);
+
+ commandLine.add(vmExePath);
+
+ if (version.isAtLeast(JavaSdkVersion.JDK_1_2)) {
+ commandLine.add("-Xmx" + javacOptions.MAXIMUM_HEAP_SIZE + "m");
+ }
+ else {
+ commandLine.add("-mx" + javacOptions.MAXIMUM_HEAP_SIZE + "m");
+ }
+
+ final List<String> additionalOptions =
+ addAdditionalSettings(commandLine, javacOptions, myAnnotationProcessorMode, version, chunk, annotationProcessorsEnabled);
+
+ CompilerUtil.addLocaleOptions(commandLine, false);
+
+ commandLine.add("-classpath");
+
+ if (isVersion1_0) {
+ commandLine.add(sdkType.getToolsPath(jdk)); // do not use JavacRunner for jdk 1.0
+ }
+ else {
+ commandLine.add(sdkType.getToolsPath(jdk) + File.pathSeparator + JavaSdkUtil.getIdeaRtJarPath());
+ commandLine.add(JavacRunner.class.getName());
+ commandLine.add("\"" + versionString + "\"");
+ }
+
+ if (version.isAtLeast(JavaSdkVersion.JDK_1_3)) {
+ commandLine.add(JAVAC_MAIN_CLASS);
+ }
+ else {
+ commandLine.add(JAVAC_MAIN_CLASS_OLD);
+ }
+
+ addCommandLineOptions(chunk, commandLine, outputPath, jdk, isVersion1_0, isVersion1_1, myTempFiles, true, true, myAnnotationProcessorMode);
+
+ commandLine.addAll(additionalOptions);
+
+ final List<VirtualFile> files = chunk.getFilesToCompile();
+
+ if (isVersion1_0) {
+ for (VirtualFile file : files) {
+ String path = file.getPath();
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Adding path for compilation " + path);
+ }
+ commandLine.add(CompilerUtil.quotePath(path));
+ }
+ }
+ else {
+ File sourcesFile = FileUtil.createTempFile("javac", ".tmp");
+ sourcesFile.deleteOnExit();
+ myTempFiles.add(sourcesFile);
+ final PrintWriter writer = new PrintWriter(new BufferedWriter(new FileWriter(sourcesFile)));
+ try {
+ for (final VirtualFile file : files) {
+ // Important: should use "/" slashes!
+ // but not for JDK 1.5 - see SCR 36673
+ final String path = version.isAtLeast(JavaSdkVersion.JDK_1_5) ? file.getPath().replace('/', File.separatorChar) : file.getPath();
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Adding path for compilation " + path);
+ }
+ writer.println(isVersion1_1 ? path : CompilerUtil.quotePath(path));
+ }
+ }
+ finally {
+ writer.close();
+ }
+ commandLine.add("@" + sourcesFile.getAbsolutePath());
+ }
+ }
+
+ public static List<String> addAdditionalSettings(List<String> commandLine, JpsJavaCompilerOptions javacOptions, boolean isAnnotationProcessing,
+ JavaSdkVersion version, ModuleChunk chunk, boolean annotationProcessorsEnabled) {
+ final List<String> additionalOptions = new ArrayList<String>();
+ StringTokenizer tokenizer = new StringTokenizer(new JavacSettingsBuilder(javacOptions).getOptionsString(chunk), " ");
+ if (!version.isAtLeast(JavaSdkVersion.JDK_1_6)) {
+ isAnnotationProcessing = false; // makes no sense for these versions
+ annotationProcessorsEnabled = false;
+ }
+ if (isAnnotationProcessing) {
+ final AnnotationProcessingConfiguration config = CompilerConfiguration.getInstance(chunk.getProject()).getAnnotationProcessingConfiguration(chunk.getModules()[0]);
+ additionalOptions.add("-Xprefer:source");
+ additionalOptions.add("-implicit:none");
+ additionalOptions.add("-proc:only");
+ if (!config.isObtainProcessorsFromClasspath()) {
+ final String processorPath = config.getProcessorPath();
+ additionalOptions.add("-processorpath");
+ additionalOptions.add(FileUtil.toSystemDependentName(processorPath));
+ }
+ final Set<String> processors = config.getProcessors();
+ if (!processors.isEmpty()) {
+ additionalOptions.add("-processor");
+ additionalOptions.add(StringUtil.join(processors, ","));
+ }
+ for (Map.Entry<String, String> entry : config.getProcessorOptions().entrySet()) {
+ additionalOptions.add("-A" + entry.getKey() + "=" +entry.getValue());
+ }
+ }
+ else {
+ if (annotationProcessorsEnabled) {
+ // Unless explicitly specified by user, disable annotation processing by default for 'java compilation' mode
+ // This is needed to suppress unwanted side-effects from auto-discovered processors from compilation classpath
+ additionalOptions.add("-proc:none");
+ }
+ }
+
+ while (tokenizer.hasMoreTokens()) {
+ @NonNls String token = tokenizer.nextToken();
+ if (version == JavaSdkVersion.JDK_1_0 && "-deprecation".equals(token)) {
+ continue; // not supported for this version
+ }
+ if (!version.isAtLeast(JavaSdkVersion.JDK_1_5) && "-Xlint".equals(token)) {
+ continue; // not supported in these versions
+ }
+ if (isAnnotationProcessing) {
+ if (token.startsWith("-proc:")) {
+ continue;
+ }
+ if (token.startsWith("-implicit:")) {
+ continue;
+ }
+ }
+ else { // compiling java
+ if (annotationProcessorsEnabled) {
+ // in this mode we have -proc:none already added above, so user's settings should be ignored
+ if (token.startsWith("-proc:")) {
+ continue;
+ }
+ }
+ }
+ if (token.startsWith("-J-")) {
+ commandLine.add(token.substring("-J".length()));
+ }
+ else {
+ additionalOptions.add(token);
+ }
+ }
+
+ return additionalOptions;
+ }
+
+ public static void addCommandLineOptions(ModuleChunk chunk, @NonNls List<String> commandLine, String outputPath, Sdk jdk,
+ boolean version1_0,
+ boolean version1_1,
+ List<File> tempFiles, boolean addSourcePath, boolean useTempFile,
+ boolean isAnnotationProcessingMode) throws IOException {
+
+ LanguageLevel languageLevel = chunk.getLanguageLevel();
+ CompilerUtil.addSourceCommandLineSwitch(jdk, languageLevel, commandLine);
+ CompilerUtil.addTargetCommandLineSwitch(chunk, commandLine);
+
+ commandLine.add("-verbose");
+
+ final String cp = chunk.getCompilationClasspath();
+ final String bootCp = chunk.getCompilationBootClasspath();
+
+ final String classPath;
+ if (version1_0 || version1_1) {
+ classPath = bootCp + File.pathSeparator + cp;
+ }
+ else {
+ classPath = cp;
+ commandLine.add("-bootclasspath");
+ addClassPathValue(jdk, false, commandLine, bootCp, "javac_bootcp", tempFiles, useTempFile);
+ }
+
+ commandLine.add("-classpath");
+ addClassPathValue(jdk, version1_0, commandLine, classPath, "javac_cp", tempFiles, useTempFile);
+
+ if (!version1_1 && !version1_0 && addSourcePath) {
+ commandLine.add("-sourcepath");
+ // this way we tell the compiler that the sourcepath is "empty". However, javac thinks that sourcepath is 'new File("")'
+ // this may cause problems if we have java code in IDEA working directory
+ if (isAnnotationProcessingMode) {
+ final int currentSourcesMode = chunk.getSourcesFilter();
+ commandLine.add(chunk.getSourcePath(currentSourcesMode == ModuleChunk.TEST_SOURCES? ModuleChunk.ALL_SOURCES : currentSourcesMode));
+ }
+ else {
+ commandLine.add("\"\"");
+ }
+ }
+
+ if (isAnnotationProcessingMode) {
+ commandLine.add("-s");
+ commandLine.add(outputPath.replace('/', File.separatorChar));
+ final String moduleOutputPath = CompilerPaths.getModuleOutputPath(chunk.getModules()[0], false);
+ if (moduleOutputPath != null) {
+ commandLine.add("-d");
+ commandLine.add(moduleOutputPath.replace('/', File.separatorChar));
+ }
+ }
+ else {
+ commandLine.add("-d");
+ commandLine.add(outputPath.replace('/', File.separatorChar));
+ }
+ }
+
+ private static void addClassPathValue(final Sdk jdk, final boolean isVersion1_0, final List<String> commandLine, final String cpString, @NonNls final String tempFileName,
+ List<File> tempFiles,
+ boolean useTempFile) throws IOException {
+ if (!useTempFile) {
+ commandLine.add(cpString);
+ return;
+ }
+ // must include output path to classpath, otherwise javac will compile all dependent files no matter were they compiled before or not
+ if (isVersion1_0) {
+ commandLine.add(((JavaSdkType)jdk.getSdkType()).getToolsPath(jdk) + File.pathSeparator + cpString);
+ }
+ else {
+ File cpFile = FileUtil.createTempFile(tempFileName, ".tmp");
+ cpFile.deleteOnExit();
+ tempFiles.add(cpFile);
+ final DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(cpFile)));
+ try {
+ CompilerIOUtil.writeString(cpString, out);
+ }
+ finally {
+ out.close();
+ }
+ commandLine.add("@" + cpFile.getAbsolutePath());
+ }
+ }
+
+ private Sdk getJdkForStartupCommand(final ModuleChunk chunk) {
+ final Sdk jdk = chunk.getJdk();
+ if (ApplicationManager.getApplication().isUnitTestMode() && JavacConfiguration.getOptions(myProject, JavacConfiguration.class).isTestsUseExternalCompiler()) {
+ final String jdkHomePath = CompilerConfigurationImpl.getTestsExternalCompilerHome();
+ if (jdkHomePath == null) {
+ throw new IllegalArgumentException("[TEST-MODE] Cannot determine home directory for JDK to use javac from");
+ }
+ // when running under Mock JDK use VM executable from the JDK on which the tests run
+ return new MockJdkWrapper(jdkHomePath, jdk);
+ }
+ return jdk;
+ }
+
+ public void compileFinished() {
+ FileUtil.asyncDelete(myTempFiles);
+ myTempFiles.clear();
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacConfigurable.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacConfigurable.java
new file mode 100644
index 0000000..c51f5f3
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacConfigurable.java
@@ -0,0 +1,97 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler.javac;
+
+import com.intellij.compiler.options.ComparingUtils;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.options.Configurable;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.ui.RawCommandLineEditor;
+import org.jetbrains.jps.model.java.compiler.JpsJavaCompilerOptions;
+
+import javax.swing.*;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 30, 2004
+ */
+public class JavacConfigurable implements Configurable{
+ private JPanel myPanel;
+ private JCheckBox myCbDebuggingInfo;
+ private JCheckBox myCbDeprecation;
+ private JCheckBox myCbGenerateNoWarnings;
+ private RawCommandLineEditor myAdditionalOptionsField;
+ private JTextField myJavacMaximumHeapField;
+ private final JpsJavaCompilerOptions myJavacSettings;
+
+ public JavacConfigurable(final JpsJavaCompilerOptions javacSettings) {
+ myJavacSettings = javacSettings;
+ myAdditionalOptionsField.setDialogCaption(CompilerBundle.message("java.compiler.option.additional.command.line.parameters"));
+ }
+
+ public String getDisplayName() {
+ return null;
+ }
+
+ public String getHelpTopic() {
+ return null;
+ }
+
+ public JComponent createComponent() {
+ return myPanel;
+ }
+
+ public boolean isModified() {
+ boolean isModified = false;
+ isModified |= ComparingUtils.isModified(myJavacMaximumHeapField, myJavacSettings.MAXIMUM_HEAP_SIZE);
+
+ isModified |= ComparingUtils.isModified(myCbDeprecation, myJavacSettings.DEPRECATION);
+ isModified |= ComparingUtils.isModified(myCbDebuggingInfo, myJavacSettings.DEBUGGING_INFO);
+ isModified |= ComparingUtils.isModified(myCbGenerateNoWarnings, myJavacSettings.GENERATE_NO_WARNINGS);
+ isModified |= ComparingUtils.isModified(myAdditionalOptionsField, myJavacSettings.ADDITIONAL_OPTIONS_STRING);
+ return isModified;
+ }
+
+ public void apply() throws ConfigurationException {
+
+ try {
+ myJavacSettings.MAXIMUM_HEAP_SIZE = Integer.parseInt(myJavacMaximumHeapField.getText());
+ if(myJavacSettings.MAXIMUM_HEAP_SIZE < 1) {
+ myJavacSettings.MAXIMUM_HEAP_SIZE = 128;
+ }
+ }
+ catch(NumberFormatException exception) {
+ myJavacSettings.MAXIMUM_HEAP_SIZE = 128;
+ }
+
+ myJavacSettings.DEPRECATION = myCbDeprecation.isSelected();
+ myJavacSettings.DEBUGGING_INFO = myCbDebuggingInfo.isSelected();
+ myJavacSettings.GENERATE_NO_WARNINGS = myCbGenerateNoWarnings.isSelected();
+ myJavacSettings.ADDITIONAL_OPTIONS_STRING = myAdditionalOptionsField.getText();
+ }
+
+ public void reset() {
+ myJavacMaximumHeapField.setText(Integer.toString(myJavacSettings.MAXIMUM_HEAP_SIZE));
+ myCbDeprecation.setSelected(myJavacSettings.DEPRECATION);
+ myCbDebuggingInfo.setSelected(myJavacSettings.DEBUGGING_INFO);
+ myCbGenerateNoWarnings.setSelected(myJavacSettings.GENERATE_NO_WARNINGS);
+ myAdditionalOptionsField.setText(myJavacSettings.ADDITIONAL_OPTIONS_STRING);
+ }
+
+ public void disposeUIResources() {
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacConfiguration.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacConfiguration.java
new file mode 100755
index 0000000..6e1f497
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacConfiguration.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl.javaCompiler.javac;
+
+import com.intellij.openapi.components.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.util.xmlb.XmlSerializerUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.jps.model.java.compiler.JpsJavaCompilerOptions;
+
+@State(
+ name = "JavacSettings",
+ storages = {
+ @Storage( file = StoragePathMacros.PROJECT_FILE)
+ ,@Storage( file = StoragePathMacros.PROJECT_CONFIG_DIR + "/compiler.xml", scheme = StorageScheme.DIRECTORY_BASED)
+ }
+)
+public class JavacConfiguration implements PersistentStateComponent<JpsJavaCompilerOptions> {
+ private final JpsJavaCompilerOptions mySettings = new JpsJavaCompilerOptions();
+ private final Project myProject;
+
+ public JavacConfiguration(Project project) {
+ myProject = project;
+ }
+
+ @Override
+ @NotNull
+ public JpsJavaCompilerOptions getState() {
+ JpsJavaCompilerOptions state = new JpsJavaCompilerOptions();
+ XmlSerializerUtil.copyBean(mySettings, state);
+ state.ADDITIONAL_OPTIONS_STRING = PathMacroManager.getInstance(myProject).collapsePathsRecursively(state.ADDITIONAL_OPTIONS_STRING);
+ return state;
+ }
+
+ @Override
+ public void loadState(JpsJavaCompilerOptions state) {
+ XmlSerializerUtil.copyBean(state, mySettings);
+ }
+
+ public static JpsJavaCompilerOptions getOptions(Project project, Class<? extends JavacConfiguration> aClass) {
+ JavacConfiguration configuration = ServiceManager.getService(project, aClass);
+ return configuration.mySettings;
+ }
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacOptionsPanel.form b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacOptionsPanel.form
new file mode 100644
index 0000000..3291deb
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacOptionsPanel.form
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.compiler.impl.javaCompiler.javac.JavacConfigurable">
+ <grid id="280f7" binding="myPanel" layout-manager="GridLayoutManager" row-count="5" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <xy x="79" y="160" width="574" height="210"/>
+ </constraints>
+ <properties/>
+ <clientProperties>
+ <BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithIndent"/>
+ </clientProperties>
+ <border type="etched" title-resource-bundle="messages/CompilerBundle" title-key="javac.options.group.title"/>
+ <children>
+ <component id="8538e" class="javax.swing.JCheckBox" binding="myCbDeprecation">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <selected value="true"/>
+ <text resource-bundle="messages/CompilerBundle" key="java.compiler.option.report.deprecated"/>
+ </properties>
+ </component>
+ <component id="3d38f" class="javax.swing.JCheckBox" binding="myCbDebuggingInfo">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <selected value="true"/>
+ <text resource-bundle="messages/CompilerBundle" key="java.compiler.option.generate.debugging.info"/>
+ </properties>
+ </component>
+ <component id="90803" class="javax.swing.JCheckBox" binding="myCbGenerateNoWarnings">
+ <constraints>
+ <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="java.compiler.option.generate.no.warnings"/>
+ </properties>
+ </component>
+ <grid id="d7ddf" layout-manager="GridLayoutManager" row-count="2" column-count="4" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="10" left="8" bottom="0" right="0"/>
+ <constraints>
+ <grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="f8a80" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="4" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="java.compiler.option.additional.command.line.parameters"/>
+ </properties>
+ </component>
+ <component id="479c2" class="com.intellij.ui.RawCommandLineEditor" binding="myAdditionalOptionsField">
+ <constraints>
+ <grid row="0" column="1" row-span="1" col-span="3" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
+ <preferred-size width="150" height="-1"/>
+ </grid>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="327dc" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="4" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="javac.option.max.heap.size"/>
+ </properties>
+ </component>
+ <component id="15c16" class="javax.swing.JTextField" binding="myJavacMaximumHeapField">
+ <constraints>
+ <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false">
+ <preferred-size width="60" height="-1"/>
+ </grid>
+ </constraints>
+ <properties>
+ <margin top="0" left="2" bottom="0" right="0"/>
+ <text value="128"/>
+ </properties>
+ <clientProperties>
+ <caretAspectRatio class="java.lang.Float" value="0.04"/>
+ </clientProperties>
+ </component>
+ <component id="9ae23" class="com.intellij.ui.components.JBLabel">
+ <constraints>
+ <grid row="1" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <enabled value="false"/>
+ <text value="(ineffective when "Use external build" is on)"/>
+ </properties>
+ </component>
+ <hspacer id="30d41">
+ <constraints>
+ <grid row="1" column="3" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ </hspacer>
+ </children>
+ </grid>
+ <vspacer id="72a3c">
+ <constraints>
+ <grid row="4" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
+ </constraints>
+ </vspacer>
+ </children>
+ </grid>
+</form>
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacOutputParser.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacOutputParser.java
new file mode 100644
index 0000000..ba12e69
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacOutputParser.java
@@ -0,0 +1,283 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler.javac;
+
+import com.intellij.compiler.OutputParser;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.compiler.CompilerMessageCategory;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.fileTypes.StdFileTypes;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
+import com.intellij.rt.compiler.JavacResourcesReader;
+import com.intellij.util.StringBuilderSpinAllocator;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class JavacOutputParser extends OutputParser {
+ private final int myTabSize;
+ @NonNls private String WARNING_PREFIX = "warning:"; // default value
+
+ public JavacOutputParser(Project project) {
+ myTabSize = CodeStyleSettingsManager.getSettings(project).getTabSize(StdFileTypes.JAVA);
+ if (ApplicationManager.getApplication().isUnitTestMode()) {
+ // emulate patterns setup if 'embedded' javac is used (javac is started not via JavacRunner)
+ addJavacPattern(JavacResourcesReader.MSG_PARSING_STARTED + JavacResourcesReader.CATEGORY_VALUE_DIVIDER + "[parsing started {0}]");
+ addJavacPattern(JavacResourcesReader.MSG_PARSING_COMPLETED + JavacResourcesReader.CATEGORY_VALUE_DIVIDER + "[parsing completed {0}ms]");
+ addJavacPattern(JavacResourcesReader.MSG_LOADING + JavacResourcesReader.CATEGORY_VALUE_DIVIDER + "[loading {0}]");
+ addJavacPattern(JavacResourcesReader.MSG_CHECKING + JavacResourcesReader.CATEGORY_VALUE_DIVIDER + "[checking {0}]");
+ addJavacPattern(JavacResourcesReader.MSG_WROTE + JavacResourcesReader.CATEGORY_VALUE_DIVIDER + "[wrote {0}]");
+ }
+ }
+
+ public boolean processMessageLine(Callback callback) {
+ if (super.processMessageLine(callback)) {
+ return true;
+ }
+ final String line = callback.getCurrentLine();
+ if (line == null) {
+ return false;
+ }
+ if (JavacResourcesReader.MSG_PATTERNS_START.equals(line)) {
+ myParserActions.clear();
+ while (true) {
+ final String patternLine = callback.getNextLine();
+ if (JavacResourcesReader.MSG_PATTERNS_END.equals(patternLine)) {
+ break;
+ }
+ addJavacPattern(patternLine);
+ }
+ return true;
+ }
+
+ int colonIndex1 = line.indexOf(':');
+ if (colonIndex1 == 1){ // drive letter
+ colonIndex1 = line.indexOf(':', colonIndex1 + 1);
+ }
+
+ if (colonIndex1 >= 0){ // looks like found something like file path
+ @NonNls String part1 = line.substring(0, colonIndex1).trim();
+ if(part1.equalsIgnoreCase("error") /*jikes*/ || part1.equalsIgnoreCase("Caused by")) {
+ addMessage(callback, CompilerMessageCategory.ERROR, line.substring(colonIndex1));
+ return true;
+ }
+ if(part1.equalsIgnoreCase("warning")) {
+ addMessage(callback, CompilerMessageCategory.WARNING, line.substring(colonIndex1));
+ return true;
+ }
+ if(part1.equals("javac")) {
+ addMessage(callback, CompilerMessageCategory.ERROR, line);
+ return true;
+ }
+
+ final int colonIndex2 = line.indexOf(':', colonIndex1 + 1);
+ if (colonIndex2 >= 0){
+ final String filePath = part1.replace(File.separatorChar, '/');
+ final Boolean fileExists = ApplicationManager.getApplication().runReadAction(new Computable<Boolean>(){
+ public Boolean compute(){
+ return LocalFileSystem.getInstance().findFileByPath(filePath) != null? Boolean.TRUE : Boolean.FALSE;
+ }
+ });
+ if (!fileExists.booleanValue()) {
+ // the part one turned out to be something else than a file path
+ return true;
+ }
+ try {
+ final int lineNum = Integer.parseInt(line.substring(colonIndex1 + 1, colonIndex2).trim());
+ String message = line.substring(colonIndex2 + 1).trim();
+ CompilerMessageCategory category = CompilerMessageCategory.ERROR;
+ if (message.startsWith(WARNING_PREFIX)){
+ message = message.substring(WARNING_PREFIX.length()).trim();
+ category = CompilerMessageCategory.WARNING;
+ }
+
+ List<String> messages = new ArrayList<String>();
+ messages.add(message);
+ int colNum;
+ String prevLine = null;
+ do{
+ final String nextLine = callback.getNextLine();
+ if (nextLine == null) {
+ return false;
+ }
+ if (nextLine.trim().equals("^")){
+ final CharSequence chars = prevLine == null ? line : prevLine;
+ final int offset = Math.max(0, Math.min(chars.length(), nextLine.indexOf('^')));
+ colNum = EditorUtil.calcColumnNumber(null, chars,0, offset, myTabSize);
+ String messageEnd = callback.getNextLine();
+ while (isMessageEnd(messageEnd)) {
+ messages.add(messageEnd.trim());
+ messageEnd = callback.getNextLine();
+ }
+ if (messageEnd != null) {
+ callback.pushBack(messageEnd);
+ }
+ break;
+ }
+ if (prevLine != null) {
+ messages.add(prevLine);
+ }
+ prevLine = nextLine;
+ }
+ while(true);
+
+ if (colNum >= 0){
+ messages = convertMessages(messages);
+ final StringBuilder buf = StringBuilderSpinAllocator.alloc();
+ try {
+ for (final String m : messages) {
+ if (buf.length() > 0) {
+ buf.append("\n");
+ }
+ buf.append(m);
+ }
+ addMessage(callback, category, buf.toString(), VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, filePath), lineNum, colNum + 1);
+ }
+ finally {
+ StringBuilderSpinAllocator.dispose(buf);
+ }
+ return true;
+ }
+ }
+ catch (NumberFormatException ignored) {
+ }
+ }
+ }
+
+ if(line.endsWith("java.lang.OutOfMemoryError")) {
+ addMessage(callback, CompilerMessageCategory.ERROR, CompilerBundle.message("error.javac.out.of.memory"));
+ return true;
+ }
+
+ addMessage(callback, CompilerMessageCategory.INFORMATION, line);
+ return true;
+ }
+
+ private static boolean isMessageEnd(String line) {
+ return line != null && line.length() > 0 && Character.isWhitespace(line.charAt(0));
+ }
+
+
+ private static List<String> convertMessages(List<String> messages) {
+ if(messages.size() <= 1) {
+ return messages;
+ }
+ final String line0 = messages.get(0);
+ final String line1 = messages.get(1);
+ final int colonIndex = line1.indexOf(':');
+ if (colonIndex > 0){
+ @NonNls String part1 = line1.substring(0, colonIndex).trim();
+ // jikes
+ if ("symbol".equals(part1)){
+ String symbol = line1.substring(colonIndex + 1).trim();
+ messages.remove(1);
+ if(messages.size() >= 2) {
+ messages.remove(1);
+ }
+ messages.set(0, line0 + " " + symbol);
+ }
+ }
+ return messages;
+ }
+
+ private void addJavacPattern(@NonNls final String line) {
+ final int dividerIndex = line.indexOf(JavacResourcesReader.CATEGORY_VALUE_DIVIDER);
+ if (dividerIndex < 0) {
+ // by reports it may happen for some IBM JDKs (empty string?)
+ return;
+ }
+ final String category = line.substring(0, dividerIndex);
+ final String resourceBundleValue = line.substring(dividerIndex + 1);
+ if (JavacResourcesReader.MSG_PARSING_COMPLETED.equals(category) ||
+ JavacResourcesReader.MSG_PARSING_STARTED.equals(category) ||
+ JavacResourcesReader.MSG_WROTE.equals(category)
+ ) {
+ myParserActions.add(new FilePathActionJavac(createMatcher(resourceBundleValue)));
+ }
+ else if (JavacResourcesReader.MSG_CHECKING.equals(category)) {
+ myParserActions.add(new JavacParserAction(createMatcher(resourceBundleValue)) {
+ protected void doExecute(final String line, String parsedData, final Callback callback) {
+ callback.setProgressText(CompilerBundle.message("progress.compiling.class", parsedData));
+ }
+ });
+ }
+ else if (JavacResourcesReader.MSG_LOADING.equals(category)) {
+ myParserActions.add(new JavacParserAction(createMatcher(resourceBundleValue)) {
+ protected void doExecute(final String line, @Nullable String parsedData, final Callback callback) {
+ callback.setProgressText(CompilerBundle.message("progress.loading.classes"));
+ }
+ });
+ }
+ else if (JavacResourcesReader.MSG_NOTE.equals(category)) {
+ myParserActions.add(new JavacParserAction(createMatcher(resourceBundleValue)) {
+ protected void doExecute(final String line, @Nullable final String filePath, final Callback callback) {
+ final boolean fileExists = filePath != null && ApplicationManager.getApplication().runReadAction(new Computable<Boolean>(){
+ public Boolean compute(){
+ return LocalFileSystem.getInstance().findFileByPath(filePath) != null? Boolean.TRUE : Boolean.FALSE;
+ }
+ });
+ if (fileExists) {
+ addMessage(callback, CompilerMessageCategory.WARNING, line, VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, filePath), -1, -1);
+ }
+ else {
+ addMessage(callback, CompilerMessageCategory.INFORMATION, line);
+ }
+ }
+ });
+ }
+ else if (JavacResourcesReader.MSG_WARNING.equals(category)) {
+ WARNING_PREFIX = resourceBundleValue;
+ }
+ else if (JavacResourcesReader.MSG_STATISTICS.equals(category)) {
+ myParserActions.add(new JavacParserAction(createMatcher(resourceBundleValue)) {
+ protected void doExecute(final String line, @Nullable String parsedData, final Callback callback) {
+ // empty
+ }
+ });
+ }
+ else if (JavacResourcesReader.MSG_IGNORED.equals(category)) {
+ myParserActions.add(new JavacParserAction(createMatcher(resourceBundleValue)) {
+ protected void doExecute(final String line, @Nullable String parsedData, final Callback callback) {
+ // ignored
+ }
+ });
+ }
+ }
+
+
+ /**
+ * made public for Tests, do not use this method directly
+ */
+ public static Matcher createMatcher(@NonNls final String resourceBundleValue) {
+ @NonNls String regexp = resourceBundleValue.replaceAll("([\\[\\]\\(\\)\\.\\*])", "\\\\$1");
+ regexp = regexp.replaceAll("\\{\\d+\\}", "(.+)");
+ return Pattern.compile(regexp, Pattern.CASE_INSENSITIVE).matcher("");
+ }
+
+ public boolean isTrimLines() {
+ return false;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacParserAction.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacParserAction.java
new file mode 100644
index 0000000..a9eefaf
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacParserAction.java
@@ -0,0 +1,48 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler.javac;
+
+import com.intellij.compiler.OutputParser;
+import com.intellij.compiler.ParserAction;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.util.regex.Matcher;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Sep 14, 2005
+ */
+public abstract class JavacParserAction extends ParserAction {
+ private final Matcher myMatcher;
+
+ protected JavacParserAction(final Matcher matcher) {
+ myMatcher = matcher;
+ }
+
+ public final boolean execute(String line, final OutputParser.Callback callback) {
+ myMatcher.reset(line);
+ if (!myMatcher.matches()) {
+ return false;
+ }
+ final String parsed = myMatcher.groupCount() >= 1 ? myMatcher.group(1).replace(File.separatorChar, '/') : null;
+ doExecute(line, parsed, callback);
+ return true;
+ }
+
+ protected abstract void doExecute(final String line, @Nullable String parsedData, final OutputParser.Callback callback);
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacSettingsBuilder.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacSettingsBuilder.java
new file mode 100644
index 0000000..7be8e1d
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/JavacSettingsBuilder.java
@@ -0,0 +1,92 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler.javac;
+
+import com.intellij.compiler.CompilerEncodingService;
+import com.intellij.compiler.impl.javaCompiler.ModuleChunk;
+import com.intellij.openapi.module.Module;
+import com.intellij.util.Chunk;
+import org.jetbrains.jps.model.java.compiler.JpsJavaCompilerOptions;
+
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.StringTokenizer;
+
+public class JavacSettingsBuilder {
+ private final JpsJavaCompilerOptions myOptions;
+
+ public JavacSettingsBuilder(JpsJavaCompilerOptions options) {
+ myOptions = options;
+ }
+
+ protected JpsJavaCompilerOptions getOptions() {
+ return myOptions;
+ }
+
+ public Collection<String> getOptions(Chunk<Module> chunk) {
+ List<String> options = new ArrayList<String>();
+ if (getOptions().DEBUGGING_INFO) {
+ options.add("-g");
+ }
+ if (getOptions().DEPRECATION) {
+ options.add("-deprecation");
+ }
+ if (getOptions().GENERATE_NO_WARNINGS) {
+ options.add("-nowarn");
+ }
+ boolean isEncodingSet = false;
+ final StringTokenizer tokenizer = new StringTokenizer(getOptions().ADDITIONAL_OPTIONS_STRING, " \t\r\n");
+ while(tokenizer.hasMoreTokens()) {
+ final String token = tokenizer.nextToken();
+ if(!acceptUserOption(token)) {
+ continue;
+ }
+ options.add(token);
+ if ("-encoding".equals(token)) {
+ isEncodingSet = true;
+ }
+ }
+ if (!isEncodingSet && acceptEncoding()) {
+ final Charset charset = CompilerEncodingService.getPreferredModuleEncoding(chunk);
+ if (charset != null) {
+ options.add("-encoding");
+ options.add(charset.name());
+ }
+ }
+ return options;
+ }
+
+ protected boolean acceptUserOption(String token) {
+ return !("-g".equals(token) || "-deprecation".equals(token) || "-nowarn".equals(token));
+ }
+
+ protected boolean acceptEncoding() {
+ return true;
+ }
+
+ public String getOptionsString(final ModuleChunk chunk) {
+ final StringBuilder options = new StringBuilder();
+ for (String option : getOptions(chunk)) {
+ if (options.length() > 0) {
+ options.append(" ");
+ }
+ options.append(option);
+ }
+ return options.toString();
+ }
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/StatisticsActionJavac.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/StatisticsActionJavac.java
new file mode 100644
index 0000000..d969e40
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/javac/StatisticsActionJavac.java
@@ -0,0 +1,39 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler.javac;
+
+import com.intellij.compiler.OutputParser;
+import com.intellij.openapi.compiler.CompilerBundle;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.regex.Matcher;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Sep 24, 2005
+ */
+public class StatisticsActionJavac extends JavacParserAction {
+ private final String myProgressMessageResourceKey;
+
+ public StatisticsActionJavac(final Matcher matcher, String progressMessageResourceKey) {
+ super(matcher);
+ myProgressMessageResourceKey = progressMessageResourceKey;
+ }
+
+ protected void doExecute(final String line, @Nullable String parsedData, final OutputParser.Callback callback) {
+ callback.setProgressText(CompilerBundle.message(myProgressMessageResourceKey));
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/JikesCompiler.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/JikesCompiler.java
new file mode 100644
index 0000000..fb4eed3
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/JikesCompiler.java
@@ -0,0 +1,237 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler.jikes;
+
+import com.intellij.compiler.CompilerConfiguration;
+import com.intellij.compiler.CompilerConfigurationImpl;
+import com.intellij.compiler.OutputParser;
+import com.intellij.compiler.impl.CompilerUtil;
+import com.intellij.compiler.impl.javaCompiler.BackendCompiler;
+import com.intellij.compiler.impl.javaCompiler.ExternalCompiler;
+import com.intellij.compiler.impl.javaCompiler.ModuleChunk;
+import com.intellij.compiler.options.JavaCompilersTab;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.CompileScope;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.options.Configurable;
+import com.intellij.openapi.options.ShowSettingsUtil;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.pom.java.LanguageLevel;
+import com.intellij.util.ArrayUtil;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.StringTokenizer;
+
+public class JikesCompiler extends ExternalCompiler {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.JikesHandler");
+ private final Project myProject;
+ private File myTempFile;
+
+ public JikesCompiler(Project project) {
+ myProject = project;
+ }
+
+ public boolean checkCompiler(final CompileScope scope) {
+ String compilerPath = getCompilerPath();
+ if (StringUtil.isEmpty(compilerPath)) {
+ Messages.showMessageDialog(
+ myProject,
+ CompilerBundle.message("jikes.error.path.to.compiler.unspecified"), CompilerBundle.message("compiler.jikes.name"),
+ Messages.getErrorIcon()
+ );
+ openConfigurationDialog();
+ compilerPath = getCompilerPath(); // update path
+ if (StringUtil.isEmpty(compilerPath)) {
+ return false;
+ }
+ }
+
+ File file = new File(compilerPath);
+ if (!file.exists()) {
+ Messages.showMessageDialog(
+ myProject,
+ CompilerBundle.message("jikes.error.path.to.compiler.missing", compilerPath),
+ CompilerBundle.message("compiler.jikes.name"),
+ Messages.getErrorIcon()
+ );
+ openConfigurationDialog();
+ compilerPath = getCompilerPath(); // update path
+ if (StringUtil.isEmpty(compilerPath)) {
+ return false;
+ }
+ if (!new File(compilerPath).exists()) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private void openConfigurationDialog() {
+ final CompilerConfigurationImpl configuration = (CompilerConfigurationImpl)CompilerConfiguration.getInstance(myProject);
+ final Collection<BackendCompiler> compilers = configuration.getRegisteredJavaCompilers();
+ final BackendCompiler defaultCompiler = configuration.getDefaultCompiler();
+ final JavaCompilersTab compilersTab = new JavaCompilersTab(myProject, compilers, defaultCompiler);
+
+ ShowSettingsUtil.getInstance().editConfigurable(myProject, compilersTab);
+ }
+
+ private String getCompilerPath() {
+ return JikesConfiguration.getOptions(myProject).JIKES_PATH.replace('/', File.separatorChar);
+ }
+
+ @NotNull
+ @NonNls
+ public String getId() // used for externalization
+ {
+ return "Jikes";
+ }
+
+ @NotNull
+ public String getPresentableName() {
+ return CompilerBundle.message("compiler.jikes.name");
+ }
+
+ @NotNull
+ public Configurable createConfigurable() {
+ return new JikesConfigurable(JikesConfiguration.getOptions(myProject));
+ }
+
+ public OutputParser createErrorParser(@NotNull final String outputDir, Process process) {
+ return new JikesOutputParser(myProject);
+ }
+
+ @Nullable
+ public OutputParser createOutputParser(@NotNull final String outputDir) {
+ return null;
+ }
+
+ @NotNull
+ public String[] createStartupCommand(final ModuleChunk chunk, final CompileContext context, final String outputPath)
+ throws IOException {
+
+ final ArrayList<String> commandLine = new ArrayList<String>();
+ final IOException[] ex = {null};
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ try {
+ _createStartupCommand(chunk, commandLine, outputPath);
+ }
+ catch (IOException e) {
+ ex[0] = e;
+ }
+ }
+ });
+ if (ex[0] != null) {
+ throw ex[0];
+ }
+ return ArrayUtil.toStringArray(commandLine);
+ }
+
+ private void _createStartupCommand(final ModuleChunk chunk, final ArrayList<String> commandLine, @NotNull final String outputPath) throws IOException {
+
+ myTempFile = FileUtil.createTempFile("jikes", ".tmp");
+ myTempFile.deleteOnExit();
+
+ final List<VirtualFile> files = chunk.getFilesToCompile();
+ PrintWriter writer = new PrintWriter(new FileWriter(myTempFile));
+ try {
+ for (VirtualFile file : files) {
+ writer.println(file.getPath());
+ }
+ }
+ finally {
+ writer.close();
+ }
+
+ String compilerPath = getCompilerPath();
+ LOG.assertTrue(compilerPath != null, "No path to compiler configured");
+
+ commandLine.add(compilerPath);
+
+ //noinspection HardCodedStringLiteral
+ commandLine.add("-verbose");
+ //noinspection HardCodedStringLiteral
+ commandLine.add("-classpath");
+
+ // must include output path to classpath, otherwise javac will compile all dependent files no matter were they compiled before or not
+ commandLine.add(chunk.getCompilationBootClasspath() + File.pathSeparator + chunk.getCompilationClasspath());
+
+ setupSourceVersion(chunk, commandLine);
+ //noinspection HardCodedStringLiteral
+ commandLine.add("-sourcepath");
+ String sourcePath = chunk.getSourcePath();
+ if (sourcePath.length() > 0) {
+ commandLine.add(sourcePath);
+ }
+ else {
+ commandLine.add("\"\"");
+ }
+
+ //noinspection HardCodedStringLiteral
+ commandLine.add("-d");
+ commandLine.add(outputPath.replace('/', File.separatorChar));
+
+ JikesSettingsBuilder jikesSettings = new JikesSettingsBuilder(JikesConfiguration.getOptions(myProject));
+ StringTokenizer tokenizer = new StringTokenizer(jikesSettings.getOptionsString(chunk), " ");
+ while (tokenizer.hasMoreTokens()) {
+ commandLine.add(tokenizer.nextToken());
+ }
+ commandLine.add("@" + myTempFile.getAbsolutePath());
+ }
+
+ @SuppressWarnings({"HardCodedStringLiteral"})
+ private static void setupSourceVersion(final ModuleChunk chunk, final ArrayList<String> commandLine) {
+ final Sdk jdk = chunk.getJdk();
+ final String versionString = jdk.getVersionString();
+
+ final LanguageLevel applicableLanguageLevel = CompilerUtil.getApplicableLanguageLevel(versionString, chunk.getLanguageLevel());
+ if (applicableLanguageLevel.equals(LanguageLevel.JDK_1_5)) {
+ commandLine.add("-source");
+ commandLine.add("1.4"); // -source 1.5 not supported yet by jikes, so use the highest possible version
+ }
+ else if (applicableLanguageLevel.equals(LanguageLevel.JDK_1_4)) {
+ commandLine.add("-source");
+ commandLine.add("1.4");
+ }
+ }
+
+
+ public void compileFinished() {
+ if (myTempFile != null) {
+ FileUtil.delete(myTempFile);
+ myTempFile = null;
+ }
+ }
+
+}
+
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/JikesConfigurable.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/JikesConfigurable.java
new file mode 100644
index 0000000..1731625
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/JikesConfigurable.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl.javaCompiler.jikes;
+
+import com.intellij.compiler.options.ComparingUtils;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.fileChooser.FileChooser;
+import com.intellij.openapi.fileChooser.FileChooserDescriptor;
+import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
+import com.intellij.openapi.options.Configurable;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.ui.TextFieldWithBrowseButton;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.RawCommandLineEditor;
+import org.jetbrains.jps.model.java.compiler.JikesCompilerOptions;
+
+import javax.swing.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.File;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 30, 2004
+ */
+public class JikesConfigurable implements Configurable {
+ private JPanel myPanel;
+ private JCheckBox myCbDebuggingInfo;
+ private JCheckBox myCbDeprecation;
+ private JCheckBox myCbGenerateNoWarnings;
+ private RawCommandLineEditor myAdditionalOptionsField;
+ private TextFieldWithBrowseButton myPathField;
+ private final JikesCompilerOptions myJikesSettings;
+
+ public JikesConfigurable(JikesCompilerOptions jikesSettings) {
+ myJikesSettings = jikesSettings;
+ myPathField.getButton().addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor();
+ VirtualFile file = FileChooser.chooseFile(descriptor, myPathField, null, null);
+ if (file != null) {
+ myPathField.setText(file.getPath().replace('/', File.separatorChar));
+ }
+ }
+ });
+ myAdditionalOptionsField.setDialogCaption(CompilerBundle.message("java.compiler.option.additional.command.line.parameters"));
+ }
+
+ public String getDisplayName() {
+ return null;
+ }
+
+ public String getHelpTopic() {
+ return null;
+ }
+
+ public JComponent createComponent() {
+ return myPanel;
+ }
+
+ public boolean isModified() {
+ boolean isModified = false;
+ isModified |= ComparingUtils.isModified(myPathField, myJikesSettings.JIKES_PATH.replace('/', File.separatorChar));
+ isModified |= ComparingUtils.isModified(myCbDeprecation, myJikesSettings.DEPRECATION);
+ isModified |= ComparingUtils.isModified(myCbDebuggingInfo, myJikesSettings.DEBUGGING_INFO);
+ isModified |= ComparingUtils.isModified(myCbGenerateNoWarnings, myJikesSettings.GENERATE_NO_WARNINGS);
+ isModified |= ComparingUtils.isModified(myAdditionalOptionsField, myJikesSettings.ADDITIONAL_OPTIONS_STRING);
+ return isModified;
+ }
+
+ public void apply() throws ConfigurationException {
+ myJikesSettings.JIKES_PATH = myPathField.getText().trim().replace(File.separatorChar, '/');
+ myJikesSettings.DEPRECATION = myCbDeprecation.isSelected();
+ myJikesSettings.DEBUGGING_INFO = myCbDebuggingInfo.isSelected();
+ myJikesSettings.GENERATE_NO_WARNINGS = myCbGenerateNoWarnings.isSelected();
+ myJikesSettings.ADDITIONAL_OPTIONS_STRING = myAdditionalOptionsField.getText();
+ }
+
+ public void reset() {
+ myPathField.setText(myJikesSettings.JIKES_PATH.replace('/', File.separatorChar));
+ myCbDeprecation.setSelected(myJikesSettings.DEPRECATION);
+ myCbDebuggingInfo.setSelected(myJikesSettings.DEBUGGING_INFO);
+ myCbGenerateNoWarnings.setSelected(myJikesSettings.GENERATE_NO_WARNINGS);
+ myAdditionalOptionsField.setText(myJikesSettings.ADDITIONAL_OPTIONS_STRING);
+ }
+
+ public void disposeUIResources() {
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/JikesConfiguration.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/JikesConfiguration.java
new file mode 100755
index 0000000..f82e26f
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/JikesConfiguration.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl.javaCompiler.jikes;
+
+import com.intellij.openapi.components.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.util.xmlb.XmlSerializerUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.jps.model.java.compiler.JikesCompilerOptions;
+
+@State(
+ name = "JikesSettings",
+ storages = {
+ @Storage( file = StoragePathMacros.PROJECT_FILE)
+ ,@Storage( file = StoragePathMacros.PROJECT_CONFIG_DIR + "/compiler.xml", scheme = StorageScheme.DIRECTORY_BASED)
+ }
+)
+public class JikesConfiguration implements PersistentStateComponent<JikesCompilerOptions> {
+ private final JikesCompilerOptions mySettings = new JikesCompilerOptions();
+
+ @NotNull
+ public JikesCompilerOptions getState() {
+ return mySettings;
+ }
+
+ public void loadState(JikesCompilerOptions state) {
+ XmlSerializerUtil.copyBean(state, mySettings);
+ }
+
+ public static JikesCompilerOptions getOptions(Project project) {
+ return ServiceManager.getService(project, JikesConfiguration.class).getState();
+ }
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/JikesOptionsPanel.form b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/JikesOptionsPanel.form
new file mode 100644
index 0000000..23055ee
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/JikesOptionsPanel.form
@@ -0,0 +1,101 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.compiler.impl.javaCompiler.jikes.JikesConfigurable">
+ <grid id="66887" binding="myPanel" layout-manager="GridLayoutManager" row-count="6" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <xy x="103" y="66" width="439" height="254"/>
+ </constraints>
+ <properties/>
+ <clientProperties>
+ <BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithIndent"/>
+ </clientProperties>
+ <border type="etched" title-resource-bundle="messages/CompilerBundle" title-key="jikes.options.group.title"/>
+ <children>
+ <component id="3d38f" class="javax.swing.JCheckBox" binding="myCbDebuggingInfo">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <margin top="5" left="2" bottom="2" right="2"/>
+ <selected value="true"/>
+ <text resource-bundle="messages/CompilerBundle" key="java.compiler.option.generate.debugging.info"/>
+ </properties>
+ </component>
+ <component id="90803" class="javax.swing.JCheckBox" binding="myCbGenerateNoWarnings">
+ <constraints>
+ <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <margin top="2" left="2" bottom="2" right="3"/>
+ <selected value="true"/>
+ <text resource-bundle="messages/CompilerBundle" key="java.compiler.option.generate.no.warnings"/>
+ </properties>
+ </component>
+ <component id="8538e" class="javax.swing.JCheckBox" binding="myCbDeprecation">
+ <constraints>
+ <grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <margin top="2" left="2" bottom="5" right="2"/>
+ <selected value="true"/>
+ <text resource-bundle="messages/CompilerBundle" key="java.compiler.option.report.deprecated"/>
+ </properties>
+ </component>
+ <vspacer id="b6f66">
+ <constraints>
+ <grid row="5" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
+ </constraints>
+ </vspacer>
+ <grid id="31c1" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="415eb" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myPathField">
+ <constraints>
+ <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="327dc" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="jikes.option.path.to.executable"/>
+ </properties>
+ </component>
+ </children>
+ </grid>
+ <grid id="918d2" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="4" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="f8a80" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="java.compiler.option.additional.command.line.parameters"/>
+ </properties>
+ </component>
+ <component id="479c2" class="com.intellij.ui.RawCommandLineEditor" binding="myAdditionalOptionsField">
+ <constraints>
+ <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
+ <preferred-size width="150" height="-1"/>
+ </grid>
+ </constraints>
+ <properties/>
+ </component>
+ </children>
+ </grid>
+ </children>
+ </grid>
+</form>
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/JikesOutputParser.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/JikesOutputParser.java
new file mode 100644
index 0000000..1d5bb00
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/JikesOutputParser.java
@@ -0,0 +1,133 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler.jikes;
+
+import com.intellij.compiler.OutputParser;
+import com.intellij.openapi.compiler.CompilerMessageCategory;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.jps.model.java.compiler.JikesCompilerOptions;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+
+public class JikesOutputParser extends OutputParser {
+ private final JikesCompilerOptions myJikesSettings;
+ @NonNls private static final String JAVA_FILE_MSG_TAIL = ".java:";
+ @NonNls private static final String CAUTION = "Caution";
+ @NonNls private static final String WARNING = "Warning";
+ @NonNls private static final String ERROR = "Error";
+ @NonNls private static final String SEMANTIC_WARNING = "Semantic Warning";
+ @NonNls private static final String SEMANTIC_ERROR = "Semantic Error";
+ @NonNls private static final String ENTER_TO_CONTINUE_REGEXP = ".*Enter\\s+to\\s+continue.*";
+
+ public JikesOutputParser(Project project) {
+ myJikesSettings = JikesConfiguration.getOptions(project);
+ myParserActions.add(new ParserActionJikes());
+ }
+
+ public boolean processMessageLine(Callback callback) {
+ if (super.processMessageLine(callback)) {
+ return true;
+ }
+ String line = callback.getCurrentLine();
+ if (line == null) {
+ return false;
+ }
+ if (line.length() == 0) {
+ return false;
+ }
+//sae
+ if (myJikesSettings.IS_EMACS_ERRORS_MODE) {
+
+ String filePath = "";
+ final int tailIndex = line.indexOf(JAVA_FILE_MSG_TAIL);
+ if (tailIndex > 5) filePath = line.substring(0, tailIndex + 5);
+ filePath = filePath.replace(File.separatorChar, '/');
+ final String url = VirtualFileManager.constructUrl(LocalFileSystem.PROTOCOL, filePath);
+ if (tailIndex > 6) {
+ line = line.substring(tailIndex + 6);
+ int lineNum;
+
+//second token = start line
+ StringTokenizer tokenizer = new StringTokenizer(line, ":");
+//first token = filename
+ String token = tokenizer.nextToken();
+
+ try {
+ lineNum = Integer.parseInt(token);
+ }
+ catch (Exception e) {
+ addMessage(callback, CompilerMessageCategory.INFORMATION, line);
+ return true;
+ }
+//thrd token = start column
+ token = tokenizer.nextToken();
+ int colNum;
+ try {
+ colNum = Integer.parseInt(token);
+ }
+ catch (Exception e) {
+ addMessage(callback, CompilerMessageCategory.INFORMATION, line);
+ return true;
+ }
+//4,5 token = end line/column tmp not used
+ tokenizer.nextToken();
+ tokenizer.nextToken();
+// 6 error type
+ CompilerMessageCategory category = CompilerMessageCategory.INFORMATION;
+ token = tokenizer.nextToken().trim();
+ if (CAUTION.equalsIgnoreCase(token)) {
+ category = CompilerMessageCategory.WARNING;
+ }
+ else if (WARNING.equalsIgnoreCase(token) || SEMANTIC_WARNING.equalsIgnoreCase(token)) { // Semantic errors/warnings were introduced in jikes 1.18
+ category = CompilerMessageCategory.WARNING;
+ }
+ else if (ERROR.equalsIgnoreCase(token) || SEMANTIC_ERROR.equalsIgnoreCase(token)) {
+ category = CompilerMessageCategory.ERROR;
+ }
+
+ String message = token;
+ message = message.concat(" ");
+ message = message.concat(tokenizer.nextToken(""));
+ ArrayList<String> messages = new ArrayList<String>();
+ messages.add(message);
+
+ if (colNum > 0 && messages.size() > 0) {
+ StringBuilder buf = new StringBuilder();
+ for (String m : messages) {
+ if (buf.length() > 0) {
+ buf.append("\n");
+ }
+ buf.append(m);
+ }
+ addMessage(callback, category, buf.toString(), url, lineNum, colNum);
+ return true;
+ }
+ }
+ }
+//--sae
+
+//Enter to continue
+ if (!line.matches(ENTER_TO_CONTINUE_REGEXP)) {
+ addMessage(callback, CompilerMessageCategory.INFORMATION, line);
+ }
+ return true;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/JikesSettingsBuilder.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/JikesSettingsBuilder.java
new file mode 100644
index 0000000..636db21
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/JikesSettingsBuilder.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl.javaCompiler.jikes;
+
+import com.intellij.compiler.impl.javaCompiler.javac.JavacSettingsBuilder;
+import com.intellij.openapi.module.Module;
+import com.intellij.util.Chunk;
+import org.jetbrains.jps.model.java.compiler.JikesCompilerOptions;
+
+import java.util.Collection;
+
+public class JikesSettingsBuilder extends JavacSettingsBuilder {
+
+ public JikesSettingsBuilder(JikesCompilerOptions options) {
+ super(options);
+ }
+
+ @Override
+ public JikesCompilerOptions getOptions() {
+ return (JikesCompilerOptions)super.getOptions();
+ }
+
+ public Collection<String> getOptions(Chunk<Module> chunk) {
+ final Collection<String> options = super.getOptions(chunk);
+ if(getOptions().IS_EMACS_ERRORS_MODE) {
+ options.add("+E");
+ }
+ return options;
+ }
+
+ protected boolean acceptUserOption(String token) {
+ if (!super.acceptUserOption(token)) {
+ return false;
+ }
+ return !("++".equals(token) || "+M".equals(token) || "+F".equals(token) || "+E".equals(token));
+ }
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/ParserActionJikes.java b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/ParserActionJikes.java
new file mode 100644
index 0000000..ba1884f
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/javaCompiler/jikes/ParserActionJikes.java
@@ -0,0 +1,88 @@
+/*
+ * 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 com.intellij.compiler.impl.javaCompiler.jikes;
+
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.fileTypes.FileType;
+import com.intellij.openapi.fileTypes.FileTypeManager;
+import com.intellij.openapi.fileTypes.StdFileTypes;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.compiler.ParserAction;
+import com.intellij.compiler.OutputParser;
+import com.intellij.compiler.impl.javaCompiler.FileObject;
+import org.jetbrains.annotations.NonNls;
+
+import java.io.File;
+
+/**
+ * This class provides compatibility with older javac(english locale) and jikes versions and
+ * implements parsing method from previous IDEA versions for jikes and javac compiler output.
+ * Used in order not to miss some compiler output messages when jikes is configured as a preferred compiler
+ * Also used in test mode in which javac is not run via JavacRunner
+ *
+ * @author Eugene Zhuravlev
+ * Date: Sep 24, 2005
+ */
+public class ParserActionJikes extends ParserAction {
+ @NonNls private static final String JAVA_EXTENSION = ".java";
+
+ public boolean execute(@NonNls String line, final OutputParser.Callback callback) {
+ if (!StringUtil.startsWithChar(line, '[') || !StringUtil.endsWithChar(line, ']')) {
+ return false;
+ }
+ if (line.startsWith("[parsing started")){ // javac
+ String filePath = line.substring("[parsing started".length(), line.length() - 1).trim();
+ processParsingMessage(callback, filePath.replace(File.separatorChar, '/'));
+ }
+ else if (line.startsWith("[parsed") && line.contains(JAVA_EXTENSION)) { // javac version 1.2.2
+ int index = line.indexOf(JAVA_EXTENSION);
+ String filePath = line.substring("[parsed".length(), index + JAVA_EXTENSION.length()).trim();
+ processParsingMessage(callback, filePath.replace(File.separatorChar, '/'));
+ }
+ else if (line.startsWith("[read") && line.endsWith(".java]")){ // jikes
+ String filePath = line.substring("[read".length(), line.length() - 1).trim();
+ processParsingMessage(callback, filePath.replace(File.separatorChar, '/'));
+ }
+ else if (line.startsWith("[parsing completed")){
+ }
+ else if (line.startsWith("[loading") || line.startsWith("[loaded") || line.startsWith("[read")){
+ callback.setProgressText(CompilerBundle.message("progress.loading.classes"));
+ }
+ else if (line.startsWith("[checking")){
+ String className = line.substring("[checking".length(), line.length() - 1).trim();
+ callback.setProgressText(CompilerBundle.message("progress.compiling.class", className));
+ }
+ else if (line.startsWith("[wrote") || line.startsWith("[write")){
+ String filePath = line.substring("[wrote".length(), line.length() - 1).trim();
+ processParsingMessage(callback, filePath.replace(File.separatorChar, '/'));
+ }
+ return true;
+ }
+
+ private static void processParsingMessage(final OutputParser.Callback callback, @NonNls final String filePath) {
+ int index = filePath.lastIndexOf('/');
+ final String name = index >= 0 ? filePath.substring(index + 1) : filePath;
+
+ final FileType fileType = FileTypeManager.getInstance().getFileTypeByFileName(name);
+ if (StdFileTypes.JAVA.equals(fileType)) {
+ callback.fileProcessed(filePath);
+ callback.setProgressText(CompilerBundle.message("progress.parsing.file", name));
+ }
+ else if (StdFileTypes.CLASS.equals(fileType)) {
+ callback.fileGenerated(new FileObject(new File(filePath)));
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/BuildInstructionBase.java b/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/BuildInstructionBase.java
new file mode 100644
index 0000000..446dd2e
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/BuildInstructionBase.java
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+/**
+ * @author cdr
+ */
+package com.intellij.compiler.impl.packagingCompiler;
+
+import com.intellij.openapi.compiler.make.BuildInstruction;
+import com.intellij.openapi.util.UserDataHolderBase;
+import com.intellij.openapi.util.io.FileUtil;
+import gnu.trove.THashSet;
+import org.jetbrains.annotations.NonNls;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+
+public abstract class BuildInstructionBase extends UserDataHolderBase implements BuildInstruction, Cloneable {
+ private final String myOutputRelativePath;
+ private Collection<File> myFilesToDelete;
+
+ protected BuildInstructionBase(String outputRelativePath) {
+ myOutputRelativePath = outputRelativePath;
+ }
+
+ public String getOutputRelativePath() {
+ return myOutputRelativePath;
+ }
+
+ public BuildInstructionBase clone() {
+ return (BuildInstructionBase)super.clone();
+ }
+
+ public void addFileToDelete(File file) {
+ if (myFilesToDelete == null) {
+ myFilesToDelete = new THashSet<File>();
+ }
+ myFilesToDelete.add(file);
+ }
+
+ public void collectFilesToDelete(Collection<File> filesToDelete) {
+ if (myFilesToDelete != null) {
+ filesToDelete.addAll(myFilesToDelete);
+ }
+ myFilesToDelete = null;
+ }
+
+ @NonNls
+ public String toString() {
+ return super.toString();
+ }
+
+ protected File createTempFile(final String prefix, final String suffix) throws IOException {
+ final File tempFile = FileUtil.createTempFile(prefix + "___", suffix);
+ addFileToDelete(tempFile);
+ return tempFile;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/BuildRecipeImpl.java b/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/BuildRecipeImpl.java
new file mode 100644
index 0000000..03e62a0
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/BuildRecipeImpl.java
@@ -0,0 +1,80 @@
+/*
+ * 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.
+ */
+
+/**
+ * @author cdr
+ */
+package com.intellij.compiler.impl.packagingCompiler;
+
+import com.intellij.openapi.compiler.make.BuildInstruction;
+import com.intellij.openapi.compiler.make.BuildInstructionVisitor;
+import com.intellij.openapi.compiler.make.BuildRecipe;
+import com.intellij.openapi.deployment.DeploymentUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+@Deprecated
+public class BuildRecipeImpl implements BuildRecipe {
+ private final List<BuildInstruction> myInstructions = new ArrayList<BuildInstruction>();
+
+ public void addInstruction(BuildInstruction instruction) {
+ if (!contains(instruction)) {
+ myInstructions.add(instruction);
+ }
+ }
+
+ public boolean contains(final BuildInstruction instruction) {
+ return myInstructions.contains(instruction);
+ }
+
+ public boolean visitInstructions(BuildInstructionVisitor visitor, boolean reverseOrder){
+ try {
+ return visitInstructionsWithExceptions(visitor, reverseOrder);
+ }
+ catch (Exception e) {
+ if (e instanceof RuntimeException) {
+ throw (RuntimeException)e;
+ }
+ return false;
+ }
+ }
+ public boolean visitInstructionsWithExceptions(BuildInstructionVisitor visitor, boolean reverseOrder) throws Exception {
+ for (int i = reverseOrder ? myInstructions.size()-1 : 0;
+ reverseOrder ? i>=0 : i < myInstructions.size();
+ i += reverseOrder ? -1 : 1) {
+ BuildInstruction instruction = myInstructions.get(i);
+ if (!instruction.accept(visitor)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void addFileCopyInstruction(@NotNull File file, boolean isDirectory, String outputRelativePath) {
+ addInstruction(new FileCopyInstructionImpl(file, isDirectory, DeploymentUtil.trimForwardSlashes(outputRelativePath)));
+ }
+
+ public String toString() {
+ String s = "Build recipe:";
+ for (BuildInstruction buildInstruction : myInstructions) {
+ s += "\n" + buildInstruction + "; ";
+ }
+ return s;
+ }
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/DependentJarsEvaluator.java b/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/DependentJarsEvaluator.java
new file mode 100644
index 0000000..9fa4e2c
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/DependentJarsEvaluator.java
@@ -0,0 +1,44 @@
+/*
+ * 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 com.intellij.compiler.impl.packagingCompiler;
+
+import com.intellij.openapi.util.Pair;
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * @author nik
+ */
+public class DependentJarsEvaluator {
+ private final Set<JarInfo> myJars = new LinkedHashSet<JarInfo>();
+
+ public void addJarWithDependencies(final JarInfo jarInfo) {
+ if (myJars.add(jarInfo)) {
+ for (JarDestinationInfo destination : jarInfo.getJarDestinations()) {
+ addJarWithDependencies(destination.getJarInfo());
+ }
+ for (Pair<String, JarInfo> pair : jarInfo.getPackedJars()) {
+ addJarWithDependencies(pair.getSecond());
+ }
+ }
+ }
+
+ public Set<JarInfo> getJars() {
+ return myJars;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/DestinationInfo.java b/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/DestinationInfo.java
new file mode 100644
index 0000000..abda982
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/DestinationInfo.java
@@ -0,0 +1,60 @@
+/*
+ * 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 com.intellij.compiler.impl.packagingCompiler;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author nik
+ */
+public abstract class DestinationInfo {
+ private VirtualFile myOutputFile;
+ private final String myOutputPath;
+ private final String myOutputFilePath;
+
+ protected DestinationInfo(@NotNull final String outputPath, @Nullable final VirtualFile outputFile, @NotNull String outputFilePath) {
+ myOutputFilePath = outputFilePath;
+ myOutputFile = outputFile;
+ myOutputPath = outputPath;
+ }
+
+ @NotNull
+ public String getOutputPath() {
+ return myOutputPath;
+ }
+
+ @Nullable
+ public VirtualFile getOutputFile() {
+ return myOutputFile;
+ }
+
+ @NotNull
+ public String getOutputFilePath() {
+ return myOutputFilePath;
+ }
+
+ public void update() {
+ if (myOutputFile != null && !myOutputFile.isValid()) {
+ myOutputFile = null;
+ }
+ if (myOutputFile == null) {
+ myOutputFile = LocalFileSystem.getInstance().findFileByPath(myOutputFilePath);
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/ExplodedDestinationInfo.java b/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/ExplodedDestinationInfo.java
new file mode 100644
index 0000000..33a3a8e
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/ExplodedDestinationInfo.java
@@ -0,0 +1,33 @@
+/*
+ * 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 com.intellij.compiler.impl.packagingCompiler;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author nik
+ */
+public class ExplodedDestinationInfo extends DestinationInfo {
+ public ExplodedDestinationInfo(final String outputPath, @Nullable final VirtualFile outputFile) {
+ super(outputPath, outputFile, outputPath);
+ }
+
+ public String toString() {
+ return getOutputPath();
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/FileCopyInstructionImpl.java b/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/FileCopyInstructionImpl.java
new file mode 100644
index 0000000..64b5855
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/FileCopyInstructionImpl.java
@@ -0,0 +1,76 @@
+/*
+ * 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 com.intellij.compiler.impl.packagingCompiler;
+
+import com.intellij.openapi.compiler.make.BuildInstructionVisitor;
+import com.intellij.openapi.compiler.make.FileCopyInstruction;
+import com.intellij.openapi.util.io.FileUtil;
+
+import java.io.File;
+
+public class FileCopyInstructionImpl extends BuildInstructionBase implements FileCopyInstruction {
+ private File myFile;
+ private boolean myIsDirectory;
+
+ public FileCopyInstructionImpl(File source, boolean isDirectory, String outputRelativePath) {
+ super(outputRelativePath);
+ setFile(source, isDirectory);
+ }
+
+ public boolean accept(BuildInstructionVisitor visitor) throws Exception {
+ return visitor.visitFileCopyInstruction(this);
+ }
+
+ public String toString() {
+ return "Copy " + getFile() + "->" + getOutputRelativePath();
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof FileCopyInstruction)) return false;
+
+ final FileCopyInstruction item = (FileCopyInstruction) o;
+
+ if (getFile() != null ? !FileUtil.filesEqual(getFile(), item.getFile()) : item.getFile() != null) return false;
+
+ if (getOutputRelativePath() != null) {
+ if (!getOutputRelativePath().equals( item.getOutputRelativePath() )) return false;
+ } else if ( item.getOutputRelativePath() != null ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public int hashCode() {
+ return (getFile() != null ? getFile().hashCode() : 0) +
+ (getOutputRelativePath() != null ? getOutputRelativePath().hashCode():0);
+ }
+
+ public File getFile() {
+ return myFile;
+ }
+
+ public boolean isDirectory() {
+ return myIsDirectory;
+ }
+
+ private void setFile(File file, boolean isDirectory) {
+ myFile = file;
+ myIsDirectory = isDirectory;
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/IgnoredFileFilter.java b/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/IgnoredFileFilter.java
new file mode 100644
index 0000000..5ad3f5a
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/IgnoredFileFilter.java
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+/**
+ * @author cdr
+ */
+package com.intellij.compiler.impl.packagingCompiler;
+
+import com.intellij.openapi.fileTypes.FileTypeManager;
+import com.intellij.openapi.fileTypes.StdFileTypes;
+
+import java.io.File;
+import java.io.FileFilter;
+
+public class IgnoredFileFilter implements FileFilter {
+ public boolean accept(File file) {
+ final FileTypeManager fileTypeManager = FileTypeManager.getInstance();
+ final String name = file.getName();
+ return !fileTypeManager.isFileIgnored(name)
+ && fileTypeManager.getFileTypeByFileName(name) != StdFileTypes.IDEA_PROJECT
+ && fileTypeManager.getFileTypeByFileName(name) != StdFileTypes.IDEA_MODULE
+ && fileTypeManager.getFileTypeByFileName(name) != StdFileTypes.IDEA_WORKSPACE
+ ;
+ }
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/JarDestinationInfo.java b/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/JarDestinationInfo.java
new file mode 100644
index 0000000..5cc45c1
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/JarDestinationInfo.java
@@ -0,0 +1,55 @@
+/*
+ * 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 com.intellij.compiler.impl.packagingCompiler;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.JarFileSystem;
+
+/**
+ * @author nik
+ */
+public class JarDestinationInfo extends DestinationInfo {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.packagingCompiler.JarDestinationInfo");
+ private final String myPathInJar;
+ private final JarInfo myJarInfo;
+
+ public JarDestinationInfo(final String pathInJar, final JarInfo jarInfo, DestinationInfo jarDestination) {
+ super(appendPathInJar(jarDestination.getOutputPath(), pathInJar), jarDestination.getOutputFile(), jarDestination.getOutputFilePath());
+ LOG.assertTrue(!pathInJar.startsWith(".."), pathInJar);
+ myPathInJar = StringUtil.startsWithChar(pathInJar, '/') ? pathInJar : "/" + pathInJar;
+ myJarInfo = jarInfo;
+ }
+
+ private static String appendPathInJar(String outputPath, String pathInJar) {
+ LOG.assertTrue(outputPath.length() > 0 && outputPath.charAt(outputPath.length() - 1) != '/');
+ LOG.assertTrue(pathInJar.length() > 0 && pathInJar.charAt(0) != '/');
+ return outputPath + JarFileSystem.JAR_SEPARATOR + pathInJar;
+ }
+
+ public String getPathInJar() {
+ return myPathInJar;
+ }
+
+ public JarInfo getJarInfo() {
+ return myJarInfo;
+ }
+
+ public String toString() {
+ return myPathInJar + "(" + getOutputPath() + ")";
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/JarInfo.java b/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/JarInfo.java
new file mode 100644
index 0000000..7626ca1
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/JarInfo.java
@@ -0,0 +1,77 @@
+/*
+ * 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 com.intellij.compiler.impl.packagingCompiler;
+
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.vfs.VirtualFile;
+
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public class JarInfo {
+ private final List<Pair<String, VirtualFile>> myPackedFiles;
+ private final LinkedHashSet<Pair<String, JarInfo>> myPackedJars;
+ private final List<DestinationInfo> myDestinations;
+
+ public JarInfo() {
+ myDestinations = new ArrayList<DestinationInfo>();
+ myPackedFiles = new ArrayList<Pair<String, VirtualFile>>();
+ myPackedJars = new LinkedHashSet<Pair<String, JarInfo>>();
+ }
+
+ public void addDestination(DestinationInfo info) {
+ myDestinations.add(info);
+ if (info instanceof JarDestinationInfo) {
+ JarDestinationInfo destinationInfo = (JarDestinationInfo)info;
+ destinationInfo.getJarInfo().myPackedJars.add(Pair.create(destinationInfo.getPathInJar(), this));
+ }
+ }
+
+ public void addContent(String pathInJar, VirtualFile sourceFile) {
+ myPackedFiles.add(Pair.create(pathInJar, sourceFile));
+ }
+
+ public List<Pair<String, VirtualFile>> getPackedFiles() {
+ return myPackedFiles;
+ }
+
+ public LinkedHashSet<Pair<String, JarInfo>> getPackedJars() {
+ return myPackedJars;
+ }
+
+ public List<JarDestinationInfo> getJarDestinations() {
+ final ArrayList<JarDestinationInfo> list = new ArrayList<JarDestinationInfo>();
+ for (DestinationInfo destination : myDestinations) {
+ if (destination instanceof JarDestinationInfo) {
+ list.add((JarDestinationInfo)destination);
+ }
+ }
+ return list;
+ }
+
+ public List<DestinationInfo> getAllDestinations() {
+ return myDestinations;
+ }
+
+ public String getPresentableDestination() {
+ return !myDestinations.isEmpty() ? myDestinations.get(0).getOutputPath() : "";
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/JarsBuilder.java b/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/JarsBuilder.java
new file mode 100644
index 0000000..a546480
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/packagingCompiler/JarsBuilder.java
@@ -0,0 +1,282 @@
+/*
+ * 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 com.intellij.compiler.impl.packagingCompiler;
+
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.compiler.CompilerMessageCategory;
+import com.intellij.openapi.deployment.DeploymentUtil;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.impl.compiler.ArtifactCompilerUtil;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.graph.CachingSemiGraph;
+import com.intellij.util.graph.DFSTBuilder;
+import com.intellij.util.graph.GraphGenerator;
+import com.intellij.util.io.ZipUtil;
+import gnu.trove.THashSet;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.*;
+import java.util.*;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+/**
+ * @author nik
+ */
+public class JarsBuilder {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.packagingCompiler.JarsBuilder");
+ private final Set<JarInfo> myJarsToBuild;
+ private final FileFilter myFileFilter;
+ private final CompileContext myContext;
+ private Map<JarInfo, File> myBuiltJars;
+
+ public JarsBuilder(Set<JarInfo> jarsToBuild, FileFilter fileFilter, CompileContext context) {
+ DependentJarsEvaluator evaluator = new DependentJarsEvaluator();
+ for (JarInfo jarInfo : jarsToBuild) {
+ evaluator.addJarWithDependencies(jarInfo);
+ }
+ myJarsToBuild = evaluator.getJars();
+ myFileFilter = fileFilter;
+ myContext = context;
+ }
+
+ public boolean buildJars(Set<String> writtenPaths) throws IOException {
+ myContext.getProgressIndicator().setText(CompilerBundle.message("packaging.compiler.message.building.archives"));
+
+ final JarInfo[] sortedJars = sortJars();
+ if (sortedJars == null) {
+ return false;
+ }
+
+ myBuiltJars = new HashMap<JarInfo, File>();
+ try {
+ for (JarInfo jar : sortedJars) {
+ myContext.getProgressIndicator().checkCanceled();
+ buildJar(jar);
+ }
+
+ myContext.getProgressIndicator().setText(CompilerBundle.message("packaging.compiler.message.copying.archives"));
+ copyJars(writtenPaths);
+ }
+ finally {
+ deleteTemporaryJars();
+ }
+
+
+ return true;
+ }
+
+ private void deleteTemporaryJars() {
+ for (File file : myBuiltJars.values()) {
+ FileUtil.delete(file);
+ }
+ }
+
+ private void copyJars(final Set<String> writtenPaths) throws IOException {
+ for (Map.Entry<JarInfo, File> entry : myBuiltJars.entrySet()) {
+ File fromFile = entry.getValue();
+ boolean first = true;
+ for (DestinationInfo destination : entry.getKey().getAllDestinations()) {
+ if (destination instanceof ExplodedDestinationInfo) {
+ File toFile = new File(FileUtil.toSystemDependentName(destination.getOutputPath()));
+
+ if (first) {
+ first = false;
+ renameFile(fromFile, toFile, writtenPaths);
+ fromFile = toFile;
+ }
+ else {
+ DeploymentUtil.getInstance().copyFile(fromFile, toFile, myContext, writtenPaths, myFileFilter);
+ }
+
+ }
+ }
+ }
+ }
+
+ private static void renameFile(final File fromFile, final File toFile, final Set<String> writtenPaths) throws IOException {
+ FileUtil.rename(fromFile, toFile);
+ writtenPaths.add(toFile.getPath());
+ }
+
+ @Nullable
+ private JarInfo[] sortJars() {
+ final DFSTBuilder<JarInfo> builder = new DFSTBuilder<JarInfo>(GraphGenerator.create(CachingSemiGraph.create(new JarsGraph())));
+ if (!builder.isAcyclic()) {
+ final Pair<JarInfo, JarInfo> dependency = builder.getCircularDependency();
+ String message = CompilerBundle.message("packaging.compiler.error.cannot.build.circular.dependency.found.between.0.and.1",
+ dependency.getFirst().getPresentableDestination(),
+ dependency.getSecond().getPresentableDestination());
+ myContext.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1);
+ return null;
+ }
+
+ JarInfo[] jars = myJarsToBuild.toArray(new JarInfo[myJarsToBuild.size()]);
+ Arrays.sort(jars, builder.comparator());
+ jars = ArrayUtil.reverseArray(jars);
+ return jars;
+ }
+
+ public Set<JarInfo> getJarsToBuild() {
+ return myJarsToBuild;
+ }
+
+ private void buildJar(final JarInfo jar) throws IOException {
+ if (jar.getPackedFiles().isEmpty() && jar.getPackedJars().isEmpty()) {
+ myContext.addMessage(CompilerMessageCategory.WARNING, "Archive '" + jar.getPresentableDestination() + "' has no files so it won't be created", null, -1, -1);
+ return;
+ }
+
+ myContext.getProgressIndicator()
+ .setText(CompilerBundle.message("packaging.compiler.message.building.0", jar.getPresentableDestination()));
+ File jarFile = FileUtil.createTempFile("artifactCompiler", "tmp");
+ myBuiltJars.put(jar, jarFile);
+
+ FileUtil.createParentDirs(jarFile);
+
+ VirtualFile manifestFile = null;
+ for (Pair<String, VirtualFile> pair : jar.getPackedFiles()) {
+ if (pair.getFirst().equals(JarFile.MANIFEST_NAME)) {
+ manifestFile = pair.getSecond();
+ }
+ }
+ final JarOutputStream jarOutputStream = createJarOutputStream(jarFile, manifestFile);
+
+ try {
+ final THashSet<String> writtenPaths = new THashSet<String>();
+ for (Pair<String, VirtualFile> pair : jar.getPackedFiles()) {
+ if (pair.getFirst().equals(JarFile.MANIFEST_NAME)) continue;
+ final VirtualFile sourceFile = pair.getSecond();
+ if (sourceFile.isInLocalFileSystem()) {
+ File file = VfsUtil.virtualToIoFile(sourceFile);
+ addFileToJar(jarOutputStream, file, pair.getFirst(), writtenPaths);
+ }
+ else {
+ extractFileAndAddToJar(jarOutputStream, sourceFile, pair.getFirst(), writtenPaths);
+ }
+ }
+
+ for (Pair<String, JarInfo> nestedJar : jar.getPackedJars()) {
+ File nestedJarFile = myBuiltJars.get(nestedJar.getSecond());
+ if (nestedJarFile != null) {
+ addFileToJar(jarOutputStream, nestedJarFile, nestedJar.getFirst(), writtenPaths);
+ }
+ else {
+ LOG.debug("nested jar file " + nestedJar.getFirst() + " for " + jar.getPresentableDestination() + " not found");
+ }
+ }
+ }
+ finally {
+ jarOutputStream.close();
+ }
+ }
+
+ private static JarOutputStream createJarOutputStream(File jarFile, VirtualFile manifestFile) throws IOException {
+ final BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(jarFile));
+ final JarOutputStream jarOutputStream;
+ if (manifestFile != null) {
+ final InputStream manifestStream = manifestFile.getInputStream();
+ try {
+ jarOutputStream = new JarOutputStream(outputStream, new Manifest(manifestStream));
+ }
+ finally {
+ manifestStream.close();
+ }
+ }
+ else {
+ jarOutputStream = new JarOutputStream(outputStream);
+ }
+ return jarOutputStream;
+ }
+
+ private void extractFileAndAddToJar(JarOutputStream jarOutputStream, VirtualFile sourceFile, String relativePath, THashSet<String> writtenPaths)
+ throws IOException {
+ relativePath = addParentDirectories(jarOutputStream, writtenPaths, relativePath);
+ myContext.getProgressIndicator().setText2(relativePath);
+ if (!writtenPaths.add(relativePath)) return;
+
+ final BufferedInputStream input = ArtifactCompilerUtil.getJarEntryInputStream(sourceFile, myContext);
+ if (input == null) return;
+
+ ZipEntry entry = new ZipEntry(relativePath);
+ entry.setTime(ArtifactCompilerUtil.getJarFile(sourceFile).lastModified());
+ jarOutputStream.putNextEntry(entry);
+ FileUtil.copy(input, jarOutputStream);
+ jarOutputStream.closeEntry();
+ }
+
+ private void addFileToJar(final @NotNull JarOutputStream jarOutputStream, final @NotNull File file, @NotNull String relativePath,
+ final @NotNull THashSet<String> writtenPaths) throws IOException {
+ if (!file.exists()) {
+ return;
+ }
+
+ relativePath = addParentDirectories(jarOutputStream, writtenPaths, relativePath);
+ myContext.getProgressIndicator().setText2(relativePath);
+ ZipUtil.addFileToZip(jarOutputStream, file, relativePath, writtenPaths, myFileFilter);
+ }
+
+ private static String addParentDirectories(JarOutputStream jarOutputStream, THashSet<String> writtenPaths, String relativePath) throws IOException {
+ while (StringUtil.startsWithChar(relativePath, '/')) {
+ relativePath = relativePath.substring(1);
+ }
+ int i = relativePath.indexOf('/');
+ while (i != -1) {
+ String prefix = relativePath.substring(0, i+1);
+ if (!writtenPaths.contains(prefix) && prefix.length() > 1) {
+ addEntry(jarOutputStream, prefix);
+ writtenPaths.add(prefix);
+ }
+ i = relativePath.indexOf('/', i + 1);
+ }
+ return relativePath;
+ }
+
+ private static void addEntry(final ZipOutputStream output, @NonNls final String relativePath) throws IOException {
+ ZipEntry e = new ZipEntry(relativePath);
+ e.setMethod(ZipEntry.STORED);
+ e.setSize(0);
+ e.setCrc(0);
+ output.putNextEntry(e);
+ output.closeEntry();
+ }
+
+ private class JarsGraph implements GraphGenerator.SemiGraph<JarInfo> {
+ public Collection<JarInfo> getNodes() {
+ return myJarsToBuild;
+ }
+
+ public Iterator<JarInfo> getIn(final JarInfo n) {
+ Set<JarInfo> ins = new HashSet<JarInfo>();
+ for (JarDestinationInfo destination : n.getJarDestinations()) {
+ ins.add(destination.getJarInfo());
+ }
+ return ins.iterator();
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/resourceCompiler/ResourceCompiler.java b/java/compiler/impl/src/com/intellij/compiler/impl/resourceCompiler/ResourceCompiler.java
new file mode 100644
index 0000000..3efbec2
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/resourceCompiler/ResourceCompiler.java
@@ -0,0 +1,236 @@
+/*
+ * 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.
+ */
+
+/*
+ * @author: Eugene Zhuravlev
+ * Date: Jan 17, 2003
+ * Time: 3:48:26 PM
+ */
+package com.intellij.compiler.impl.resourceCompiler;
+
+import com.intellij.compiler.CompilerConfiguration;
+import com.intellij.compiler.CompilerConfigurationImpl;
+import com.intellij.compiler.impl.CompilerUtil;
+import com.intellij.compiler.make.MakeUtil;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.*;
+import com.intellij.openapi.compiler.ex.CompileContextEx;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ProjectFileIndex;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.intellij.util.Chunk;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+
+public class ResourceCompiler implements TranslatingCompiler {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.resourceCompiler.ResourceCompiler");
+ private final Project myProject;
+ private final CompilerConfiguration myConfiguration;
+ private final ResourceCompilerExtension[] myResourceCompilerExtensions = ResourceCompilerExtension.EP_NAME.getExtensions();
+
+ public ResourceCompiler(Project project, CompilerConfiguration compilerConfiguration) {
+ myProject = project;
+ myConfiguration = compilerConfiguration;
+ }
+
+ @NotNull
+ public String getDescription() {
+ return CompilerBundle.message("resource.compiler.description");
+ }
+
+ public boolean validateConfiguration(CompileScope scope) {
+ ((CompilerConfigurationImpl) CompilerConfiguration.getInstance(myProject)).convertPatterns();
+ return true;
+ }
+
+ public boolean isCompilableFile(VirtualFile file, CompileContext context) {
+ final Module module = context.getModuleByFile(file);
+ if (module != null && skipStandardResourceCompiler(module)) {
+ return false;
+ }
+ return myConfiguration.isResourceFile(file);
+ }
+
+ public void compile(final CompileContext context, Chunk<Module> moduleChunk, final VirtualFile[] files, OutputSink sink) {
+ context.getProgressIndicator().pushState();
+ context.getProgressIndicator().setText(CompilerBundle.message("progress.copying.resources"));
+
+ final Map<String, Collection<OutputItem>> processed = new HashMap<String, Collection<OutputItem>>();
+ final LinkedList<CopyCommand> copyCommands = new LinkedList<CopyCommand>();
+ final Module singleChunkModule = moduleChunk.getNodes().size() == 1? moduleChunk.getNodes().iterator().next() : null;
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
+ for (final VirtualFile file : files) {
+ if (context.getProgressIndicator().isCanceled()) {
+ break;
+ }
+ final Module module = singleChunkModule != null? singleChunkModule : context.getModuleByFile(file);
+ if (module == null) {
+ continue; // looks like file invalidated
+ }
+ final VirtualFile fileRoot = MakeUtil.getSourceRoot(context, module, file);
+ if (fileRoot == null) {
+ continue;
+ }
+ final String sourcePath = file.getPath();
+ final String relativePath = VfsUtilCore.getRelativePath(file, fileRoot, '/');
+ final boolean inTests = ((CompileContextEx)context).isInTestSourceContent(file);
+ final VirtualFile outputDir = inTests? context.getModuleOutputDirectoryForTests(module) : context.getModuleOutputDirectory(module);
+ if (outputDir == null) {
+ continue;
+ }
+ final String outputPath = outputDir.getPath();
+
+ final String packagePrefix = fileIndex.getPackageNameByDirectory(fileRoot);
+ final String targetPath;
+ if (packagePrefix != null && packagePrefix.length() > 0) {
+ targetPath = outputPath + "/" + packagePrefix.replace('.', '/') + "/" + relativePath;
+ }
+ else {
+ targetPath = outputPath + "/" + relativePath;
+ }
+ if (sourcePath.equals(targetPath)) {
+ addToMap(processed, outputPath, new MyOutputItem(targetPath, file));
+ }
+ else {
+ copyCommands.add(new CopyCommand(outputPath, sourcePath, targetPath, file));
+ }
+ }
+ }
+ });
+
+ final List<File> filesToRefresh = new ArrayList<File>();
+ // do actual copy outside of read action to reduce the time the application is locked on it
+ while (!copyCommands.isEmpty()) {
+ final CopyCommand command = copyCommands.removeFirst();
+ if (context.getProgressIndicator().isCanceled()) {
+ break;
+ }
+ //context.getProgressIndicator().setFraction((idx++) * 1.0 / total);
+ context.getProgressIndicator().setText2("Copying " + command.getFromPath() + "...");
+ try {
+ final MyOutputItem outputItem = command.copy(filesToRefresh);
+ addToMap(processed, command.getOutputPath(), outputItem);
+ }
+ catch (IOException e) {
+ context.addMessage(
+ CompilerMessageCategory.ERROR,
+ CompilerBundle.message("error.copying", command.getFromPath(), command.getToPath(), e.getMessage()),
+ command.getSourceFileUrl(), -1, -1
+ );
+ }
+ }
+
+ if (!filesToRefresh.isEmpty()) {
+ CompilerUtil.refreshIOFiles(filesToRefresh);
+ filesToRefresh.clear();
+ }
+
+ for (Iterator<Map.Entry<String, Collection<OutputItem>>> it = processed.entrySet().iterator(); it.hasNext();) {
+ Map.Entry<String, Collection<OutputItem>> entry = it.next();
+ sink.add(entry.getKey(), entry.getValue(), VirtualFile.EMPTY_ARRAY);
+ it.remove(); // to free memory
+ }
+ context.getProgressIndicator().popState();
+ }
+
+ private boolean skipStandardResourceCompiler(final Module module) {
+ for (ResourceCompilerExtension extension : myResourceCompilerExtensions) {
+ if (extension.skipStandardResourceCompiler(module)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static void addToMap(Map<String, Collection<OutputItem>> map, String outputDir, OutputItem item) {
+ Collection<OutputItem> list = map.get(outputDir);
+ if (list == null) {
+ list = new ArrayList<OutputItem>();
+ map.put(outputDir, list);
+ }
+ list.add(item);
+ }
+
+ private static class CopyCommand {
+ private final String myOutputPath;
+ private final String myFromPath;
+ private final String myToPath;
+ private final VirtualFile mySourceFile;
+
+ private CopyCommand(String outputPath, String fromPath, String toPath, VirtualFile sourceFile) {
+ myOutputPath = outputPath;
+ myFromPath = fromPath;
+ myToPath = toPath;
+ mySourceFile = sourceFile;
+ }
+
+ public MyOutputItem copy(List<File> filesToRefresh) throws IOException {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Copying " + myFromPath + " to " + myToPath);
+ }
+ final File targetFile = new File(myToPath);
+ FileUtil.copyContent(new File(myFromPath), targetFile);
+ filesToRefresh.add(targetFile);
+ return new MyOutputItem(myToPath, mySourceFile);
+ }
+
+ public String getOutputPath() {
+ return myOutputPath;
+ }
+
+ public String getFromPath() {
+ return myFromPath;
+ }
+
+ public String getToPath() {
+ return myToPath;
+ }
+
+ public String getSourceFileUrl() {
+ // do not use mySourseFile.getUrl() directly as it requires read action
+ return VirtualFileManager.constructUrl(mySourceFile.getFileSystem().getProtocol(), myFromPath);
+ }
+ }
+
+ private static class MyOutputItem implements OutputItem {
+ private final String myTargetPath;
+ private final VirtualFile myFile;
+
+ private MyOutputItem(String targetPath, VirtualFile sourceFile) {
+ myTargetPath = targetPath;
+ myFile = sourceFile;
+ }
+
+ public String getOutputPath() {
+ return myTargetPath;
+ }
+
+ public VirtualFile getSourceFile() {
+ return myFile;
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/resourceCompiler/ResourceCompilerExtension.java b/java/compiler/impl/src/com/intellij/compiler/impl/resourceCompiler/ResourceCompilerExtension.java
new file mode 100644
index 0000000..47d901c
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/resourceCompiler/ResourceCompilerExtension.java
@@ -0,0 +1,14 @@
+package com.intellij.compiler.impl.resourceCompiler;
+
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.module.Module;
+import org.jetbrains.annotations.NotNull;
+
+public abstract class ResourceCompilerExtension {
+ public static final ExtensionPointName<ResourceCompilerExtension> EP_NAME =
+ ExtensionPointName.create("com.intellij.compiler.resourceCompilerExtension");
+
+ public boolean skipStandardResourceCompiler(final @NotNull Module module) {
+ return false;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/rmiCompiler/JavacOutputParserPool.java b/java/compiler/impl/src/com/intellij/compiler/impl/rmiCompiler/JavacOutputParserPool.java
new file mode 100644
index 0000000..cc99f2d
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/rmiCompiler/JavacOutputParserPool.java
@@ -0,0 +1,117 @@
+/*
+ * 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 com.intellij.compiler.impl.rmiCompiler;
+
+import com.intellij.compiler.OutputParser;
+import com.intellij.compiler.impl.CompilerUtil;
+import com.intellij.compiler.impl.javaCompiler.CompilerParsingThread;
+import com.intellij.compiler.impl.javaCompiler.javac.JavacOutputParser;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.JavaSdkType;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.projectRoots.ex.JavaSdkUtil;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.rt.compiler.JavacResourcesReader;
+import com.intellij.util.ArrayUtil;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Oct 10, 2005
+ */
+public class JavacOutputParserPool {
+ protected final Project myProject;
+ private final CompileContext myContext;
+ private final Map<Sdk, OutputParser> myProjectToParserMap = new HashMap<Sdk, OutputParser>();
+
+ protected JavacOutputParserPool(Project project, final CompileContext context) {
+ myProject = project;
+ myContext = context;
+ }
+
+ public OutputParser getJavacOutputParser(Sdk jdk) throws IOException {
+ OutputParser outputParser = myProjectToParserMap.get(jdk);
+ if (outputParser == null) {
+ outputParser = createJavacOutputParser(jdk);
+ myProjectToParserMap.put(jdk, outputParser);
+ }
+ return outputParser;
+ }
+
+ private OutputParser createJavacOutputParser(final Sdk jdk) throws IOException {
+ final JavacOutputParser outputParser = new JavacOutputParser(myProject);
+ // first, need to setup the output parser
+ final String[] setupCmdLine = ApplicationManager.getApplication().runReadAction(new Computable<String[]>() {
+ public String[] compute() {
+ return createParserSetupCommand(jdk);
+ }
+ });
+ final Process setupProcess = Runtime.getRuntime().exec(setupCmdLine);
+
+ final CompilerParsingThread setupProcessParsingThread = new CompilerParsingThread(setupProcess, outputParser, true, true,myContext);
+ final Future<?> parsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(setupProcessParsingThread);
+ try {
+ setupProcess.waitFor();
+ }
+ catch (InterruptedException ignored) {
+ }
+ finally {
+ setupProcessParsingThread.setProcessTerminated(true);
+ }
+ try {
+ parsingThreadFuture.get();
+ }
+ catch (InterruptedException e) {
+ }
+ catch (ExecutionException e) {
+ }
+ return outputParser;
+ }
+
+ private static String[] createParserSetupCommand(final Sdk jdk) {
+
+ final VirtualFile homeDirectory = jdk.getHomeDirectory();
+ if (homeDirectory == null) {
+ throw new IllegalArgumentException(CompilerBundle.jdkHomeNotFoundMessage(jdk));
+ }
+
+ final List<String> commandLine = new ArrayList<String>();
+ commandLine.add(((JavaSdkType)jdk.getSdkType()).getVMExecutablePath(jdk));
+
+ CompilerUtil.addLocaleOptions(commandLine, false);
+
+ //noinspection HardCodedStringLiteral
+ commandLine.add("-classpath");
+ commandLine.add(((JavaSdkType)jdk.getSdkType()).getToolsPath(jdk) + File.pathSeparator + JavaSdkUtil.getIdeaRtJarPath());
+
+ commandLine.add(JavacResourcesReader.class.getName());
+
+ return ArrayUtil.toStringArray(commandLine);
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/rmiCompiler/RmicCompiler.java b/java/compiler/impl/src/com/intellij/compiler/impl/rmiCompiler/RmicCompiler.java
new file mode 100644
index 0000000..ae0f0b7
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/rmiCompiler/RmicCompiler.java
@@ -0,0 +1,522 @@
+/*
+ * 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 com.intellij.compiler.impl.rmiCompiler;
+
+import com.intellij.compiler.OutputParser;
+import com.intellij.compiler.impl.CompilerUtil;
+import com.intellij.compiler.impl.javaCompiler.CompilerParsingThread;
+import com.intellij.compiler.impl.javaCompiler.FileObject;
+import com.intellij.compiler.make.Cache;
+import com.intellij.compiler.make.CacheCorruptedException;
+import com.intellij.compiler.make.DependencyCache;
+import com.intellij.compiler.make.MakeUtil;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.*;
+import com.intellij.openapi.compiler.ex.CompileContextEx;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.roots.ModuleRootManager;
+import com.intellij.openapi.roots.OrderEnumerator;
+import com.intellij.openapi.roots.ProjectFileIndex;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.Chunk;
+import com.intellij.util.PathsList;
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 29, 2004
+ */
+
+public class RmicCompiler implements ClassPostProcessingCompiler{
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.impl.rmiCompiler.RmicCompiler");
+ //private static final FileFilter CLASSES_AND_DIRECTORIES_FILTER = new FileFilter() {
+ // public boolean accept(File pathname) {
+ // return pathname.isDirectory() || pathname.getName().endsWith(".class");
+ // }
+ //};
+ //private static final String REMOTE_INTERFACE_NAME = Remote.class.getName();
+
+ @NotNull
+ public ProcessingItem[] getProcessingItems(final CompileContext context) {
+ if (!RmicConfiguration.getOptions(context.getProject()).IS_EANABLED) {
+ return ProcessingItem.EMPTY_ARRAY;
+ }
+ final Project project = context.getProject();
+ final List<ProcessingItem> items = new ArrayList<ProcessingItem>();
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ DependencyCache dependencyCache = ((CompileContextEx)context).getDependencyCache();
+ try {
+ final Cache cache = dependencyCache.getCache();
+ final int[] allClassNames = cache.getAllClassNames();
+ final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
+ final LocalFileSystem lfs = LocalFileSystem.getInstance();
+ for (final int className : allClassNames) {
+ final boolean isRemoteObject = cache.isRemote(className) && !MakeUtil.isInterface(cache.getFlags(className));
+ if (!isRemoteObject && !dependencyCache.wasRemote(className)) {
+ continue;
+ }
+ final String outputPath = cache.getPath(className);
+ if (outputPath == null) {
+ continue;
+ }
+ final VirtualFile outputClassFile = lfs.findFileByPath(outputPath.replace(File.separatorChar, '/'));
+ if (outputClassFile == null) {
+ continue;
+ }
+ final VirtualFile sourceFile = ((CompileContextEx)context).getSourceFileByOutputFile(outputClassFile);
+ if (sourceFile == null) {
+ continue;
+ }
+ final Module module = context.getModuleByFile(sourceFile);
+ if (module == null) {
+ continue;
+ }
+ final VirtualFile outputDir = fileIndex.isInTestSourceContent(sourceFile)
+ ? context.getModuleOutputDirectoryForTests(module)
+ : context.getModuleOutputDirectory(module);
+ if (outputDir == null) {
+ continue;
+ }
+
+ if (!VfsUtil.isAncestor(outputDir, outputClassFile, true)) {
+ LOG.error(outputClassFile.getPath() + " should be located under the output root " + outputDir.getPath());
+ }
+
+ final ProcessingItem item = createProcessingItem(module, outputClassFile, outputDir,
+ isRemoteObject, dependencyCache.resolve(className));
+ items.add(item);
+ }
+ }
+ catch (CacheCorruptedException e) {
+ context.addMessage(CompilerMessageCategory.ERROR, e.getMessage(), null, -1, -1);
+ LOG.info(e);
+ }
+ }
+ });
+
+ return items.toArray(new ProcessingItem[items.size()]);
+ }
+
+ public static ProcessingItem createProcessingItem(final Module module,
+ final VirtualFile outputClassFile,
+ final VirtualFile outputDir,
+ final boolean remoteObject, String qName) {
+ final RmicProcessingItem item = new RmicProcessingItem(
+ module, outputClassFile, new File(outputDir.getPath()), qName
+ );
+ item.setIsRemoteObject(remoteObject);
+ return item;
+ }
+
+ public ProcessingItem[] process(CompileContext context, ProcessingItem[] items) {
+ final Project project = context.getProject();
+ if (!RmicConfiguration.getOptions(project).IS_EANABLED) {
+ return ProcessingItem.EMPTY_ARRAY;
+ }
+ final ProgressIndicator progressIndicator = context.getProgressIndicator();
+ progressIndicator.pushState();
+ try {
+ progressIndicator.setText(CompilerBundle.message("progress.generating.rmi.stubs"));
+ final Map<Pair<Module, File>, List<RmicProcessingItem>> sortedByModuleAndOutputPath = new HashMap<Pair<Module,File>, List<RmicProcessingItem>>();
+ for (ProcessingItem item1 : items) {
+ final RmicProcessingItem item = (RmicProcessingItem)item1;
+ final Pair<Module, File> moduleOutputPair = new Pair<Module, File>(item.getModule(), item.getOutputDir());
+ List<RmicProcessingItem> dirItems = sortedByModuleAndOutputPath.get(moduleOutputPair);
+ if (dirItems == null) {
+ dirItems = new ArrayList<RmicProcessingItem>();
+ sortedByModuleAndOutputPath.put(moduleOutputPair, dirItems);
+ }
+ dirItems.add(item);
+ }
+ final List<ProcessingItem> processed = new ArrayList<ProcessingItem>();
+
+ final JavacOutputParserPool parserPool = new JavacOutputParserPool(project, context);
+
+ for (final Pair<Module, File> pair : sortedByModuleAndOutputPath.keySet()) {
+ if (progressIndicator.isCanceled()) {
+ break;
+ }
+ final List<RmicProcessingItem> dirItems = sortedByModuleAndOutputPath.get(pair);
+ try {
+ // should delete all previously generated files for the remote class if there are any
+ for (Iterator itemIterator = dirItems.iterator(); itemIterator.hasNext();) {
+ final RmicProcessingItem item = (RmicProcessingItem)itemIterator.next();
+ item.deleteGeneratedFiles();
+ if (!item.isRemoteObject()) {
+ itemIterator
+ .remove(); // the object was remote and currently is not, so remove it from the list and do not generate stubs for it
+ }
+ }
+ if (!dirItems.isEmpty()) {
+ final RmicProcessingItem[] successfullyProcessed = invokeRmic(context, parserPool, pair.getFirst(), dirItems, pair.getSecond());
+ ContainerUtil.addAll(processed, successfullyProcessed);
+ }
+ progressIndicator.setFraction(((double)processed.size()) / ((double)items.length));
+ }
+ catch (IOException e) {
+ context.addMessage(CompilerMessageCategory.ERROR, e.getMessage(), null, -1, -1);
+ LOG.info(e);
+ }
+ }
+ // update state so that the latest timestamps are recorded by make
+ final ProcessingItem[] processedItems = processed.toArray(new ProcessingItem[processed.size()]);
+ final List<File> filesToRefresh = new ArrayList<File>(processedItems.length * 3);
+ for (ProcessingItem processedItem : processedItems) {
+ RmicProcessingItem item = (RmicProcessingItem)processedItem;
+ item.updateState();
+ filesToRefresh.add(item.myStub);
+ filesToRefresh.add(item.mySkel);
+ filesToRefresh.add(item.myTie);
+ }
+ CompilerUtil.refreshIOFiles(filesToRefresh);
+ return processedItems;
+ }
+ finally {
+ progressIndicator.popState();
+ }
+ }
+
+ private static RmicProcessingItem[] invokeRmic(final CompileContext context,
+ final JavacOutputParserPool parserPool, final Module module,
+ final List<RmicProcessingItem> dirItems,
+ final File outputDir
+ ) throws IOException{
+
+ final Sdk jdk = ModuleRootManager.getInstance(module).getSdk();
+
+ final Map<String, RmicProcessingItem> pathToItemMap = new HashMap<String, RmicProcessingItem>();
+ final String[] cmdLine = ApplicationManager.getApplication().runReadAction(new Computable<String[]>() {
+ public String[] compute() {
+ for (final RmicProcessingItem item : dirItems) {
+ pathToItemMap.put(item.myStub.getPath().replace(File.separatorChar, '/'), item);
+ pathToItemMap.put(item.mySkel.getPath().replace(File.separatorChar, '/'), item);
+ pathToItemMap.put(item.myTie.getPath().replace(File.separatorChar, '/'), item);
+ }
+ return createStartupCommand(module, outputDir.getPath(), dirItems.toArray(new RmicProcessingItem[dirItems.size()]));
+ }
+ });
+
+ if (LOG.isDebugEnabled()) {
+ StringBuilder buf = new StringBuilder();
+ for (int idx = 0; idx < cmdLine.length; idx++) {
+ if (idx > 0) {
+ buf.append(" ");
+ }
+ buf.append(cmdLine[idx]);
+ }
+ LOG.debug(buf.toString());
+ }
+
+ // obtain parser before running the process because configuring parser may involve starting another process
+ final OutputParser outputParser = parserPool.getJavacOutputParser(jdk);
+
+ final Process process = Runtime.getRuntime().exec(cmdLine);
+ final Set<RmicProcessingItem> successfullyCompiledItems = new HashSet<RmicProcessingItem>();
+ final CompilerParsingThread parsingThread = new CompilerParsingThread(process, outputParser, false, true,context) {
+ protected void processCompiledClass(FileObject classFileToProcess) {
+ String key = classFileToProcess.getFile().getPath().replace(File.separatorChar, '/');
+ final RmicProcessingItem item = pathToItemMap.get(key);
+ if (item != null) {
+ successfullyCompiledItems.add(item);
+ }
+ }
+ };
+
+ final Future<?> parsingThreadFuture = ApplicationManager.getApplication().executeOnPooledThread(parsingThread);
+ try {
+ process.waitFor();
+ }
+ catch (InterruptedException ignored) {
+ }
+ finally {
+ parsingThread.setProcessTerminated(true);
+ }
+
+ try {
+ parsingThreadFuture.get();
+ }
+ catch (InterruptedException ignored) {
+ }
+ catch (ExecutionException ignored) {
+ }
+ return successfullyCompiledItems.toArray(new RmicProcessingItem[successfullyCompiledItems.size()]);
+ }
+
+ private static String[] createStartupCommand(final Module module, final String outputPath, final RmicProcessingItem[] items) {
+ final Sdk jdk = ModuleRootManager.getInstance(module).getSdk();
+
+ final VirtualFile homeDirectory = jdk.getHomeDirectory();
+ if (homeDirectory == null) {
+ throw new IllegalArgumentException(CompilerBundle.jdkHomeNotFoundMessage(jdk));
+ }
+ final String jdkPath = homeDirectory.getPath().replace('/', File.separatorChar);
+
+ @NonNls final String compilerPath = jdkPath + File.separator + "bin" + File.separator + "rmic";
+
+ @NonNls final List<String> commandLine = new ArrayList<String>();
+ commandLine.add(compilerPath);
+
+ CompilerUtil.addLocaleOptions(commandLine, true);
+
+ commandLine.add("-verbose");
+
+ final Project project = module.getProject();
+ ContainerUtil.addAll(commandLine, new RmicSettingsBuilder(RmicConfiguration.getOptions(project)).getOptions(new Chunk<Module>(module)));
+
+ commandLine.add("-classpath");
+
+ commandLine.add(getCompilationClasspath(module));
+
+ commandLine.add("-d");
+
+ commandLine.add(outputPath);
+
+ for (RmicProcessingItem item : items) {
+ commandLine.add(item.getClassQName());
+ }
+ return ArrayUtil.toStringArray(commandLine);
+ }
+
+ @NotNull
+ public String getDescription() {
+ return CompilerBundle.message("rmi.compiler.description");
+ }
+
+ public boolean validateConfiguration(CompileScope scope) {
+ return true;
+ }
+
+ /*
+ private void addAllRemoteFilesFromModuleOutput(final CompileContext context, final Module module, final List<ProcessingItem> items, final File outputDir, File fromDir, final JavaClass remoteInterface) {
+ final File[] children = fromDir.listFiles(CLASSES_AND_DIRECTORIES_FILTER);
+ for (int idx = 0; idx < children.length; idx++) {
+ final File child = children[idx];
+ if (child.isDirectory()) {
+ addAllRemoteFilesFromModuleOutput(context, module, items, outputDir, child, remoteInterface);
+ }
+ else {
+ final String path = child.getPath();
+ try {
+ final ClassParser classParser = new ClassParser(path);
+ final JavaClass javaClass = classParser.parse();
+ // important! Need this in order to resolve other classes in the project (e.g. superclasses)
+ javaClass.setRepository(BcelUtils.getActiveRepository());
+ if (isRmicCompilable(javaClass, remoteInterface)) {
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ final VirtualFile outputClassFile = LocalFileSystem.getInstance().findFileByIoFile(child);
+ if (outputClassFile != null) {
+ items.add(new RmicProcessingItem(module, outputClassFile, outputDir, javaClass.getClassName()));
+ }
+ }
+ });
+ }
+ }
+ catch (IOException e) {
+ context.addMessage(CompilerMessageCategory.ERROR, "Cannot parse class file " + path + ": " + e.toString(), null, -1, -1);
+ }
+ catch (ClassFormatException e) {
+ context.addMessage(CompilerMessageCategory.ERROR, "Class format exception: " + e.getMessage() + " File: " + path, null, -1, -1);
+ }
+ }
+ }
+ }
+ */
+
+ /*
+ private boolean isRmicCompilable(final JavaClass javaClass, final JavaClass remoteInterface) {
+ // stubs are needed for classes that _directly_ implement remote interfaces
+ if (javaClass.isInterface() || isGenerated(javaClass)) {
+ return false;
+ }
+ final JavaClass[] directlyImplementedInterfaces = javaClass.getInterfaces();
+ if (directlyImplementedInterfaces != null) {
+ for (int i = 0; i < directlyImplementedInterfaces.length; i++) {
+ if (directlyImplementedInterfaces[i].instanceOf(remoteInterface)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ */
+
+ /*
+ private boolean isGenerated(JavaClass javaClass) {
+ final String sourceFileName = javaClass.getSourceFileName();
+ return sourceFileName == null || !sourceFileName.endsWith(".java");
+ }
+ */
+
+
+ public ValidityState createValidityState(DataInput in) throws IOException {
+ return new RemoteClassValidityState(in.readLong(), in.readLong(), in.readLong(), in.readLong());
+ }
+
+ private static String getCompilationClasspath(Module module) {
+ final OrderEnumerator enumerator = ModuleRootManager.getInstance(module).orderEntries().withoutSdk().compileOnly().recursively().exportedOnly();
+ final PathsList pathsList = enumerator.getPathsList();
+ return pathsList.getPathsString();
+ }
+
+ private static final class RemoteClassValidityState implements ValidityState {
+ private final long myRemoteClassTimestamp;
+ private final long myStubTimestamp;
+ private final long mySkelTimestamp;
+ private final long myTieTimestamp;
+
+ private RemoteClassValidityState(long remoteClassTimestamp, long stubTimestamp, long skelTimestamp, long tieTimestamp) {
+ myRemoteClassTimestamp = remoteClassTimestamp;
+ myStubTimestamp = stubTimestamp;
+ mySkelTimestamp = skelTimestamp;
+ myTieTimestamp = tieTimestamp;
+ }
+
+ public boolean equalsTo(ValidityState otherState) {
+ if (otherState instanceof RemoteClassValidityState) {
+ final RemoteClassValidityState state = (RemoteClassValidityState)otherState;
+ return myRemoteClassTimestamp == state.myRemoteClassTimestamp &&
+ myStubTimestamp == state.myStubTimestamp &&
+ mySkelTimestamp == state.mySkelTimestamp &&
+ myTieTimestamp == state.myTieTimestamp;
+ }
+ return false;
+ }
+
+ public void save(DataOutput out) throws IOException {
+ out.writeLong(myRemoteClassTimestamp);
+ out.writeLong(myStubTimestamp);
+ out.writeLong(mySkelTimestamp);
+ out.writeLong(myTieTimestamp);
+ }
+ }
+ private static final class RmicProcessingItem implements ProcessingItem {
+ private final Module myModule;
+ private final VirtualFile myOutputClassFile;
+ private final File myOutputDir;
+ private final String myQName;
+ private RemoteClassValidityState myState;
+ private final File myStub;
+ private final File mySkel;
+ private final File myTie;
+ private boolean myIsRemoteObject = false;
+
+ private RmicProcessingItem(Module module, final VirtualFile outputClassFile, File outputDir, String qName) {
+ myModule = module;
+ myOutputClassFile = outputClassFile;
+ myOutputDir = outputDir;
+ myQName = qName;
+ final String relativePath;
+ final String baseName;
+
+ final int index = qName.lastIndexOf('.');
+ if (index >= 0) {
+ relativePath = qName.substring(0, index + 1).replace('.', '/');
+ baseName = qName.substring(index + 1);
+ }
+ else {
+ relativePath = "";
+ baseName = qName;
+ }
+ final String path = outputDir.getPath().replace(File.separatorChar, '/') + "/" + relativePath;
+ //noinspection HardCodedStringLiteral
+ myStub = new File(path + "/" + baseName + "_Stub.class");
+ //noinspection HardCodedStringLiteral
+ mySkel = new File(path + "/" + baseName + "_Skel.class");
+ //noinspection HardCodedStringLiteral
+ myTie = new File(path + "/_" + baseName + "_Tie.class");
+ updateState();
+ }
+
+ public boolean isRemoteObject() {
+ return myIsRemoteObject;
+ }
+
+ public void setIsRemoteObject(boolean isRemote) {
+ myIsRemoteObject = isRemote;
+ }
+
+ @NotNull
+ public VirtualFile getFile() {
+ return myOutputClassFile;
+ }
+
+ public ValidityState getValidityState() {
+ return myState;
+ }
+
+ public void updateState() {
+ myState = new RemoteClassValidityState(
+ myOutputClassFile.getTimeStamp(),
+ getTimestamp(myStub),
+ getTimestamp(mySkel),
+ getTimestamp(myTie)
+ );
+
+ }
+
+ private static long getTimestamp(File file) {
+ long l = file.lastModified();
+ return l == 0 ? -1L : l;
+ }
+
+ public void deleteGeneratedFiles() {
+ if (FileUtil.delete(myStub)) {
+ CompilerUtil.refreshIOFile(myStub);
+ }
+ if (FileUtil.delete(mySkel)) {
+ CompilerUtil.refreshIOFile(mySkel);
+ }
+ if (FileUtil.delete(myTie)) {
+ CompilerUtil.refreshIOFile(myTie);
+ }
+ }
+
+ public String getClassQName() {
+ return myQName;
+ }
+
+ public File getOutputDir() {
+ return myOutputDir;
+ }
+
+ public Module getModule() {
+ return myModule;
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/rmiCompiler/RmicConfiguration.java b/java/compiler/impl/src/com/intellij/compiler/impl/rmiCompiler/RmicConfiguration.java
new file mode 100755
index 0000000..70b8b42
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/rmiCompiler/RmicConfiguration.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl.rmiCompiler;
+
+import com.intellij.openapi.components.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.util.xmlb.XmlSerializerUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.jps.model.java.compiler.RmicCompilerOptions;
+
+@State(
+ name = "RmicSettings",
+ storages = {
+ @Storage( file = StoragePathMacros.PROJECT_FILE)
+ ,@Storage( file = StoragePathMacros.PROJECT_CONFIG_DIR + "/compiler.xml", scheme = StorageScheme.DIRECTORY_BASED)
+ }
+)
+public class RmicConfiguration implements PersistentStateComponent<RmicCompilerOptions> {
+ private final RmicCompilerOptions mySettings = new RmicCompilerOptions();
+
+ @NotNull
+ public RmicCompilerOptions getState() {
+ return mySettings;
+ }
+
+ public void loadState(RmicCompilerOptions state) {
+ XmlSerializerUtil.copyBean(state, mySettings);
+ }
+
+ public static RmicCompilerOptions getOptions(Project project) {
+ return ServiceManager.getService(project, RmicConfiguration.class).getState();
+ }
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/impl/rmiCompiler/RmicSettingsBuilder.java b/java/compiler/impl/src/com/intellij/compiler/impl/rmiCompiler/RmicSettingsBuilder.java
new file mode 100644
index 0000000..e4b0aee
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/impl/rmiCompiler/RmicSettingsBuilder.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.impl.rmiCompiler;
+
+import com.intellij.compiler.impl.javaCompiler.javac.JavacSettingsBuilder;
+import com.intellij.openapi.module.Module;
+import com.intellij.util.Chunk;
+import org.jetbrains.jps.model.java.compiler.RmicCompilerOptions;
+
+import java.util.Collection;
+
+public class RmicSettingsBuilder extends JavacSettingsBuilder {
+
+ public RmicSettingsBuilder(final RmicCompilerOptions options) {
+ super(options);
+ getOptions().DEPRECATION = false; // in this configuration deprecation is false by default
+ }
+
+ @Override
+ public RmicCompilerOptions getOptions() {
+ return (RmicCompilerOptions)super.getOptions();
+ }
+
+ public Collection<String> getOptions(Chunk<Module> chunk) {
+ final Collection<String> options = super.getOptions(chunk);
+ if(getOptions().GENERATE_IIOP_STUBS) {
+ options.add("-iiop");
+ }
+ return options;
+ }
+
+ protected boolean acceptUserOption(String token) {
+ if (!super.acceptUserOption(token)) {
+ return false;
+ }
+ return !("-iiop".equals(token));
+ }
+
+ protected boolean acceptEncoding() {
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/make/AnnotationTargets.java b/java/compiler/impl/src/com/intellij/compiler/make/AnnotationTargets.java
new file mode 100644
index 0000000..100fd4a
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/make/AnnotationTargets.java
@@ -0,0 +1,66 @@
+/*
+ * 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 com.intellij.compiler.make;
+
+import org.jetbrains.annotations.NonNls;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Apr 7, 2004
+ */
+public interface AnnotationTargets {
+ /** Class, interface or enum declaration */
+ int TYPE = 0x1;
+
+ @NonNls String TYPE_STR = "TYPE";
+
+ /** Field declaration (includes enum constants) */
+ int FIELD = 0x2;
+
+ @NonNls String FIELD_STR = "FIELD";
+
+ /** Method declaration */
+ int METHOD = 0x4;
+
+ @NonNls String METHOD_STR = "METHOD";
+
+ /** Parameter declaration */
+ int PARAMETER = 0x8;
+
+ @NonNls String PARAMETER_STR = "PARAMETER";
+
+ /** Constructor declaration */
+ int CONSTRUCTOR = 0x10;
+
+ @NonNls String CONSTRUCTOR_STR = "CONSTRUCTOR";
+
+ /** Local variable declaration */
+ int LOCAL_VARIABLE = 0x20;
+
+ @NonNls String LOCAL_VARIABLE_STR = "LOCAL_VARIABLE";
+
+ /** Annotation type declaration */
+ int ANNOTATION_TYPE = 0x40;
+
+ @NonNls String ANNOTATION_TYPE_STR = "ANNOTATION_TYPE";
+
+ /** Package declaration */
+ int PACKAGE = 0x80;
+
+ @NonNls String PACKAGE_STR = "PACKAGE";
+
+ int ALL = TYPE | FIELD | METHOD | PARAMETER | CONSTRUCTOR | LOCAL_VARIABLE | ANNOTATION_TYPE | PACKAGE;
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/make/BackwardDependenciesStorage.java b/java/compiler/impl/src/com/intellij/compiler/make/BackwardDependenciesStorage.java
new file mode 100644
index 0000000..fb7a5c6
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/make/BackwardDependenciesStorage.java
@@ -0,0 +1,511 @@
+/*
+ * 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 com.intellij.compiler.make;
+
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.Ref;
+import com.intellij.util.containers.SLRUCache;
+import com.intellij.util.io.DataExternalizer;
+import com.intellij.util.io.EnumeratorIntegerDescriptor;
+import com.intellij.util.io.PersistentHashMap;
+import gnu.trove.*;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Dec 1, 2008
+ */
+public class BackwardDependenciesStorage implements Flushable, Disposable {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.make.CompilerDependencyStorage");
+ protected final PersistentHashMap<Integer, DependenciesSet> myMap;
+ protected final SLRUCache<Integer, ReferencerSetHolder> myCache;
+ private Integer myKeyToRemove;
+ private static final int FIELD = 1;
+ private static final int METHOD = 2;
+ private static final int CLASS = 3;
+
+ public BackwardDependenciesStorage(File file, final int cacheSize) throws IOException {
+ myMap = new PersistentHashMap<Integer, DependenciesSet>(file, EnumeratorIntegerDescriptor.INSTANCE, new MyDataExternalizer());
+
+ myCache = new SLRUCache<Integer, ReferencerSetHolder>(cacheSize * 2, cacheSize) {
+ @NotNull
+ public ReferencerSetHolder createValue(Integer key) {
+ return new ReferencerSetHolder(key);
+ }
+
+ protected void onDropFromCache(Integer key, final ReferencerSetHolder holder) {
+ if (key.equals(myKeyToRemove) || !holder.isDirty()) {
+ return;
+ }
+ try {
+ if (holder.isDataLoaded() || !myMap.containsMapping(key)) {
+ myMap.put(key, new DependenciesSet(holder.getData()));
+ }
+ else {
+ myMap.appendData(key, new PersistentHashMap.ValueDataAppender() {
+ public void append(final DataOutput out) throws IOException {
+ final Ref<IOException> exception = new Ref<IOException>(null);
+ // process removed
+ holder.myRemoveRequested.forEach(new TIntProcedure() {
+ public boolean execute(int qName) {
+ try {
+ out.writeInt(-qName);
+ return true;
+ }
+ catch (IOException e) {
+ exception.set(e);
+ }
+ return false;
+ }
+ });
+ final IOException _ex = exception.get();
+ if (_ex != null) {
+ throw _ex;
+ }
+ // process added members
+ for (ReferencerItem item : holder.myAdded) {
+ item.save(out);
+ }
+ }
+ });
+ }
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
+ };
+ }
+
+ public synchronized void remove(Integer qName) throws IOException {
+ myKeyToRemove = qName;
+ try {
+ myCache.remove(qName);
+ }
+ finally {
+ myKeyToRemove = null;
+ }
+ myMap.remove(qName);
+ }
+
+ public synchronized void removeReferencer(Integer qName, int referencerQName) throws IOException {
+ if (myMap.containsMapping(qName)) {
+ final ReferencerSetHolder set = myCache.get(qName);
+ set.removeReferencer(referencerQName);
+ }
+ }
+
+ public synchronized void addClassReferencer(Integer qName, int referencerQName) {
+ myCache.get(qName).addReferencer(new ReferencerItem(referencerQName));
+ }
+
+ public synchronized void addFieldReferencer(Integer qName, int referencerQName, int fieldName) {
+ myCache.get(qName).addReferencer(new FieldReferencerItem(referencerQName, fieldName));
+ }
+
+ public synchronized void addMethodReferencer(Integer qName, int referencerQName, int methodName, int descriptor) {
+ myCache.get(qName).addReferencer(new MethodReferencerItem(referencerQName, methodName, descriptor));
+ }
+
+ public synchronized Dependency[] getDependencies(Integer classQName) throws CacheCorruptedException {
+ try {
+ if (!myMap.containsMapping(classQName)) {
+ return Dependency.EMPTY_ARRAY;
+ }
+
+ return convertToDependencies(classQName.intValue(), myCache.get(classQName).getData());
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ private static Dependency[] convertToDependencies(int classToSkip, Set<ReferencerItem> data) {
+ final TIntObjectHashMap<Dependency> dependencies = new TIntObjectHashMap<Dependency>();
+ for (ReferencerItem item : data) {
+ if (item.qName == classToSkip) {
+ continue; // skip self-dependencies
+ }
+ final Dependency dependency = addDependency(dependencies, item.qName);
+ if (item instanceof FieldReferencerItem) {
+ dependency.addField(((FieldReferencerItem)item).name);
+ }
+ else if (item instanceof MethodReferencerItem) {
+ final MethodReferencerItem methodItem = (MethodReferencerItem)item;
+ dependency.addMethod(methodItem.name, methodItem.descriptor);
+ }
+ }
+
+ final Dependency[] dependencyArray = new Dependency[dependencies.size()];
+ dependencies.forEachValue(new TObjectProcedure<Dependency>() {
+ private int index = 0;
+ public boolean execute(Dependency object) {
+ dependencyArray[index++] = object;
+ return true;
+ }
+ });
+ return dependencyArray;
+ }
+
+ private static Dependency addDependency(TIntObjectHashMap<Dependency> container, int classQName) {
+ Dependency dependency = container.get(classQName);
+ if (dependency == null) {
+ dependency = new Dependency(classQName);
+ container.put(classQName, dependency);
+ }
+ return dependency;
+ }
+
+ public synchronized void flush() throws IOException {
+ myCache.clear();
+ myMap.force();
+ }
+
+ private void flush(Integer key) {
+ myCache.remove(key); // makes changes into PersistentHashMap
+ myMap.force(); // flushes internal caches (which consume memory) and writes unsaved data to disk
+ }
+
+ public synchronized void dispose() {
+ try {
+ flush();
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ }
+ try {
+ myMap.close();
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ }
+ }
+
+ private static class ReferencerItem {
+ public final int qName;
+
+ private ReferencerItem(int qName) {
+ this.qName = qName;
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ ReferencerItem that = (ReferencerItem)o;
+
+ if (qName != that.qName) return false;
+
+ return true;
+ }
+
+ public int hashCode() {
+ return qName;
+ }
+
+ public void save(DataOutput out) throws IOException {
+ out.writeInt(qName);
+ out.writeByte(CLASS);
+ }
+ }
+
+ private static final class MethodReferencerItem extends ReferencerItem {
+ public final int name;
+ public final int descriptor;
+
+ MethodReferencerItem(int qName, int name, int descriptor) {
+ super(qName);
+ this.name = name;
+ this.descriptor = descriptor;
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+
+ MethodReferencerItem that = (MethodReferencerItem)o;
+
+ if (descriptor != that.descriptor) return false;
+ if (name != that.name) return false;
+
+ return true;
+ }
+
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + name;
+ result = 31 * result + descriptor;
+ return result;
+ }
+
+ public void save(DataOutput out) throws IOException {
+ out.writeInt(qName);
+ out.writeByte(METHOD);
+ out.writeInt(name);
+ out.writeInt(descriptor);
+ }
+ }
+
+ private static final class FieldReferencerItem extends ReferencerItem {
+ public final int name;
+
+ FieldReferencerItem(int qName, int name) {
+ super(qName);
+ this.name = name;
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+
+ FieldReferencerItem that = (FieldReferencerItem)o;
+
+ if (name != that.name) return false;
+
+ return true;
+ }
+
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + name;
+ return result;
+ }
+
+ public void save(DataOutput out) throws IOException {
+ out.writeInt(qName);
+ out.writeByte(FIELD);
+ out.writeInt(name);
+ }
+ }
+
+ private class ReferencerSetHolder {
+ private final Integer myKey;
+ private TIntHashSet myRemoveRequested = new TIntHashSet();
+ private Set<ReferencerItem> myAdded = new THashSet<ReferencerItem>();
+
+ private Set<ReferencerItem> myData = null;
+ private boolean myIsDirty = false;
+
+ public ReferencerSetHolder(Integer key) {
+ myKey = key;
+ }
+
+ public void addReferencer(ReferencerItem referencer) {
+ if (myData != null) {
+ myIsDirty |= myData.add(referencer);
+ return;
+ }
+ myAdded.add(referencer);
+ }
+
+ public void removeReferencer(int qName) {
+ if (myData != null) {
+ myIsDirty |= removeAllReferencerItems(myData, qName);
+ return;
+ }
+ myRemoveRequested.add(qName);
+ removeAllReferencerItems(myAdded, qName);
+ }
+
+ public boolean isDirty() {
+ return myData != null? myIsDirty : myRemoveRequested.size() > 0 || myAdded.size() > 0;
+ }
+
+ public boolean isDataLoaded() {
+ return myData != null;
+ }
+
+ public Set<ReferencerItem> getData() throws IOException {
+ if (myData == null) {
+ final DependenciesSet ds = myMap.get(myKey);
+ Set<ReferencerItem> set = null;
+ if (ds != null) {
+ set = ds.set;
+ myIsDirty |= ds.needsCompacting;
+ }
+ if (set == null) {
+ set = new THashSet<ReferencerItem>();
+ }
+ myIsDirty |= removeAllReferencerItems(set, myRemoveRequested);
+ myIsDirty |= set.addAll(myAdded);
+ myData = set;
+ myAdded = null;
+ myRemoveRequested = null;
+ }
+ return Collections.unmodifiableSet(myData);
+ }
+
+ }
+
+ private static class MyDataExternalizer implements DataExternalizer<DependenciesSet> {
+ public void save(DataOutput out, DependenciesSet ds) throws IOException {
+ final TIntHashSet classes = new TIntHashSet();
+ final Map<Dependency.FieldRef, TIntHashSet> fieldsMap = new HashMap<Dependency.FieldRef, TIntHashSet>();
+ final Map<Dependency.MethodRef, TIntHashSet> methodsMap = new HashMap<Dependency.MethodRef, TIntHashSet>();
+ for (ReferencerItem item : ds.set) {
+ if (item instanceof FieldReferencerItem) {
+ final Dependency.FieldRef ref = new Dependency.FieldRef(((FieldReferencerItem)item).name);
+ TIntHashSet referencers = fieldsMap.get(ref);
+ if (referencers == null) {
+ referencers = new TIntHashSet();
+ fieldsMap.put(ref, referencers);
+ }
+ referencers.add(item.qName);
+ }
+ else if (item instanceof MethodReferencerItem) {
+ final MethodReferencerItem _item = (MethodReferencerItem)item;
+ final Dependency.MethodRef ref = new Dependency.MethodRef(_item.name, _item.descriptor);
+ TIntHashSet referencers = methodsMap.get(ref);
+ if (referencers == null) {
+ referencers = new TIntHashSet();
+ methodsMap.put(ref, referencers);
+ }
+ referencers.add(item.qName);
+ }
+ else {
+ classes.add(item.qName);
+ }
+ }
+
+ out.writeInt(classes.size());
+ for (TIntIterator it = classes.iterator(); it.hasNext();) {
+ out.writeInt(it.next());
+ }
+
+ out.writeInt(fieldsMap.size());
+ for (Map.Entry<Dependency.FieldRef, TIntHashSet> entry : fieldsMap.entrySet()) {
+ out.writeInt(entry.getKey().name);
+ final TIntHashSet referencers = entry.getValue();
+ out.writeInt(referencers.size());
+ for (TIntIterator rit = referencers.iterator(); rit.hasNext();) {
+ out.writeInt(rit.next());
+ }
+ }
+
+ out.writeInt(methodsMap.size());
+ for (Map.Entry<Dependency.MethodRef, TIntHashSet> entry : methodsMap.entrySet()) {
+ final Dependency.MethodRef ref = entry.getKey();
+ out.writeInt(ref.name);
+ out.writeInt(ref.descriptor);
+ final TIntHashSet referencers = entry.getValue();
+ out.writeInt(referencers.size());
+ for (TIntIterator rit = referencers.iterator(); rit.hasNext();) {
+ out.writeInt(rit.next());
+ }
+ }
+ }
+
+ public DependenciesSet read(DataInput in) throws IOException {
+ final Set<ReferencerItem> set = new THashSet<ReferencerItem>();
+
+ int classesCount = in.readInt();
+ while (classesCount-- > 0) {
+ set.add(new ReferencerItem(in.readInt()));
+ }
+
+ int fieldsCount = in.readInt();
+ while (fieldsCount-- > 0) {
+ final int fieldName = in.readInt();
+ int referencersCount = in.readInt();
+ while (referencersCount-- > 0) {
+ set.add(new FieldReferencerItem(in.readInt(), fieldName));
+ }
+ }
+
+ int methodsCount = in.readInt();
+ while (methodsCount-- > 0) {
+ final int methodName = in.readInt();
+ final int methodDescriptor = in.readInt();
+ int referensersCount = in.readInt();
+ while (referensersCount-- > 0) {
+ set.add(new MethodReferencerItem(in.readInt(), methodName, methodDescriptor));
+ }
+ }
+ boolean needsCompacting = false;
+ // manage appends if exist qName, kind, {field | method}
+ final DataInputStream _in = (DataInputStream)in;
+ while (_in.available() > 0) {
+ needsCompacting = true;
+ final int qName = _in.readInt();
+ if (qName < 0) {
+ removeAllReferencerItems(set, -qName);
+ }
+ else {
+ final byte kind = _in.readByte();
+ if (kind == FIELD) {
+ set.add(new FieldReferencerItem(qName, _in.readInt()));
+ }
+ else if (kind == METHOD) {
+ final int name = _in.readInt();
+ final int descriptor = _in.readInt();
+ set.add(new MethodReferencerItem(qName, name, descriptor));
+ }
+ else if (kind == CLASS){
+ set.add(new ReferencerItem(qName));
+ }
+ }
+ }
+
+ return new DependenciesSet(set, needsCompacting);
+ }
+ }
+
+ private static final class DependenciesSet {
+ public final Set<ReferencerItem> set;
+ public final boolean needsCompacting;
+
+ public DependenciesSet(Set<ReferencerItem> set, boolean needsCompacting) {
+ this.set = set;
+ this.needsCompacting = needsCompacting;
+ }
+
+ public DependenciesSet(Set<ReferencerItem> set) {
+ this(set, false);
+ }
+ }
+
+
+ private static boolean removeAllReferencerItems(final Set<ReferencerItem> from, final TIntHashSet qNames) {
+ boolean removed = false;
+ for (Iterator<ReferencerItem> it = from.iterator(); it.hasNext();) {
+ final ReferencerItem item = it.next();
+ if (qNames.contains(item.qName)) {
+ it.remove();
+ removed = true;
+ }
+ }
+ return removed;
+ }
+
+ private static boolean removeAllReferencerItems(final Set<ReferencerItem> from, final int qNames) {
+ boolean removed = false;
+ for (Iterator<ReferencerItem> it = from.iterator(); it.hasNext();) {
+ final ReferencerItem item = it.next();
+ if (qNames == item.qName) {
+ it.remove();
+ removed = true;
+ }
+ }
+ return removed;
+ }
+
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/make/BoundsParser.java b/java/compiler/impl/src/com/intellij/compiler/make/BoundsParser.java
new file mode 100644
index 0000000..0ab9489
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/make/BoundsParser.java
@@ -0,0 +1,83 @@
+/*
+ * 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 com.intellij.compiler.make;
+
+import com.intellij.compiler.classParsing.SignatureParser;
+import com.intellij.compiler.classParsing.SignatureParsingException;
+import com.intellij.util.ArrayUtil;
+
+import java.text.CharacterIterator;
+import java.text.StringCharacterIterator;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 10, 2004
+ */
+public class BoundsParser extends SignatureParser{
+ final List<String> myInterfaceBounds = new ArrayList<String>();
+ private int myParsingBound;
+
+ public void parseClassBound(CharacterIterator it, StringBuilder buf) throws SignatureParsingException {
+ myParsingBound += 1;
+ try {
+ super.parseClassBound(it, buf);
+ }
+ finally {
+ myParsingBound -= 1;
+ }
+ }
+
+ public void parseInterfaceBound(CharacterIterator it, StringBuilder buf) throws SignatureParsingException {
+ myParsingBound += 1;
+ try {
+ super.parseInterfaceBound(it, buf);
+ }
+ finally {
+ myParsingBound -= 1;
+ }
+ }
+
+ public void parseClassTypeSignature(CharacterIterator it, StringBuilder buf) throws SignatureParsingException {
+ if (myParsingBound > 0) {
+ final int start = buf.length();
+
+ super.parseClassTypeSignature(it, buf);
+
+ final String qName = convertToQalifiedName(buf.substring(start + 1, buf.length() - 1));
+ myInterfaceBounds.add(qName);
+ }
+ else {
+ super.parseClassTypeSignature(it, buf);
+ }
+ }
+
+ private static String convertToQalifiedName(String ifaceSignature) {
+ ifaceSignature = ifaceSignature.replaceAll("<.*>", "");
+ return ifaceSignature.replace('/', '.');
+ }
+
+ public String[] getBounds() {
+ return ArrayUtil.toStringArray(myInterfaceBounds);
+ }
+
+ public static String[] getBounds(String classSignature) throws SignatureParsingException {
+ final BoundsParser parser = new BoundsParser();
+ parser.parseClassSignature(new StringCharacterIterator(classSignature), new StringBuilder());
+ return parser.getBounds();
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/make/Cache.java b/java/compiler/impl/src/com/intellij/compiler/make/Cache.java
new file mode 100644
index 0000000..2e3d554
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/make/Cache.java
@@ -0,0 +1,520 @@
+/*
+ * 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 com.intellij.compiler.make;
+
+import com.intellij.compiler.SymbolTable;
+import com.intellij.compiler.classParsing.*;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.cls.ClsFormatException;
+import com.intellij.util.io.DataExternalizer;
+import com.intellij.util.io.EnumeratorIntegerDescriptor;
+import com.intellij.util.io.PersistentHashMap;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Aug 8, 2003
+ * Time: 7:03:56 PM
+ */
+public class Cache {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.make.Cache");
+ public static final int UNKNOWN = -1;
+
+ private final PersistentHashMap<Integer, ClassInfo> myQNameToClassInfoMap;
+
+ private final BackwardDependenciesStorage myDependencies;
+ private final CompilerDependencyStorage<Integer> myQNameToReferencedClassesMap;
+ private final CompilerDependencyStorage<Integer> myQNameToSubclassesMap;
+ private final PersistentHashMap<Integer, Boolean> myRemoteQNames;
+ private final String myStorePath;
+
+ public Cache(@NonNls final String storePath, final int cacheSize) throws IOException {
+ myStorePath = storePath;
+ new File(storePath).mkdirs();
+ myQNameToClassInfoMap = new CachedPersistentHashMap<Integer, ClassInfo>(getOrCreateFile("classes"), EnumeratorIntegerDescriptor.INSTANCE, new DataExternalizer<ClassInfo>() {
+ public void save(DataOutput out, ClassInfo value) throws IOException {
+ value.save(out);
+ }
+ public ClassInfo read(DataInput in) throws IOException {
+ return new ClassInfo(in);
+ }
+ }, cacheSize * 2) {
+ protected boolean isValueDirty(ClassInfo classInfo) {
+ return classInfo.isDirty();
+ }
+ };
+
+ myDependencies = new BackwardDependenciesStorage(getOrCreateFile("bdeps"), cacheSize);
+ myQNameToReferencedClassesMap = new CompilerDependencyStorage<Integer>(getOrCreateFile("fdeps"), EnumeratorIntegerDescriptor.INSTANCE, cacheSize);
+ myQNameToSubclassesMap = new CompilerDependencyStorage<Integer>(getOrCreateFile("subclasses"), EnumeratorIntegerDescriptor.INSTANCE, cacheSize);
+
+ myRemoteQNames = new PersistentHashMap<Integer, Boolean>(getOrCreateFile("remote"), EnumeratorIntegerDescriptor.INSTANCE, new DataExternalizer<Boolean>() {
+ public void save(DataOutput out, Boolean value) throws IOException {
+ out.writeBoolean(value.booleanValue());
+ }
+
+ public Boolean read(DataInput in) throws IOException {
+ return in.readBoolean();
+ }
+ }, cacheSize);
+ }
+
+ private File getOrCreateFile(final String fileName) throws IOException {
+ final File file = new File(myStorePath, fileName);
+ FileUtil.createIfDoesntExist(file);
+ return file;
+ }
+
+ public void dispose() throws CacheCorruptedException {
+ CacheCorruptedException ex = null;
+ try {
+ myQNameToClassInfoMap.close();
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ ex = new CacheCorruptedException(e);
+ }
+ try {
+ myRemoteQNames.close();
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ if (ex != null) {
+ ex = new CacheCorruptedException(e);
+ }
+ }
+
+ myQNameToReferencedClassesMap.dispose();
+ myDependencies.dispose();
+ myQNameToSubclassesMap.dispose();
+
+ if (ex != null) {
+ throw ex;
+ }
+
+ }
+
+ public int[] getAllClassNames() throws CacheCorruptedException {
+ try {
+ final Collection<Integer> allKeys = myQNameToClassInfoMap.getAllKeysWithExistingMapping();
+ final int[] array = ArrayUtil.newIntArray(allKeys.size());
+ int idx = 0;
+ for (Integer id : allKeys) {
+ array[idx++] = id.intValue();
+ }
+ return array;
+ }
+ catch (IOException e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public int importClassInfo(ClassFileReader reader, SymbolTable symbolTable) throws ClsFormatException, CacheCorruptedException {
+ try {
+ final ClassInfo classInfo = new ClassInfo(reader, symbolTable);
+ myQNameToClassInfoMap.put(classInfo.getQualifiedName(), classInfo);
+ return classInfo.getQualifiedName();
+ }
+ catch (IOException e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public void importClassInfo(Cache fromCache, final int qName) throws CacheCorruptedException {
+ try {
+ final ClassInfo classInfo = fromCache.myQNameToClassInfoMap.get(qName);
+ if (classInfo != null) {
+ final ClassInfo clone = classInfo.clone();
+ clone.clearReferences();
+ myQNameToClassInfoMap.put(qName, clone);
+ }
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public AnnotationConstantValue[] getRuntimeVisibleAnnotations(int classId) throws CacheCorruptedException {
+ try {
+ final ClassInfo classInfo = myQNameToClassInfoMap.get(classId);
+ return classInfo != null? classInfo.getRuntimeVisibleAnnotations() : AnnotationConstantValue.EMPTY_ARRAY;
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public AnnotationConstantValue[] getRuntimeInvisibleAnnotations(int classId) throws CacheCorruptedException {
+ try {
+ final ClassInfo classInfo = myQNameToClassInfoMap.get(classId);
+ return classInfo != null? classInfo.getRuntimeInvisibleAnnotations() : AnnotationConstantValue.EMPTY_ARRAY;
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public int[] getSubclasses(int classId) throws CacheCorruptedException {
+ try {
+ return myQNameToSubclassesMap.getValues(classId);
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public void addSubclass(int classId, int subclassQName) throws CacheCorruptedException {
+ try {
+ myQNameToSubclassesMap.addValue(classId, subclassQName);
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public void removeSubclass(int classId, int subclassQName) throws CacheCorruptedException {
+ try {
+ myQNameToSubclassesMap.removeValue(classId, subclassQName);
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public int[] getReferencedClasses(int qName) throws CacheCorruptedException {
+ try {
+ return myQNameToReferencedClassesMap.getValues(qName);
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public void clearReferencedClasses(int qName) throws CacheCorruptedException {
+ try {
+ myQNameToReferencedClassesMap.remove(qName);
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public Collection<ReferenceInfo> getReferences(int classId) throws CacheCorruptedException {
+ try {
+ final ClassInfo classInfo = myQNameToClassInfoMap.get(classId);
+ return classInfo != null? Arrays.asList(classInfo.getReferences()) : Collections.<ReferenceInfo>emptyList();
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public String getSourceFileName(int classId) throws CacheCorruptedException {
+ try {
+ final ClassInfo classInfo = myQNameToClassInfoMap.get(classId);
+ return classInfo != null? classInfo.getSourceFileName() : "";
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public boolean isRemote(int classId) throws CacheCorruptedException {
+ try {
+ return myRemoteQNames.containsMapping(classId);
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public void setRemote(int classId, boolean remote) throws CacheCorruptedException {
+ try {
+ if (remote) {
+ myRemoteQNames.put(classId, Boolean.TRUE);
+ }
+ else {
+ myRemoteQNames.remove(classId);
+ }
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public int getSuperQualifiedName(int classId) throws CacheCorruptedException {
+ try {
+ final ClassInfo classInfo = myQNameToClassInfoMap.get(classId);
+ return classInfo != null? classInfo.getSuperQualifiedName() : UNKNOWN;
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public String getPath(int classId) throws CacheCorruptedException {
+ try {
+ final ClassInfo classInfo = myQNameToClassInfoMap.get(classId);
+ return classInfo != null? classInfo.getPath() : "";
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public void setPath(int classId, String path) throws CacheCorruptedException {
+ try {
+ final ClassInfo classInfo = myQNameToClassInfoMap.get(classId);
+ if (classInfo != null) {
+ classInfo.setPath(path);
+ }
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public int getGenericSignature(int classId) throws CacheCorruptedException {
+ try {
+ final ClassInfo classInfo = myQNameToClassInfoMap.get(classId);
+ return classInfo != null? classInfo.getGenericSignature() : UNKNOWN;
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public boolean containsClass(int qName) throws CacheCorruptedException {
+ try {
+ return myQNameToClassInfoMap.containsMapping(qName);
+ }
+ catch (IOException e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public int[] getSuperInterfaces(int classId) throws CacheCorruptedException {
+ try {
+ final ClassInfo classInfo = myQNameToClassInfoMap.get(classId);
+ return classInfo != null? classInfo.getSuperInterfaces() : ArrayUtil.EMPTY_INT_ARRAY;
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public int getFlags(int classId) throws CacheCorruptedException {
+ try {
+ final ClassInfo classInfo = myQNameToClassInfoMap.get(classId);
+ return classInfo != null? classInfo.getFlags() : UNKNOWN;
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public FieldInfo[] getFields(int qName) throws CacheCorruptedException{
+ try {
+ final ClassInfo classInfo = myQNameToClassInfoMap.get(qName);
+ return classInfo != null? classInfo.getFields() : FieldInfo.EMPTY_ARRAY;
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ @Nullable
+ public FieldInfo findField(final int classDeclarationId, final int name, final int descriptor) throws CacheCorruptedException{
+ try {
+ for (FieldInfo fieldInfo : getFields(classDeclarationId)) {
+ if (fieldInfo.getName() == name && fieldInfo.getDescriptor() == descriptor) {
+ return fieldInfo;
+ }
+ }
+ return null;
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ @Nullable
+ public FieldInfo findFieldByName(final int classDeclarationId, final int name) throws CacheCorruptedException{
+ try {
+ for (FieldInfo fieldInfo : getFields(classDeclarationId)) {
+ if (fieldInfo.getName() == name) {
+ return fieldInfo;
+ }
+ }
+ return null;
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public MethodInfo[] getMethods(int classQName) throws CacheCorruptedException{
+ try {
+ final ClassInfo classInfo = myQNameToClassInfoMap.get(classQName);
+ return classInfo != null? classInfo.getMethods() : MethodInfo.EMPTY_ARRAY;
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ @Nullable
+ public MethodInfo findMethod(final int classQName, final int name, final int descriptor) throws CacheCorruptedException{
+ try {
+ for (MethodInfo methodInfo : getMethods(classQName)) {
+ if (methodInfo.getName() == name && methodInfo.getDescriptor() == descriptor) {
+ return methodInfo;
+ }
+ }
+ return null;
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public List<MethodInfo> findMethodsByName(final int classDeclarationId, final int name) throws CacheCorruptedException{
+ try {
+ final List<MethodInfo> methods = new ArrayList<MethodInfo>();
+ for (MethodInfo methodInfo : getMethods(classDeclarationId)) {
+ if (methodInfo.getName() == name) {
+ methods.add(methodInfo);
+ }
+ }
+ return methods;
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ @Nullable
+ public MethodInfo findMethodsBySignature(final int classDeclarationId, final String signature, SymbolTable symbolTable) throws CacheCorruptedException{
+ try {
+ for (MethodInfo methodInfo : getMethods(classDeclarationId)) {
+ if (signature.equals(CacheUtils.getMethodSignature(symbolTable.getSymbol(methodInfo.getName()), symbolTable.getSymbol(methodInfo.getDescriptor())))) {
+ return methodInfo;
+ }
+ }
+ return null;
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public void addClassReferencer(int qName, int referencerQName) throws CacheCorruptedException {
+ try {
+ if (qName == referencerQName) {
+ return; // do not log self-dependencies
+ }
+ if (myQNameToClassInfoMap.containsMapping(qName)) {
+ myDependencies.addClassReferencer(qName, referencerQName);
+ myQNameToReferencedClassesMap.addValue(referencerQName, qName);
+ }
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public void removeClassReferencer(int qName, int referencerQName) throws CacheCorruptedException {
+ try {
+ myDependencies.removeReferencer(qName, referencerQName);
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public void addFieldReferencer(int qName, int fieldName, int referencerQName) throws CacheCorruptedException {
+ try {
+ if (qName != referencerQName && myQNameToClassInfoMap.containsMapping(qName)) {
+ myDependencies.addFieldReferencer(qName, referencerQName, fieldName);
+ myQNameToReferencedClassesMap.addValue(referencerQName, qName);
+ }
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public void addMethodReferencer(int qName, int methodName, int methodDescriptor, int referencerQName) throws CacheCorruptedException {
+ try {
+ if (qName != referencerQName && myQNameToClassInfoMap.containsMapping(qName)) {
+ myDependencies.addMethodReferencer(qName, referencerQName, methodName, methodDescriptor);
+ myQNameToReferencedClassesMap.addValue(referencerQName, qName);
+ }
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ /** @NotNull */
+ public Dependency[] getBackDependencies(final int classQName) throws CacheCorruptedException{
+ return myDependencies.getDependencies(classQName);
+ }
+
+ public void wipe() {
+ try {
+ dispose();
+ }
+ catch (CacheCorruptedException ignored) {
+ }
+ finally {
+ final File[] files = new File(myStorePath).listFiles();
+ if (files != null) {
+ for (File file : files) {
+ if (!file.isDirectory()) {
+ FileUtil.delete(file);
+ }
+ }
+ }
+ }
+ }
+
+ public void removeClass(final int qName) throws CacheCorruptedException {
+ try {
+ final ClassInfo classInfo = myQNameToClassInfoMap.get(qName);
+ if (classInfo == null) {
+ return;
+ }
+ myDependencies.remove(qName);
+ myQNameToClassInfoMap.remove(qName);
+ myQNameToReferencedClassesMap.remove(qName);
+ myQNameToSubclassesMap.remove(qName);
+ myRemoteQNames.remove(qName);
+ }
+ catch (Throwable e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/make/CacheCorruptedException.java b/java/compiler/impl/src/com/intellij/compiler/make/CacheCorruptedException.java
new file mode 100644
index 0000000..916a370
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/make/CacheCorruptedException.java
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+/*
+ * @author: Eugene Zhuravlev
+ * Date: Jul 10, 2003
+ * Time: 4:51:25 PM
+ */
+package com.intellij.compiler.make;
+
+import com.intellij.openapi.compiler.CompilerBundle;
+
+public class CacheCorruptedException extends Exception{
+ private static final String DEFAULT_MESSAGE = CompilerBundle.message("error.dependency.info.on.disk.corrupted");
+ public CacheCorruptedException(String message) {
+ super((message == null || message.length() == 0)? DEFAULT_MESSAGE : message);
+ }
+
+ public CacheCorruptedException(Throwable cause) {
+ super(DEFAULT_MESSAGE, cause);
+ }
+
+ public CacheCorruptedException(String message, Throwable cause) {
+ super((message == null || message.length() == 0)? DEFAULT_MESSAGE : message, cause);
+ }
+
+ public String getMessage() {
+ return super.getMessage();
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/make/CacheUtils.java b/java/compiler/impl/src/com/intellij/compiler/make/CacheUtils.java
new file mode 100644
index 0000000..bf2a58e
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/make/CacheUtils.java
@@ -0,0 +1,211 @@
+/*
+ * 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 com.intellij.compiler.make;
+
+import com.intellij.compiler.CompilerConfiguration;
+import com.intellij.compiler.CompilerManagerImpl;
+import com.intellij.compiler.SymbolTable;
+import com.intellij.compiler.classParsing.MethodInfo;
+import com.intellij.compiler.impl.ExitException;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.compiler.CompilerMessage;
+import com.intellij.openapi.compiler.CompilerMessageCategory;
+import com.intellij.openapi.compiler.ex.CompileContextEx;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.Function;
+import com.intellij.util.StringBuilderSpinAllocator;
+import gnu.trove.TIntHashSet;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Aug 18, 2003
+ * Time: 6:32:32 PM
+ */
+public class CacheUtils {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.make.CacheUtils");
+
+ public static String[] getParameterSignatures(MethodInfo methodDeclarationId, SymbolTable symbolTable) throws CacheCorruptedException {
+ String descriptor = symbolTable.getSymbol(methodDeclarationId.getDescriptor());
+ int endIndex = descriptor.indexOf(')');
+ if (endIndex <= 0) {
+ LOG.error("Corrupted method descriptor: " + descriptor);
+ }
+ return parseSignature(descriptor.substring(1, endIndex));
+ }
+
+ private static String[] parseSignature(String signature) {
+ final ArrayList<String> list = new ArrayList<String>();
+ String paramSignature = parseParameterSignature(signature);
+ while (paramSignature != null && !"".equals(paramSignature)) {
+ list.add(paramSignature);
+ signature = signature.substring(paramSignature.length());
+ paramSignature = parseParameterSignature(signature);
+ }
+ return ArrayUtil.toStringArray(list);
+ }
+
+ @SuppressWarnings({"HardCodedStringLiteral"})
+ private static @Nullable String parseParameterSignature(String signature) {
+ if (StringUtil.startsWithChar(signature, 'B')) {
+ return "B";
+ }
+ if (StringUtil.startsWithChar(signature, 'C')) {
+ return "C";
+ }
+ if (StringUtil.startsWithChar(signature, 'D')) {
+ return "D";
+ }
+ if (StringUtil.startsWithChar(signature, 'F')) {
+ return "F";
+ }
+ if (StringUtil.startsWithChar(signature, 'I')) {
+ return "I";
+ }
+ if (StringUtil.startsWithChar(signature, 'J')) {
+ return "J";
+ }
+ if (StringUtil.startsWithChar(signature, 'S')) {
+ return "S";
+ }
+ if (StringUtil.startsWithChar(signature, 'Z')) {
+ return "Z";
+ }
+ if (StringUtil.startsWithChar(signature, 'L')) {
+ return signature.substring(0, signature.indexOf(";") + 1);
+ }
+ if (StringUtil.startsWithChar(signature, '[')) {
+ String s = parseParameterSignature(signature.substring(1));
+ return (s != null) ? ("[" + s) : null;
+ }
+ return null;
+ }
+
+ public static String getMethodSignature(String name, String descriptor) {
+ final StringBuilder builder = StringBuilderSpinAllocator.alloc();
+ try {
+ builder.append(name);
+ builder.append(descriptor.substring(0, descriptor.indexOf(')') + 1));
+ return builder.toString();
+ }
+ finally {
+ StringBuilderSpinAllocator.dispose(builder);
+ }
+ }
+
+ public static boolean areArraysContentsEqual(int[] exceptions1, int[] exceptions2) {
+ if (exceptions1.length != exceptions2.length) {
+ return false;
+ }
+ if (exceptions1.length != 0) { // optimization
+ TIntHashSet exceptionsSet = new TIntHashSet(exceptions1);
+ for (int exception : exceptions2) {
+ if (!exceptionsSet.contains(exception)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ public static Collection<VirtualFile> findDependentFiles(
+ final CompileContextEx context,
+ final Set<VirtualFile> compiledWithErrors,
+ final @Nullable Function<Pair<int[], Set<VirtualFile>>, Pair<int[], Set<VirtualFile>>> filter)
+ throws CacheCorruptedException, ExitException {
+
+ if (!CompilerConfiguration.MAKE_ENABLED) {
+ return Collections.emptyList();
+ }
+ context.getProgressIndicator().setText(CompilerBundle.message("progress.checking.dependencies"));
+
+ final DependencyCache dependencyCache = context.getDependencyCache();
+
+ final Pair<int[], Set<VirtualFile>> deps = dependencyCache.findDependentClasses(context, context.getProject(), compiledWithErrors);
+ final Pair<int[], Set<VirtualFile>> filteredDeps = filter != null? filter.fun(deps) : deps;
+
+ final Set<VirtualFile> dependentFiles = new HashSet<VirtualFile>();
+ final CacheCorruptedException[] _ex = {null};
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ try {
+ CompilerConfiguration compilerConfiguration = CompilerConfiguration.getInstance(context.getProject());
+ SourceFileFinder sourceFileFinder = new SourceFileFinder(context.getProject(), context);
+ final Cache cache = dependencyCache.getCache();
+ for (final int infoQName : filteredDeps.getFirst()) {
+ final String qualifiedName = dependencyCache.resolve(infoQName);
+ final String sourceFileName = cache.getSourceFileName(infoQName);
+ final VirtualFile file = sourceFileFinder.findSourceFile(qualifiedName, sourceFileName, true);
+ if (file != null) {
+ dependentFiles.add(file);
+ if (ApplicationManager.getApplication().isUnitTestMode()) {
+ LOG.assertTrue(file.isValid());
+ CompilerManagerImpl.addRecompiledPath(file.getPath());
+ }
+ }
+ else {
+ LOG.info("No source file for " + dependencyCache.resolve(infoQName) + " found; source file name=" + sourceFileName);
+ }
+ }
+ for (final VirtualFile file : filteredDeps.getSecond()) {
+ if (!compilerConfiguration.isExcludedFromCompilation(file)) {
+ dependentFiles.add(file);
+ if (ApplicationManager.getApplication().isUnitTestMode()) {
+ LOG.assertTrue(file.isValid());
+ CompilerManagerImpl.addRecompiledPath(file.getPath());
+ }
+ }
+ }
+ }
+ catch (CacheCorruptedException e) {
+ _ex[0] = e;
+ }
+ }
+ });
+ if (_ex[0] != null) {
+ throw _ex[0];
+ }
+ context.getProgressIndicator().setText(
+ dependentFiles.size() > 0? CompilerBundle.message("progress.found.dependent.files", dependentFiles.size()) : ""
+ );
+
+ return dependentFiles;
+ }
+
+ @NotNull
+ public static Set<VirtualFile> getFilesCompiledWithErrors(final CompileContextEx context) {
+ CompilerMessage[] messages = context.getMessages(CompilerMessageCategory.ERROR);
+ Set<VirtualFile> compiledWithErrors = Collections.emptySet();
+ if (messages.length > 0) {
+ compiledWithErrors = new HashSet<VirtualFile>(messages.length);
+ for (CompilerMessage message : messages) {
+ final VirtualFile file = message.getVirtualFile();
+ if (file != null) {
+ compiledWithErrors.add(file);
+ }
+ }
+ }
+ return compiledWithErrors;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/make/CachedPersistentHashMap.java b/java/compiler/impl/src/com/intellij/compiler/make/CachedPersistentHashMap.java
new file mode 100644
index 0000000..b8b45cb
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/make/CachedPersistentHashMap.java
@@ -0,0 +1,115 @@
+/*
+ * 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 com.intellij.compiler.make;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.util.containers.SLRUMap;
+import com.intellij.util.io.DataExternalizer;
+import com.intellij.util.io.KeyDescriptor;
+import com.intellij.util.io.PersistentHashMap;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Dec 1, 2008
+ */
+public class CachedPersistentHashMap<Key, Value> extends PersistentHashMap<Key, Value> {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.make.CachedPersistentHashMap");
+ protected final SLRUMap<Key, Value> myCache;
+
+ public CachedPersistentHashMap(File file, KeyDescriptor<Key> keyDescriptor, DataExternalizer<Value> valDescriptor, final int cacheSize) throws IOException {
+ super(file, keyDescriptor, valDescriptor);
+ myCache = new SLRUMap<Key,Value>(cacheSize * 2, cacheSize) {
+ protected void onDropFromCache(Key key, Value value) {
+ if (isValueDirty(value)) {
+ try {
+ CachedPersistentHashMap.super.put(key, value);
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ }
+ }
+ }
+ };
+ }
+
+ protected boolean isValueDirty(Value value) {
+ return false;
+ }
+
+ @Override
+ protected void doPut(Key key, Value value) throws IOException {
+ myCache.remove(key);
+ super.doPut(key, value);
+ }
+
+ @Override
+ protected void doAppendData(Key key, ValueDataAppender appender) throws IOException {
+ myCache.remove(key);
+ super.doAppendData(key, appender);
+ }
+
+ @Nullable
+ protected Value doGet(Key key) throws IOException {
+ Value value = myCache.get(key);
+ if (value == null) {
+ value = super.doGet(key);
+ if (value != null) {
+ myCache.put(key, value);
+ }
+ }
+ return value;
+ }
+
+ @Override
+ protected boolean doContainsMapping(Key key) throws IOException {
+ final Value value = myCache.get(key);
+ return value != null || super.doContainsMapping(key);
+ }
+
+ @Override
+ protected void doRemove(Key key) throws IOException {
+ myCache.remove(key);
+ super.doRemove(key);
+ }
+
+ @Override
+ protected void doForce() {
+ try {
+ clearCache();
+ }
+ finally {
+ super.doForce();
+ }
+ }
+
+ @Override
+ protected void doClose() throws IOException {
+ try {
+ clearCache();
+ }
+ finally {
+ super.doClose();
+ }
+ }
+
+ private void clearCache() {
+ myCache.clear();
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/make/CachingSearcher.java b/java/compiler/impl/src/com/intellij/compiler/make/CachingSearcher.java
new file mode 100644
index 0000000..8ac89b3
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/make/CachingSearcher.java
@@ -0,0 +1,57 @@
+/*
+ * 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 com.intellij.compiler.make;
+
+import com.intellij.openapi.fileTypes.StdFileTypes;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Pair;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReference;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.search.searches.ReferencesSearch;
+import com.intellij.util.Query;
+import com.intellij.util.containers.SoftHashMap;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Oct 15
+ * @author 2003
+ */
+public class CachingSearcher {
+ private final Project myProject;
+ private final Map<Pair<PsiElement, Boolean>, Collection<PsiReference>> myElementToReferencersMap = new SoftHashMap<Pair<PsiElement, Boolean>, Collection<PsiReference>>();
+
+ public CachingSearcher(Project project) {
+ myProject = project;
+ }
+
+ public Collection<PsiReference> findReferences(PsiElement element, final boolean ignoreAccessScope) {
+ final Pair<PsiElement, Boolean> key = new Pair<PsiElement, Boolean>(element, ignoreAccessScope? Boolean.TRUE : Boolean.FALSE);
+ Collection<PsiReference> psiReferences = myElementToReferencersMap.get(key);
+ if (psiReferences == null) {
+ GlobalSearchScope searchScope = GlobalSearchScope.projectScope(myProject);
+ searchScope = GlobalSearchScope.getScopeRestrictedByFileTypes(searchScope, StdFileTypes.JAVA);
+ final Query<PsiReference> query = ReferencesSearch.search(element, searchScope, ignoreAccessScope);
+ psiReferences = query.findAll();
+ myElementToReferencersMap.put(key, psiReferences);
+ }
+ return psiReferences;
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/make/ChangeDescription.java b/java/compiler/impl/src/com/intellij/compiler/make/ChangeDescription.java
new file mode 100644
index 0000000..1f5f8a3
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/make/ChangeDescription.java
@@ -0,0 +1,24 @@
+/*
+ * 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 com.intellij.compiler.make;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Apr 7, 2004
+ */
+abstract class ChangeDescription {
+ public abstract boolean isChanged();
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/make/ChangedConstantsDependencyProcessor.java b/java/compiler/impl/src/com/intellij/compiler/make/ChangedConstantsDependencyProcessor.java
new file mode 100644
index 0000000..bd919a6
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/make/ChangedConstantsDependencyProcessor.java
@@ -0,0 +1,373 @@
+/*
+ * 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.
+ */
+
+/**
+ * created at Feb 24, 2002
+ * @author Jeka
+ */
+package com.intellij.compiler.make;
+
+import com.intellij.compiler.classParsing.FieldInfo;
+import com.intellij.compiler.impl.ExitException;
+import com.intellij.compiler.impl.ExitStatus;
+import com.intellij.lang.StdLanguages;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.openapi.project.DumbService;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.util.registry.Registry;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.*;
+import com.intellij.psi.search.*;
+import com.intellij.psi.util.PsiUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+public class ChangedConstantsDependencyProcessor {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.make.ChangedConstantsDependencyProcessor");
+ private final Project myProject;
+ private final CachingSearcher mySearcher;
+ private final DependencyCache myDependencyCache;
+ private final int myQName;
+ private final CompileContext myContext;
+ private final FieldChangeInfo[] myChangedFields;
+ private final FieldChangeInfo[] myRemovedFields;
+ private final int MAX_CONSTANT_SEARCHES = Registry.intValue("compiler.max.static.constants.searches");
+
+
+ public ChangedConstantsDependencyProcessor(Project project,
+ CachingSearcher searcher,
+ DependencyCache dependencyCache,
+ int qName, CompileContext context, FieldChangeInfo[] changedFields,
+ FieldChangeInfo[] removedFields) {
+ myProject = project;
+ mySearcher = searcher;
+ myDependencyCache = dependencyCache;
+ myQName = qName;
+ myContext = context;
+ myChangedFields = changedFields;
+ myRemovedFields = removedFields;
+ }
+
+ public void run() throws CacheCorruptedException, ExitException {
+ final Ref<CacheCorruptedException> _ex = new Ref<CacheCorruptedException>();
+ final Ref<ExitException> exitException = new Ref<ExitException>(null);
+ DumbService.getInstance(myProject).waitForSmartMode(); // ensure running in smart mode
+
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ try {
+ final String qName = myDependencyCache.resolve(myQName);
+ PsiClass[] classes = JavaPsiFacade.getInstance(myProject).findClasses(qName.replace('$', '.'), GlobalSearchScope.allScope(myProject));
+ for (PsiClass aClass : classes) {
+ PsiField[] psiFields = aClass.getFields();
+ for (PsiField psiField : psiFields) {
+ final FieldChangeInfo changeInfo = findChangeInfo(psiField);
+ if (changeInfo != null) { // this field has been changed
+ processFieldChanged(psiField, aClass, changeInfo.isAccessibilityChange);
+ }
+ }
+ for (FieldChangeInfo removedField : myRemovedFields) {
+ processFieldRemoved(removedField.fieldInfo, aClass);
+ }
+ }
+ }
+ catch (CacheCorruptedException e) {
+ _ex.set(e);
+ }
+ catch (ExitException e) {
+ exitException.set(e);
+ }
+ catch (ProcessCanceledException e) {
+ // supressed deliberately
+ }
+ }
+ });
+ if (_ex.get() != null) {
+ throw _ex.get();
+ }
+ if (exitException.get() != null) {
+ throw exitException.get();
+ }
+ }
+
+ private void processFieldRemoved(FieldInfo info, final PsiClass aClass) throws CacheCorruptedException {
+ if (info.isPrivate()) {
+ return; // optimization: don't need to search, cause may be used only in this class
+ }
+ SearchScope searchScope = GlobalSearchScope.projectScope(myProject);
+ if (info.isPackageLocal()) {
+ final PsiFile containingFile = aClass.getContainingFile();
+ if (containingFile instanceof PsiJavaFile) {
+ final String packageName = ((PsiJavaFile)containingFile).getPackageName();
+ final PsiPackage aPackage = JavaPsiFacade.getInstance(myProject).findPackage(packageName);
+ if (aPackage != null) {
+ searchScope = PackageScope.packageScope(aPackage, false);
+ searchScope = searchScope.intersectWith(aClass.getUseScope());
+ }
+ }
+ }
+ final PsiSearchHelper psiSearchHelper = PsiSearchHelper.SERVICE.getInstance(myProject);
+
+ final Ref<CacheCorruptedException> exRef = new Ref<CacheCorruptedException>(null);
+ processIdentifiers(psiSearchHelper, new PsiElementProcessor<PsiIdentifier>() {
+ @Override
+ public synchronized boolean execute(@NotNull PsiIdentifier identifier) {
+ try {
+ final PsiElement parent = identifier.getParent();
+ if (parent instanceof PsiReferenceExpression) {
+ final PsiClass ownerClass = getOwnerClass(parent);
+ if (ownerClass != null && !ownerClass.equals(aClass)) {
+ final String _qName = ownerClass.getQualifiedName();
+ if (_qName != null) {
+ int qualifiedName = myDependencyCache.getSymbolTable().getId(_qName);
+ // should force marking of the class no matter was it compiled or not
+ // This will ensure the class was recompiled _after_ all the constants get their new values
+ if (myDependencyCache.markClass(qualifiedName, true)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(qualifiedName) + "; reason: some constants were removed from " + myDependencyCache.resolve(myQName));
+ }
+ }
+ }
+ else {
+ LOG.warn("Class with null qualified name was not expected here: " + ownerClass);
+ }
+ }
+ }
+ return true;
+ }
+ catch (CacheCorruptedException e) {
+ exRef.set(e);
+ return false;
+ }
+ }
+ }, myDependencyCache.resolve(info.getName()), searchScope, UsageSearchContext.IN_CODE);
+
+ final CacheCorruptedException cacheCorruptedException = exRef.get();
+ if (cacheCorruptedException != null) {
+ throw cacheCorruptedException;
+ }
+ }
+
+ private static boolean processIdentifiers(PsiSearchHelper helper,
+ @NotNull final PsiElementProcessor<PsiIdentifier> processor,
+ @NotNull final String identifier,
+ @NotNull SearchScope searchScope,
+ short searchContext) {
+ TextOccurenceProcessor processor1 = new TextOccurenceProcessor() {
+ public boolean execute(PsiElement element, int offsetInElement) {
+ return !(element instanceof PsiIdentifier) || processor.execute((PsiIdentifier)element);
+ }
+ };
+ return helper.processElementsWithWord(processor1, searchScope, identifier, searchContext, true);
+ }
+
+ private void processFieldChanged(PsiField field, PsiClass aClass, final boolean isAccessibilityChange)
+ throws CacheCorruptedException, ExitException {
+ if (!isAccessibilityChange && field.hasModifierProperty(PsiModifier.PRIVATE)) {
+ return; // optimization: don't need to search, cause may be used only in this class
+ }
+ Set<PsiElement> usages = new HashSet<PsiElement>();
+ addUsages(field, usages, isAccessibilityChange);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("++++++++++++++++++++++++++++++++++++++++++++++++");
+ LOG.debug("Processing changed field: " + aClass.getQualifiedName() + "." + field.getName());
+ }
+ for (final PsiElement usage : usages) {
+ PsiClass ownerClass = getOwnerClass(usage);
+ if (LOG.isDebugEnabled()) {
+ if (ownerClass != null) {
+ LOG.debug("Usage " + usage + " found in class: " + ownerClass.getQualifiedName());
+ }
+ else {
+ LOG.debug("Usage " + usage + " found in class: null");
+ }
+ }
+ if (ownerClass != null && !ownerClass.equals(aClass)) {
+ int qualifiedName = myDependencyCache.getSymbolTable().getId(ownerClass.getQualifiedName());
+ // should force marking of the class no matter was it compiled or not
+ // This will ensure the class was recompiled _after_ all the constants get their new values
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Marking class id = [" + qualifiedName + "], name=[" + myDependencyCache.resolve(qualifiedName) + "]");
+ }
+ if (myDependencyCache.markClass(qualifiedName, true)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Marked dependent class " + myDependencyCache.resolve(qualifiedName) + "; reason: constants changed in " +
+ myDependencyCache.resolve(myQName));
+ }
+ }
+ }
+ else if (ownerClass == null) {
+ final PsiFile containingFile = usage.getContainingFile();
+ if (containingFile != null) {
+ final VirtualFile file = containingFile.getVirtualFile();
+ if (file != null) {
+ myDependencyCache.markFile(file);
+ }
+ }
+ }
+ }
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("+++++++++++++++++++++++++++++++++++++++++++++++");
+ }
+ }
+
+ private void addUsages(PsiField psiField, Collection<PsiElement> usages, final boolean ignoreAccessScope) throws ExitException {
+ final int count = getConstantSearchesCount();
+ if (count > MAX_CONSTANT_SEARCHES) {
+ myContext.requestRebuildNextTime("Too many changed compile-time constants, project rebuild scheduled");
+ throw new ExitException(ExitStatus.CANCELLED);
+ }
+ Collection<PsiReference> references = mySearcher.findReferences(psiField, ignoreAccessScope)/*doFindReferences(searchHelper, psiField)*/;
+
+ incConstantSearchesCount();
+
+ for (final PsiReference ref : references) {
+ if (!(ref instanceof PsiReferenceExpression)) {
+ continue;
+ }
+ PsiElement e = ref.getElement();
+ usages.add(e);
+ PsiField ownerField = getOwnerField(e);
+ if (ownerField != null) {
+ if (ownerField.hasModifierProperty(PsiModifier.FINAL)) {
+ PsiExpression initializer = ownerField.getInitializer();
+ if (initializer != null && PsiUtil.isConstantExpression(initializer)) {
+ // if the field depends on the compile-time-constant expression and is itself final
+ addUsages(ownerField, usages, ignoreAccessScope);
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ private PsiReference[] doFindReferences(final PsiSearchHelper searchHelper, final PsiField psiField) {
+ final ProgressManager progressManager = ProgressManager.getInstance();
+ final ProgressIndicator currentProgress = progressManager.getProgressIndicator();
+ final PsiReference[][] references = new PsiReference[][] {null};
+ progressManager.runProcess(new Runnable() {
+ public void run() {
+ references[0] = searchHelper.findReferences(psiField, GlobalSearchScope.projectScope(myProject), false);
+ if (ENABLE_TRACING) {
+ System.out.println("Finding referencers for " + psiField);
+ }
+ }
+ }, new NonCancellableProgressAdapter(currentProgress));
+ return references[0];
+ }
+ */
+
+ @Nullable
+ private static PsiField getOwnerField(PsiElement element) {
+ while (!(element instanceof PsiFile)) {
+ if (element instanceof PsiClass) {
+ break;
+ }
+ if (element instanceof PsiField) { // top-level class
+ return (PsiField)element;
+ }
+ element = element.getParent();
+ }
+ return null;
+ }
+
+ @Nullable
+ private FieldChangeInfo findChangeInfo(PsiField field) throws CacheCorruptedException {
+ String name = field.getName();
+ for (final FieldChangeInfo changeInfo : myChangedFields) {
+ if (name.equals(myDependencyCache.resolve(changeInfo.fieldInfo.getName()))) {
+ return changeInfo;
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ private static PsiClass getOwnerClass(PsiElement element) {
+ while (!(element instanceof PsiFile)) {
+ if (element instanceof PsiClass && element.getParent() instanceof PsiJavaFile) { // top-level class
+ final PsiClass psiClass = (PsiClass)element;
+ if (JspPsiUtil.isInJspFile(psiClass)) {
+ return null;
+ }
+ final PsiFile containingFile = psiClass.getContainingFile();
+ if (containingFile == null) {
+ return null;
+ }
+ return StdLanguages.JAVA.equals(containingFile.getLanguage())? psiClass : null;
+ }
+ element = element.getParent();
+ }
+ return null;
+ }
+
+ public static class FieldChangeInfo {
+ final FieldInfo fieldInfo;
+ final boolean isAccessibilityChange;
+
+ public FieldChangeInfo(final FieldInfo fieldId) {
+ this(fieldId, false);
+ }
+
+ public FieldChangeInfo(final FieldInfo fieldInfo, final boolean accessibilityChange) {
+ this.fieldInfo = fieldInfo;
+ isAccessibilityChange = accessibilityChange;
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ final FieldChangeInfo fieldChangeInfo = (FieldChangeInfo)o;
+
+ if (isAccessibilityChange != fieldChangeInfo.isAccessibilityChange) return false;
+ if (!fieldInfo.equals(fieldChangeInfo.fieldInfo)) return false;
+
+ return true;
+ }
+
+ public int hashCode() {
+ int result;
+ result = fieldInfo.hashCode();
+ result = 29 * result + (isAccessibilityChange ? 1 : 0);
+ return result;
+ }
+ }
+
+ private static final Key<Integer> CONSTANTS_COUNTER = Key.create("_constant_searches_counter_");
+
+ private int getConstantSearchesCount() {
+ return getConstantSearchesCount(myContext);
+ }
+
+ public static int getConstantSearchesCount(CompileContext context) {
+ final Integer value = CONSTANTS_COUNTER.get(context);
+ return value != null? value.intValue() : 0;
+ }
+
+ private void incConstantSearchesCount() {
+ CONSTANTS_COUNTER.set(myContext, getConstantSearchesCount() + 1);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/make/ChangedRetentionPolicyDependencyProcessor.java b/java/compiler/impl/src/com/intellij/compiler/make/ChangedRetentionPolicyDependencyProcessor.java
new file mode 100644
index 0000000..314fce9
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/make/ChangedRetentionPolicyDependencyProcessor.java
@@ -0,0 +1,107 @@
+/*
+ * 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 com.intellij.compiler.make;
+
+import com.intellij.compiler.SymbolTable;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.*;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.util.cls.ClsUtil;
+
+import java.util.Collection;
+
+public class ChangedRetentionPolicyDependencyProcessor {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.make.ChangedConstantsDependencyProcessor");
+ private final Project myProject;
+ private final CachingSearcher mySearcher;
+ private final DependencyCache myDependencyCache;
+
+ public ChangedRetentionPolicyDependencyProcessor(Project project, CachingSearcher searcher, DependencyCache dependencyCache) {
+ myProject = project;
+ mySearcher = searcher;
+ myDependencyCache = dependencyCache;
+ }
+
+ public void checkAnnotationRetentionPolicyChanges(final int annotationQName) throws CacheCorruptedException {
+ final Cache oldCache = myDependencyCache.getCache();
+ if (!ClsUtil.isAnnotation(oldCache.getFlags(annotationQName))) {
+ return;
+ }
+ if (!hasRetentionPolicyChanged(annotationQName, oldCache, myDependencyCache.getNewClassesCache(), myDependencyCache.getSymbolTable())) {
+ return;
+ }
+ final CacheCorruptedException[] _ex = new CacheCorruptedException[] {null};
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ try {
+ final String qName = myDependencyCache.resolve(annotationQName);
+ PsiClass[] classes = JavaPsiFacade.getInstance(myProject).findClasses(qName.replace('$', '.'), GlobalSearchScope.allScope(myProject));
+ for (final PsiClass aClass : classes) {
+ if (!aClass.isAnnotationType()) {
+ continue;
+ }
+ final Collection<PsiReference> references = mySearcher.findReferences(aClass, true);
+ for (PsiReference reference : references) {
+ final PsiClass ownerClass = getOwnerClass(reference.getElement());
+ if (ownerClass != null && !ownerClass.equals(aClass)) {
+ int qualifiedName = myDependencyCache.getSymbolTable().getId(ownerClass.getQualifiedName());
+ if (myDependencyCache.markClass(qualifiedName, false)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Marked dependent class " + myDependencyCache.resolve(qualifiedName) +
+ "; reason: annotation's retention policy changed from SOURCE to CLASS or RUNTIME " +
+ myDependencyCache.resolve(annotationQName));
+ }
+ }
+ }
+ }
+ }
+ }
+ catch (CacheCorruptedException e) {
+ _ex[0] = e;
+ }
+ catch (ProcessCanceledException e) {
+ // supressed deliberately
+ }
+ }
+ });
+ if (_ex[0] != null) {
+ throw _ex[0];
+ }
+ }
+
+ private boolean hasRetentionPolicyChanged(int annotationQName, final Cache oldCache, final Cache newCache, SymbolTable symbolTable) throws CacheCorruptedException {
+ // if retention policy changed from SOURCE to CLASS or RUNTIME, all sources should be recompiled to propagate changes
+ final int oldPolicy = MakeUtil.getAnnotationRetentionPolicy(annotationQName, oldCache, symbolTable);
+ final int newPolicy = MakeUtil.getAnnotationRetentionPolicy(annotationQName, newCache, symbolTable);
+ if ((oldPolicy == RetentionPolicies.SOURCE) && (newPolicy == RetentionPolicies.CLASS || newPolicy == RetentionPolicies.RUNTIME)) {
+ return true;
+ }
+ return false;
+ }
+
+ private static PsiClass getOwnerClass(PsiElement element) {
+ while (!(element instanceof PsiFile)) {
+ if (element instanceof PsiClass && element.getParent() instanceof PsiJavaFile) { // top-level class
+ return (PsiClass)element;
+ }
+ element = element.getParent();
+ }
+ return null;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/make/ClassInfoProcessor.java b/java/compiler/impl/src/com/intellij/compiler/make/ClassInfoProcessor.java
new file mode 100644
index 0000000..5298a8c
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/make/ClassInfoProcessor.java
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+/*
+ * @author: Eugene Zhuravlev
+ * Date: May 14, 2003
+ * Time: 10:42:29 AM
+ */
+package com.intellij.compiler.make;
+
+
+
+public interface ClassInfoProcessor {
+ /**
+ * @param classQName of a class info to be processed
+ * @return true if superclasses of info should be processed and false otherwise
+ */
+ boolean process(int classQName) throws CacheCorruptedException;
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/make/CompilerDependencyStorage.java b/java/compiler/impl/src/com/intellij/compiler/make/CompilerDependencyStorage.java
new file mode 100644
index 0000000..ea93f21
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/make/CompilerDependencyStorage.java
@@ -0,0 +1,257 @@
+/*
+ * 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 com.intellij.compiler.make;
+
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.util.Ref;
+import com.intellij.util.containers.SLRUCache;
+import com.intellij.util.io.DataExternalizer;
+import com.intellij.util.io.KeyDescriptor;
+import com.intellij.util.io.PersistentHashMap;
+import gnu.trove.TIntHashSet;
+import gnu.trove.TIntProcedure;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.*;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Dec 1, 2008
+ */
+public class CompilerDependencyStorage<Key> implements Flushable, Disposable {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.make.CompilerDependencyStorage");
+ protected final PersistentHashMap<Key, int[]> myMap;
+ protected final SLRUCache<Key, IntSet> myCache;
+ private Key myKeyToRemove;
+
+ public CompilerDependencyStorage(File file, KeyDescriptor<Key> keyDescriptor, final int cacheSize) throws IOException {
+ myMap = new PersistentHashMap<Key, int[]>(file, keyDescriptor, new DataExternalizer<int[]>() {
+ public void save(DataOutput out, int[] array) throws IOException {
+ out.writeInt(array.length);
+ for (int value : array) {
+ out.writeInt(value);
+ }
+ }
+
+ public int[] read(DataInput in) throws IOException {
+ final TIntHashSet set = new TIntHashSet();
+ DataInputStream stream = (DataInputStream)in;
+ while(stream.available() > 0) {
+ final int size = stream.readInt();
+ final int _size = Math.abs(size);
+ for (int idx = 0; idx < _size; idx++) {
+ if (size > 0) {
+ set.add(stream.readInt());
+ }
+ else {
+ set.remove(stream.readInt());
+ }
+ }
+ }
+ return set.toArray();
+ }
+ });
+
+ myCache = new SLRUCache<Key, IntSet>(cacheSize * 2, cacheSize) {
+ @NotNull
+ public IntSet createValue(Key key) {
+ return new IntSet(key);
+ }
+
+ protected void onDropFromCache(Key key, final IntSet set) {
+ if (key == myKeyToRemove || !set.isDirty()) {
+ return;
+ }
+ try {
+ if (set.needsCompacting()) {
+ myMap.put(key, set.getValues());
+ }
+ else {
+ myMap.appendData(key, new PersistentHashMap.ValueDataAppender() {
+ public void append(final DataOutput out) throws IOException {
+ final Ref<IOException> exception = new Ref<IOException>(null);
+ final TIntProcedure saveProc = new TIntProcedure() {
+ public boolean execute(int value) {
+ try {
+ out.writeInt(value);
+ return true;
+ }
+ catch (IOException e) {
+ exception.set(e);
+ return false;
+ }
+ }
+ };
+
+ out.writeInt(-set.getRemovedCount());
+ set.processRemovedValues(saveProc);
+ if (exception.get() != null) {
+ throw exception.get();
+ }
+
+ out.writeInt(set.getAddedCount());
+ set.processAddedValues(saveProc);
+ if (exception.get() != null) {
+ throw exception.get();
+ }
+ }
+ });
+ }
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
+ };
+ }
+
+ public synchronized void remove(Key key) throws IOException {
+ myKeyToRemove = key;
+ try {
+ myCache.remove(key);
+ }
+ finally {
+ myKeyToRemove = null;
+ }
+ myMap.remove(key);
+ }
+
+ public synchronized void removeValue(Key key, int value) throws IOException {
+ final IntSet set = myCache.get(key);
+ set.remove(value);
+ if (set.needsFlushing()) {
+ flush(key);
+ }
+ }
+
+
+ public synchronized void addValue(Key key, int value) throws IOException {
+ final IntSet set = myCache.get(key);
+ set.add(value);
+ if (set.needsFlushing()) {
+ flush(key);
+ }
+ }
+
+ public synchronized int[] getValues(Key key) throws IOException {
+ return myCache.get(key).getValues();
+ }
+
+
+ public synchronized void flush() throws IOException {
+ myCache.clear();
+ myMap.force();
+ }
+
+ private void flush(Key key) {
+ myCache.remove(key); // makes changes into PersistentHashMap
+ myMap.force(); // flushes internal caches (which consume memory) and writes unsaved data to disk
+ }
+
+ public synchronized void dispose() {
+ try {
+ flush();
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ }
+ try {
+ myMap.close();
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ }
+ }
+
+ private class IntSet {
+ private final TIntHashSet myAdded = new TIntHashSet();
+ private final TIntHashSet myRemoved = new TIntHashSet();
+ private TIntHashSet myMerged = null;
+ private final Key myKey;
+
+ public IntSet(Key key) {
+ myKey = key;
+ }
+
+ public void add(int value) {
+ if (myMerged != null) {
+ myMerged.add(value);
+ }
+ if (!myRemoved.remove(value)) {
+ myAdded.add(value);
+ }
+ }
+
+ public void remove(int value) {
+ if (myMerged != null) {
+ myMerged.remove(value);
+ }
+ if (!myAdded.remove(value)) {
+ myRemoved.add(value);
+ }
+ }
+
+ public boolean isDirty() {
+ return myAdded.size() > 0 || myRemoved.size() > 0;
+ }
+
+ public boolean needsCompacting() {
+ return myMerged != null;
+ }
+
+ public boolean needsFlushing() {
+ return myAdded.size() > 3000 || myRemoved.size() > 3000;
+ }
+
+ public int getAddedCount() {
+ return myAdded.size();
+ }
+
+ public void processAddedValues(final TIntProcedure procedure) {
+ myAdded.forEach(procedure);
+ }
+
+ public int getRemovedCount() {
+ return myRemoved.size();
+ }
+
+ public void processRemovedValues(final TIntProcedure procedure) {
+ myRemoved.forEach(procedure);
+ }
+
+ public int[] getValues() throws IOException {
+ return getMerged().toArray();
+ }
+
+ private TIntHashSet getMerged() throws IOException {
+ if (myMerged == null) {
+ myMerged = new TIntHashSet();
+ final int[] fromDisk = myMap.get(myKey);
+ if (fromDisk != null) {
+ myMerged.addAll(fromDisk);
+ }
+ if (myRemoved.size() > 0) {
+ myMerged.removeAll(myRemoved.toArray());
+ }
+ if (myAdded.size() > 0) {
+ myMerged.addAll(myAdded.toArray());
+ }
+ }
+ return myMerged;
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/make/Dependency.java b/java/compiler/impl/src/com/intellij/compiler/make/Dependency.java
new file mode 100644
index 0000000..bd2c5cf
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/make/Dependency.java
@@ -0,0 +1,188 @@
+/*
+ * 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.
+ */
+
+/**
+ * created at Jan 4, 2002
+ * @author Jeka
+ */
+package com.intellij.compiler.make;
+
+import com.intellij.compiler.SymbolTable;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.util.ArrayUtil;
+import org.jetbrains.annotations.NonNls;
+
+import java.util.*;
+
+public class Dependency {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.make.Dependency");
+ public static final Dependency[] EMPTY_ARRAY = new Dependency[0];
+ private final int myClassQualifiedName;
+ private Set<FieldRef> myUsedFields;
+ private Set<MethodRef> myUsedMethods;
+
+ public static class FieldRef {
+ public final int name;
+
+ public FieldRef(int name) {
+ this.name = name;
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ FieldRef fieldRef = (FieldRef)o;
+
+ if (name != fieldRef.name) return false;
+
+ return true;
+ }
+
+ public int hashCode() {
+ return name;
+ }
+ }
+
+ public static class MethodRef {
+ public final int name;
+ public final int descriptor;
+ private String[] myParameterDescriptors;
+
+ public MethodRef(int name, int descriptor) {
+ this.name = name;
+ this.descriptor = descriptor;
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ MethodRef methodRef = (MethodRef)o;
+
+ if (descriptor != methodRef.descriptor) return false;
+ if (name != methodRef.name) return false;
+
+ return true;
+ }
+
+ public int hashCode() {
+ int result = name;
+ result = 31 * result + descriptor;
+ return result;
+ }
+
+ public String getDescriptor(SymbolTable symbolTable) throws CacheCorruptedException {
+ final String descriptorStr = symbolTable.getSymbol(descriptor);
+ final String nameStr = symbolTable.getSymbol(name);
+ return CacheUtils.getMethodSignature(nameStr, descriptorStr);
+ }
+
+
+ public String[] getParameterDescriptors(SymbolTable symbolTable) throws CacheCorruptedException {
+ if (myParameterDescriptors == null) {
+ String descriptorStr = symbolTable.getSymbol(descriptor);
+ int endIndex = descriptorStr.indexOf(')');
+ if (endIndex <= 0) {
+ LOG.error("Corrupted method descriptor: " + descriptorStr);
+ }
+ myParameterDescriptors = parseParameterDescriptors(descriptorStr.substring(1, endIndex));
+ }
+ return myParameterDescriptors;
+ }
+ }
+
+ public Dependency(int classQualifiedName) {
+ myClassQualifiedName = classQualifiedName;
+ }
+
+ public int getClassQualifiedName() {
+ return myClassQualifiedName;
+ }
+
+ public void addMethod(int name, int descriptor) {
+ if (myUsedMethods == null) {
+ myUsedMethods = new HashSet<MethodRef>();
+ }
+ myUsedMethods.add(new MethodRef(name, descriptor));
+ }
+
+ public void addField(int name) {
+ if (myUsedFields == null) {
+ myUsedFields = new HashSet<FieldRef>();
+ }
+ myUsedFields.add(new FieldRef(name));
+ }
+
+ public Collection<FieldRef> getFieldRefs() {
+ return myUsedFields != null? Collections.unmodifiableSet(myUsedFields) : Collections.<FieldRef>emptySet();
+ }
+
+ public Collection<MethodRef> getMethodRefs() {
+ return myUsedMethods != null? Collections.unmodifiableSet(myUsedMethods) : Collections.<MethodRef>emptySet();
+ }
+
+ private static String[] parseParameterDescriptors(String signature) {
+ ArrayList<String> list = new ArrayList<String>();
+ String paramSignature = parseFieldType(signature);
+ while (paramSignature != null && !paramSignature.isEmpty()) {
+ list.add(paramSignature);
+ signature = signature.substring(paramSignature.length());
+ paramSignature = parseFieldType(signature);
+ }
+ return ArrayUtil.toStringArray(list);
+ }
+
+ private static String parseFieldType(@NonNls String signature) {
+ if (signature.isEmpty()) {
+ return null;
+ }
+ char first = signature.charAt(0);
+ if (first == 'I') {
+ return "I";
+ }
+ if (first == 'L') {
+ return signature.substring(0, signature.indexOf(';') + 1);
+ }
+ if (first == 'B') {
+ return "B";
+ }
+ if (first == 'C') {
+ return "C";
+ }
+ if (first == 'D') {
+ return "D";
+ }
+ if (first == 'F') {
+ return "F";
+ }
+ if (first == 'J') {
+ return "J";
+ }
+ if (first == 'S') {
+ return "S";
+ }
+ if (first == 'Z') {
+ return "Z";
+ }
+ if (first == '[') {
+ String s = parseFieldType(signature.substring(1));
+ return s == null ? null : "[" + s;
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/make/DependencyCache.java b/java/compiler/impl/src/com/intellij/compiler/make/DependencyCache.java
new file mode 100644
index 0000000..92f3285
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/make/DependencyCache.java
@@ -0,0 +1,659 @@
+/*
+ * 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.
+ */
+
+/**
+ * created at Jan 7, 2002
+ * @author Jeka
+ */
+package com.intellij.compiler.make;
+
+import com.intellij.compiler.DependencyProcessor;
+import com.intellij.compiler.SymbolTable;
+import com.intellij.compiler.classParsing.*;
+import com.intellij.compiler.impl.ExitException;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.cls.ClsFormatException;
+import com.intellij.util.cls.ClsUtil;
+import gnu.trove.TIntHashSet;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.rmi.Remote;
+import java.util.*;
+
+public class DependencyCache {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.make.DependencyCache");
+
+ private volatile Cache myCache;
+ private volatile Cache myNewClassesCache;
+
+ private static final String REMOTE_INTERFACE_NAME = Remote.class.getName();
+ private final TIntHashSet myToUpdate = new TIntHashSet(); // qName strings to be updated.
+ private final TIntHashSet myTraverseRoots = new TIntHashSet(); // Dependencies are calculated from these clasess
+ private final TIntHashSet myClassesWithSourceRemoved = new TIntHashSet();
+ private final TIntHashSet myPreviouslyRemoteClasses = new TIntHashSet(); // classes that were Remote, but became non-Remote for some reason
+ private final TIntHashSet myMarkedInfos = new TIntHashSet(); // classes to be recompiled
+ private final Set<VirtualFile> myMarkedFiles = new HashSet<VirtualFile>();
+
+ private volatile DependencyCacheNavigator myCacheNavigator;
+ private volatile SymbolTable mySymbolTable;
+ private final String mySymbolTableFilePath;
+ private final String myStoreDirectoryPath;
+ @NonNls
+ private static final String SYMBOLTABLE_FILE_NAME = "symboltable.dat";
+
+ public DependencyCache(@NonNls String storeDirectoryPath) {
+ myStoreDirectoryPath = storeDirectoryPath;
+ LOG.assertTrue(myStoreDirectoryPath != null);
+
+ mySymbolTableFilePath = myStoreDirectoryPath + "/" + SYMBOLTABLE_FILE_NAME;
+ }
+
+ public DependencyCacheNavigator getCacheNavigator() throws CacheCorruptedException {
+ if (myCacheNavigator == null) {
+ myCacheNavigator = new DependencyCacheNavigator(getCache());
+ }
+ return myCacheNavigator;
+ }
+
+ public void wipe() throws CacheCorruptedException {
+ getCache().wipe();
+ getNewClassesCache().wipe();
+ }
+
+ public Cache getCache() throws CacheCorruptedException {
+ try {
+ if (myCache == null) {
+ // base number of cached record views of each type
+ myCache = new Cache(myStoreDirectoryPath, 512);
+ }
+
+ return myCache;
+ }
+ catch (IOException e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public Cache getNewClassesCache() throws CacheCorruptedException {
+ try {
+ if (myNewClassesCache == null) {
+ myNewClassesCache = new Cache(myStoreDirectoryPath + "/tmp", 16);
+ }
+ return myNewClassesCache;
+ }
+ catch (IOException e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+
+ public void addTraverseRoot(int qName) {
+ myTraverseRoots.add(qName);
+ }
+
+ public void clearTraverseRoots() {
+ myTraverseRoots.clear();
+ }
+
+ public boolean hasUnprocessedTraverseRoots() {
+ return !myTraverseRoots.isEmpty();
+ }
+
+ public void markSourceRemoved(int qName) {
+ myClassesWithSourceRemoved.add(qName);
+ }
+
+ public void addClassToUpdate(int qName) {
+ myToUpdate.add(qName);
+ }
+
+ public int reparseClassFile(@NotNull File file, final byte[] fileContent) throws ClsFormatException, CacheCorruptedException {
+ SymbolTable symbolTable = getSymbolTable();
+
+ final int qName = getNewClassesCache().importClassInfo(new ClassFileReader(file, symbolTable, fileContent), symbolTable);
+ addClassToUpdate(qName);
+ addTraverseRoot(qName);
+ return qName;
+ }
+
+ // for profiling purposes
+ /*
+ private static void pause() {
+ System.out.println("PAUSED. ENTER A CHAR.");
+ byte[] buf = new byte[1];
+ try {
+ System.in.read(buf);
+ }
+ catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ */
+
+ public void update() throws CacheCorruptedException {
+ if (myToUpdate.isEmpty()) {
+ return; // optimization
+ }
+
+ //pause();
+
+ final int[] namesToUpdate = myToUpdate.toArray();
+ final Cache cache = getCache();
+ final Cache newCache = getNewClassesCache();
+ final DependencyCacheNavigator navigator = getCacheNavigator();
+
+ // remove unnecesary dependencies
+ for (final int qName : namesToUpdate) {
+ // process use-dependencies
+ for (int referencedClassQName : cache.getReferencedClasses(qName)) {
+ if (!cache.containsClass(referencedClassQName)) {
+ continue;
+ }
+ cache.removeClassReferencer(referencedClassQName, qName);
+ }
+ cache.clearReferencedClasses(qName);
+ // process inheritance dependencies
+ navigator.walkSuperClasses(qName, new ClassInfoProcessor() {
+ public boolean process(int classQName) throws CacheCorruptedException {
+ cache.removeSubclass(classQName, qName);
+ return true;
+ }
+ });
+ }
+
+ // do update of classInfos
+ for (final int qName : namesToUpdate) {
+ cache.importClassInfo(newCache, qName);
+ }
+
+ // build forward-dependencies for the new infos, all new class infos must be already in the main cache!
+
+ final SymbolTable symbolTable = getSymbolTable();
+
+ for (final int qName : namesToUpdate) {
+ if (!newCache.containsClass(qName)) {
+ continue;
+ }
+ buildForwardDependencies(qName, newCache.getReferences(qName));
+ boolean isRemote = false;
+ // "remote objects" are classes that _directly_ implement remote interfaces
+ final int[] superInterfaces = cache.getSuperInterfaces(qName);
+ if (superInterfaces.length > 0) {
+ final int remoteInterfaceName = symbolTable.getId(REMOTE_INTERFACE_NAME);
+ for (int superInterface : superInterfaces) {
+ if (isRemoteInterface(cache, superInterface, remoteInterfaceName)) {
+ isRemote = true;
+ break;
+ }
+ }
+ }
+ final boolean wasRemote = cache.isRemote(qName);
+ if (wasRemote && !isRemote) {
+ synchronized (myPreviouslyRemoteClasses) {
+ myPreviouslyRemoteClasses.add(qName);
+ }
+ }
+ cache.setRemote(qName, isRemote);
+ }
+
+ // building subclass dependencies
+ for (final int qName : namesToUpdate) {
+ buildSubclassDependencies(getCache(), qName, qName);
+ }
+
+ for (final int qName : myClassesWithSourceRemoved.toArray()) {
+ cache.removeClass(qName);
+ }
+ myToUpdate.clear();
+
+ //pause();
+ }
+
+ private void buildForwardDependencies(final int classQName, final Collection<ReferenceInfo> references) throws CacheCorruptedException {
+ final Cache cache = getCache();
+
+ final int genericSignature = cache.getGenericSignature(classQName);
+ if (genericSignature != -1) {
+ final String genericClassSignature = resolve(genericSignature);
+ final int[] bounds = findBounds(genericClassSignature);
+ for (int boundClassQName : bounds) {
+ cache.addClassReferencer(boundClassQName, classQName);
+ }
+ }
+
+ buildAnnotationDependencies(classQName, cache.getRuntimeVisibleAnnotations(classQName));
+ buildAnnotationDependencies(classQName, cache.getRuntimeInvisibleAnnotations(classQName));
+
+ for (final ReferenceInfo refInfo : references) {
+ final int declaringClassName = getActualDeclaringClassForReference(refInfo);
+ if (declaringClassName == Cache.UNKNOWN) {
+ continue;
+ }
+ if (refInfo instanceof MemberReferenceInfo) {
+ final MemberInfo memberInfo = ((MemberReferenceInfo)refInfo).getMemberInfo();
+ if (memberInfo instanceof FieldInfo) {
+ cache.addFieldReferencer(declaringClassName, memberInfo.getName(), classQName);
+ }
+ else if (memberInfo instanceof MethodInfo) {
+ cache.addMethodReferencer(declaringClassName, memberInfo.getName(), memberInfo.getDescriptor(), classQName);
+ }
+ else {
+ LOG.error("Unknown member info class: " + memberInfo.getClass().getName());
+ }
+ }
+ else { // reference to class
+ cache.addClassReferencer(declaringClassName, classQName);
+ }
+ }
+ final SymbolTable symbolTable = getSymbolTable();
+
+ for (final FieldInfo fieldInfo : cache.getFields(classQName)) {
+ buildAnnotationDependencies(classQName, fieldInfo.getRuntimeVisibleAnnotations());
+ buildAnnotationDependencies(classQName, fieldInfo.getRuntimeInvisibleAnnotations());
+
+ String className = MakeUtil.parseObjectType(symbolTable.getSymbol(fieldInfo.getDescriptor()), 0);
+ if (className == null) {
+ continue;
+ }
+ final int cls = symbolTable.getId(className);
+ cache.addClassReferencer(cls, classQName);
+ }
+
+ for (final MethodInfo methodInfo : cache.getMethods(classQName)) {
+ buildAnnotationDependencies(classQName, methodInfo.getRuntimeVisibleAnnotations());
+ buildAnnotationDependencies(classQName, methodInfo.getRuntimeInvisibleAnnotations());
+ buildAnnotationDependencies(classQName, methodInfo.getRuntimeVisibleParameterAnnotations());
+ buildAnnotationDependencies(classQName, methodInfo.getRuntimeInvisibleParameterAnnotations());
+
+ if (methodInfo.isConstructor()) {
+ continue;
+ }
+
+ final String returnTypeClassName = MakeUtil.parseObjectType(methodInfo.getReturnTypeDescriptor(symbolTable), 0);
+ if (returnTypeClassName != null) {
+ final int returnTypeClassQName = symbolTable.getId(returnTypeClassName);
+ cache.addClassReferencer(returnTypeClassQName, classQName);
+ }
+
+ String[] parameterSignatures = CacheUtils.getParameterSignatures(methodInfo, symbolTable);
+ for (String parameterSignature : parameterSignatures) {
+ String paramClassName = MakeUtil.parseObjectType(parameterSignature, 0);
+ if (paramClassName != null) {
+ final int paramClassId = symbolTable.getId(paramClassName);
+ cache.addClassReferencer(paramClassId, classQName);
+ }
+ }
+ }
+ }
+
+ private static boolean isRemoteInterface(Cache cache, int ifaceName, final int remoteInterfaceName) throws CacheCorruptedException {
+ if (ifaceName == remoteInterfaceName) {
+ return true;
+ }
+ for (int superInterfaceName : cache.getSuperInterfaces(ifaceName)) {
+ if (isRemoteInterface(cache, superInterfaceName, remoteInterfaceName)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ private void buildAnnotationDependencies(int classQName, AnnotationConstantValue[][] annotations) throws CacheCorruptedException {
+ if (annotations == null || annotations.length == 0) {
+ return;
+ }
+ for (AnnotationConstantValue[] annotation : annotations) {
+ buildAnnotationDependencies(classQName, annotation);
+ }
+ }
+
+ private void buildAnnotationDependencies(int classQName, AnnotationConstantValue[] annotations) throws CacheCorruptedException {
+ if (annotations == null || annotations.length == 0) {
+ return;
+ }
+ final Cache cache = getCache();
+ for (AnnotationConstantValue annotation : annotations) {
+ final int annotationQName = annotation.getAnnotationQName();
+
+ cache.addClassReferencer(annotationQName, classQName);
+
+ final AnnotationNameValuePair[] memberValues = annotation.getMemberValues();
+ for (final AnnotationNameValuePair nameValuePair : memberValues) {
+ for (MethodInfo annotationMember : cache.findMethodsByName(annotationQName, nameValuePair.getName())) {
+ cache.addMethodReferencer(annotationQName, annotationMember.getName(), annotationMember.getDescriptor(), classQName);
+ }
+ }
+ }
+ }
+
+ private int[] findBounds(final String genericClassSignature) throws CacheCorruptedException{
+ try {
+ final String[] boundInterfaces = BoundsParser.getBounds(genericClassSignature);
+ int[] ids = ArrayUtil.newIntArray(boundInterfaces.length);
+ for (int i = 0; i < boundInterfaces.length; i++) {
+ ids[i] = getSymbolTable().getId(boundInterfaces[i]);
+ }
+ return ids;
+ }
+ catch (SignatureParsingException e) {
+ return ArrayUtil.EMPTY_INT_ARRAY;
+ }
+ }
+
+ // fixes JDK 1.4 javac bug that generates references in the constant pool
+ // to the subclass even if the field was declared in a superclass
+ private int getActualDeclaringClassForReference(final ReferenceInfo refInfo) throws CacheCorruptedException {
+ if (!(refInfo instanceof MemberReferenceInfo)) {
+ return refInfo.getClassName();
+ }
+ final int declaringClassName = refInfo.getClassName();
+ final Cache cache = getCache();
+ final MemberInfo memberInfo = ((MemberReferenceInfo)refInfo).getMemberInfo();
+ if (memberInfo instanceof FieldInfo) {
+ if (cache.findFieldByName(declaringClassName, memberInfo.getName()) != null) {
+ return declaringClassName;
+ }
+ }
+ else if (memberInfo instanceof MethodInfo) {
+ if (cache.findMethod(declaringClassName, memberInfo.getName(), memberInfo.getDescriptor()) != null) {
+ return declaringClassName;
+ }
+ }
+ final DeclaringClassFinder finder = new DeclaringClassFinder(memberInfo);
+ getCacheNavigator().walkSuperClasses(declaringClassName, finder);
+ return finder.getDeclaringClassName();
+ }
+
+ /**
+ * @return qualified names of the classes that should be additionally recompiled
+ */
+ public Pair<int[], Set<VirtualFile>> findDependentClasses(CompileContext context, Project project, Set<VirtualFile> compiledWithErrors)
+ throws CacheCorruptedException, ExitException {
+
+ markDependencies(context, project, compiledWithErrors);
+ return new Pair<int[], Set<VirtualFile>>(myMarkedInfos.toArray(), Collections.unmodifiableSet(myMarkedFiles));
+ }
+
+ private void markDependencies(CompileContext context, Project project, final Set<VirtualFile> compiledWithErrors)
+ throws CacheCorruptedException, ExitException {
+ try {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("====================Marking dependent files=====================");
+ }
+ // myToUpdate can be modified during the mark procedure, so use toArray() to iterate it
+ final int[] traverseRoots = myTraverseRoots.toArray();
+ final SourceFileFinder sourceFileFinder = new SourceFileFinder(project, context);
+ final CachingSearcher searcher = new CachingSearcher(project);
+ final ChangedRetentionPolicyDependencyProcessor changedRetentionPolicyDependencyProcessor = new ChangedRetentionPolicyDependencyProcessor(project, searcher, this);
+ for (final int qName : traverseRoots) {
+ if (!getCache().containsClass(qName)) {
+ continue;
+ }
+ if (getNewClassesCache().containsClass(qName)) { // there is a new class file created
+ new JavaDependencyProcessor(project, this, qName).run();
+ ArrayList<ChangedConstantsDependencyProcessor.FieldChangeInfo> changed =
+ new ArrayList<ChangedConstantsDependencyProcessor.FieldChangeInfo>();
+ ArrayList<ChangedConstantsDependencyProcessor.FieldChangeInfo> removed =
+ new ArrayList<ChangedConstantsDependencyProcessor.FieldChangeInfo>();
+ findModifiedConstants(qName, changed, removed);
+ if (!changed.isEmpty() || !removed.isEmpty()) {
+ new ChangedConstantsDependencyProcessor(
+ project, searcher, this, qName, context,
+ changed.toArray(new ChangedConstantsDependencyProcessor.FieldChangeInfo[changed.size()]),
+ removed.toArray(new ChangedConstantsDependencyProcessor.FieldChangeInfo[removed.size()])
+ ).run();
+ }
+ changedRetentionPolicyDependencyProcessor.checkAnnotationRetentionPolicyChanges(qName);
+ for (DependencyProcessor additionalProcessor : DependencyProcessor.EXTENSION_POINT_NAME.getExtensions()) {
+ additionalProcessor.processDependencies(context, qName, searcher);
+ }
+ }
+ else {
+ boolean isSourceDeleted = false;
+ if (myClassesWithSourceRemoved.contains(qName)) { // no recompiled class file, check whether the classfile exists
+ isSourceDeleted = true;
+ }
+ else if (!new File(getCache().getPath(qName)).exists()) {
+ final String qualifiedName = resolve(qName);
+ final String sourceFileName = getCache().getSourceFileName(qName);
+ final boolean markAsRemovedSource = ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
+ public Boolean compute() {
+ VirtualFile sourceFile = sourceFileFinder.findSourceFile(qualifiedName, sourceFileName, false);
+ return sourceFile == null || !compiledWithErrors.contains(sourceFile) ? Boolean.TRUE : Boolean.FALSE;
+ }
+ }).booleanValue();
+ if (markAsRemovedSource) {
+ // for Inner classes: sourceFile may exist, but the inner class declaration inside it may not,
+ // thus the source for the class info should be considered removed
+ isSourceDeleted = true;
+ markSourceRemoved(qName);
+ myMarkedInfos.remove(qName); // if the info has been marked already, the mark should be removed
+ }
+ }
+ if (isSourceDeleted) {
+ Dependency[] backDependencies = getCache().getBackDependencies(qName);
+ for (Dependency backDependency : backDependencies) {
+ if (markTargetClassInfo(backDependency)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(
+ "Mark dependent class " + backDependency.getClassQualifiedName() + "; reason: no class file found for " + qName);
+ }
+ }
+ }
+ }
+ }
+ }
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("================================================================");
+ }
+ }
+ catch (ProcessCanceledException ignored) {
+ // deliberately suppressed
+ }
+ }
+
+ private void findModifiedConstants(
+ final int qName,
+ Collection<ChangedConstantsDependencyProcessor.FieldChangeInfo> changedConstants,
+ Collection<ChangedConstantsDependencyProcessor.FieldChangeInfo> removedConstants) throws CacheCorruptedException {
+
+ final Cache cache = getCache();
+ for (final FieldInfo field : cache.getFields(qName)) {
+ final int oldFlags = field.getFlags();
+ if (ClsUtil.isStatic(oldFlags) && ClsUtil.isFinal(oldFlags)) {
+ final Cache newClassesCache = getNewClassesCache();
+ FieldInfo newField = newClassesCache.findFieldByName(qName, field.getName());
+ if (newField == null) {
+ if (!ConstantValue.EMPTY_CONSTANT_VALUE.equals(field.getConstantValue())) {
+ // if the field was really compile time constant
+ removedConstants.add(new ChangedConstantsDependencyProcessor.FieldChangeInfo(field));
+ }
+ }
+ else {
+ final boolean visibilityRestricted = MakeUtil.isMoreAccessible(oldFlags, newField.getFlags());
+ if (!field.getConstantValue().equals(newField.getConstantValue()) || visibilityRestricted) {
+ changedConstants.add(new ChangedConstantsDependencyProcessor.FieldChangeInfo(field, visibilityRestricted));
+ }
+ }
+ }
+ }
+ }
+
+ private static void buildSubclassDependencies(Cache cache, final int qName, int targetClassId) throws CacheCorruptedException {
+ final int superQName = cache.getSuperQualifiedName(targetClassId);
+ if (superQName != Cache.UNKNOWN) {
+ cache.addSubclass(superQName, qName);
+ buildSubclassDependencies(cache, qName, superQName);
+ }
+
+ int[] interfaces = cache.getSuperInterfaces(targetClassId);
+ for (final int interfaceName : interfaces) {
+ cache.addSubclass(interfaceName, qName);
+ buildSubclassDependencies(cache, qName, interfaceName);
+ }
+ }
+
+
+ /**
+ * Marks ClassInfo targeted by the dependency
+ * @return true if really added, false otherwise
+ */
+ public boolean markTargetClassInfo(Dependency dependency) throws CacheCorruptedException {
+ return markClassInfo(dependency.getClassQualifiedName(), false);
+ }
+
+ /**
+ * Marks ClassInfo that corresponds to the specified qualified name
+ * If class info is already recompiled, it is not marked
+ * @return true if really added, false otherwise
+ */
+ public boolean markClass(int qualifiedName) throws CacheCorruptedException {
+ return markClass(qualifiedName, false);
+ }
+
+ /**
+ * Marks ClassInfo that corresponds to the specified qualified name
+ * If class info is already recompiled, it is not marked unless force parameter is true
+ * @return true if really added, false otherwise
+ */
+ public boolean markClass(int qualifiedName, boolean force) throws CacheCorruptedException {
+ return markClassInfo(qualifiedName, force);
+ }
+
+ public boolean isTargetClassInfoMarked(Dependency dependency) {
+ return isClassInfoMarked(dependency.getClassQualifiedName());
+ }
+
+ public boolean isClassInfoMarked(int qName) {
+ return myMarkedInfos.contains(qName);
+ }
+
+ public void markFile(VirtualFile file) {
+ myMarkedFiles.add(file);
+ }
+
+ /**
+ * @return true if really marked, false otherwise
+ */
+ private boolean markClassInfo(int qName, boolean force) throws CacheCorruptedException {
+ if (!getCache().containsClass(qName)) {
+ return false;
+ }
+ if (myClassesWithSourceRemoved.contains(qName)) {
+ return false; // no need to recompile since source has been removed
+ }
+ if (!force) {
+ if (getNewClassesCache().containsClass(qName)) { // already recompiled
+ return false;
+ }
+ }
+ return myMarkedInfos.add(qName);
+ }
+
+ public void resetState() {
+ myClassesWithSourceRemoved.clear();
+ myMarkedFiles.clear();
+ myMarkedInfos.clear();
+ myToUpdate.clear();
+ myTraverseRoots.clear();
+ if (myNewClassesCache != null) {
+ myNewClassesCache.wipe();
+ myNewClassesCache = null;
+ }
+ myCacheNavigator = null;
+ try {
+ if (myCache != null) {
+ myCache.dispose();
+ myCache = null;
+ }
+ }
+ catch (CacheCorruptedException e) {
+ LOG.info(e);
+ }
+ try {
+ if (mySymbolTable != null) {
+ mySymbolTable.dispose();
+ mySymbolTable = null;
+ }
+ }
+ catch (CacheCorruptedException e) {
+ LOG.info(e);
+ }
+ }
+
+
+ public SymbolTable getSymbolTable() throws CacheCorruptedException {
+ if (mySymbolTable == null) {
+ mySymbolTable = new SymbolTable(new File(mySymbolTableFilePath));
+ }
+ return mySymbolTable;
+ }
+
+ public String resolve(int id) throws CacheCorruptedException {
+ return getSymbolTable().getSymbol(id);
+ }
+
+ public boolean wasRemote(int qName) {
+ return myPreviouslyRemoteClasses.contains(qName);
+ }
+
+ private class DeclaringClassFinder implements ClassInfoProcessor {
+ private final int myMemberName;
+ private final int myMemberDescriptor;
+ private int myDeclaringClass = Cache.UNKNOWN;
+ private final boolean myIsField;
+
+ private DeclaringClassFinder(MemberInfo memberInfo) {
+ myMemberName = memberInfo.getName();
+ myMemberDescriptor = memberInfo.getDescriptor();
+ myIsField = memberInfo instanceof FieldInfo;
+ }
+
+ public int getDeclaringClassName() {
+ return myDeclaringClass;
+ }
+
+ public boolean process(int classQName) throws CacheCorruptedException {
+ final Cache cache = getCache();
+ if (myIsField) {
+ final FieldInfo fieldId = cache.findField(classQName, myMemberName, myMemberDescriptor);
+ if (fieldId != null) {
+ myDeclaringClass = classQName;
+ return false;
+ }
+ }
+ else {
+ final MethodInfo methodId = cache.findMethod(classQName, myMemberName, myMemberDescriptor);
+ if (methodId != null) {
+ myDeclaringClass = classQName;
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/make/DependencyCacheNavigator.java b/java/compiler/impl/src/com/intellij/compiler/make/DependencyCacheNavigator.java
new file mode 100644
index 0000000..a93caa2
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/make/DependencyCacheNavigator.java
@@ -0,0 +1,84 @@
+/*
+ * 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.
+ */
+
+/*
+ * @author: Eugene Zhuravlev
+ * Date: May 26, 2003
+ * Time: 8:13:56 PM
+ */
+package com.intellij.compiler.make;
+
+import com.intellij.openapi.diagnostic.Logger;
+
+public class DependencyCacheNavigator {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.make.DependencyCacheNavigator");
+
+ private final Cache myCache;
+
+ public DependencyCacheNavigator(Cache cache) {
+ myCache = cache;
+ }
+
+ public void walkSuperClasses(int classQName, ClassInfoProcessor processor) throws CacheCorruptedException {
+ if (classQName == Cache.UNKNOWN) {
+ return;
+ }
+ int superQName = myCache.getSuperQualifiedName(classQName);
+
+ if (classQName == superQName) {
+ LOG.error("Superclass qualified name is the same as class' name: " + classQName);
+ return;
+ }
+
+ if (superQName != Cache.UNKNOWN) {
+ if (processor.process(superQName)) {
+ walkSuperClasses(superQName, processor);
+ }
+ }
+ for (int superInterfaceQName : myCache.getSuperInterfaces(classQName)) {
+ if (processor.process(superInterfaceQName)) {
+ walkSuperClasses(superInterfaceQName, processor);
+ }
+ }
+ }
+
+ public void walkSuperInterfaces(int classQName, ClassInfoProcessor processor) throws CacheCorruptedException {
+ if (classQName == Cache.UNKNOWN) {
+ return;
+ }
+
+ for (int superInterfaceQName : myCache.getSuperInterfaces(classQName)) {
+ if (processor.process(superInterfaceQName)) {
+ walkSuperInterfaces(superInterfaceQName, processor);
+ }
+ }
+ }
+
+ public void walkSubClasses(int fromClassQName, ClassInfoProcessor processor) throws CacheCorruptedException {
+ for (int subQName : myCache.getSubclasses(fromClassQName)) {
+ if (fromClassQName == subQName) {
+ LOG.error("Subclass qualified name is the same as class' name: " + fromClassQName);
+ return;
+ }
+ if (subQName != Cache.UNKNOWN) {
+ if (!processor.process(subQName)) {
+ break;
+ }
+ }
+ }
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/make/FieldChangeDescription.java b/java/compiler/impl/src/com/intellij/compiler/make/FieldChangeDescription.java
new file mode 100644
index 0000000..9d0ef47
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/make/FieldChangeDescription.java
@@ -0,0 +1,38 @@
+/*
+ * 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 com.intellij.compiler.make;
+
+import com.intellij.compiler.classParsing.FieldInfo;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Apr 7, 2004
+ */
+class FieldChangeDescription extends ChangeDescription {
+ public final boolean flagsChanged;
+ public final boolean descriptorChanged;
+ public final boolean genericSignatureChanged;
+
+ public FieldChangeDescription(final FieldInfo oldField, final FieldInfo newField) {
+ descriptorChanged = oldField.getDescriptor() != newField.getDescriptor();
+ flagsChanged = oldField.getFlags() != newField.getFlags();
+ genericSignatureChanged = oldField.getGenericSignature() != newField.getGenericSignature();
+ }
+
+ public boolean isChanged() {
+ return flagsChanged || descriptorChanged || genericSignatureChanged;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/make/JavaDependencyProcessor.java b/java/compiler/impl/src/com/intellij/compiler/make/JavaDependencyProcessor.java
new file mode 100644
index 0000000..b00d886
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/make/JavaDependencyProcessor.java
@@ -0,0 +1,1322 @@
+/*
+ * 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.
+ */
+
+/**
+ * created at Feb 19, 2002
+ * @author Jeka
+ */
+package com.intellij.compiler.make;
+
+import com.intellij.compiler.SymbolTable;
+import com.intellij.compiler.classParsing.*;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Computable;
+import com.intellij.pom.java.LanguageLevel;
+import com.intellij.psi.*;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.util.IncorrectOperationException;
+import com.intellij.util.cls.ClsUtil;
+import gnu.trove.TIntHashSet;
+import gnu.trove.TIntObjectHashMap;
+import gnu.trove.TIntObjectIterator;
+import gnu.trove.TIntObjectProcedure;
+import org.jetbrains.annotations.NonNls;
+
+import java.util.*;
+
+class JavaDependencyProcessor {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.make.JavaDependencyProcessor");
+ private final DependencyCache myDependencyCache;
+ private final int myQName;
+ private final Map<Dependency.MethodRef, MethodInfo> myRefToMethodMap = new HashMap<Dependency.MethodRef, MethodInfo>();
+ private final Map<Dependency.FieldRef, FieldInfo> myRefToFieldMap = new HashMap<Dependency.FieldRef, FieldInfo>();
+ private final Set<MemberInfo> myAddedMembers = new HashSet<MemberInfo>();
+ private final Set<MemberInfo> myRemovedMembers = new HashSet<MemberInfo>();
+ private final Set<MemberInfo> myChangedMembers = new HashSet<MemberInfo>();
+ private final Map<MemberInfo, ChangeDescription> myChangeDescriptions = new HashMap<MemberInfo, ChangeDescription>();
+ private Dependency[] myBackDependencies;
+ private final boolean myMembersChanged;
+ private final boolean mySuperInterfaceAdded;
+ private final boolean mySuperInterfaceRemoved;
+ private final boolean mySuperClassChanged;
+ private final boolean mySuperlistGenericSignatureChanged;
+ private final boolean mySuperClassAdded;
+ private final Project myProject;
+ private final boolean myIsAnnotation;
+ private final boolean myIsRemoteInterface;
+ private final boolean myWereAnnotationTargetsRemoved;
+ private final boolean myRetentionPolicyChanged;
+ private final boolean myAnnotationSemanticsChanged;
+
+ public JavaDependencyProcessor(Project project, DependencyCache dependencyCache, int qName) throws CacheCorruptedException {
+ myProject = project;
+ myDependencyCache = dependencyCache;
+ myQName = qName;
+ final Cache cache = dependencyCache.getCache();
+ final Cache newClassesCache = dependencyCache.getNewClassesCache();
+
+ final MethodInfo[] oldMethods = cache.getMethods(qName);
+ for (MethodInfo method : oldMethods) {
+ myRefToMethodMap.put(new Dependency.MethodRef(method.getName(), method.getDescriptor()), method);
+ }
+ final TIntObjectHashMap<FieldInfo> oldFieldsMap = getFieldInfos(cache, qName);
+ oldFieldsMap.forEachEntry(new TIntObjectProcedure<FieldInfo>() {
+ public boolean execute(int fieldName, FieldInfo fieldInfo) {
+ myRefToFieldMap.put(new Dependency.FieldRef(fieldName), fieldInfo);
+ return true;
+ }
+ });
+ final Map<String, MethodInfoContainer> oldMethodsMap = getMethodInfos(oldMethods);
+ final Map<String, MethodInfoContainer> newMethodsMap = getMethodInfos(newClassesCache.getMethods(qName));
+ final TIntObjectHashMap<FieldInfo> newFieldsMap = getFieldInfos(newClassesCache, qName);
+ addAddedMembers(oldFieldsMap, oldMethodsMap, newFieldsMap, newMethodsMap, myAddedMembers);
+ addRemovedMembers(oldFieldsMap, oldMethodsMap, newFieldsMap, newMethodsMap, myRemovedMembers);
+ addChangedMembers(oldFieldsMap, oldMethodsMap, newFieldsMap, newMethodsMap, myChangedMembers);
+
+ myMembersChanged = !myAddedMembers.isEmpty() || !myRemovedMembers.isEmpty() || !myChangedMembers.isEmpty();
+ // track changes in super list
+
+ myIsRemoteInterface = MakeUtil.isInterface(cache.getFlags(myQName)) && cache.isRemote(qName);
+ myIsAnnotation = ClsUtil.isAnnotation(cache.getFlags(qName));
+ myWereAnnotationTargetsRemoved = myIsAnnotation && wereAnnotationTargesRemoved(cache, newClassesCache);
+ myRetentionPolicyChanged = myIsAnnotation && hasRetentionPolicyChanged(cache, newClassesCache);
+ myAnnotationSemanticsChanged = myIsAnnotation && hasAnnotationSemanticsChanged(cache, newClassesCache);
+
+ int[] oldInterfaces = cache.getSuperInterfaces(qName);
+ int[] newInterfaces = newClassesCache.getSuperInterfaces(qName);
+ mySuperInterfaceRemoved = wereInterfacesRemoved(oldInterfaces, newInterfaces);
+ mySuperInterfaceAdded = wereInterfacesRemoved(newInterfaces, oldInterfaces);
+
+ mySuperlistGenericSignatureChanged = isSuperlistGenericSignatureChanged(cache.getGenericSignature(qName), newClassesCache.getGenericSignature(qName));
+
+ boolean superclassesDiffer = cache.getSuperQualifiedName(qName) != newClassesCache.getSuperQualifiedName(qName);
+ boolean wasDerivedFromObject = CommonClassNames.JAVA_LANG_OBJECT.equals(dependencyCache.resolve(cache.getSuperQualifiedName(qName)));
+ mySuperClassChanged = !wasDerivedFromObject && superclassesDiffer;
+ mySuperClassAdded = wasDerivedFromObject && superclassesDiffer;
+ }
+
+ private static boolean hasMembersWithoutDefaults(Set<MemberInfo> addedMembers) {
+ for (final Object addedMember : addedMembers) {
+ MemberInfo memberInfo = (MemberInfo)addedMember;
+ if (memberInfo instanceof MethodInfo) {
+ final ConstantValue annotationDefault = ((MethodInfo)memberInfo).getAnnotationDefault();
+ if (annotationDefault == null || ConstantValue.EMPTY_CONSTANT_VALUE.equals(annotationDefault)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean wereAnnotationDefaultsRemoved() {
+ for (final MemberInfo memberInfo : myChangeDescriptions.keySet()) {
+ if (memberInfo instanceof MethodInfo) {
+ MethodChangeDescription description = (MethodChangeDescription)myChangeDescriptions.get(memberInfo);
+ if (description.removedAnnotationDefault) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean isSuperlistGenericSignatureChanged(int oldGenericSignature, int newGenericSignature) throws CacheCorruptedException {
+ if (oldGenericSignature == newGenericSignature) {
+ return false;
+ }
+ if (oldGenericSignature != -1 && newGenericSignature != -1) {
+ final SymbolTable symbolTable = myDependencyCache.getSymbolTable();
+ final String _oldGenericMethodSignature = cutFormalParams(symbolTable.getSymbol(oldGenericSignature));
+ final String _newGenericMethodSignature = cutFormalParams(symbolTable.getSymbol(newGenericSignature));
+ return !_oldGenericMethodSignature.equals(_newGenericMethodSignature);
+ }
+ return true;
+ }
+
+ private static String cutFormalParams(String genericClassSignature) {
+ if (genericClassSignature.charAt(0) == '<') {
+ int idx = genericClassSignature.indexOf('>');
+ return genericClassSignature.substring(idx + 1);
+ }
+ return genericClassSignature;
+ }
+
+ public void run() throws CacheCorruptedException {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Checking dependencies for " + myDependencyCache.resolve(myQName));
+ }
+ final boolean superListChanged = mySuperClassChanged || mySuperClassAdded || mySuperInterfaceAdded || mySuperInterfaceRemoved || mySuperlistGenericSignatureChanged;
+ final Cache oldCache = myDependencyCache.getCache();
+ final Cache newCache = myDependencyCache.getNewClassesCache();
+
+ if (!myMembersChanged &&
+ oldCache.getFlags(myQName) == newCache.getFlags(myQName) &&
+ !superListChanged && !myWereAnnotationTargetsRemoved && !myRetentionPolicyChanged && !myAnnotationSemanticsChanged) {
+ return; // nothing to do
+ }
+
+ if (myIsAnnotation) {
+ if (myAnnotationSemanticsChanged) {
+ final TIntHashSet visited = new TIntHashSet();
+ visited.add(myQName);
+ markAnnotationDependenciesRecursively(getBackDependencies(), LOG.isDebugEnabled()? "; reason: semantics changed for " + myDependencyCache.resolve(myQName) : "", visited);
+ return;
+ }
+ if (hasMembersWithoutDefaults(myAddedMembers)) {
+ markAll(getBackDependencies(), LOG.isDebugEnabled()? "; reason: added annotation type member without default " + myDependencyCache.resolve(myQName) : "");
+ return;
+ }
+ if (!myRemovedMembers.isEmpty()) {
+ markAll(getBackDependencies(), LOG.isDebugEnabled()? "; reason: removed annotation type member " + myDependencyCache.resolve(myQName) : "");
+ return;
+ }
+ if (!myChangedMembers.isEmpty()) { // for annotations "changed" means return type changed
+ markAll(getBackDependencies(), LOG.isDebugEnabled()? "; reason: changed annotation member's type " + myDependencyCache.resolve(myQName) : "");
+ return;
+ }
+ if (wereAnnotationDefaultsRemoved()) {
+ markAll(getBackDependencies(), LOG.isDebugEnabled()? "; reason: removed annotation member's default value " + myDependencyCache.resolve(myQName): "");
+ return;
+ }
+ if (myWereAnnotationTargetsRemoved) {
+ markAll(getBackDependencies(), LOG.isDebugEnabled()? "; reason: removed annotation's targets " + myDependencyCache.resolve(myQName) : "");
+ return;
+ }
+ if (myRetentionPolicyChanged) {
+ markAll(getBackDependencies(), LOG.isDebugEnabled()? "; reason: retention policy changed for " + myDependencyCache.resolve(myQName) : "");
+ return;
+ }
+ }
+
+ final DependencyCacheNavigator cacheNavigator = myDependencyCache.getCacheNavigator();
+
+ if (mySuperClassChanged || mySuperInterfaceRemoved || mySuperlistGenericSignatureChanged) {
+ // superclass changed == old removed and possibly new added
+ // if anything (class or interface) in the superlist was removed, should recompile all subclasses (both direct and indirect)
+ // and all back-dependencies of this class and its subclasses
+ markAll(getBackDependencies(), LOG.isDebugEnabled()? "; reason: deleted items from the superlist or changed superlist generic signature of " + myDependencyCache.resolve(myQName) : "");
+ cacheNavigator.walkSubClasses(myQName, new ClassInfoProcessor() {
+ public boolean process(int classQName) throws CacheCorruptedException {
+ markAll(oldCache.getBackDependencies(classQName), LOG.isDebugEnabled()? "; reason: deleted items from the superlist or changed superlist generic signature of " + myDependencyCache.resolve(myQName) : "");
+ return true;
+ }
+ });
+ return;
+ }
+
+ final boolean isKindChanged =
+ (MakeUtil.isInterface(oldCache.getFlags(myQName)) && !MakeUtil.isInterface(newCache.getFlags(myQName))) ||
+ (!MakeUtil.isInterface(oldCache.getFlags(myQName)) && MakeUtil.isInterface(newCache.getFlags(myQName)));
+ if (isKindChanged) {
+ markAll(getBackDependencies(), LOG.isDebugEnabled()? "; reason: class kind changed (class/interface) " + myDependencyCache.resolve(myQName) : "");
+ cacheNavigator.walkSubClasses(myQName, new ClassInfoProcessor() {
+ public boolean process(int classQName) throws CacheCorruptedException {
+ markAll(oldCache.getBackDependencies(classQName), LOG.isDebugEnabled()? "; reason: class kind changed (class/interface) " + myDependencyCache.resolve(myQName) : "");
+ return true;
+ }
+ });
+ return;
+ }
+
+ boolean becameFinal = !ClsUtil.isFinal(oldCache.getFlags(myQName)) && ClsUtil.isFinal(newCache.getFlags(myQName));
+ if (becameFinal) {
+ markAll(getBackDependencies(), LOG.isDebugEnabled()? "; reason: class became final: " + myDependencyCache.resolve(myQName) : "");
+ }
+ else {
+ boolean becameAbstract = !ClsUtil.isAbstract(oldCache.getFlags(myQName)) && ClsUtil.isAbstract(newCache.getFlags(myQName));
+ boolean accessRestricted = MakeUtil.isMoreAccessible(oldCache.getFlags(myQName), newCache.getFlags(myQName));
+ Set<MethodInfo> removedMethods = null;
+ Set<MethodInfo> addedMethods = null;
+ for (Dependency backDependency : getBackDependencies()) {
+ if (myDependencyCache.isTargetClassInfoMarked(backDependency)) continue;
+
+ if (accessRestricted) {
+ if (myDependencyCache.markTargetClassInfo(backDependency)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(backDependency.getClassQualifiedName()) + "; reason: " +
+ myDependencyCache.resolve(myQName) + " made less accessible");
+ }
+ }
+ continue;
+ }
+ if (becameAbstract) {
+ if (processClassBecameAbstract(backDependency)) {
+ continue;
+ }
+ }
+ if (isDependentOnRemovedMembers(backDependency)) {
+ if (myDependencyCache.markTargetClassInfo(backDependency)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(backDependency.getClassQualifiedName()) +
+ "; reason: the class uses removed members of " + myDependencyCache.resolve(myQName));
+ }
+ }
+ continue;
+ }
+ if (isDependentOnChangedMembers(backDependency)) {
+ if (myDependencyCache.markTargetClassInfo(backDependency)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(backDependency.getClassQualifiedName()) +
+ "; reason: the class uses changed members of " + myDependencyCache.resolve(myQName));
+ }
+ }
+ continue;
+ }
+ final Collection<Dependency.MethodRef> usedMethods = backDependency.getMethodRefs();
+ if (removedMethods == null) {
+ removedMethods = extractMethods(myRemovedMembers, true);
+ }
+ if (isDependentOnEquivalentMethods(usedMethods, removedMethods)) {
+ if (myDependencyCache.markTargetClassInfo(backDependency)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(backDependency.getClassQualifiedName()) +
+ "; reason: some overloaded methods of " + myDependencyCache.resolve(myQName) + " were removed");
+ }
+ }
+ continue;
+ }
+ if (addedMethods == null) {
+ addedMethods = extractMethods(myAddedMembers, true);
+ }
+ if (isDependentOnEquivalentMethods(usedMethods, addedMethods)) {
+ if (myDependencyCache.markTargetClassInfo(backDependency)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(backDependency.getClassQualifiedName()) +
+ "; reason: some overloaded methods of " + myDependencyCache.resolve(myQName) + " were added");
+ }
+ }
+ }
+ }
+ }
+
+ final Set<MethodInfo> methodsToCheck = new HashSet<MethodInfo>();
+ extractMethods(myRemovedMembers, methodsToCheck, false);
+
+ processInheritanceDependencies(methodsToCheck);
+
+ extractMethods(myAddedMembers, methodsToCheck, false);
+
+ if (!MakeUtil.isAnonymous(myDependencyCache.resolve(myQName))) {
+ // these checks make no sense for anonymous classes
+
+ final TIntHashSet fieldNames = new TIntHashSet();
+ extractFieldNames(myAddedMembers, fieldNames);
+ int addedFieldsCount = fieldNames.size();
+ extractFieldNames(myRemovedMembers, fieldNames);
+
+ if (!fieldNames.isEmpty()) {
+ cacheNavigator.walkSuperClasses(myQName, new ClassInfoProcessor() {
+ public boolean process(final int classQName) throws CacheCorruptedException {
+ markUseDependenciesOnFields(classQName, fieldNames);
+ return true;
+ }
+ });
+ }
+
+ if (addedFieldsCount > 0 && MakeUtil.isInterface(oldCache.getFlags(myQName))) {
+ final TIntHashSet visitedClasses = new TIntHashSet();
+ visitedClasses.add(myQName);
+ cacheNavigator.walkSubClasses(myQName, new ClassInfoProcessor() {
+ public boolean process(int subclassQName) throws CacheCorruptedException {
+ markUseDependenciesOnFields(subclassQName, fieldNames);
+ visitedClasses.add(subclassQName);
+ cacheNavigator.walkSuperClasses(subclassQName, new ClassInfoProcessor() {
+ public boolean process(int superclassQName) throws CacheCorruptedException {
+ if (visitedClasses.contains(superclassQName)) {
+ return false;
+ }
+ markUseDependenciesOnFields(superclassQName, fieldNames);
+ visitedClasses.add(superclassQName);
+ return true;
+ }
+ });
+ return true;
+ }
+ });
+ }
+
+ if (!methodsToCheck.isEmpty()) {
+ cacheNavigator.walkSuperClasses(myQName, new ClassInfoProcessor() {
+ public boolean process(int classQName) throws CacheCorruptedException {
+ markUseDependenciesOnEquivalentMethods(classQName, methodsToCheck, myQName);
+ return true;
+ }
+ });
+
+ cacheNavigator.walkSubClasses(myQName, new ClassInfoProcessor() {
+ public boolean process(int classQName) throws CacheCorruptedException {
+ markUseDependenciesOnEquivalentMethods(classQName, methodsToCheck, myQName);
+ return true;
+ }
+ });
+ }
+ // check referencing members in subclasses
+
+ final TIntHashSet addedOrRemovedFields = new TIntHashSet();
+ final TIntHashSet addedOrRemovedMethods = new TIntHashSet();
+ for (Set<MemberInfo> infos : Arrays.asList(myAddedMembers, myRemovedMembers)) {
+ for (MemberInfo member : infos) {
+ if (!member.isPrivate()) {
+ if (member instanceof FieldInfo) {
+ addedOrRemovedFields.add(member.getName());
+ }
+ else if (member instanceof MethodInfo){
+ addedOrRemovedMethods.add(member.getName());
+ }
+ }
+ }
+
+ }
+ if (!addedOrRemovedFields.isEmpty() || !addedOrRemovedMethods.isEmpty()) {
+ cacheNavigator.walkSubClasses(myQName, new ClassInfoProcessor() {
+ public boolean process(final int subclassQName) throws CacheCorruptedException {
+ if (!myDependencyCache.isClassInfoMarked(subclassQName)) {
+ if (referencesMembersWithNames(oldCache, subclassQName, addedOrRemovedFields, addedOrRemovedMethods)) {
+ final boolean marked = myDependencyCache.markClass(subclassQName);
+ if (marked && LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(subclassQName) + "; Reason: members were added/removed in superclass with names, that may clash with the names of members of another classes that this class references");
+ }
+ }
+ }
+ return true;
+ }
+ });
+ }
+ }
+ }
+
+ private static boolean referencesMembersWithNames(Cache cache, final int qName, TIntHashSet fieldNames, TIntHashSet methodNames) throws CacheCorruptedException {
+ for (final int referencedClass : cache.getReferencedClasses(qName)) {
+ for (Dependency dependency : cache.getBackDependencies(referencedClass)) {
+ if (dependency.getClassQualifiedName() == qName) {
+ for (Dependency.FieldRef ref : dependency.getFieldRefs()) {
+ if (fieldNames.contains(ref.name)) {
+ return true;
+ }
+ }
+ for (Dependency.MethodRef ref : dependency.getMethodRefs()) {
+ if (methodNames.contains(ref.name)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private void markAnnotationDependenciesRecursively(final Dependency[] dependencies, final @NonNls String reason, final TIntHashSet visitedAnnotations)
+ throws CacheCorruptedException {
+ final Cache oldCache = myDependencyCache.getCache();
+ for (Dependency dependency : dependencies) {
+ if (myDependencyCache.markTargetClassInfo(dependency)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(dependency.getClassQualifiedName()) + reason);
+ }
+ }
+ final int depQName = dependency.getClassQualifiedName();
+ if (ClsUtil.isAnnotation(oldCache.getFlags(depQName))) {
+ if (!visitedAnnotations.contains(depQName)) {
+ visitedAnnotations.add(depQName);
+ markAnnotationDependenciesRecursively(oldCache.getBackDependencies(depQName), LOG.isDebugEnabled()? "; reason: cascade semantics change for " + myDependencyCache.resolve(depQName) : "", visitedAnnotations);
+ }
+ }
+ }
+ }
+
+ private static final int[] ALL_TARGETS = {
+ AnnotationTargets.ANNOTATION_TYPE,
+ AnnotationTargets.CONSTRUCTOR,
+ AnnotationTargets.FIELD,
+ AnnotationTargets.LOCAL_VARIABLE,
+ AnnotationTargets.METHOD,
+ AnnotationTargets.PACKAGE,
+ AnnotationTargets.PARAMETER,
+ AnnotationTargets.TYPE
+ };
+ private boolean wereAnnotationTargesRemoved(final Cache oldCache, final Cache newCache) throws CacheCorruptedException {
+ final int oldAnnotationTargets = MakeUtil.getAnnotationTargets(oldCache, myQName, myDependencyCache.getSymbolTable());
+ final int newAnnotationTargets = MakeUtil.getAnnotationTargets(newCache, myQName, myDependencyCache.getSymbolTable());
+ if (oldAnnotationTargets == newAnnotationTargets) {
+ return false;
+ }
+ for (final int target : ALL_TARGETS) {
+ if ((oldAnnotationTargets & target) != 0 && (newAnnotationTargets & target) == 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean hasRetentionPolicyChanged(final Cache oldCache, final Cache newCache) throws CacheCorruptedException {
+ // if retention policy changed from SOURCE to CLASS or RUNTIME, all sources should be recompiled to propagate changes
+ final int oldPolicy = MakeUtil.getAnnotationRetentionPolicy(myQName, oldCache, myDependencyCache.getSymbolTable());
+ final int newPolicy = MakeUtil.getAnnotationRetentionPolicy(myQName, newCache, myDependencyCache.getSymbolTable());
+ if (oldPolicy == RetentionPolicies.SOURCE && (newPolicy == RetentionPolicies.CLASS || newPolicy == RetentionPolicies.RUNTIME)) {
+ return true;
+ }
+ return oldPolicy == RetentionPolicies.CLASS && newPolicy == RetentionPolicies.RUNTIME;
+ }
+
+ private boolean hasAnnotationSemanticsChanged(final Cache oldCache, final Cache newCache) throws CacheCorruptedException {
+ final TIntObjectHashMap<AnnotationConstantValue> oldAnnotations = fetchAllAnnotations(oldCache);
+ final TIntObjectHashMap<AnnotationConstantValue> newAnnotations = fetchAllAnnotations(newCache);
+ // filter certain known annotation which are processed separately
+ final int retentionAnnotation = myDependencyCache.getSymbolTable().getId("java.lang.annotation.Retention");
+ final int targetAnnotation = myDependencyCache.getSymbolTable().getId("java.lang.annotation.Target");
+ oldAnnotations.remove(retentionAnnotation);
+ oldAnnotations.remove(targetAnnotation);
+ newAnnotations.remove(retentionAnnotation);
+ newAnnotations.remove(targetAnnotation);
+
+ if (oldAnnotations.size() != newAnnotations.size()) {
+ return true; // number of annotation has changed
+ }
+ for (int annotName : oldAnnotations.keys()) {
+ if (!newAnnotations.contains(annotName)) {
+ return true;
+ }
+ final AnnotationNameValuePair[] oldValues = oldAnnotations.get(annotName).getMemberValues();
+ final AnnotationNameValuePair[] newValues = newAnnotations.get(annotName).getMemberValues();
+ if (annotationValuesDiffer(oldValues, newValues)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean annotationValuesDiffer(final AnnotationNameValuePair[] oldValues, final AnnotationNameValuePair[] newValues) {
+ if (oldValues.length != newValues.length) {
+ return true;
+ }
+ final TIntObjectHashMap<ConstantValue> names = new TIntObjectHashMap<ConstantValue>();
+ for (AnnotationNameValuePair value : oldValues) {
+ names.put(value.getName(), value.getValue());
+ }
+ for (AnnotationNameValuePair value : newValues) {
+ if (!names.containsKey(value.getName())) {
+ return true;
+ }
+ if (!value.getValue().equals(names.get(value.getName()))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ private TIntObjectHashMap<AnnotationConstantValue> fetchAllAnnotations(final Cache cache) throws CacheCorruptedException {
+ final int classId = myQName;
+ TIntObjectHashMap<AnnotationConstantValue> oldAnnotations = new TIntObjectHashMap<AnnotationConstantValue>();
+ for (AnnotationConstantValue annot : cache.getRuntimeVisibleAnnotations(classId)) {
+ oldAnnotations.put(annot.getAnnotationQName(), annot);
+ }
+ for (AnnotationConstantValue annot : cache.getRuntimeInvisibleAnnotations(classId)) {
+ oldAnnotations.put(annot.getAnnotationQName(), annot);
+ }
+ return oldAnnotations;
+ }
+
+ private void markAll(Dependency[] backDependencies, @NonNls String reason) throws CacheCorruptedException {
+ for (Dependency backDependency : backDependencies) {
+ if (myDependencyCache.markTargetClassInfo(backDependency)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(backDependency.getClassQualifiedName()) + reason);
+ }
+ }
+ }
+ }
+
+ private static void extractFieldNames(Collection<MemberInfo> fromCollection, TIntHashSet toCollection) {
+ for (final Object aFromCollection : fromCollection) {
+ MemberInfo memberInfo = (MemberInfo)aFromCollection;
+ if (memberInfo instanceof FieldInfo) {
+ toCollection.add(memberInfo.getName());
+ }
+ }
+ }
+
+ private static Set<MethodInfo> extractMethods(Collection<MemberInfo> fromCollection, boolean includeConstructors) {
+ final Set<MethodInfo> methods = new HashSet<MethodInfo>();
+ extractMethods(fromCollection, methods, includeConstructors);
+ return methods;
+ }
+
+ private static void extractMethods(Collection<MemberInfo> fromCollection, Collection<MethodInfo> toCollection, boolean includeConstructors) {
+ for (final MemberInfo memberInfo : fromCollection) {
+ if (memberInfo instanceof MethodInfo) {
+ final MethodInfo methodInfo = (MethodInfo)memberInfo;
+ if (includeConstructors) {
+ toCollection.add(methodInfo);
+ }
+ else {
+ if (!methodInfo.isConstructor()) {
+ toCollection.add(methodInfo);
+ }
+ }
+ }
+ }
+ }
+
+ private boolean processClassBecameAbstract(Dependency dependency) throws CacheCorruptedException {
+ for (Dependency.MethodRef ref : dependency.getMethodRefs()) {
+ final MethodInfo usedMethod = myRefToMethodMap.get(ref);
+ if (usedMethod == null) {
+ continue;
+ }
+ if (usedMethod.isConstructor()) {
+ if (myDependencyCache.markTargetClassInfo(dependency)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(dependency.getClassQualifiedName()) + "; reason: " +
+ myDependencyCache.resolve(myQName) + " made abstract");
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isDependentOnRemovedMembers(Dependency dependency) {
+ for (Dependency.MethodRef ref : dependency.getMethodRefs()) {
+ if (myRemovedMembers.contains(myRefToMethodMap.get(ref))) {
+ return true;
+ }
+ }
+ for (Dependency.FieldRef ref : dependency.getFieldRefs()) {
+ if (myRemovedMembers.contains(myRefToFieldMap.get(ref))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean isDependentOnChangedMembers(Dependency dependency) {
+ for (Dependency.FieldRef ref : dependency.getFieldRefs()) {
+ final FieldInfo fieldInfo = myRefToFieldMap.get(ref);
+ if (myChangedMembers.contains(fieldInfo)) {
+ return true;
+ }
+ }
+
+ for (Dependency.MethodRef ref : dependency.getMethodRefs()) {
+ final MethodInfo methodInfo = myRefToMethodMap.get(ref);
+ if (myChangedMembers.contains(methodInfo)) {
+ final MethodChangeDescription changeDescription = (MethodChangeDescription)myChangeDescriptions.get(methodInfo);
+ if (changeDescription.returnTypeDescriptorChanged ||
+ changeDescription.returnTypeGenericSignatureChanged ||
+ changeDescription.paramsGenericSignatureChanged ||
+ changeDescription.throwsListChanged ||
+ changeDescription.staticPropertyChanged ||
+ changeDescription.accessRestricted) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ private boolean isDependentOnEquivalentMethods(Collection<Dependency.MethodRef> checkedMembers, Set<MethodInfo> members) throws CacheCorruptedException {
+ // check if 'members' contains method with the same name and the same numbers of parameters, but with different types
+ if (checkedMembers.isEmpty() || members.isEmpty()) {
+ return false; // optimization
+ }
+ for (Dependency.MethodRef checkedMethod : checkedMembers) {
+ if (hasEquivalentMethod(members, checkedMethod)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void markUseDependenciesOnEquivalentMethods(final int checkedInfoQName, Set<MethodInfo> methodsToCheck, int methodsClassName) throws CacheCorruptedException {
+ final Dependency[] backDependencies = myDependencyCache.getCache().getBackDependencies(checkedInfoQName);
+ for (Dependency dependency : backDependencies) {
+ if (myDependencyCache.isTargetClassInfoMarked(dependency)) {
+ continue;
+ }
+ if (isDependentOnEquivalentMethods(dependency.getMethodRefs(), methodsToCheck)) {
+ if (myDependencyCache.markTargetClassInfo(dependency)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(dependency.getClassQualifiedName()) +
+ "; reason: more specific methods added to " + myDependencyCache.resolve(methodsClassName));
+ }
+ }
+ myDependencyCache.addClassToUpdate(checkedInfoQName);
+ }
+ }
+ }
+
+ private void markUseDependenciesOnFields(final int classQName, TIntHashSet fieldNames) throws CacheCorruptedException {
+ final Cache oldCache = myDependencyCache.getCache();
+ for (Dependency useDependency : oldCache.getBackDependencies(classQName)) {
+ if (!myDependencyCache.isTargetClassInfoMarked(useDependency)) {
+ for (Dependency.FieldRef field : useDependency.getFieldRefs()) {
+ if (fieldNames.contains(field.name)) {
+ if (myDependencyCache.markTargetClassInfo(useDependency)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(useDependency.getClassQualifiedName()) +
+ "; reason: conflicting fields were added to the hierarchy of the class " + myDependencyCache.resolve(classQName));
+ }
+ }
+ myDependencyCache.addClassToUpdate(classQName);
+ break; // stop iterating fields
+ }
+ }
+ }
+ }
+ }
+
+ private void processInheritanceDependencies(final Set<MethodInfo> removedMethods) throws CacheCorruptedException {
+ final Cache oldCache = myDependencyCache.getCache();
+ final Cache newCache = myDependencyCache.getNewClassesCache();
+
+ final boolean becameFinal = !ClsUtil.isFinal(oldCache.getFlags(myQName)) && ClsUtil.isFinal(newCache.getFlags(myQName));
+ final SymbolTable symbolTable = myDependencyCache.getSymbolTable();
+
+ final Set<MemberInfo> removedConcreteMethods = fetchNonAbstractMethods(myRemovedMembers);
+ final Set<MethodInfo> removedOverridableMethods;
+ if (!removedMethods.isEmpty()) {
+ removedOverridableMethods = new HashSet<MethodInfo>(removedMethods);
+ for (Iterator<MethodInfo> it = removedOverridableMethods.iterator(); it.hasNext();) {
+ final MethodInfo method = it.next();
+ if (method.isFinal() || method.isStatic() || method.isPrivate() || method.isConstructor()) {
+ it.remove();
+ }
+ }
+ }
+ else {
+ removedOverridableMethods = Collections.emptySet();
+ }
+ myDependencyCache.getCacheNavigator().walkSubClasses(myQName, new ClassInfoProcessor() {
+ public boolean process(final int subclassQName) throws CacheCorruptedException {
+ if (myDependencyCache.isClassInfoMarked(subclassQName)) {
+ return true;
+ }
+
+ if (!oldCache.containsClass(subclassQName)) {
+ return true;
+ }
+
+ if (!removedMethods.isEmpty() && myIsRemoteInterface && !MakeUtil.isInterface(oldCache.getFlags(subclassQName))) {
+ if (myDependencyCache.markClass(subclassQName)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(subclassQName) +
+ "; reason: methods were removed from remote interface: " + myDependencyCache.resolve(myQName));
+ }
+ }
+ return true;
+ }
+
+ if (mySuperClassAdded || mySuperInterfaceAdded) {
+ if (myDependencyCache.markClass(subclassQName)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(subclassQName) + "; reason: the superlist of " +
+ myDependencyCache.resolve(myQName) + " is changed");
+ }
+ }
+ return true;
+ }
+
+ // if info became final, mark direct inheritors
+ if (becameFinal) {
+ if (myQName == oldCache.getSuperQualifiedName(subclassQName)) {
+ if (myDependencyCache.markClass(subclassQName)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(subclassQName) + "; reason: the class " +
+ myDependencyCache.resolve(myQName) + " was made final");
+ }
+ }
+ return true;
+ }
+ }
+
+ // process added members
+ for (final MemberInfo member : myAddedMembers) {
+ if (member instanceof MethodInfo) {
+ final MethodInfo method = (MethodInfo)member;
+ if (method.isAbstract()) {
+ // all derived classes should be marked in case an abstract method was added
+ if (myDependencyCache.markClass(subclassQName)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(subclassQName) + "; reason: added abstract method to " +
+ myDependencyCache.resolve(myQName));
+ }
+ }
+ return true;
+ }
+ if (!method.isPrivate()) {
+ final MethodInfo derivedMethod = oldCache.findMethodsBySignature(subclassQName, method.getDescriptor(symbolTable), symbolTable);
+ if (derivedMethod != null) {
+ if (!method.getReturnTypeDescriptor(symbolTable).equals(derivedMethod.getReturnTypeDescriptor(symbolTable))) {
+ if (myDependencyCache.markClass(subclassQName)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(subclassQName) + "; reason: return types of method " +
+ method + " in base and derived classes are different");
+ }
+ }
+ return true;
+ }
+ if (MakeUtil.isMoreAccessible(method.getFlags(), derivedMethod.getFlags())) {
+ if (myDependencyCache.markClass(subclassQName)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(subclassQName) + "; reason: the method " + method +
+ " in derived class is less accessible than in base class");
+ }
+ }
+ return true;
+ }
+ if (!method.isStatic() && derivedMethod.isStatic()) {
+ if (myDependencyCache.markClass(subclassQName)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(subclassQName) + "; reason: the method " + method +
+ " in derived class is static, but added method in the base class is not");
+ }
+ }
+ return true;
+ }
+ if (method.isFinal() && !derivedMethod.isFinal()) {
+ if (myDependencyCache.markClass(subclassQName)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(subclassQName) + "; reason: the method " + method +
+ " in base class is final, but in derived class is not");
+ }
+ }
+ return true;
+ }
+ if (!CacheUtils.areArraysContentsEqual(method.getThrownExceptions(), derivedMethod.getThrownExceptions())) {
+ if (myDependencyCache.markClass(subclassQName)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(subclassQName) + "; reason: exception lists of " +
+ method + " in base and derived classes are different");
+ }
+ }
+ return true;
+ }
+ }
+ if (hasGenericsNameClashes(method, oldCache, subclassQName)) {
+ if (myDependencyCache.markClass(subclassQName)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(subclassQName) +
+ "; reason: found method with the same name, different generic signature, but the same erasure as " + method);
+ }
+ }
+ return true;
+ }
+ }
+ }
+ else if (member instanceof FieldInfo) {
+ if (oldCache.findFieldByName(subclassQName, member.getName()) != null) {
+ if (myDependencyCache.markClass(subclassQName)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(subclassQName) + "; reason: added field " + member +
+ " to base class");
+ }
+ }
+ return true;
+ }
+ }
+ }
+
+ // process changed members
+ for (final MemberInfo changedMember : myChangedMembers) {
+ if (changedMember instanceof MethodInfo) {
+ final MethodInfo oldMethod = (MethodInfo)changedMember;
+ MethodChangeDescription changeDescription = (MethodChangeDescription)myChangeDescriptions.get(oldMethod);
+ if (changeDescription.becameAbstract) {
+ if (!ClsUtil.isAbstract(oldCache.getFlags(subclassQName))) { // if the subclass was not abstract
+ if (myDependencyCache.markClass(subclassQName)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(
+ "Mark dependent class " + myDependencyCache.resolve(subclassQName) + "; reason: changed base method " + oldMethod);
+ }
+ }
+ return true;
+ }
+ }
+
+ final String oldMethodDescriptor = oldMethod.getDescriptor(symbolTable);
+
+ final MethodInfo derivedMethod = oldCache.findMethodsBySignature(subclassQName, oldMethodDescriptor, symbolTable);
+ if (derivedMethod != null) {
+ if (myDependencyCache.markClass(subclassQName)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(subclassQName) + "; reason: changed base method " + oldMethod);
+ }
+ }
+ return true;
+ }
+ // now check if the changed method is compatible with methods declared in implemented interfaces of subclasses
+ myDependencyCache.getCacheNavigator().walkSuperInterfaces(subclassQName, new ClassInfoProcessor() {
+ boolean found = false;
+ public boolean process(final int ifaceQName) throws CacheCorruptedException {
+ if (found) {
+ return false;
+ }
+ final MethodInfo implementee = oldCache.findMethodsBySignature(ifaceQName, oldMethodDescriptor, symbolTable);
+ if (implementee != null) {
+ found = true;
+ if (myDependencyCache.markClass(subclassQName)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(subclassQName) + "; reason: changed base method, implementing corresponding method inherited from an interface" + oldMethod);
+ }
+ }
+ }
+ return !found;
+ }
+ });
+ if (myDependencyCache.isClassInfoMarked(subclassQName)) {
+ return true;
+ }
+ }
+ }
+
+ if (!ClsUtil.isAbstract(oldCache.getFlags(subclassQName))) {
+ if (hasUnimplementedAbstractMethods(subclassQName, new HashSet<MemberInfo>(removedConcreteMethods))) {
+ if (myDependencyCache.markClass(subclassQName)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent class " + myDependencyCache.resolve(subclassQName) + "; reason: the class should be declared abstract because abstract method implementation was removed from its superclass: " +
+ myDependencyCache.resolve(myQName));
+ }
+ }
+ return true;
+ }
+ }
+
+ if (!removedOverridableMethods.isEmpty() && !myDependencyCache.isClassInfoMarked(subclassQName) && !myDependencyCache.getNewClassesCache().containsClass(subclassQName) /*not compiled in this session*/) {
+ final Cache cache = myDependencyCache.getCache();
+ for (MethodInfo subclassMethod : cache.getMethods(subclassQName)) {
+ if (!subclassMethod.isConstructor()) {
+ for (MethodInfo removedMethod : removedOverridableMethods) {
+ if (removedMethod.getName() == subclassMethod.getName() /*todo: check param signatures here for better accuracy*/) {
+ // got it
+ if (myDependencyCache.markClass(subclassQName)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Mark dependent subclass " + myDependencyCache.resolve(subclassQName) + "; reason: the class has methods annotated with @Override and some methods were changed or removed in a base class" +
+ myDependencyCache.resolve(myQName));
+ }
+ }
+ return true;
+ }
+ }
+ }
+ }
+ }
+ // end of subclass processor
+ return true;
+ }
+ });
+ }
+
+ private static boolean hasGenericsNameClashes(final MethodInfo baseMethod, final Cache oldCache, final int subclassQName) throws CacheCorruptedException {
+ // it is illegal if 2 methods in a hierarchy have 1) same name 2) different signatures 3) same erasure
+ final List<MethodInfo> methods = oldCache.findMethodsByName(subclassQName, baseMethod.getName());
+ if (methods.size() > 0) {
+ for (final MethodInfo methodInSubclass : methods) {
+ if (ClsUtil.isBridge(methodInSubclass.getFlags())) {
+ continue;
+ }
+ if (baseMethod.getDescriptor() == methodInSubclass.getDescriptor() && baseMethod.getGenericSignature() != methodInSubclass.getGenericSignature()) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private static Set<MemberInfo> fetchNonAbstractMethods(Set<MemberInfo> membersToCheck) {
+ final Set<MemberInfo> methodsToCheck = new HashSet<MemberInfo>();
+ for (final Object aMembersToCheck : membersToCheck) {
+ final MemberInfo memberInfo = (MemberInfo)aMembersToCheck;
+ if (memberInfo instanceof MethodInfo) {
+ final MethodInfo methodInfo = (MethodInfo)memberInfo;
+ if (!methodInfo.isAbstract() && !methodInfo.isConstructor()) {
+ methodsToCheck.add(memberInfo);
+ }
+ }
+ }
+ return methodsToCheck;
+ }
+
+ private boolean hasUnimplementedAbstractMethods(int superQName, final Set methodsToCheck) throws CacheCorruptedException {
+ if (myDependencyCache.getCache().containsClass(superQName)) {
+ return hasBaseAbstractMethods(superQName, methodsToCheck) ||
+ hasBaseAbstractMethodsInHierarchy(superQName, methodsToCheck);
+ }
+ else {
+ final String qName = myDependencyCache.resolve(superQName);
+ if (!CommonClassNames.JAVA_LANG_OBJECT.equals(qName)) {
+ if (hasBaseAbstractMethods2(qName, methodsToCheck)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean hasBaseAbstractMethodsInHierarchy(int fromClassQName, final Set methodsToCheck) throws CacheCorruptedException {
+ if (fromClassQName == Cache.UNKNOWN || methodsToCheck.isEmpty()) {
+ return false;
+ }
+ final Cache cache = myDependencyCache.getCache();
+ int superName = cache.getSuperQualifiedName(fromClassQName);
+ if (superName != Cache.UNKNOWN) {
+ if (hasUnimplementedAbstractMethods(superName, methodsToCheck)) {
+ return true;
+ }
+ }
+ if (methodsToCheck.isEmpty()) {
+ return false;
+ }
+ int[] superInterfaces = cache.getSuperInterfaces(fromClassQName);
+ for (int superInterface : superInterfaces) {
+ if (hasUnimplementedAbstractMethods(superInterface, methodsToCheck)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private boolean hasBaseAbstractMethods(int qName, Set methodsToCheck) throws CacheCorruptedException {
+ final SymbolTable symbolTable = myDependencyCache.getSymbolTable();
+ final Cache oldCache = myDependencyCache.getCache();
+ final Cache newCache = myDependencyCache.getNewClassesCache();
+ final Cache cache = newCache.containsClass(qName)? newCache : oldCache; // use recompiled version (if any) for searching methods
+ for (Iterator it = methodsToCheck.iterator(); it.hasNext();) {
+ final MethodInfo methodInfo = (MethodInfo)it.next();
+ final MethodInfo superMethod = cache.findMethodsBySignature(qName, methodInfo.getDescriptor(symbolTable), symbolTable);
+ if (superMethod != null) {
+ if (ClsUtil.isAbstract(superMethod.getFlags())) {
+ return true;
+ }
+ it.remove();
+ }
+ }
+ return false;
+ }
+
+ // search using PSI
+ private boolean hasBaseAbstractMethods2(final String qName, final Set methodsToCheck) throws CacheCorruptedException {
+ final boolean[] found = {false};
+ final CacheCorruptedException ex = ApplicationManager.getApplication().runReadAction(new Computable<CacheCorruptedException>() {
+ public CacheCorruptedException compute() {
+ try {
+ final PsiManager psiManager = PsiManager.getInstance(myProject);
+ final PsiClass aClass = JavaPsiFacade.getInstance(psiManager.getProject()).findClass(qName, GlobalSearchScope.allScope(myProject));
+ if (aClass == null) {
+ return null;
+ }
+ final PsiElementFactory factory = JavaPsiFacade.getInstance(psiManager.getProject()).getElementFactory();
+ final PsiNameHelper nameHelper = JavaPsiFacade.getInstance(myProject).getNameHelper();
+ for (Iterator it = methodsToCheck.iterator(); it.hasNext();) {
+ final MethodInfo methodInfo = (MethodInfo)it.next();
+ if (!nameHelper.isIdentifier(myDependencyCache.resolve(methodInfo.getName()), LanguageLevel.JDK_1_3)) { // fix for SCR 16068
+ continue;
+ }
+ // language level 1.3 will prevent exceptions from PSI if there are methods named "assert"
+ final PsiMethod methodPattern = factory.createMethodFromText(getMethodText(methodInfo), null, LanguageLevel.JDK_1_3);
+ final PsiMethod superMethod = aClass.findMethodBySignature(methodPattern, true);
+ if (superMethod != null) {
+ if (superMethod.hasModifierProperty(PsiModifier.ABSTRACT)) {
+ found[0] = true;
+ return null;
+ }
+ it.remove();
+ }
+ }
+ }
+ catch (IncorrectOperationException e) {
+ LOG.error(e);
+ }
+ catch (CacheCorruptedException e) {
+ return e;
+ }
+ return null;
+ }
+ });
+ if (ex != null) {
+ throw ex;
+ }
+ return found[0];
+ }
+
+ @SuppressWarnings({"HardCodedStringLiteral"})
+ private @NonNls String getMethodText(MethodInfo methodInfo) throws CacheCorruptedException {
+ final SymbolTable symbolTable = myDependencyCache.getSymbolTable();
+ StringBuilder text = new StringBuilder(16);
+ final String returnType = signatureToSourceTypeName(methodInfo.getReturnTypeDescriptor(symbolTable));
+ text.append(returnType);
+ text.append(" ");
+ text.append(myDependencyCache.resolve(methodInfo.getName()));
+ text.append("(");
+ final String[] parameterSignatures = methodInfo.getParameterDescriptors(symbolTable);
+ for (int idx = 0; idx < parameterSignatures.length; idx++) {
+ String parameterSignature = parameterSignatures[idx];
+ if (idx > 0) {
+ text.append(",");
+ }
+ text.append(signatureToSourceTypeName(parameterSignature));
+ text.append(" arg");
+ text.append(idx);
+ }
+ text.append(")");
+ return text.toString();
+ }
+
+ private static boolean wereInterfacesRemoved(int[] oldInterfaces, int[] newInterfaces) {
+ for (int oldInterface : oldInterfaces) {
+ boolean found = false;
+ for (int newInterface : newInterfaces) {
+ found = oldInterface == newInterface;
+ if (found) {
+ break;
+ }
+ }
+ if (!found) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /** @return a map [fieldName->FieldInfo]*/
+ private static TIntObjectHashMap<FieldInfo> getFieldInfos(Cache cache, int qName) throws CacheCorruptedException {
+ final TIntObjectHashMap<FieldInfo> map = new TIntObjectHashMap<FieldInfo>();
+ for (FieldInfo fieldInfo : cache.getFields(qName)) {
+ map.put(fieldInfo.getName(), fieldInfo);
+ }
+ return map;
+ }
+
+ /** @return a map [methodSignature->MethodInfo]*/
+ private Map<String, MethodInfoContainer> getMethodInfos(final MethodInfo[] methods) throws CacheCorruptedException {
+ final Map<String, MethodInfoContainer> map = new HashMap<String, MethodInfoContainer>();
+ final SymbolTable symbolTable = myDependencyCache.getSymbolTable();
+ for (MethodInfo methodInfo : methods) {
+ final String signature = methodInfo.getDescriptor(symbolTable);
+ final MethodInfoContainer currentValue = map.get(signature);
+ // covariant methods have the same signature, so there might be several MethodInfos for one key
+ if (currentValue == null) {
+ map.put(signature, new MethodInfoContainer(methodInfo));
+ }
+ else {
+ currentValue.add(methodInfo);
+ }
+ }
+ return map;
+ }
+
+ private static void addAddedMembers(TIntObjectHashMap<FieldInfo> oldFields, Map<String, MethodInfoContainer> oldMethods,
+ TIntObjectHashMap<FieldInfo> newFields, Map<String, MethodInfoContainer> newMethods,
+ Collection<MemberInfo> members) {
+
+ for (final TIntObjectIterator<FieldInfo> it = newFields.iterator(); it.hasNext();) {
+ it.advance();
+ final int fieldName = it.key();
+ final FieldInfo fieldInfo = it.value();
+ if (!oldFields.containsKey(fieldName)) {
+ members.add(fieldInfo);
+ }
+ }
+ for (final String signature : newMethods.keySet()) {
+ if (!oldMethods.containsKey(signature)) {
+ members.addAll(newMethods.get(signature).getMethods());
+ }
+ }
+ }
+
+ private static void addRemovedMembers(TIntObjectHashMap<FieldInfo> oldFields, Map<String, MethodInfoContainer> oldMethods,
+ TIntObjectHashMap<FieldInfo> newFields, Map<String, MethodInfoContainer> newMethods,
+ Collection<MemberInfo> members) {
+ addAddedMembers(newFields, newMethods, oldFields, oldMethods, members);
+ }
+
+ private void addChangedMembers(TIntObjectHashMap<FieldInfo> oldFields, Map<String, MethodInfoContainer> oldMethods,
+ TIntObjectHashMap<FieldInfo> newFields, Map<String, MethodInfoContainer> newMethods,
+ Collection<MemberInfo> members) throws CacheCorruptedException {
+ for (final TIntObjectIterator<FieldInfo> it = oldFields.iterator(); it.hasNext();) {
+ it.advance();
+ final int fieldName = it.key();
+ final FieldInfo oldInfo = it.value();
+ final FieldInfo newInfo = newFields.get(fieldName);
+ if (newInfo != null) {
+ final FieldChangeDescription changeDescription = new FieldChangeDescription(oldInfo, newInfo);
+ if (changeDescription.isChanged()) {
+ members.add(oldInfo);
+ myChangeDescriptions.put(oldInfo, changeDescription);
+ }
+ }
+ }
+
+ if (!oldMethods.isEmpty()) {
+ final SymbolTable symbolTable = myDependencyCache.getSymbolTable();
+ final Set<MethodInfo> processed = new HashSet<MethodInfo>();
+ for (final String signature : oldMethods.keySet()) {
+ final MethodInfoContainer oldMethodsContainer = oldMethods.get(signature);
+ final MethodInfoContainer newMethodsContainer = newMethods.get(signature);
+ if (newMethodsContainer != null) {
+ processed.clear();
+ if (oldMethodsContainer.size() == newMethodsContainer.size()) {
+ // first, process all corresponding method infos
+ for (MethodInfo oldInfo : oldMethodsContainer.getMethods()) {
+ MethodInfo _newInfo = null;
+ for (MethodInfo newInfo : newMethodsContainer.getMethods()) {
+ if (oldInfo.equals(newInfo)) {
+ _newInfo = newInfo;
+ break;
+ }
+ }
+ if (_newInfo != null) {
+ processed.add(oldInfo);
+ processed.add(_newInfo);
+ final MethodChangeDescription changeDescription = new MethodChangeDescription(oldInfo, _newInfo, symbolTable);
+ if (changeDescription.isChanged()) {
+ members.add(oldInfo);
+ myChangeDescriptions.put(oldInfo, changeDescription);
+ }
+ }
+ }
+ }
+ // processing the rest of infos, each pair
+ for (MethodInfo oldInfo : oldMethodsContainer.getMethods()) {
+ if (processed.contains(oldInfo)) {
+ continue;
+ }
+ for (MethodInfo newInfo : newMethodsContainer.getMethods()) {
+ if (processed.contains(newInfo)) {
+ continue;
+ }
+ final MethodChangeDescription changeDescription = new MethodChangeDescription(oldInfo, newInfo, symbolTable);
+ if (changeDescription.isChanged()) {
+ members.add(oldInfo);
+ myChangeDescriptions.put(oldInfo, changeDescription);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private boolean hasEquivalentMethod(Collection<MethodInfo> members, Dependency.MethodRef modelMethod) throws CacheCorruptedException {
+ final String[] modelSignature = modelMethod.getParameterDescriptors(myDependencyCache.getSymbolTable());
+ for (final MethodInfo method : members) {
+ if (modelMethod.name != method.getName()) {
+ continue;
+ }
+ final String[] methodSignature = method.getParameterDescriptors(myDependencyCache.getSymbolTable());
+ if (modelSignature.length != methodSignature.length) {
+ continue;
+ }
+
+ for (int i = 0; i < methodSignature.length; i++) {
+ if (!methodSignature[i].equals(modelSignature[i])) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Equivalent: " + modelMethod.getDescriptor(myDependencyCache.getSymbolTable()) + " <=> " +
+ method.getDescriptor(myDependencyCache.getSymbolTable()));
+ }
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private static @NonNls
+ String signatureToSourceTypeName(String signature)
+ {
+ try {
+ switch(signature.charAt(0)) {
+ case 'B' : return "byte";
+ case 'C' : return "char";
+ case 'D' : return "double";
+ case 'F' : return "float";
+ case 'I' : return "int";
+ case 'J' : return "long";
+
+ case 'L' : { // Full class name
+ int index = signature.indexOf(';'); // Look for closing `;'
+
+ if(index < 0)
+ throw new RuntimeException("Invalid signature: " + signature);
+
+ return signature.substring(1, index).replace('/', '.');
+ }
+
+ case 'S' : return "short";
+ case 'Z' : return "boolean";
+
+ case '[' : { // Array declaration
+ int n;
+ StringBuffer brackets;
+ String type;
+
+ brackets = new StringBuffer(); // Accumulate []'s
+
+ // Count opening brackets and look for optional size argument
+ for(n=0; signature.charAt(n) == '['; n++)
+ brackets.append("[]");
+
+
+ // The rest of the string denotes a `<field_type>'
+ type = signatureToSourceTypeName(signature.substring(n));
+
+ return type + brackets.toString();
+ }
+
+ case 'V' : return "void";
+
+ default : throw new RuntimeException("Invalid signature: `" +
+ signature + "'");
+ }
+ } catch(StringIndexOutOfBoundsException e) { // Should never occur
+ throw new RuntimeException("Invalid signature: " + e + ":" + signature);
+ }
+ }
+
+ private Dependency[] getBackDependencies() throws CacheCorruptedException {
+ if (myBackDependencies == null) {
+ myBackDependencies = myDependencyCache.getCache().getBackDependencies(myQName);
+ }
+ return myBackDependencies;
+ }
+
+ private static class MethodInfoContainer {
+ private List<MethodInfo> myInfos = null;
+
+ protected MethodInfoContainer(MethodInfo info) {
+ myInfos = Collections.singletonList(info);
+ }
+
+ public List<MethodInfo> getMethods() {
+ return myInfos;
+ }
+
+ public int size() {
+ return myInfos.size();
+ }
+
+ public void add(MethodInfo info) {
+ if (myInfos.size() == 1) {
+ myInfos = new ArrayList<MethodInfo>(myInfos);
+ }
+ myInfos.add(info);
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/make/MakeUtil.java b/java/compiler/impl/src/com/intellij/compiler/make/MakeUtil.java
new file mode 100644
index 0000000..52f347f
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/make/MakeUtil.java
@@ -0,0 +1,300 @@
+/*
+ * 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.
+ */
+
+/**
+ * created at Jan 17, 2002
+ * @author Jeka
+ */
+package com.intellij.compiler.make;
+
+import com.intellij.compiler.SymbolTable;
+import com.intellij.compiler.classParsing.*;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.roots.CompilerModuleExtension;
+import com.intellij.openapi.roots.ProjectFileIndex;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.StringBuilderSpinAllocator;
+import com.intellij.util.cls.ClsUtil;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.Nullable;
+
+public class MakeUtil {
+
+ private MakeUtil() {
+ }
+
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.make.MakeUtil");
+
+
+ public static VirtualFile getSourceRoot(CompileContext context, Module module, VirtualFile file) {
+ final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(module.getProject()).getFileIndex();
+ final VirtualFile root = fileIndex.getSourceRootForFile(file);
+ if (root != null) {
+ return root;
+ }
+ // try to find among roots of generated files.
+ final VirtualFile[] sourceRoots = context.getSourceRoots(module);
+ for (final VirtualFile sourceRoot : sourceRoots) {
+ if (fileIndex.isInSourceContent(sourceRoot)) {
+ continue; // skip content source roots, need only roots for generated files
+ }
+ if (VfsUtil.isAncestor(sourceRoot, file, false)) {
+ return sourceRoot;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * cuts inner or anonymous class' parts and translates package names to lower case
+ */
+ private static String normalizeClassName(String qName) {
+ int index = qName.indexOf('$');
+ if (index >= 0) {
+ qName = qName.substring(0, index);
+ }
+ if (SystemInfo.isFileSystemCaseSensitive) {
+ return qName;
+ }
+ // the name of a dir should be lowercased because javac seem to allow difference in case
+ // between the physical directory and package name.
+ final int dotIndex = qName.lastIndexOf('.');
+ final StringBuilder builder = StringBuilderSpinAllocator.alloc();
+ try {
+ builder.append(qName);
+ for (int idx = 0; idx < dotIndex; idx++) {
+ builder.setCharAt(idx, Character.toLowerCase(builder.charAt(idx)));
+ }
+ return builder.toString();
+ }
+ finally {
+ StringBuilderSpinAllocator.dispose(builder);
+ }
+ }
+
+ public static boolean isAnonymous(String name) {
+ int index = name.lastIndexOf('$');
+ if (index >= 0) {
+ index++;
+ if (index < name.length()) {
+ try {
+ Integer.parseInt(name.substring(index));
+ return true;
+ }
+ catch (NumberFormatException e) {
+ }
+ }
+ }
+ return false;
+ }
+
+ /*
+ not needed currently
+ public static String getEnclosingClassName(String anonymousQName) {
+ return anonymousQName.substring(0, anonymousQName.lastIndexOf('$'));
+ }
+ */
+
+ /*
+ not needed currently
+ public static boolean isNative(int flags) {
+ return (ClsUtil.ACC_NATIVE & flags) != 0;
+ }
+ */
+
+ /**
+ * tests if the accessibility, denoted by flags1 is less restricted than the accessibility denoted by flags2
+ * @return true means flags1 is less restricted than flags2 <br>
+ * false means flags1 define more restricted access than flags2 or they have equal accessibility
+ */
+ public static boolean isMoreAccessible(int flags1, int flags2) {
+ if (ClsUtil.isPrivate(flags2)) {
+ return ClsUtil.isPackageLocal(flags1) || ClsUtil.isProtected(flags1) || ClsUtil.isPublic(flags1);
+ }
+ if (ClsUtil.isPackageLocal(flags2)) {
+ return ClsUtil.isProtected(flags1) || ClsUtil.isPublic(flags1);
+ }
+ if (ClsUtil.isProtected(flags2)) {
+ return ClsUtil.isPublic(flags1);
+ }
+ return false;
+ }
+
+ @Nullable
+ @SuppressWarnings({"HardCodedStringLiteral"})
+ public static String relativeClassPathToQName(String relativePath, char separator) {
+ if (!relativePath.endsWith(".class")) {
+ return null;
+ }
+ int start = 0;
+ int end = relativePath.length() - ".class".length();
+ if (relativePath.startsWith(String.valueOf(separator))) {
+ start += 1;
+ }
+ return (start <= end)? relativePath.substring(start, end).replace(separator, '.') : null;
+ }
+
+ public static @NonNls String parseObjectType(final String descriptor, int fromIndex) {
+ int semicolonIndex = descriptor.indexOf(';', fromIndex);
+ if (descriptor.charAt(fromIndex) == 'L' && semicolonIndex > fromIndex) { // isObjectType
+ return descriptor.substring(fromIndex + 1, semicolonIndex).replace('/', '.');
+ }
+ if (descriptor.charAt(fromIndex) == '[' && (descriptor.length() - fromIndex) > 0) { // isArrayType
+ return parseObjectType(descriptor, fromIndex + 1);
+ }
+ return null;
+ }
+
+ @SuppressWarnings({"HardCodedStringLiteral"})
+ public static boolean isPrimitiveType(String descriptor) {
+ return
+ "V".equals(descriptor) ||
+ "B".equals(descriptor) ||
+ "C".equals(descriptor) ||
+ "D".equals(descriptor) ||
+ "F".equals(descriptor) ||
+ "I".equals(descriptor) ||
+ "J".equals(descriptor) ||
+ "S".equals(descriptor) ||
+ "Z".equals(descriptor);
+ }
+
+ public static boolean isArrayType(String descriptor) {
+ return StringUtil.startsWithChar(descriptor, '[');
+ }
+
+ public static String getComponentType(String descriptor) {
+ if (!isArrayType(descriptor)) {
+ return null;
+ }
+ return descriptor.substring(1);
+ }
+
+
+ /**
+ * @return a normalized path to source relative to a source root by class qualified name and sourcefile short name.
+ * The path uses forward slashes "/".
+ */
+ public static String createRelativePathToSource(String qualifiedName, String srcName) {
+ qualifiedName = normalizeClassName(qualifiedName);
+ int index = qualifiedName.lastIndexOf('.');
+ if (index >= 0) {
+ srcName = qualifiedName.substring(0, index).replace('.', '/') + "/" + srcName;
+ }
+ return srcName;
+ }
+
+ public static boolean isInterface(int flags) {
+ return (ClsUtil.ACC_INTERFACE & flags) != 0;
+ }
+
+ public static int getAnnotationTargets(final Cache cache, final int annotationQName, final SymbolTable symbolTable) throws CacheCorruptedException {
+ final AnnotationConstantValue targetAnnotation = findAnnotation(
+ "java.lang.annotation.Target",
+ cache.getRuntimeVisibleAnnotations(annotationQName), symbolTable);
+ if (targetAnnotation == null) {
+ return AnnotationTargets.ALL; // all program elements are annotation targets by default
+ }
+ final AnnotationNameValuePair[] memberValues = targetAnnotation.getMemberValues();
+ ConstantValueArray value = (ConstantValueArray)memberValues[0].getValue();
+ final ConstantValue[] targets = value.getValue();
+ int annotationTargets = 0;
+ for (final ConstantValue target : targets) {
+ if (target instanceof EnumConstantValue) {
+ final String constantName = symbolTable.getSymbol(((EnumConstantValue)target).getConstantName());
+ if (AnnotationTargets.TYPE_STR.equals(constantName)) {
+ annotationTargets |= AnnotationTargets.TYPE;
+ }
+ if (AnnotationTargets.FIELD_STR.equals(constantName)) {
+ annotationTargets |= AnnotationTargets.FIELD;
+ }
+ if (AnnotationTargets.METHOD_STR.equals(constantName)) {
+ annotationTargets |= AnnotationTargets.METHOD;
+ }
+ if (AnnotationTargets.PARAMETER_STR.equals(constantName)) {
+ annotationTargets |= AnnotationTargets.PARAMETER;
+ }
+ if (AnnotationTargets.CONSTRUCTOR_STR.equals(constantName)) {
+ annotationTargets |= AnnotationTargets.CONSTRUCTOR;
+ }
+ if (AnnotationTargets.LOCAL_VARIABLE_STR.equals(constantName)) {
+ annotationTargets |= AnnotationTargets.LOCAL_VARIABLE;
+ }
+ if (AnnotationTargets.ANNOTATION_TYPE_STR.equals(constantName)) {
+ annotationTargets |= AnnotationTargets.ANNOTATION_TYPE;
+ }
+ if (AnnotationTargets.PACKAGE_STR.equals(constantName)) {
+ annotationTargets |= AnnotationTargets.PACKAGE;
+ }
+ }
+ }
+ return annotationTargets;
+ }
+
+ public static int getAnnotationRetentionPolicy(final int annotationQName, final Cache cache, final SymbolTable symbolTable) throws CacheCorruptedException {
+ final AnnotationConstantValue retentionPolicyAnnotation = findAnnotation(
+ "java.lang.annotation.Retention",
+ cache.getRuntimeVisibleAnnotations(annotationQName), symbolTable
+ );
+ if (retentionPolicyAnnotation == null) {
+ return RetentionPolicies.CLASS; // default retention policy
+ }
+ final AnnotationNameValuePair[] memberValues = retentionPolicyAnnotation.getMemberValues();
+ final EnumConstantValue value = (EnumConstantValue)memberValues[0].getValue();
+ final String constantName = symbolTable.getSymbol(value.getConstantName());
+ if (RetentionPolicies.SOURCE_STR.equals(constantName)) {
+ return RetentionPolicies.SOURCE;
+ }
+ if (RetentionPolicies.CLASS_STR.equals(constantName)) {
+ return RetentionPolicies.CLASS;
+ }
+ if (RetentionPolicies.RUNTIME_STR.equals(constantName)) {
+ return RetentionPolicies.RUNTIME;
+ }
+ LOG.error("Unknown retention policy: " + constantName);
+ return -1;
+ }
+
+ public static AnnotationConstantValue findAnnotation(@NonNls final String annotationQName,
+ AnnotationConstantValue[] annotations, final SymbolTable symbolTable) throws CacheCorruptedException {
+ for (final AnnotationConstantValue annotation : annotations) {
+ if (annotationQName.equals(symbolTable.getSymbol(annotation.getAnnotationQName()))) {
+ return annotation;
+ }
+ }
+ return null;
+ }
+
+ public static String getModuleOutputDirPath(final Module module) {
+ return ApplicationManager.getApplication().runReadAction(new Computable<String>() {
+ public String compute() {
+ final String url = CompilerModuleExtension.getInstance(module).getCompilerOutputUrl();
+ if (url == null) {
+ return null;
+ }
+ return VfsUtil.urlToPath(url);
+ }
+ });
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/make/MethodChangeDescription.java b/java/compiler/impl/src/com/intellij/compiler/make/MethodChangeDescription.java
new file mode 100644
index 0000000..2465703
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/make/MethodChangeDescription.java
@@ -0,0 +1,90 @@
+/*
+ * 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 com.intellij.compiler.make;
+
+import com.intellij.compiler.SymbolTable;
+import com.intellij.compiler.classParsing.ConstantValue;
+import com.intellij.compiler.classParsing.GenericMethodSignature;
+import com.intellij.compiler.classParsing.MethodInfo;
+import com.intellij.compiler.classParsing.SignatureParsingException;
+import com.intellij.util.cls.ClsUtil;
+
+import java.util.Arrays;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Apr 7, 2004
+ */
+class MethodChangeDescription extends ChangeDescription {
+ public final boolean returnTypeDescriptorChanged;
+ public final boolean returnTypeGenericSignatureChanged;
+ public final boolean paramsGenericSignatureChanged;
+ public final boolean throwsListChanged;
+ public final boolean flagsChanged;
+ public final boolean staticPropertyChanged;
+ public final boolean accessRestricted;
+ public final boolean becameAbstract;
+ public final boolean removedAnnotationDefault;
+
+ public MethodChangeDescription(final MethodInfo oldMethod, final MethodInfo newMethod, SymbolTable symbolTable) throws CacheCorruptedException {
+ final String oldRtDescriptor = oldMethod.getReturnTypeDescriptor(symbolTable);
+ final String newRtDescriptor = newMethod.getReturnTypeDescriptor(symbolTable);
+ returnTypeDescriptorChanged = !oldRtDescriptor.equals(newRtDescriptor);
+
+ final int oldGenericSignature = oldMethod.getGenericSignature();
+ final int newGenericSignature = newMethod.getGenericSignature();
+ if (oldGenericSignature == newGenericSignature) {
+ returnTypeGenericSignatureChanged = false;
+ paramsGenericSignatureChanged = false;
+ }
+ else {
+ if (oldGenericSignature != -1 && newGenericSignature != -1) {
+ try {
+ final GenericMethodSignature _oldGenericMethodSignature = GenericMethodSignature.parse(symbolTable.getSymbol(oldGenericSignature));
+ final GenericMethodSignature _newGenericMethodSignature = GenericMethodSignature.parse(symbolTable.getSymbol(newGenericSignature));
+ returnTypeGenericSignatureChanged = !_oldGenericMethodSignature.getReturnTypeSignature().equals(_newGenericMethodSignature.getReturnTypeSignature());
+ paramsGenericSignatureChanged = !_oldGenericMethodSignature.getFormalTypeParams().equals(_newGenericMethodSignature.getFormalTypeParams()) ||
+ !Arrays.equals(_oldGenericMethodSignature.getParamSignatures(), _newGenericMethodSignature.getParamSignatures());
+ }
+ catch (SignatureParsingException e) {
+ throw new CacheCorruptedException(e);
+ }
+ }
+ else {
+ returnTypeGenericSignatureChanged = true;
+ paramsGenericSignatureChanged = true;
+ }
+ }
+
+ throwsListChanged = !CacheUtils.areArraysContentsEqual(oldMethod.getThrownExceptions(), newMethod.getThrownExceptions());
+
+ final int oldFlags = oldMethod.getFlags();
+ final int newFlags = newMethod.getFlags();
+ flagsChanged = oldFlags != newFlags;
+
+ staticPropertyChanged = (ClsUtil.isStatic(oldFlags) && !ClsUtil.isStatic(newFlags)) || (!ClsUtil.isStatic(oldFlags) && ClsUtil.isStatic(newFlags)); // was not static and became static or was static and became not static
+ accessRestricted = MakeUtil.isMoreAccessible(oldFlags, newFlags);
+ becameAbstract = !ClsUtil.isAbstract(oldFlags) && ClsUtil.isAbstract(newFlags);
+
+ final ConstantValue oldDefault = oldMethod.getAnnotationDefault();
+ final ConstantValue newDefault = newMethod.getAnnotationDefault();
+ removedAnnotationDefault = (oldDefault != null && !ConstantValue.EMPTY_CONSTANT_VALUE.equals(oldDefault)) && (newDefault == null || ConstantValue.EMPTY_CONSTANT_VALUE.equals(newDefault));
+ }
+
+ public boolean isChanged() {
+ return returnTypeDescriptorChanged || throwsListChanged || flagsChanged || returnTypeGenericSignatureChanged || paramsGenericSignatureChanged || removedAnnotationDefault;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/make/RetentionPolicies.java b/java/compiler/impl/src/com/intellij/compiler/make/RetentionPolicies.java
new file mode 100644
index 0000000..13cfaa5
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/make/RetentionPolicies.java
@@ -0,0 +1,50 @@
+/*
+ * 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 com.intellij.compiler.make;
+
+import org.jetbrains.annotations.NonNls;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Apr 7, 2004
+ */
+public interface RetentionPolicies {
+ /**
+ * Annotations are to be discarded by the compiler.
+ */
+ int SOURCE = 0x1;
+
+ @NonNls String SOURCE_STR = "SOURCE";
+
+ /**
+ * Annotations are to be recorded in the class file by the compiler
+ * but need not be retained by the VM at run time. This is the default
+ * behavior.
+ */
+ int CLASS = 0x2;
+
+ @NonNls String CLASS_STR = "CLASS";
+
+ /**
+ * Annotations are to be recorded in the class file by the compiler and
+ * retained by the VM at run time, so they may be read reflectively.
+ */
+ int RUNTIME = 0x4;
+
+ @NonNls String RUNTIME_STR = "RUNTIME";
+
+ int ALL = SOURCE | CLASS | RUNTIME;
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/make/SourceFileFinder.java b/java/compiler/impl/src/com/intellij/compiler/make/SourceFileFinder.java
new file mode 100644
index 0000000..1e17b86
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/make/SourceFileFinder.java
@@ -0,0 +1,122 @@
+/*
+ * 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 com.intellij.compiler.make;
+
+import com.intellij.compiler.CompilerConfiguration;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ProjectFileIndex;
+import com.intellij.openapi.roots.ProjectRootManager;
+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.VirtualFile;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.util.containers.HashMap;
+
+import java.util.Map;
+
+/**
+ * Assumes that source roots in the project has not changed and caches the snapshot of source roots for effective searching
+ * User: JEKA
+ * Date: Jul 17, 2003
+ * Time: 9:52:26 PM
+ */
+public class SourceFileFinder {
+ private final Project myProject;
+ private final CompileContext myCompileContext;
+ private Map<VirtualFile, String> myProjectSourceRoots = null;
+ private final CompilerConfiguration myCompilerConfiguration;
+
+ public SourceFileFinder(Project project, CompileContext compileContext) {
+ myProject = project;
+ myCompileContext = compileContext;
+ myCompilerConfiguration = CompilerConfiguration.getInstance(project);
+ }
+
+ public VirtualFile findSourceFile(String qualifiedName, final String srcName, boolean checkIfExcludedFromMake) {
+ // optimization
+ final int dollar = qualifiedName.indexOf('$');
+ final String outerQName = (dollar >= 0)? qualifiedName.substring(0, dollar) : qualifiedName;
+ final PsiClass[] classes = JavaPsiFacade.getInstance(myProject).findClasses(outerQName, GlobalSearchScope.projectScope(myProject));
+ for (PsiClass aClass : classes) {
+ final PsiFile file = aClass.getContainingFile();
+ if (srcName.equals(file.getName())) {
+ final VirtualFile vFile = file.getVirtualFile();
+ if (vFile != null && (!checkIfExcludedFromMake || !myCompilerConfiguration.isExcludedFromCompilation(vFile))) {
+ return vFile;
+ }
+ }
+ }
+
+ String relativePath = MakeUtil.createRelativePathToSource(qualifiedName, srcName);
+ Map<VirtualFile, String> dirs = getAllSourceRoots();
+ if (!StringUtil.startsWithChar(relativePath, '/')) {
+ relativePath = "/" + relativePath;
+ }
+ LocalFileSystem fs = LocalFileSystem.getInstance();
+ for (final VirtualFile virtualFile : dirs.keySet()) {
+ final String prefix = dirs.get(virtualFile);
+ String path;
+ if (prefix.length() > 0) {
+ if (FileUtil.startsWith(relativePath, prefix)) {
+ // if there is package prefix assigned to the root, the relative path should be corrected
+ path = virtualFile.getPath() + relativePath.substring(prefix.length() - 1);
+ }
+ else {
+ // if there is package prefix, but the relative path does not match it, skip the root
+ continue;
+ }
+ }
+ else {
+ path = virtualFile.getPath() + relativePath;
+ }
+ VirtualFile file = fs.findFileByPath(path);
+ if (file != null && (!checkIfExcludedFromMake || !myCompilerConfiguration.isExcludedFromCompilation(virtualFile))) {
+ return file;
+ }
+ }
+ return null;
+ }
+
+ private Map<VirtualFile, String> getAllSourceRoots() {
+ if (myProjectSourceRoots == null) {
+ myProjectSourceRoots = new HashMap<VirtualFile, String>();
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ final ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
+ final Module[] allModules = ModuleManager.getInstance(myProject).getModules();
+ for (Module allModule : allModules) {
+ final VirtualFile[] sourceRoots = myCompileContext.getSourceRoots(allModule);
+ for (final VirtualFile sourceRoot : sourceRoots) {
+ String packageName = fileIndex.getPackageNameByDirectory(sourceRoot);
+ myProjectSourceRoots
+ .put(sourceRoot, packageName == null || packageName.length() == 0 ? "" : "/" + packageName.replace('.', '/') + "/");
+ }
+ }
+ }
+ });
+ }
+ return myProjectSourceRoots;
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/options/AnnotationProcessorsConfigurable.java b/java/compiler/impl/src/com/intellij/compiler/options/AnnotationProcessorsConfigurable.java
new file mode 100644
index 0000000..74ed9e1
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/options/AnnotationProcessorsConfigurable.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.options;
+
+import com.intellij.compiler.CompilerConfiguration;
+import com.intellij.compiler.CompilerConfigurationImpl;
+import com.intellij.openapi.options.Configurable;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.options.SearchableConfigurable;
+import com.intellij.openapi.project.Project;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.jps.model.java.compiler.ProcessorConfigProfile;
+
+import javax.swing.*;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Oct 5, 2009
+ */
+public class AnnotationProcessorsConfigurable implements SearchableConfigurable, Configurable.NoScroll {
+
+ private final Project myProject;
+ private AnnotationProcessorsPanel myMainPanel;
+
+ public AnnotationProcessorsConfigurable(final Project project) {
+ myProject = project;
+ }
+
+ public String getDisplayName() {
+ return "Annotation Processors";
+ }
+
+ public String getHelpTopic() {
+ return "reference.projectsettings.compiler.annotationProcessors";
+ }
+
+ @NotNull
+ public String getId() {
+ return getHelpTopic();
+ }
+
+ public Runnable enableSearch(String option) {
+ return null;
+ }
+
+ public JComponent createComponent() {
+ myMainPanel = new AnnotationProcessorsPanel(myProject);
+ return myMainPanel;
+ }
+
+ public boolean isModified() {
+ final CompilerConfigurationImpl config = (CompilerConfigurationImpl)CompilerConfiguration.getInstance(myProject);
+
+ if (!config.getDefaultProcessorProfile().equals(myMainPanel.getDefaultProfile())) {
+ return true;
+ }
+
+ final Map<String, ProcessorConfigProfile> configProfiles = new java.util.HashMap<String, ProcessorConfigProfile>();
+ for (ProcessorConfigProfile profile : config.getModuleProcessorProfiles()) {
+ configProfiles.put(profile.getName(), profile);
+ }
+ final List<ProcessorConfigProfile> panelProfiles = myMainPanel.getModuleProfiles();
+ if (configProfiles.size() != panelProfiles.size()) {
+ return true;
+ }
+ for (ProcessorConfigProfile panelProfile : panelProfiles) {
+ final ProcessorConfigProfile configProfile = configProfiles.get(panelProfile.getName());
+ if (configProfile == null || !configProfile.equals(panelProfile)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public void apply() throws ConfigurationException {
+ final CompilerConfigurationImpl config = (CompilerConfigurationImpl)CompilerConfiguration.getInstance(myProject);
+ config.setDefaultProcessorProfile(myMainPanel.getDefaultProfile());
+ config.setModuleProcessorProfiles(myMainPanel.getModuleProfiles());
+ }
+
+ public void reset() {
+ final CompilerConfigurationImpl config = (CompilerConfigurationImpl)CompilerConfiguration.getInstance(myProject);
+ myMainPanel.initProfiles(config.getDefaultProcessorProfile(), config.getModuleProcessorProfiles());
+ }
+
+ public void disposeUIResources() {
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/options/AnnotationProcessorsPanel.java b/java/compiler/impl/src/com/intellij/compiler/options/AnnotationProcessorsPanel.java
new file mode 100644
index 0000000..37a8ceb
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/options/AnnotationProcessorsPanel.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.options;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.actionSystem.ActionManager;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.ShortcutSet;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.InputValidatorEx;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.ui.Splitter;
+import com.intellij.openapi.ui.popup.JBPopup;
+import com.intellij.openapi.ui.popup.JBPopupFactory;
+import com.intellij.openapi.util.Comparing;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.ui.AnActionButton;
+import com.intellij.ui.ColoredTreeCellRenderer;
+import com.intellij.ui.IdeBorderFactory;
+import com.intellij.ui.ToolbarDecorator;
+import com.intellij.ui.awt.RelativePoint;
+import com.intellij.ui.components.JBList;
+import com.intellij.ui.treeStructure.Tree;
+import com.intellij.util.ui.EditableTreeModel;
+import com.intellij.util.ui.tree.TreeUtil;
+import org.jetbrains.jps.model.java.compiler.ProcessorConfigProfile;
+import org.jetbrains.jps.model.java.impl.compiler.ProcessorConfigProfileImpl;
+
+import javax.swing.*;
+import javax.swing.event.TreeSelectionEvent;
+import javax.swing.event.TreeSelectionListener;
+import javax.swing.tree.DefaultMutableTreeNode;
+import javax.swing.tree.DefaultTreeModel;
+import javax.swing.tree.TreePath;
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.util.*;
+import java.util.List;
+
+/**
+ * @author Konstantin Bulenkov
+ */
+@SuppressWarnings({"unchecked", "UseOfObsoleteCollectionType"})
+public class AnnotationProcessorsPanel extends JPanel {
+ private final ProcessorConfigProfile myDefaultProfile = new ProcessorConfigProfileImpl("");
+ private final List<ProcessorConfigProfile> myModuleProfiles = new ArrayList<ProcessorConfigProfile>();
+ private final Map<String, Module> myAllModulesMap = new HashMap<String, Module>();
+ private final Project myProject;
+ private final Tree myTree;
+ private final ProcessorProfilePanel myProfilePanel;
+ private ProcessorConfigProfile mySelectedProfile = null;
+
+ public AnnotationProcessorsPanel(Project project) {
+ super(new BorderLayout());
+ Splitter splitter = new Splitter(false, 0.3f);
+ add(splitter, BorderLayout.CENTER);
+ myProject = project;
+ for (Module module : ModuleManager.getInstance(project).getModules()) {
+ myAllModulesMap.put(module.getName(), module);
+ }
+ myTree = new Tree(new MyTreeModel());
+ myTree.setRootVisible(false);
+ final JPanel treePanel =
+ ToolbarDecorator.createDecorator(myTree).addExtraAction(new AnActionButton("Move to", AllIcons.Actions.Nextfile) {
+ @Override
+ public void actionPerformed(AnActionEvent e) {
+ final MyModuleNode node = (MyModuleNode)myTree.getSelectionPath().getLastPathComponent();
+ final TreePath[] selectedNodes = myTree.getSelectionPaths();
+ final ProcessorConfigProfile nodeProfile = ((ProfileNode)node.getParent()).myProfile;
+ final List<ProcessorConfigProfile> profiles = new ArrayList<ProcessorConfigProfile>();
+ profiles.add(myDefaultProfile);
+ for (ProcessorConfigProfile profile : myModuleProfiles) {
+ profiles.add(profile);
+ }
+ profiles.remove(nodeProfile);
+ final JBList list = new JBList(profiles);
+ final JBPopup popup = JBPopupFactory.getInstance().createListPopupBuilder(list)
+ .setTitle("Move to")
+ .setItemChoosenCallback(new Runnable() {
+ @Override
+ public void run() {
+ final Object value = list.getSelectedValue();
+ if (value instanceof ProcessorConfigProfile) {
+ final ProcessorConfigProfile chosenProfile = (ProcessorConfigProfile)value;
+ final Module toSelect = (Module)node.getUserObject();
+ if (selectedNodes != null) {
+ for (TreePath selectedNode : selectedNodes) {
+ final Object node = selectedNode.getLastPathComponent();
+ if (node instanceof MyModuleNode) {
+ final Module module = (Module)((MyModuleNode)node).getUserObject();
+ if (nodeProfile != myDefaultProfile) {
+ nodeProfile.removeModuleName(module.getName());
+ }
+ if (chosenProfile != myDefaultProfile) {
+ chosenProfile.addModuleName(module.getName());
+ }
+ }
+ }
+ }
+
+ final RootNode root = (RootNode)myTree.getModel().getRoot();
+ root.sync();
+ final DefaultMutableTreeNode node = TreeUtil.findNodeWithObject(root, toSelect);
+ if (node != null) {
+ TreeUtil.selectNode(myTree, node);
+ }
+ }
+ }
+ })
+ .createPopup();
+ RelativePoint point =
+ e.getInputEvent() instanceof MouseEvent ? getPreferredPopupPoint() : TreeUtil.getPointForSelection(myTree);
+ popup.show(point);
+ }
+
+ @Override
+ public ShortcutSet getShortcut() {
+ return ActionManager.getInstance().getAction("Move").getShortcutSet();
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return myTree.getSelectionPath() != null
+ && myTree.getSelectionPath().getLastPathComponent() instanceof MyModuleNode
+ && !myModuleProfiles.isEmpty();
+ }
+ }).createPanel();
+ splitter.setFirstComponent(treePanel);
+ myTree.setCellRenderer(new MyCellRenderer());
+
+ myTree.addTreeSelectionListener(new TreeSelectionListener() {
+ @Override
+ public void valueChanged(TreeSelectionEvent e) {
+ final TreePath path = myTree.getSelectionPath();
+ if (path != null) {
+ Object node = path.getLastPathComponent();
+ if (node instanceof MyModuleNode) {
+ node = ((MyModuleNode)node).getParent();
+ }
+ if (node instanceof ProfileNode) {
+ final ProcessorConfigProfile nodeProfile = ((ProfileNode)node).myProfile;
+ final ProcessorConfigProfile selectedProfile = mySelectedProfile;
+ if (nodeProfile != selectedProfile) {
+ if (selectedProfile != null) {
+ myProfilePanel.saveTo(selectedProfile);
+ }
+ mySelectedProfile = nodeProfile;
+ myProfilePanel.setProfile(nodeProfile);
+ }
+ }
+ }
+ }
+ });
+ myProfilePanel = new ProcessorProfilePanel(project);
+ myProfilePanel.setBorder(IdeBorderFactory.createEmptyBorder(0, 6, 0, 0));
+ splitter.setSecondComponent(myProfilePanel);
+ }
+
+ public void initProfiles(ProcessorConfigProfile defaultProfile, Collection<ProcessorConfigProfile> moduleProfiles) {
+ myDefaultProfile.initFrom(defaultProfile);
+ myModuleProfiles.clear();
+ for (ProcessorConfigProfile profile : moduleProfiles) {
+ ProcessorConfigProfile copy = new ProcessorConfigProfileImpl("");
+ copy.initFrom(profile);
+ myModuleProfiles.add(copy);
+ }
+ final RootNode root = (RootNode)myTree.getModel().getRoot();
+ root.sync();
+ final DefaultMutableTreeNode node = TreeUtil.findNodeWithObject(root, myDefaultProfile);
+ if (node != null) {
+ TreeUtil.selectNode(myTree, node);
+ }
+
+ }
+
+ public ProcessorConfigProfile getDefaultProfile() {
+ final ProcessorConfigProfile selectedProfile = mySelectedProfile;
+ if (myDefaultProfile == selectedProfile) {
+ myProfilePanel.saveTo(selectedProfile);
+ }
+ return myDefaultProfile;
+ }
+
+ public List<ProcessorConfigProfile> getModuleProfiles() {
+ final ProcessorConfigProfile selectedProfile = mySelectedProfile;
+ if (myDefaultProfile != selectedProfile) {
+ myProfilePanel.saveTo(selectedProfile);
+ }
+ return myModuleProfiles;
+ }
+
+ private static void expand(JTree tree) {
+ int oldRowCount = 0;
+ do {
+ int rowCount = tree.getRowCount();
+ if (rowCount == oldRowCount) break;
+ oldRowCount = rowCount;
+ for (int i = 0; i < rowCount; i++) {
+ tree.expandRow(i);
+ }
+ }
+ while (true);
+ }
+
+ private class MyTreeModel extends DefaultTreeModel implements EditableTreeModel{
+ public MyTreeModel() {
+ super(new RootNode());
+ }
+
+ @Override
+ public TreePath addNode(TreePath parentOrNeighbour) {
+ final String newProfileName = Messages.showInputDialog(
+ myProject, "Profile name", "Create new profile", null, "",
+ new InputValidatorEx() {
+ @Override
+ public boolean checkInput(String inputString) {
+ if (StringUtil.isEmpty(inputString) ||
+ Comparing.equal(inputString, myDefaultProfile.getName())) {
+ return false;
+ }
+ for (ProcessorConfigProfile profile : myModuleProfiles) {
+ if (Comparing.equal(inputString, profile.getName())) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean canClose(String inputString) {
+ return checkInput(inputString);
+ }
+
+ @Override
+ public String getErrorText(String inputString) {
+ if (checkInput(inputString)) {
+ return null;
+ }
+ return StringUtil.isEmpty(inputString)
+ ? "Profile name shouldn't be empty"
+ : "Profile " + inputString + " already exists";
+ }
+ });
+ if (newProfileName != null) {
+ final ProcessorConfigProfile profile = new ProcessorConfigProfileImpl(newProfileName);
+ myModuleProfiles.add(profile);
+ ((DataSynchronizable)getRoot()).sync();
+ final DefaultMutableTreeNode object = TreeUtil.findNodeWithObject((DefaultMutableTreeNode)getRoot(), profile);
+ if (object != null) {
+ TreeUtil.selectNode(myTree, object);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void removeNode(TreePath nodePath) {
+ Object node = nodePath.getLastPathComponent();
+ if (node instanceof ProfileNode) {
+ final ProcessorConfigProfile nodeProfile = ((ProfileNode)node).myProfile;
+ if (nodeProfile != myDefaultProfile) {
+ if (mySelectedProfile == nodeProfile) {
+ mySelectedProfile = null;
+ }
+ myModuleProfiles.remove(nodeProfile);
+ ((DataSynchronizable)getRoot()).sync();
+ final DefaultMutableTreeNode object = TreeUtil.findNodeWithObject((DefaultMutableTreeNode)getRoot(), myDefaultProfile);
+ if (object != null) {
+ TreeUtil.selectNode(myTree, object);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void moveNodeTo(TreePath parentOrNeighbour) {
+ }
+
+ }
+
+
+ private class RootNode extends DefaultMutableTreeNode implements DataSynchronizable {
+ @Override
+ public DataSynchronizable sync() {
+ final Vector newKids = new Vector();
+ newKids.add(new ProfileNode(myDefaultProfile, this, true).sync());
+ for (ProcessorConfigProfile profile : myModuleProfiles) {
+ newKids.add(new ProfileNode(profile, this, false).sync());
+ }
+ children = newKids;
+ ((DefaultTreeModel)myTree.getModel()).reload();
+ expand(myTree);
+ return this;
+ }
+ }
+
+ private interface DataSynchronizable {
+ DataSynchronizable sync();
+ }
+
+ private class ProfileNode extends DefaultMutableTreeNode implements DataSynchronizable {
+ private final ProcessorConfigProfile myProfile;
+ private final boolean myIsDefault;
+
+ public ProfileNode(ProcessorConfigProfile profile, RootNode parent, boolean isDefault) {
+ super(profile);
+ setParent(parent);
+ myIsDefault = isDefault;
+ myProfile = profile;
+ }
+
+ @Override
+ public DataSynchronizable sync() {
+ final List<Module> nodeModules = new ArrayList<Module>();
+ if (myIsDefault) {
+ final Set<String> nonDefaultProfileModules = new HashSet<String>();
+ for (ProcessorConfigProfile profile : myModuleProfiles) {
+ nonDefaultProfileModules.addAll(profile.getModuleNames());
+ }
+ for (Map.Entry<String, Module> entry : myAllModulesMap.entrySet()) {
+ if (!nonDefaultProfileModules.contains(entry.getKey())) {
+ nodeModules.add(entry.getValue());
+ }
+ }
+ }
+ else {
+ for (String moduleName : myProfile.getModuleNames()) {
+ final Module module = myAllModulesMap.get(moduleName);
+ if (module != null) {
+ nodeModules.add(module);
+ }
+ }
+ }
+ Collections.sort(nodeModules, ModuleComparator.INSTANCE);
+ final Vector vector = new Vector();
+ for (Module module : nodeModules) {
+ vector.add(new MyModuleNode(module, this));
+ }
+ children = vector;
+ return this;
+ }
+
+ }
+
+ private static class MyModuleNode extends DefaultMutableTreeNode {
+ public MyModuleNode(Module module, ProfileNode parent) {
+ super(module);
+ setParent(parent);
+ setAllowsChildren(false);
+ }
+ }
+
+ private static class MyCellRenderer extends ColoredTreeCellRenderer {
+ @Override
+ public void customizeCellRenderer(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
+ if (value instanceof ProfileNode) {
+ append(((ProfileNode)value).myProfile.getName());
+ }
+ else if (value instanceof MyModuleNode) {
+ final Module module = (Module)((MyModuleNode)value).getUserObject();
+ setIcon(AllIcons.Nodes.Module);
+ append(module.getName());
+ }
+ }
+ }
+
+ private static class ModuleComparator implements Comparator<Module> {
+ static final ModuleComparator INSTANCE = new ModuleComparator();
+ @Override
+ public int compare(Module o1, Module o2) {
+ return o1.getName().compareTo(o2.getName());
+ }
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/options/ComparingUtils.java b/java/compiler/impl/src/com/intellij/compiler/options/ComparingUtils.java
new file mode 100644
index 0000000..bac1d24
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/options/ComparingUtils.java
@@ -0,0 +1,53 @@
+/*
+ * 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 com.intellij.compiler.options;
+
+import com.intellij.openapi.ui.TextFieldWithBrowseButton;
+import com.intellij.ui.RawCommandLineEditor;
+
+import javax.swing.*;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 30, 2004
+ */
+public class ComparingUtils {
+ public static boolean isModified(TextFieldWithBrowseButton field, String value) {
+ return !field.getText().equals(value);
+ }
+
+ public static boolean isModified(JCheckBox checkBox, boolean value) {
+ return checkBox.isSelected() != value;
+ }
+
+ public static boolean isModified(JTextField textField, int value) {
+ try {
+ int fieldValue = Integer.parseInt(textField.getText().trim());
+ return fieldValue != value;
+ }
+ catch(NumberFormatException e) {
+ return false;
+ }
+ }
+
+ public static boolean isModified(RawCommandLineEditor editor, String value) {
+ return !editor.getText().equals(value);
+ }
+
+ public static boolean isModified(JTextField textField, String value) {
+ return !textField.getText().equals(value);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/options/CompilerConfigurable.java b/java/compiler/impl/src/com/intellij/compiler/options/CompilerConfigurable.java
new file mode 100644
index 0000000..3cb2305
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/options/CompilerConfigurable.java
@@ -0,0 +1,103 @@
+/*
+ * 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 com.intellij.compiler.options;
+
+import com.intellij.compiler.CompilerSettingsFactory;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.extensions.Extensions;
+import com.intellij.openapi.options.Configurable;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.options.SearchableConfigurable;
+import com.intellij.openapi.project.Project;
+import com.intellij.util.NullableFunction;
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+
+public class CompilerConfigurable implements SearchableConfigurable.Parent, Configurable.NoScroll {
+
+ private final Project myProject;
+ private final CompilerUIConfigurable myCompilerUIConfigurable;
+ private Configurable[] myKids;
+
+ public CompilerConfigurable(Project project) {
+ myProject = project;
+ myCompilerUIConfigurable = new CompilerUIConfigurable(myProject);
+ }
+
+ public String getDisplayName() {
+ return CompilerBundle.message("compiler.configurable.display.name");
+ }
+
+ public String getHelpTopic() {
+ return "project.propCompiler";
+ }
+
+ @NotNull
+ public String getId() {
+ return getHelpTopic();
+ }
+
+ @Nullable
+ public Runnable enableSearch(String option) {
+ return null;
+ }
+
+ public JComponent createComponent() {
+ return myCompilerUIConfigurable.createComponent();
+ }
+
+ public boolean hasOwnContent() {
+ return true;
+ }
+
+ public boolean isVisible() {
+ return true;
+ }
+
+ public boolean isModified() {
+ return myCompilerUIConfigurable.isModified();
+ }
+
+ public void apply() throws ConfigurationException {
+ myCompilerUIConfigurable.apply();
+ }
+
+ public void reset() {
+ myCompilerUIConfigurable.reset();
+ }
+
+ public void disposeUIResources() {
+ myCompilerUIConfigurable.disposeUIResources();
+ }
+
+ public Configurable[] getConfigurables() {
+ if (myKids == null) {
+ final CompilerSettingsFactory[] factories = Extensions.getExtensions(CompilerSettingsFactory.EP_NAME, myProject);
+ myKids = ContainerUtil.mapNotNull(factories, new NullableFunction<CompilerSettingsFactory, Configurable>() {
+ @Nullable
+ @Override
+ public Configurable fun(CompilerSettingsFactory factory) {
+ return factory.create(myProject);
+ }
+ }, new Configurable[0]);
+ }
+
+ return myKids;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/options/CompilerOptionsPanel.form b/java/compiler/impl/src/com/intellij/compiler/options/CompilerOptionsPanel.form
new file mode 100644
index 0000000..729a320
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/options/CompilerOptionsPanel.form
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.compiler.options.CompilerUIConfigurable">
+ <grid id="1663f" binding="myPanel" layout-manager="GridLayoutManager" row-count="10" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <xy x="28" y="24" width="883" height="379"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <vspacer id="67edf">
+ <constraints>
+ <grid row="9" column="0" row-span="1" col-span="3" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
+ </constraints>
+ </vspacer>
+ <grid id="b341d" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="3" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="b0748" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="label.option.resource.patterns.text"/>
+ </properties>
+ </component>
+ <component id="263f1" class="javax.swing.JTextField" binding="myResourcePatternsField">
+ <constraints>
+ <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
+ <preferred-size width="150" height="-1"/>
+ </grid>
+ </constraints>
+ <properties>
+ <focusable value="true"/>
+ <margin top="0" left="2" bottom="0" right="0"/>
+ <text value="" noi18n="true"/>
+ </properties>
+ </component>
+ <component id="78174" class="com.intellij.ui.components.JBLabel" binding="myPatternLegendLabel">
+ <constraints>
+ <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="8" fill="0" indent="1" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <componentStyle value="SMALL"/>
+ <fontColor value="BRIGHTER"/>
+ <text value="label"/>
+ </properties>
+ </component>
+ </children>
+ </grid>
+ <component id="889a8" class="javax.swing.JCheckBox" binding="myCbClearOutputDirectory">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="3" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="label.option.clear.output.directory.on.rebuild"/>
+ </properties>
+ </component>
+ <component id="366e" class="javax.swing.JCheckBox" binding="myCbAssertNotNull">
+ <constraints>
+ <grid row="2" column="0" row-span="1" col-span="3" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="add.notnull.assertions"/>
+ </properties>
+ </component>
+ <component id="348ee" class="javax.swing.JCheckBox" binding="myCbAutoShowFirstError">
+ <constraints>
+ <grid row="3" column="0" row-span="1" col-span="3" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="label.option.autoshow.first.error"/>
+ </properties>
+ </component>
+ <component id="ce617" class="javax.swing.JCheckBox" binding="myCbUseExternalBuild" default-binding="true">
+ <constraints>
+ <grid row="4" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="Use external build"/>
+ </properties>
+ </component>
+ <component id="b9b2d" class="javax.swing.JCheckBox" binding="myCbEnableAutomake">
+ <constraints>
+ <grid row="5" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="2" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="Make project automatically"/>
+ </properties>
+ </component>
+ <component id="17126" class="javax.swing.JLabel" binding="myHeapSizeLabel">
+ <constraints>
+ <grid row="7" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="2" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="Compiler process heap size (Mbytes):"/>
+ </properties>
+ </component>
+ <component id="a28b8" class="javax.swing.JTextField" binding="myHeapSizeField">
+ <constraints>
+ <grid row="7" column="1" row-span="1" col-span="2" vsize-policy="0" hsize-policy="6" anchor="8" fill="0" indent="0" use-parent-layout="false">
+ <preferred-size width="50" height="-1"/>
+ </grid>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="5b86a" class="javax.swing.JLabel" binding="myVMOptionsLabel">
+ <constraints>
+ <grid row="8" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="2" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="Additional compiler process VM options:"/>
+ </properties>
+ </component>
+ <component id="b5547" class="javax.swing.JTextField" binding="myVMOptionsField">
+ <constraints>
+ <grid row="8" column="1" row-span="1" col-span="2" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
+ <preferred-size width="150" height="-1"/>
+ </grid>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="ba694" class="javax.swing.JCheckBox" binding="myCbParallelCompilation">
+ <constraints>
+ <grid row="6" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="2" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="Compile independent modules in parallel"/>
+ </properties>
+ </component>
+ <component id="91979" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="6" column="1" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="(may require larger heap size)"/>
+ </properties>
+ </component>
+ <component id="732b1" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="5" column="1" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="(only works while not running / debugging)"/>
+ </properties>
+ </component>
+ </children>
+ </grid>
+</form>
diff --git a/java/compiler/impl/src/com/intellij/compiler/options/CompilerUIConfigurable.java b/java/compiler/impl/src/com/intellij/compiler/options/CompilerUIConfigurable.java
new file mode 100644
index 0000000..21863bd
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/options/CompilerUIConfigurable.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.options;
+
+import com.intellij.compiler.CompilerConfiguration;
+import com.intellij.compiler.CompilerConfigurationImpl;
+import com.intellij.compiler.CompilerWorkspaceConfiguration;
+import com.intellij.compiler.MalformedPatternException;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.options.Configurable;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.options.SearchableConfigurable;
+import com.intellij.openapi.project.Project;
+import com.intellij.ui.Gray;
+import com.intellij.ui.JBColor;
+import com.intellij.ui.components.JBLabel;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+public class CompilerUIConfigurable implements SearchableConfigurable, Configurable.NoScroll {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.options.CompilerUIConfigurable");
+ private JPanel myPanel;
+ private final Project myProject;
+
+ private JTextField myResourcePatternsField;
+ private JCheckBox myCbClearOutputDirectory;
+ private JCheckBox myCbAssertNotNull;
+ private JBLabel myPatternLegendLabel;
+ private JCheckBox myCbAutoShowFirstError;
+ private JCheckBox myCbUseExternalBuild;
+ private JCheckBox myCbEnableAutomake;
+ private JCheckBox myCbParallelCompilation;
+ private JTextField myHeapSizeField;
+ private JTextField myVMOptionsField;
+ private JLabel myHeapSizeLabel;
+ private JLabel myVMOptionsLabel;
+
+ public CompilerUIConfigurable(final Project project) {
+ myProject = project;
+
+ myPatternLegendLabel.setText("<html><body>" +
+ "Use <b>;</b> to separate patterns and <b>!</b> to negate a pattern. " +
+ "Accepted wildcards: <b>?</b> — exactly one symbol; <b>*</b> — zero or more symbols; " +
+ "<b>/</b> — path separator; <b>/**/</b> — any number of directories; " +
+ "<i><dir_name></i>:<i><pattern></i> — restrict to source roots with the specified name" +
+ "</body></html>");
+ myPatternLegendLabel.setForeground(new JBColor(Gray._50, Gray._130));
+ myCbUseExternalBuild.addItemListener(new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ updateExternalMakeOptionControls(myCbUseExternalBuild.isSelected());
+ }
+ });
+ }
+
+ public void reset() {
+
+ final CompilerConfigurationImpl configuration = (CompilerConfigurationImpl)CompilerConfiguration.getInstance(myProject);
+ final CompilerWorkspaceConfiguration workspaceConfiguration = CompilerWorkspaceConfiguration.getInstance(myProject);
+ myCbAutoShowFirstError.setSelected(workspaceConfiguration.AUTO_SHOW_ERRORS_IN_EDITOR);
+ myCbClearOutputDirectory.setSelected(workspaceConfiguration.CLEAR_OUTPUT_DIRECTORY);
+ myCbAssertNotNull.setSelected(configuration.isAddNotNullAssertions());
+ myCbUseExternalBuild.setSelected(workspaceConfiguration.USE_COMPILE_SERVER);
+ myCbEnableAutomake.setSelected(workspaceConfiguration.MAKE_PROJECT_ON_SAVE);
+ myCbParallelCompilation.setSelected(workspaceConfiguration.PARALLEL_COMPILATION);
+ myHeapSizeField.setText(String.valueOf(workspaceConfiguration.COMPILER_PROCESS_HEAP_SIZE));
+ final String options = workspaceConfiguration.COMPILER_PROCESS_ADDITIONAL_VM_OPTIONS;
+ myVMOptionsField.setText(options == null? "" : options.trim());
+ updateExternalMakeOptionControls(myCbUseExternalBuild.isSelected());
+
+ configuration.convertPatterns();
+
+ myResourcePatternsField.setText(patternsToString(configuration.getResourceFilePatterns()));
+ }
+
+ private static String patternsToString(final String[] patterns) {
+ final StringBuilder extensionsString = new StringBuilder();
+ for (int idx = 0; idx < patterns.length; idx++) {
+ if (idx > 0) {
+ extensionsString.append(";");
+ }
+ extensionsString.append(patterns[idx]);
+ }
+ return extensionsString.toString();
+ }
+
+ public void apply() throws ConfigurationException {
+
+ CompilerConfigurationImpl configuration = (CompilerConfigurationImpl)CompilerConfiguration.getInstance(myProject);
+ final CompilerWorkspaceConfiguration workspaceConfiguration = CompilerWorkspaceConfiguration.getInstance(myProject);
+ workspaceConfiguration.AUTO_SHOW_ERRORS_IN_EDITOR = myCbAutoShowFirstError.isSelected();
+ workspaceConfiguration.CLEAR_OUTPUT_DIRECTORY = myCbClearOutputDirectory.isSelected();
+ boolean wasUsingExternalMake = workspaceConfiguration.USE_COMPILE_SERVER;
+ workspaceConfiguration.USE_COMPILE_SERVER = myCbUseExternalBuild.isSelected();
+ workspaceConfiguration.MAKE_PROJECT_ON_SAVE = myCbEnableAutomake.isSelected();
+ workspaceConfiguration.PARALLEL_COMPILATION = myCbParallelCompilation.isSelected();
+ try {
+ workspaceConfiguration.COMPILER_PROCESS_HEAP_SIZE = Integer.parseInt(myHeapSizeField.getText().trim());
+ }
+ catch (NumberFormatException ignored) {
+ LOG.info(ignored);
+ }
+ workspaceConfiguration.COMPILER_PROCESS_ADDITIONAL_VM_OPTIONS = myVMOptionsField.getText().trim();
+
+ configuration.setAddNotNullAssertions(myCbAssertNotNull.isSelected());
+ configuration.removeResourceFilePatterns();
+ String extensionString = myResourcePatternsField.getText().trim();
+ applyResourcePatterns(extensionString, (CompilerConfigurationImpl)CompilerConfiguration.getInstance(myProject));
+ if (wasUsingExternalMake != workspaceConfiguration.USE_COMPILE_SERVER) {
+ myProject.getMessageBus().syncPublisher(ExternalBuildOptionListener.TOPIC).externalBuildOptionChanged(workspaceConfiguration.USE_COMPILE_SERVER);
+ }
+ }
+
+ private static void applyResourcePatterns(String extensionString, final CompilerConfigurationImpl configuration)
+ throws ConfigurationException {
+ StringTokenizer tokenizer = new StringTokenizer(extensionString, ";", false);
+ List<String[]> errors = new ArrayList<String[]>();
+
+ while (tokenizer.hasMoreTokens()) {
+ String namePattern = tokenizer.nextToken();
+ try {
+ configuration.addResourceFilePattern(namePattern);
+ }
+ catch (MalformedPatternException e) {
+ errors.add(new String[]{namePattern, e.getLocalizedMessage()});
+ }
+ }
+
+ if (errors.size() > 0) {
+ final StringBuilder pattersnsWithErrors = new StringBuilder();
+ for (final Object error : errors) {
+ String[] pair = (String[])error;
+ pattersnsWithErrors.append("\n");
+ pattersnsWithErrors.append(pair[0]);
+ pattersnsWithErrors.append(": ");
+ pattersnsWithErrors.append(pair[1]);
+ }
+
+ throw new ConfigurationException(
+ CompilerBundle.message("error.compiler.configurable.malformed.patterns", pattersnsWithErrors.toString()), CompilerBundle.message("bad.resource.patterns.dialog.title")
+ );
+ }
+ }
+
+ public boolean isModified() {
+ boolean isModified = false;
+ final CompilerWorkspaceConfiguration workspaceConfiguration = CompilerWorkspaceConfiguration.getInstance(myProject);
+ isModified |= ComparingUtils.isModified(myCbAutoShowFirstError, workspaceConfiguration.AUTO_SHOW_ERRORS_IN_EDITOR);
+ isModified |= ComparingUtils.isModified(myCbUseExternalBuild, workspaceConfiguration.USE_COMPILE_SERVER);
+ isModified |= ComparingUtils.isModified(myCbEnableAutomake, workspaceConfiguration.MAKE_PROJECT_ON_SAVE);
+ isModified |= ComparingUtils.isModified(myCbParallelCompilation, workspaceConfiguration.PARALLEL_COMPILATION);
+ isModified |= ComparingUtils.isModified(myHeapSizeField, workspaceConfiguration.COMPILER_PROCESS_HEAP_SIZE);
+ isModified |= ComparingUtils.isModified(myVMOptionsField, workspaceConfiguration.COMPILER_PROCESS_ADDITIONAL_VM_OPTIONS);
+
+ final CompilerConfigurationImpl compilerConfiguration = (CompilerConfigurationImpl)CompilerConfiguration.getInstance(myProject);
+ isModified |= ComparingUtils.isModified(myCbAssertNotNull, compilerConfiguration.isAddNotNullAssertions());
+ isModified |= ComparingUtils.isModified(myCbClearOutputDirectory, workspaceConfiguration.CLEAR_OUTPUT_DIRECTORY);
+ isModified |= ComparingUtils.isModified(myResourcePatternsField, patternsToString(compilerConfiguration.getResourceFilePatterns()));
+
+ return isModified;
+ }
+
+ public String getDisplayName() {
+ return "General";
+ }
+
+ public String getHelpTopic() {
+ return null;
+ }
+
+ @NotNull
+ public String getId() {
+ return "compiler.general";
+ }
+
+ public Runnable enableSearch(String option) {
+ return null;
+ }
+
+ public JComponent createComponent() {
+ return myPanel;
+ }
+
+ public void disposeUIResources() {
+ }
+
+ private void updateExternalMakeOptionControls(boolean enabled) {
+ myCbEnableAutomake.setEnabled(enabled);
+ myCbParallelCompilation.setEnabled(enabled);
+ myHeapSizeField.setEnabled(enabled);
+ myVMOptionsField.setEnabled(enabled);
+ myHeapSizeLabel.setEnabled(enabled);
+ myVMOptionsLabel.setEnabled(enabled);
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/options/ExternalBuildOptionListener.java b/java/compiler/impl/src/com/intellij/compiler/options/ExternalBuildOptionListener.java
new file mode 100644
index 0000000..c3c92ad
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/options/ExternalBuildOptionListener.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.options;
+
+import com.intellij.util.messages.Topic;
+
+import java.util.EventListener;
+
+/**
+ * @author nik
+ */
+public interface ExternalBuildOptionListener extends EventListener {
+ Topic<ExternalBuildOptionListener> TOPIC = Topic.create("External build option", ExternalBuildOptionListener.class);
+
+ void externalBuildOptionChanged(boolean externalBuildEnabled);
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/options/JavaCompilersTab.form b/java/compiler/impl/src/com/intellij/compiler/options/JavaCompilersTab.form
new file mode 100644
index 0000000..40c0fc0
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/options/JavaCompilersTab.form
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.compiler.options.JavaCompilersTab">
+ <grid id="3d361" binding="myPanel" layout-manager="GridLayoutManager" row-count="4" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <xy x="63" y="62" width="325" height="176"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <grid id="d786c" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="1" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="9fc5b" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <labelFor value="99748"/>
+ <text resource-bundle="messages/CompilerBundle" key="option.use.compiler.text"/>
+ </properties>
+ </component>
+ <component id="99748" class="javax.swing.JComboBox" binding="myCompiler">
+ <constraints>
+ <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ </children>
+ </grid>
+ <xy id="12adb" binding="myContentPanel" layout-manager="XYLayout" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="3" anchor="1" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children/>
+ </xy>
+ <grid id="a6152" binding="myTargetOptionsPanel" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children/>
+ </grid>
+ <vspacer id="7c703">
+ <constraints>
+ <grid row="3" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
+ </constraints>
+ </vspacer>
+ </children>
+ </grid>
+</form>
diff --git a/java/compiler/impl/src/com/intellij/compiler/options/JavaCompilersTab.java b/java/compiler/impl/src/com/intellij/compiler/options/JavaCompilersTab.java
new file mode 100644
index 0000000..09adaba
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/options/JavaCompilersTab.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.options;
+
+import com.intellij.compiler.CompilerConfiguration;
+import com.intellij.compiler.CompilerConfigurationImpl;
+import com.intellij.compiler.impl.javaCompiler.BackendCompiler;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.options.Configurable;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.options.SearchableConfigurable;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Comparing;
+import com.intellij.ui.ListCellRendererWrapper;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Vector;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 30, 2004
+ */
+public class JavaCompilersTab implements SearchableConfigurable, Configurable.NoScroll {
+ private JPanel myPanel;
+ private JPanel myContentPanel;
+ private JComboBox myCompiler;
+ private JPanel myTargetOptionsPanel;
+ private final CardLayout myCardLayout;
+
+ private final Project myProject;
+ private final BackendCompiler myDefaultCompiler;
+ private BackendCompiler mySelectedCompiler;
+ private final CompilerConfigurationImpl myCompilerConfiguration;
+ private final Collection<Configurable> myConfigurables;
+ private final TargetOptionsComponent myTargetLevelComponent;
+
+ public JavaCompilersTab(final Project project) {
+ this(project, ((CompilerConfigurationImpl)CompilerConfiguration.getInstance(project)).getRegisteredJavaCompilers(),
+ ((CompilerConfigurationImpl)CompilerConfiguration.getInstance(project)).getDefaultCompiler());
+ }
+
+ public JavaCompilersTab(final Project project, Collection<BackendCompiler> compilers, BackendCompiler defaultCompiler) {
+ myProject = project;
+ myDefaultCompiler = defaultCompiler;
+ myCompilerConfiguration = (CompilerConfigurationImpl)CompilerConfiguration.getInstance(project);
+ myConfigurables = new ArrayList<Configurable>(compilers.size());
+
+ myCardLayout = new CardLayout();
+ myContentPanel.setLayout(myCardLayout);
+
+ myTargetOptionsPanel.setLayout(new BorderLayout());
+ myTargetLevelComponent = new TargetOptionsComponent(project);
+ myTargetOptionsPanel.add(myTargetLevelComponent, BorderLayout.CENTER);
+
+ for (BackendCompiler compiler : compilers) {
+ Configurable configurable = compiler.createConfigurable();
+ myConfigurables.add(configurable);
+
+ myContentPanel.add(configurable.createComponent(), compiler.getId());
+ }
+ myCompiler.setModel(new DefaultComboBoxModel(new Vector(compilers)));
+ myCompiler.setRenderer(new ListCellRendererWrapper<BackendCompiler>() {
+ @Override
+ public void customize(final JList list, final BackendCompiler value, final int index, final boolean selected, final boolean hasFocus) {
+ setText(value != null ? value.getPresentableName() : "");
+ }
+ });
+ myCompiler.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ final BackendCompiler compiler = (BackendCompiler)myCompiler.getSelectedItem();
+ if (compiler != null) {
+ selectCompiler(compiler);
+ }
+ }
+ });
+ }
+
+ public String getDisplayName() {
+ return CompilerBundle.message("java.compiler.description");
+ }
+
+ public String getHelpTopic() {
+ return "reference.projectsettings.compiler.javacompiler";
+ }
+
+ @NotNull
+ public String getId() {
+ return getHelpTopic();
+ }
+
+ public Runnable enableSearch(String option) {
+ return null;
+ }
+
+ public JComponent createComponent() {
+ return myPanel;
+ }
+
+ public boolean isModified() {
+ if (!Comparing.equal(mySelectedCompiler, myCompilerConfiguration.getDefaultCompiler())) {
+ return true;
+ }
+ for (Configurable configurable : myConfigurables) {
+ if (configurable.isModified()) {
+ return true;
+ }
+ }
+ if (!Comparing.equal(myTargetLevelComponent.getProjectBytecodeTarget(), myCompilerConfiguration.getProjectBytecodeTarget())) {
+ return true;
+ }
+ if (!Comparing.equal(myTargetLevelComponent.getModulesBytecodeTargetMap(), myCompilerConfiguration.getModulesBytecodeTargetMap())) {
+ return true;
+ }
+ return false;
+ }
+
+ public void apply() throws ConfigurationException {
+ for (Configurable configurable : myConfigurables) {
+ configurable.apply();
+ }
+ myCompilerConfiguration.setDefaultCompiler(mySelectedCompiler);
+ myCompilerConfiguration.setProjectBytecodeTarget(myTargetLevelComponent.getProjectBytecodeTarget());
+ myCompilerConfiguration.setModulesBytecodeTargetMap(myTargetLevelComponent.getModulesBytecodeTargetMap());
+
+ myTargetLevelComponent.setProjectBytecodeTargetLevel(myCompilerConfiguration.getProjectBytecodeTarget());
+ myTargetLevelComponent.setModuleTargetLevels(myCompilerConfiguration.getModulesBytecodeTargetMap());
+ }
+
+ public void reset() {
+ for (Configurable configurable : myConfigurables) {
+ configurable.reset();
+ }
+ selectCompiler(myCompilerConfiguration.getDefaultCompiler());
+ myTargetLevelComponent.setProjectBytecodeTargetLevel(myCompilerConfiguration.getProjectBytecodeTarget());
+ myTargetLevelComponent.setModuleTargetLevels(myCompilerConfiguration.getModulesBytecodeTargetMap());
+ }
+
+ public void disposeUIResources() {
+ }
+
+ private void selectCompiler(BackendCompiler compiler) {
+ if(compiler == null) {
+ compiler = myDefaultCompiler;
+ }
+ myCompiler.setSelectedItem(compiler);
+ mySelectedCompiler = compiler;
+ myCardLayout.show(myContentPanel, compiler.getId());
+ myContentPanel.revalidate();
+ myContentPanel.repaint();
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/options/ProcessedModulesTable.java b/java/compiler/impl/src/com/intellij/compiler/options/ProcessedModulesTable.java
new file mode 100644
index 0000000..f95025b
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/options/ProcessedModulesTable.java
@@ -0,0 +1,439 @@
+/*
+ * 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 com.intellij.compiler.options;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.module.ModuleType;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ui.configuration.ChooseModulesDialog;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.ui.SpeedSearchBase;
+import com.intellij.ui.SpeedSearchComparator;
+import com.intellij.ui.TableUtil;
+import com.intellij.ui.ToolbarDecorator;
+import com.intellij.ui.table.JBTable;
+import com.intellij.util.ui.EditableModel;
+import com.intellij.util.ui.UIUtil;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import javax.swing.table.*;
+import java.awt.*;
+import java.util.*;
+import java.util.List;
+
+public class ProcessedModulesTable extends JPanel {
+ private JBTable myTable = null;
+ private MyTableModel myTableModel = null;
+
+ public ProcessedModulesTable(final Project project) {
+ super(new BorderLayout());
+
+ myTableModel = new MyTableModel(project);
+ myTable = new JBTable(myTableModel);
+ myTable.getEmptyText().setText("No modules configured");
+
+ //myTable.setShowGrid(false);
+ myTable.setIntercellSpacing(new Dimension(0, 0));
+ myTable.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
+ myTable.setColumnSelectionAllowed(false);
+
+ final TableColumnModel columnModel = myTable.getColumnModel();
+
+ final TableColumn dirNameColumn = columnModel.getColumn(myTableModel.DIRNAME_COLUMN_INDEX);
+ final String title = "Generated Sources Directory Name";
+ dirNameColumn.setHeaderValue(title);
+ final JTableHeader tableHeader = myTable.getTableHeader();
+ final FontMetrics metrics = tableHeader.getFontMetrics(tableHeader.getFont());
+ final int preferredWidth = metrics.stringWidth(title) + 12;
+ dirNameColumn.setPreferredWidth(preferredWidth);
+ dirNameColumn.setMaxWidth(preferredWidth + 20);
+ dirNameColumn.setCellRenderer(new MyElementColumnCellRenderer());
+
+ final TableColumn moduleColumn = columnModel.getColumn(myTableModel.ELEMENT_COLUMN_INDEX);
+ moduleColumn.setHeaderValue("Module");
+ moduleColumn.setCellRenderer(new MyElementColumnCellRenderer());
+
+ final JPanel panel = ToolbarDecorator.createDecorator(myTable)
+ .disableUpDownActions()
+ .setPreferredSize(new Dimension(100, 155))
+ .createPanel();
+ add(panel, BorderLayout.CENTER);
+
+ final SpeedSearchBase<JBTable> speedSearch = new SpeedSearchBase<JBTable>(myTable) {
+ public int getSelectedIndex() {
+ return myTable.getSelectedRow();
+ }
+
+ @Override
+ protected int convertIndexToModel(int viewIndex) {
+ return myTable.convertRowIndexToModel(viewIndex);
+ }
+
+ public Object[] getAllElements() {
+ final int count = myTableModel.getRowCount();
+ Object[] elements = new Object[count];
+ for (int idx = 0; idx < count; idx++) {
+ elements[idx] = myTableModel.getModuleAt(idx);
+ }
+ return elements;
+ }
+
+ public String getElementText(Object element) {
+ return ((Module)element).getName() + " (" + FileUtil.toSystemDependentName(((Module)element).getModuleFilePath()) + ")";
+ }
+
+ public void selectElement(Object element, String selectedText) {
+ final int count = myTableModel.getRowCount();
+ for (int row = 0; row < count; row++) {
+ if (element.equals(myTableModel.getModuleAt(row))) {
+ final int viewRow = myTable.convertRowIndexToView(row);
+ myTable.getSelectionModel().setSelectionInterval(viewRow, viewRow);
+ TableUtil.scrollSelectionToVisible(myTable);
+ break;
+ }
+ }
+ }
+ };
+ speedSearch.setComparator(new SpeedSearchComparator(false));
+ }
+
+ public void refresh() {
+ myTableModel.fireTableDataChanged();
+ }
+
+ public void refresh(Module element) {
+ final int row = myTableModel.getElementRow(element);
+ if (row >= 0) {
+ myTableModel.fireTableRowsUpdated(row, row);
+ }
+ }
+
+ private int[] mySavedSelection = null;
+ public void saveSelection() {
+ mySavedSelection = myTable.getSelectedRows();
+ }
+
+ public void restoreSelection() {
+ if (mySavedSelection != null) {
+ TableUtil.selectRows(myTable, mySavedSelection);
+ mySavedSelection = null;
+ }
+ }
+
+ public void addModule(Module element, String dirName) {
+ myTableModel.addElement(element, dirName);
+ selectRow(myTableModel.getRowCount() - 1);
+ myTable.requestFocus();
+ }
+
+ public void removeModule(Module element) {
+ final int elementRow = myTableModel.getElementRow(element);
+ if (elementRow < 0) {
+ return; // no such element
+ }
+ final boolean wasSelected = myTable.getSelectionModel().isSelectedIndex(elementRow);
+
+ myTableModel.removeElement(element);
+
+ if (wasSelected) {
+ final int rowCount = myTableModel.getRowCount();
+ if (rowCount > 0) {
+ selectRow(elementRow % rowCount);
+ }
+ else {
+ myTable.getSelectionModel().clearSelection();
+ }
+ }
+ myTable.requestFocus();
+ }
+
+ public void removeAllElements() {
+ myTableModel.removeAllElements();
+ myTable.getSelectionModel().clearSelection();
+ }
+
+ private void selectRow(final int row) {
+ myTable.getSelectionModel().setSelectionInterval(row, row);
+ myTable.scrollRectToVisible(myTable.getCellRect(row, 0, true));
+ }
+
+ @Nullable
+ public Module getSelectedElement() {
+ final int selectedRow = getSelectedElementRow();
+ return selectedRow < 0? null : myTableModel.getModuleAt(selectedRow);
+ }
+
+ public int getSelectedElementRow() {
+ return myTable.getSelectedRow();
+ }
+
+ public List<Module> getSelectedElements() {
+ final List<Module> elements = new ArrayList<Module>();
+ final int[] selectedRows = myTable.getSelectedRows();
+ for (int selectedRow : selectedRows) {
+ if (selectedRow < 0) {
+ continue;
+ }
+ elements.add(myTableModel.getModuleAt(selectedRow));
+ }
+ return elements;
+ }
+
+ public void selectElements(Collection<? extends Module> elements) {
+ if (elements.size() == 0) {
+ myTable.clearSelection();
+ return;
+ }
+ final int[] rows = getElementsRows(elements);
+ TableUtil.selectRows(myTable, rows);
+ TableUtil.scrollSelectionToVisible(myTable);
+ myTable.requestFocus();
+ }
+
+ private int[] getElementsRows(final Collection<? extends Module> elements) {
+ final int[] rows = new int[elements.size()];
+ int index = 0;
+ for (final Module element : elements) {
+ rows[index++] = myTableModel.getElementRow(element);
+ }
+ return rows;
+ }
+
+ public List<Pair<Module, String>> getAllModules() {
+ final int count = myTableModel.getRowCount();
+ List<Pair<Module, String>> elements = new ArrayList<Pair<Module, String>>();
+ for (int idx = 0; idx < count; idx++) {
+ final Module module = myTableModel.getModuleAt(idx);
+ elements.add(new Pair<Module, String>(module, myTableModel.getGenDirName(module)));
+ }
+ return elements;
+ }
+
+ public void sort(Comparator<Module> comparator) {
+ myTableModel.sort(comparator);
+ }
+
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ myTable.setRowSelectionAllowed(enabled);
+ myTableModel.fireTableDataChanged();
+ }
+
+ public void stopEditing() {
+ TableCellEditor editor = myTable.getCellEditor();
+ if (editor != null) {
+ editor.stopCellEditing();
+ }
+ }
+
+ public JComponent getComponent() {
+ return myTable;
+ }
+
+ public void clear() {
+ myTableModel.clear();
+ }
+
+ public int getElementCount() {
+ return myTableModel.getRowCount();
+ }
+
+ public Module getElementAt(int row) {
+ return myTableModel.getModuleAt(row);
+ }
+
+ private final class MyTableModel extends AbstractTableModel implements EditableModel {
+ private final List<Module> myElements = new ArrayList<Module>();
+ private final Map<Module, String> myDirNameMap = new HashMap<Module, String>();
+ public final int ELEMENT_COLUMN_INDEX = 0;
+ public final int DIRNAME_COLUMN_INDEX = 1;
+ private final Project myProject;
+
+ private MyTableModel(Project project) {
+ myProject = project;
+ }
+
+ public void sort(Comparator<Module> comparator) {
+ Collections.sort(myElements, comparator);
+ fireTableDataChanged();
+ }
+
+ public List<Module> getAllModules() {
+ return Collections.unmodifiableList(myElements);
+ }
+
+ public Module getModuleAt(int index) {
+ return myElements.get(index);
+ }
+
+ public String getGenDirName(Module module) {
+ return myDirNameMap.get(module);
+ }
+
+ void addElement(Module module, final String dirName) {
+ myElements.add(module);
+ if (dirName != null && dirName.length() > 0) {
+ myDirNameMap.put(module, dirName);
+ }
+ int row = myElements.size() - 1;
+ fireTableRowsInserted(row, row);
+ }
+
+ @Override
+ public void addRow() {
+ final Set<Module> projectModules = new HashSet<Module>(Arrays.asList(ModuleManager.getInstance(myProject).getModules()));
+ projectModules.removeAll(getAllModules());
+ final ChooseModulesDialog chooser = new ChooseModulesDialog(ProcessedModulesTable.this, new ArrayList<Module>(projectModules), "ChooseModule");
+ chooser.show();
+ if (chooser.isOK()) {
+ final List<Module> chosen = chooser.getChosenElements();
+ for (Module module : chosen) {
+ addElement(module, null);
+ }
+ }
+ }
+
+ public void removeRow(int idx) {
+ final Module element = myElements.remove(idx);
+ myDirNameMap.remove(element);
+ fireTableRowsDeleted(idx, idx);
+ }
+
+ @Override
+ public void exchangeRows(int oldIndex, int newIndex) {
+ }
+
+ @Override
+ public boolean canExchangeRows(int oldIndex, int newIndex) {
+ return false;
+ }
+
+ public void removeElement(Module element) {
+ final boolean reallyRemoved = myElements.remove(element);
+ if (reallyRemoved) {
+ myDirNameMap.remove(element);
+ fireTableDataChanged();
+ }
+ }
+
+ public int getElementRow(Module element) {
+ return myElements.indexOf(element);
+ }
+
+ public void removeAllElements() {
+ myElements.clear();
+ fireTableDataChanged();
+ }
+
+ public int getRowCount() {
+ return myElements.size();
+ }
+
+ public int getColumnCount() {
+ return 2;
+ }
+
+ @Nullable
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ Module element = myElements.get(rowIndex);
+ if (columnIndex == ELEMENT_COLUMN_INDEX) {
+ return element;
+ }
+ if (columnIndex == DIRNAME_COLUMN_INDEX) {
+ return myDirNameMap.get(element);
+ }
+ return null;
+ }
+
+ public void setValueAt(Object value, int rowIndex, int columnIndex) {
+ if (columnIndex == DIRNAME_COLUMN_INDEX) {
+ final Module module = myElements.get(rowIndex);
+ if (value != null) {
+ String dir = FileUtil.toSystemIndependentName((String)value);
+ while (dir.startsWith("/")) {
+ dir = dir.substring(1);
+ }
+ if (dir.length() > 0) {
+ myDirNameMap.put(module, dir);
+ }
+ else {
+ myDirNameMap.remove(module);
+ }
+ }
+ else {
+ myDirNameMap.remove(module);
+ }
+ fireTableRowsUpdated(rowIndex, rowIndex);
+ }
+ }
+
+ public Class getColumnClass(int columnIndex) {
+ if (columnIndex == DIRNAME_COLUMN_INDEX) {
+ return String.class;
+ }
+ return super.getColumnClass(columnIndex);
+ }
+
+ public boolean isCellEditable(int rowIndex, int columnIndex) {
+ if (!ProcessedModulesTable.this.isEnabled()) {
+ return false;
+ }
+ if (columnIndex == DIRNAME_COLUMN_INDEX) {
+ return true;
+ }
+ return false;
+ }
+
+ public void clear() {
+ myElements.clear();
+ myDirNameMap.clear();
+ fireTableDataChanged();
+ }
+ }
+
+ private class MyElementColumnCellRenderer extends DefaultTableCellRenderer {
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
+ final Color color = UIUtil.getTableFocusCellBackground();
+ Component component;
+ final Module module = value instanceof Module? (Module)value : null;
+ try {
+ UIManager.put(UIUtil.TABLE_FOCUS_CELL_BACKGROUND_PROPERTY, table.getSelectionBackground());
+ component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
+ if (module != null) {
+ setText(module.getName() + " (" + FileUtil.toSystemDependentName(module.getModuleFilePath()) + ")");
+ }
+ if (component instanceof JLabel) {
+ ((JLabel)component).setBorder(noFocusBorder);
+ }
+ }
+ finally {
+ UIManager.put(UIUtil.TABLE_FOCUS_CELL_BACKGROUND_PROPERTY, color);
+ }
+ component.setEnabled(ProcessedModulesTable.this.isEnabled());
+ if (component instanceof JLabel) {
+ final Icon icon = module != null ? ModuleType.get(module).getIcon() : null;
+ JLabel label = (JLabel)component;
+ label.setIcon(icon);
+ label.setDisabledIcon(icon);
+ }
+ component.setForeground(isSelected ? table.getSelectionForeground() : table.getForeground());
+ return component;
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/options/ProcessorProfilePanel.java b/java/compiler/impl/src/com/intellij/compiler/options/ProcessorProfilePanel.java
new file mode 100644
index 0000000..1553b84
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/options/ProcessorProfilePanel.java
@@ -0,0 +1,513 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.options;
+
+import com.intellij.openapi.fileChooser.FileChooser;
+import com.intellij.openapi.fileChooser.FileChooserDescriptor;
+import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.TextFieldWithBrowseButton;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.ui.*;
+import com.intellij.ui.table.JBTable;
+import com.intellij.util.ui.EditableModel;
+import org.jetbrains.jps.model.java.compiler.ProcessorConfigProfile;
+
+import javax.swing.*;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.JTableHeader;
+import javax.swing.table.TableCellEditor;
+import javax.swing.table.TableModel;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.io.File;
+import java.util.*;
+import java.util.List;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 5/28/12
+ */
+public class ProcessorProfilePanel extends JPanel {
+ private final Project myProject;
+
+ private JRadioButton myRbClasspath;
+ private JRadioButton myRbProcessorsPath;
+ private TextFieldWithBrowseButton myProcessorPathField;
+ private JTextField myGeneratedProductionDirField;
+ private JTextField myGeneratedTestsDirField;
+ private JRadioButton myRbRelativeToOutputRoot;
+ private JRadioButton myRbRelativeToContentRoot;
+ private ProcessorTableModel myProcessorsModel;
+ private JCheckBox myCbEnableProcessing;
+ private JBTable myProcessorTable;
+ private JBTable myOptionsTable;
+ private JPanel myProcessorPanel;
+ private JPanel myOptionsPanel;
+ private OptionsTableModel myOptionsModel;
+ private JLabel myWarninglabel;
+ private JLabel myStoreGenSourcesLabel;
+ private JLabel myProductionLabel;
+ private JLabel myTestLabel;
+ private JPanel myProcessorTablePanel;
+ private JPanel myOptionsTablePanel;
+
+
+ public ProcessorProfilePanel(Project project) {
+ super(new GridBagLayout());
+ myProject = project;
+
+ myCbEnableProcessing = new JCheckBox("Enable annotation processing");
+
+ {
+ myRbClasspath = new JRadioButton("Obtain processors from project classpath");
+ myRbProcessorsPath = new JRadioButton("Processor path:");
+ ButtonGroup group = new ButtonGroup();
+ group.add(myRbClasspath);
+ group.add(myRbProcessorsPath);
+ }
+
+ {
+ myRbRelativeToContentRoot = new JRadioButton("Module content root");
+ myRbRelativeToOutputRoot = new JRadioButton("Module output directory");
+ final ButtonGroup group = new ButtonGroup();
+ group.add(myRbRelativeToContentRoot);
+ group.add(myRbRelativeToOutputRoot);
+ }
+
+ myProcessorPathField = new TextFieldWithBrowseButton(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ final FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createAllButJarContentsDescriptor();
+ final VirtualFile[] files = FileChooser.chooseFiles(descriptor, myProcessorPathField, myProject, null);
+ if (files.length > 0) {
+ final StringBuilder builder = new StringBuilder();
+ for (VirtualFile file : files) {
+ if (builder.length() > 0) {
+ builder.append(File.pathSeparator);
+ }
+ builder.append(FileUtil.toSystemDependentName(file.getPath()));
+ }
+ myProcessorPathField.setText(builder.toString());
+ }
+ }
+ });
+
+ myProcessorTablePanel = new JPanel(new BorderLayout());
+ myProcessorsModel = new ProcessorTableModel();
+ myProcessorTablePanel.setBorder(IdeBorderFactory.createTitledBorder("Annotation Processors", false));
+ myProcessorTable = new JBTable(myProcessorsModel);
+ myProcessorTable.getEmptyText().setText("Compiler will run all automatically discovered processors");
+ myProcessorPanel = createTablePanel(myProcessorTable);
+ myProcessorTablePanel.add(myProcessorPanel, BorderLayout.CENTER);
+
+ myOptionsTablePanel = new JPanel(new BorderLayout());
+ myOptionsModel = new OptionsTableModel();
+ myOptionsTablePanel.setBorder(IdeBorderFactory.createTitledBorder("Annotation Processor options", false));
+ myOptionsTable = new JBTable(myOptionsModel);
+ myOptionsTable.getEmptyText().setText("No processor-specific options configured");
+ myOptionsPanel = createTablePanel(myOptionsTable);
+ myOptionsTablePanel.add(myOptionsPanel, BorderLayout.CENTER);
+
+ myGeneratedProductionDirField = new JTextField();
+ myGeneratedTestsDirField = new JTextField();
+
+ myWarninglabel = new JLabel("<html>WARNING!<br>" +
+ /*"All source files located in the generated sources output directory WILL BE EXCLUDED from annotation processing. " +*/
+ "If option 'Clear output directory on rebuild' is enabled, " +
+ "the entire contents of directories where generated sources are stored WILL BE CLEARED on rebuild.</html>");
+ myWarninglabel.setFont(myWarninglabel.getFont().deriveFont(Font.BOLD));
+
+ add(myCbEnableProcessing,
+ new GridBagConstraints(0, GridBagConstraints.RELATIVE, 3, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, new Insets(0, 0, 0, 0), 0, 0));
+ add(myRbClasspath,
+ new GridBagConstraints(0, GridBagConstraints.RELATIVE, 3, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(10, 0, 0, 0), 0, 0));
+ add(myRbProcessorsPath,
+ new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(5, 0, 0, 0), 0, 0));
+ add(myProcessorPathField,
+ new GridBagConstraints(1, GridBagConstraints.RELATIVE, 2, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(5, 5, 0, 0), 0, 0));
+
+ myStoreGenSourcesLabel = new JLabel("Store generated sources relative to: ");
+ add(myStoreGenSourcesLabel,
+ new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(15, 5, 0, 0), 0, 0));
+ add(myRbRelativeToOutputRoot,
+ new GridBagConstraints(1, GridBagConstraints.RELATIVE, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(15, 5, 0, 0), 0, 0));
+ add(myRbRelativeToContentRoot,
+ new GridBagConstraints(2, GridBagConstraints.RELATIVE, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(15, 5, 0, 0), 0, 0));
+
+ myProductionLabel = new JLabel("Production sources directory:");
+ add(myProductionLabel,
+ new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(10, 5, 0, 0), 0, 0));
+ add(myGeneratedProductionDirField,
+ new GridBagConstraints(1, GridBagConstraints.RELATIVE, 2, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(10, 5, 0, 0), 0, 0));
+
+ myTestLabel = new JLabel("Test sources directory:");
+ add(myTestLabel,
+ new GridBagConstraints(0, GridBagConstraints.RELATIVE, 1, 1, 0.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(10, 5, 0, 0), 0, 0));
+ add(myGeneratedTestsDirField,
+ new GridBagConstraints(1, GridBagConstraints.RELATIVE, 2, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(10, 5, 0, 0), 0, 0));
+
+ add(myProcessorTablePanel,
+ new GridBagConstraints(0, GridBagConstraints.RELATIVE, 3, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, new Insets(10, 0, 0, 0), 0, 0));
+ add(myOptionsTablePanel,
+ new GridBagConstraints(0, GridBagConstraints.RELATIVE, 3, 1, 1.0, 1.0, GridBagConstraints.NORTHWEST, GridBagConstraints.BOTH, new Insets(10, 0, 0, 0), 0, 0));
+ add(myWarninglabel,
+ new GridBagConstraints(0, GridBagConstraints.RELATIVE, 3, 1, 1.0, 0.0, GridBagConstraints.WEST, GridBagConstraints.HORIZONTAL, new Insets(10, 5, 0, 0), 0, 0));
+
+ myRbClasspath.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ updateEnabledState();
+ }
+ });
+
+ myProcessorTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+ public void valueChanged(ListSelectionEvent e) {
+ if (!e.getValueIsAdjusting()) {
+ updateEnabledState();
+ }
+ }
+ });
+
+ myCbEnableProcessing.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ updateEnabledState();
+ }
+ });
+
+ updateEnabledState();
+
+ }
+
+ public void setProfile(ProcessorConfigProfile config) {
+ myCbEnableProcessing.setSelected(config.isEnabled());
+
+ (config.isObtainProcessorsFromClasspath()? myRbClasspath : myRbProcessorsPath).setSelected(true);
+ myProcessorPathField.setText(FileUtil.toSystemDependentName(config.getProcessorPath()));
+
+ final String productionDirName = config.getGeneratedSourcesDirectoryName(false);
+ myGeneratedProductionDirField.setText(productionDirName != null? productionDirName.trim() : "");
+ final String testsDirName = config.getGeneratedSourcesDirectoryName(true);
+ myGeneratedTestsDirField.setText(testsDirName != null? testsDirName.trim() : "");
+ if (config.isOutputRelativeToContentRoot()) {
+ myRbRelativeToContentRoot.setSelected(true);
+ }
+ else {
+ myRbRelativeToOutputRoot.setSelected(true);
+ }
+ myProcessorsModel.setProcessors(config.getProcessors());
+ myOptionsModel.setOptions(config.getProcessorOptions());
+
+ updateEnabledState();
+ }
+
+ public void saveTo(ProcessorConfigProfile profile) {
+ profile.setEnabled(myCbEnableProcessing.isSelected());
+ profile.setObtainProcessorsFromClasspath(myRbClasspath.isSelected());
+ profile.setProcessorPath(myProcessorPathField.getText().trim());
+
+ final String productionDir = myGeneratedProductionDirField.getText().trim();
+ profile.setGeneratedSourcesDirectoryName(StringUtil.isEmpty(productionDir)? null : productionDir, false);
+ final String testsDir = myGeneratedTestsDirField.getText().trim();
+ profile.setGeneratedSourcesDirectoryName(StringUtil.isEmpty(testsDir)? null : testsDir, true);
+
+ profile.setOutputRelativeToContentRoot(myRbRelativeToContentRoot.isSelected());
+
+ profile.clearProcessors();
+ for (String processor : myProcessorsModel.getProcessors()) {
+ profile.addProcessor(processor);
+ }
+ profile.clearProcessorOptions();
+ for (Map.Entry<String, String> entry : myOptionsModel.getOptions().entrySet()) {
+ profile.setOption(entry.getKey(), entry.getValue());
+ }
+ }
+
+ private static JPanel createTablePanel(final JBTable table) {
+ return ToolbarDecorator.createDecorator(table)
+ .disableUpAction()
+ .disableDownAction()
+ .setAddAction(new AnActionButtonRunnable() {
+ @Override
+ public void run(AnActionButton anActionButton) {
+ final TableCellEditor cellEditor = table.getCellEditor();
+ if (cellEditor != null) {
+ cellEditor.stopCellEditing();
+ }
+ final TableModel model = table.getModel();
+ ((EditableModel)model).addRow();
+ TableUtil.editCellAt(table, model.getRowCount() - 1, 0);
+ }
+ })
+ .createPanel();
+ }
+
+ private void updateEnabledState() {
+ final boolean enabled = myCbEnableProcessing.isSelected();
+ final boolean useProcessorpath = !myRbClasspath.isSelected();
+ myRbClasspath.setEnabled(enabled);
+ myRbProcessorsPath.setEnabled(enabled);
+ myProcessorPathField.setEnabled(enabled && useProcessorpath);
+ updateTable(myProcessorPanel, myProcessorTable, enabled);
+ updateTable(myOptionsPanel, myOptionsTable, enabled);
+ myGeneratedProductionDirField.setEnabled(enabled);
+ myGeneratedTestsDirField.setEnabled(enabled);
+ myRbRelativeToOutputRoot.setEnabled(enabled);
+ myRbRelativeToContentRoot.setEnabled(enabled);
+ myWarninglabel.setEnabled(enabled);
+ myStoreGenSourcesLabel.setEnabled(enabled);
+ myProductionLabel.setEnabled(enabled);
+ myTestLabel.setEnabled(enabled);
+ myProcessorTablePanel.setEnabled(enabled);
+ myOptionsTablePanel.setEnabled(enabled);
+ }
+
+ private static void updateTable(final JPanel tablePanel, final JBTable table, boolean enabled) {
+ final AnActionButton addButton = ToolbarDecorator.findAddButton(tablePanel);
+ if (addButton != null) {
+ addButton.setEnabled(enabled);
+ }
+ final AnActionButton removeButton = ToolbarDecorator.findRemoveButton(tablePanel);
+ if (removeButton != null) {
+ removeButton.setEnabled(enabled && table.getSelectedRow() >= 0);
+ }
+ table.setEnabled(enabled);
+ final JTableHeader header = table.getTableHeader();
+ if (header != null) {
+ header.repaint();
+ }
+ }
+
+ private static class OptionsTableModel extends AbstractTableModel implements EditableModel {
+ private final java.util.List<KeyValuePair> myRows = new ArrayList<KeyValuePair>();
+
+ public String getColumnName(int column) {
+ switch (column) {
+ case 0: return "Option Name";
+ case 1: return "Value";
+ }
+ return super.getColumnName(column);
+ }
+
+ public Class<?> getColumnClass(int columnIndex) {
+ return String.class;
+ }
+
+ public int getRowCount() {
+ return myRows.size();
+ }
+
+ public int getColumnCount() {
+ return 2;
+ }
+
+ public boolean isCellEditable(int rowIndex, int columnIndex) {
+ return columnIndex == 0 || columnIndex == 1;
+ }
+
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ switch (columnIndex) {
+ case 0: return myRows.get(rowIndex).key;
+ case 1: return myRows.get(rowIndex).value;
+ }
+ return null;
+ }
+
+ public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
+ if (aValue != null) {
+ switch (columnIndex) {
+ case 0:
+ myRows.get(rowIndex).key = (String)aValue;
+ break;
+ case 1:
+ myRows.get(rowIndex).value = (String)aValue;
+ break;
+ }
+ }
+ }
+
+ public void removeRow(int idx) {
+ myRows.remove(idx);
+ fireTableRowsDeleted(idx, idx);
+ }
+
+ @Override
+ public void exchangeRows(int oldIndex, int newIndex) {
+ }
+
+ @Override
+ public boolean canExchangeRows(int oldIndex, int newIndex) {
+ return false;
+ }
+
+ public void addRow() {
+ myRows.add(new KeyValuePair());
+ final int index = myRows.size() - 1;
+ fireTableRowsInserted(index, index);
+ }
+
+ public void setOptions(Map<String, String> options) {
+ clear();
+ if (!options.isEmpty()) {
+ for (Map.Entry<String, String> entry : options.entrySet()) {
+ myRows.add(new KeyValuePair(entry.getKey(), entry.getValue()));
+ }
+ Collections.sort(myRows, new Comparator<KeyValuePair>() {
+ @Override
+ public int compare(KeyValuePair o1, KeyValuePair o2) {
+ return o1.key.compareToIgnoreCase(o2.key);
+ }
+ });
+ fireTableRowsInserted(0, options.size()-1);
+ }
+ }
+
+ public void clear() {
+ final int count = myRows.size();
+ if (count > 0) {
+ myRows.clear();
+ fireTableRowsDeleted(0, count-1);
+ }
+ }
+
+ public Map<String, String> getOptions() {
+ final Map<String, String> map = new java.util.HashMap<String, String>();
+ for (KeyValuePair pair : myRows) {
+ map.put(pair.key.trim(), pair.value.trim());
+ }
+ map.remove("");
+ return map;
+ }
+
+ private static final class KeyValuePair {
+ String key;
+ String value;
+
+ KeyValuePair() {
+ this("", "");
+ }
+
+ KeyValuePair(String key, String value) {
+ this.key = key;
+ this.value = value;
+ }
+ }
+ }
+
+ private static class ProcessorTableModel extends AbstractTableModel implements EditableModel {
+ private final List<String> myRows = new ArrayList<String>();
+
+ public String getColumnName(int column) {
+ switch (column) {
+ case 0: return "Processor FQ Name";
+ }
+ return super.getColumnName(column);
+ }
+
+ public Class<?> getColumnClass(int columnIndex) {
+ return String.class;
+ }
+
+ public int getRowCount() {
+ return myRows.size();
+ }
+
+ public int getColumnCount() {
+ return 1;
+ }
+
+ public boolean isCellEditable(int rowIndex, int columnIndex) {
+ return columnIndex == 0;
+ }
+
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ switch (columnIndex) {
+ case 0: return myRows.get(rowIndex);
+ }
+ return null;
+ }
+
+ public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
+ if (aValue != null) {
+ switch (columnIndex) {
+ case 0:
+ myRows.set(rowIndex, (String)aValue);
+ break;
+ }
+ }
+ }
+
+ public void removeRow(int idx) {
+ myRows.remove(idx);
+ fireTableRowsDeleted(idx, idx);
+ }
+
+ @Override
+ public void exchangeRows(int oldIndex, int newIndex) {
+ }
+
+ @Override
+ public boolean canExchangeRows(int oldIndex, int newIndex) {
+ return false;
+ }
+
+ public void addRow() {
+ myRows.add("");
+ final int index = myRows.size() - 1;
+ fireTableRowsInserted(index, index);
+ }
+
+ public void setProcessors(Collection<String> processors) {
+ clear();
+ if (!processors.isEmpty()) {
+ for (String processor : processors) {
+ myRows.add(processor);
+ }
+ Collections.sort(myRows, new Comparator<String>() {
+ public int compare(String o1, String o2) {
+ return o1.compareToIgnoreCase(o2);
+ }
+ });
+ fireTableRowsInserted(0, processors.size()-1);
+ }
+ }
+
+ public void clear() {
+ final int count = myRows.size();
+ if (count > 0) {
+ myRows.clear();
+ fireTableRowsDeleted(0, count-1);
+ }
+ }
+
+ public Collection<String> getProcessors() {
+ final Set<String> set = new HashSet<String>();
+ for (String row : myRows) {
+ if (row != null) {
+ set.add(row.trim());
+ }
+ }
+ set.remove("");
+ return set;
+ }
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/options/RmicConfigurable.java b/java/compiler/impl/src/com/intellij/compiler/options/RmicConfigurable.java
new file mode 100644
index 0000000..96b4aab
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/options/RmicConfigurable.java
@@ -0,0 +1,115 @@
+/*
+ * 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 com.intellij.compiler.options;
+
+import com.intellij.compiler.impl.rmiCompiler.RmicConfiguration;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.options.Configurable;
+import com.intellij.openapi.options.ConfigurationException;
+import com.intellij.openapi.options.SearchableConfigurable;
+import com.intellij.openapi.project.Project;
+import com.intellij.ui.RawCommandLineEditor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.jps.model.java.compiler.RmicCompilerOptions;
+
+import javax.swing.*;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: Mar 30, 2004
+ */
+public class RmicConfigurable implements SearchableConfigurable, Configurable.NoScroll {
+ private JPanel myPanel;
+ private JCheckBox myCbEnabled;
+ private JCheckBox myCbGenerateIiopStubs;
+ private JCheckBox myCbDebuggingInfo;
+ private JCheckBox myCbGenerateNoWarnings;
+ private RawCommandLineEditor myAdditionalOptionsField;
+ private final RmicCompilerOptions myRmicSettings;
+ private JLabel myFieldLabel;
+
+ public RmicConfigurable(final Project project) {
+ myRmicSettings = RmicConfiguration.getOptions(project);
+ myCbEnabled.addItemListener(new ItemListener() {
+ public void itemStateChanged(ItemEvent e) {
+ setOptionsEnabled(e.getStateChange() == ItemEvent.SELECTED);
+ }
+ });
+ myAdditionalOptionsField.setDialogCaption(myFieldLabel.getText());
+ }
+
+ private void setOptionsEnabled(final boolean selected) {
+ myCbGenerateIiopStubs.setEnabled(selected);
+ myCbGenerateNoWarnings.setEnabled(selected);
+ myCbDebuggingInfo.setEnabled(selected);
+ myFieldLabel.setEnabled(selected);
+ myAdditionalOptionsField.setEnabled(selected);
+ }
+
+ public String getDisplayName() {
+ return CompilerBundle.message("rmi.compiler.description");
+ }
+
+ public String getHelpTopic() {
+ return "reference.projectsettings.compiler.rmicompiler";
+ }
+
+ @NotNull
+ public String getId() {
+ return getHelpTopic();
+ }
+
+ public Runnable enableSearch(String option) {
+ return null;
+ }
+
+ public JComponent createComponent() {
+ return myPanel;
+ }
+
+ public boolean isModified() {
+ boolean isModified = false;
+ isModified |= ComparingUtils.isModified(myCbEnabled, myRmicSettings.IS_EANABLED);
+ isModified |= ComparingUtils.isModified(myCbGenerateIiopStubs, myRmicSettings.GENERATE_IIOP_STUBS);
+ isModified |= ComparingUtils.isModified(myCbDebuggingInfo, myRmicSettings.DEBUGGING_INFO);
+ isModified |= ComparingUtils.isModified(myCbGenerateNoWarnings, myRmicSettings.GENERATE_NO_WARNINGS);
+ isModified |= ComparingUtils.isModified(myAdditionalOptionsField, myRmicSettings.ADDITIONAL_OPTIONS_STRING);
+ return isModified;
+ }
+
+ public void apply() throws ConfigurationException {
+ myRmicSettings.IS_EANABLED = myCbEnabled.isSelected();
+ myRmicSettings.GENERATE_IIOP_STUBS = myCbGenerateIiopStubs.isSelected();
+ myRmicSettings.DEBUGGING_INFO = myCbDebuggingInfo.isSelected();
+ myRmicSettings.GENERATE_NO_WARNINGS = myCbGenerateNoWarnings.isSelected();
+ myRmicSettings.ADDITIONAL_OPTIONS_STRING = myAdditionalOptionsField.getText();
+ }
+
+ public void reset() {
+ myCbEnabled.setSelected(myRmicSettings.IS_EANABLED);
+ setOptionsEnabled(myRmicSettings.IS_EANABLED);
+ myCbGenerateIiopStubs.setSelected(myRmicSettings.GENERATE_IIOP_STUBS);
+ myCbDebuggingInfo.setSelected(myRmicSettings.DEBUGGING_INFO);
+ myCbGenerateNoWarnings.setSelected(myRmicSettings.GENERATE_NO_WARNINGS);
+ myAdditionalOptionsField.setText(myRmicSettings.ADDITIONAL_OPTIONS_STRING);
+ }
+
+ public void disposeUIResources() {
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/options/RmicOptionsPanel.form b/java/compiler/impl/src/com/intellij/compiler/options/RmicOptionsPanel.form
new file mode 100644
index 0000000..a5a4aaf
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/options/RmicOptionsPanel.form
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.compiler.options.RmicConfigurable">
+ <grid id="49284" binding="myPanel" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <xy x="37" y="129" width="521" height="280"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <grid id="9b938" layout-manager="GridLayoutManager" row-count="4" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="1" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="3c8c0" class="javax.swing.JCheckBox" binding="myCbEnabled">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="rmic.option.enable.rmi.stubs"/>
+ </properties>
+ </component>
+ <component id="70faa" class="javax.swing.JCheckBox" binding="myCbGenerateIiopStubs">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="rmic.option.generate.iiop.stubs"/>
+ </properties>
+ </component>
+ <component id="3d38f" class="javax.swing.JCheckBox" binding="myCbDebuggingInfo">
+ <constraints>
+ <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <selected value="false"/>
+ <text resource-bundle="messages/CompilerBundle" key="java.compiler.option.generate.debugging.info"/>
+ </properties>
+ </component>
+ <component id="90803" class="javax.swing.JCheckBox" binding="myCbGenerateNoWarnings">
+ <constraints>
+ <grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="java.compiler.option.generate.no.warnings"/>
+ </properties>
+ </component>
+ </children>
+ </grid>
+ <grid id="3f1e9" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="10" left="8" bottom="0" right="0"/>
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="1" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="f8a80" class="javax.swing.JLabel" binding="myFieldLabel">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="java.compiler.option.additional.command.line.parameters"/>
+ </properties>
+ </component>
+ <component id="479c2" class="com.intellij.ui.RawCommandLineEditor" binding="myAdditionalOptionsField">
+ <constraints>
+ <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false">
+ <preferred-size width="150" height="-1"/>
+ </grid>
+ </constraints>
+ <properties/>
+ </component>
+ </children>
+ </grid>
+ </children>
+ </grid>
+</form>
diff --git a/java/compiler/impl/src/com/intellij/compiler/options/TargetOptionsComponent.java b/java/compiler/impl/src/com/intellij/compiler/options/TargetOptionsComponent.java
new file mode 100644
index 0000000..a4864c8
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/options/TargetOptionsComponent.java
@@ -0,0 +1,395 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.options;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.module.ModuleType;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ui.configuration.ChooseModulesDialog;
+import com.intellij.openapi.ui.ComboBox;
+import com.intellij.ui.*;
+import com.intellij.ui.table.JBTable;
+import com.intellij.util.ui.ItemRemovable;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import javax.swing.plaf.basic.BasicComboBoxEditor;
+import javax.swing.table.AbstractTableModel;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.TableColumn;
+import java.awt.*;
+import java.util.*;
+import java.util.List;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 5/9/12
+ */
+public class TargetOptionsComponent extends JPanel {
+ private static final String[] KNOWN_TARGETS = new String[] {"1.1", "1.2", "1.3","1.4","1.5", "1.6", "1.7", "1.8"};
+ private static final String COMPILER_DEFAULT = "JDK default";
+
+ private ComboBox myCbProjectTargetLevel;
+ private JBTable myTable;
+ private final Project myProject;
+
+ public TargetOptionsComponent(Project project) {
+ super(new GridBagLayout());
+ myProject = project;
+ //setBorder(BorderFactory.createTitledBorder("Bytecode target level"));
+ myCbProjectTargetLevel = createTargetOptionsCombo();
+
+ myTable = new JBTable(new TargetLevelTableModel());
+ myTable.setRowHeight(22);
+ myTable.getEmptyText().setText("All modules will be compiled with project bytecode version");
+
+ final TableColumn moduleColumn = myTable.getColumnModel().getColumn(0);
+ moduleColumn.setHeaderValue("Module");
+ moduleColumn.setCellRenderer(new ModuleCellRenderer());
+
+ final TableColumn targetLevelColumn = myTable.getColumnModel().getColumn(1);
+ final String columnTitle = "Target bytecode version";
+ targetLevelColumn.setHeaderValue(columnTitle);
+ targetLevelColumn.setCellEditor(new TargetLevelCellEditor());
+ targetLevelColumn.setCellRenderer(new TargetLevelCellRenderer());
+ final int width = myTable.getFontMetrics(myTable.getFont()).stringWidth(columnTitle) + 10;
+ targetLevelColumn.setPreferredWidth(width);
+ targetLevelColumn.setMinWidth(width);
+ targetLevelColumn.setMaxWidth(width);
+
+ add(new JLabel("Project bytecode version (leave blank for jdk default): "),
+ constraints(0, 0, 1, 1, 0.0, 0.0, GridBagConstraints.NONE));
+ add(myCbProjectTargetLevel, constraints(1, 0, 1, 1, 1.0, 0.0, GridBagConstraints.NONE));
+ add(new JLabel("Per-module bytecode version:"), constraints(0, 1, 2, 1, 1.0, 0.0, GridBagConstraints.NONE));
+ final JPanel tableComp = ToolbarDecorator.createDecorator(myTable)
+ .disableUpAction()
+ .disableDownAction()
+ .setAddAction(new AnActionButtonRunnable() {
+ @Override
+ public void run(AnActionButton anActionButton) {
+ addModules();
+ }
+ })
+ .setRemoveAction(new AnActionButtonRunnable() {
+ @Override
+ public void run(AnActionButton anActionButton) {
+ removeSelectedModules();
+ }
+ }).createPanel();
+
+ tableComp.setPreferredSize(new Dimension(myTable.getWidth(), 150));
+ add(tableComp, constraints(0, 2, 2, 1, 1.0, 1.0, GridBagConstraints.BOTH));
+ }
+
+ private void removeSelectedModules() {
+ final int[] rows = myTable.getSelectedRows();
+ if (rows.length > 0) {
+ TableUtil.removeSelectedItems(myTable);
+ }
+ }
+
+ private void addModules() {
+ final TargetLevelTableModel model = (TargetLevelTableModel)myTable.getModel();
+ final List<Module> items = new ArrayList<Module>(Arrays.asList(ModuleManager.getInstance(myProject).getModules()));
+ Set<Module> alreadyAdded = new HashSet<Module>();
+ for (TargetLevelTableModel.Item item : model.getItems()) {
+ alreadyAdded.add(item.module);
+ }
+ for (Iterator<Module> it = items.iterator(); it.hasNext(); ) {
+ Module module = it.next();
+ if (alreadyAdded.contains(module)) {
+ it.remove();
+ }
+ }
+ Collections.sort(items, new Comparator<Module>() {
+ @Override
+ public int compare(Module o1, Module o2) {
+ return o1.getName().compareTo(o2.getName());
+ }
+ });
+ final ChooseModulesDialog chooser = new ChooseModulesDialog(this, items, "Choose module");
+ chooser.show();
+ final List<Module> elements = chooser.getChosenElements();
+ if (!elements.isEmpty()) {
+ model.addItems(elements);
+ }
+ }
+
+ public void setProjectBytecodeTargetLevel(String level) {
+ myCbProjectTargetLevel.setSelectedItem(level == null? "" : level);
+ }
+
+ @Nullable
+ public String getProjectBytecodeTarget() {
+ final String item = ((String)myCbProjectTargetLevel.getSelectedItem()).trim();
+ return "".equals(item)? null : item;
+ }
+
+ public Map<String, String> getModulesBytecodeTargetMap() {
+ TargetLevelTableModel model = (TargetLevelTableModel)myTable.getModel();
+ final Map<String, String> map = new HashMap<String, String>();
+ for (TargetLevelTableModel.Item item : model.getItems()) {
+ map.put(item.module.getName(), item.targetLevel);
+ }
+ return map;
+ }
+
+ public void setModuleTargetLevels(Map<String, String> moduleLevels) {
+ final Map<Module, String> map = new HashMap<Module, String>();
+ for (Module module : ModuleManager.getInstance(myProject).getModules()) {
+ final String target = moduleLevels.get(module.getName());
+ if (target != null) {
+ map.put(module, target);
+ }
+ }
+ ((TargetLevelTableModel)myTable.getModel()).setItems(map);
+ }
+
+ private static GridBagConstraints constraints(final int gridx, final int gridy, final int gridwidth, final int gridheight, final double weightx, final double weighty, final int fill) {
+ return new GridBagConstraints(gridx, gridy, gridwidth, gridheight, weightx, weighty, GridBagConstraints.WEST, fill, new Insets(5, 5, 0, 0), 0, 0);
+ }
+
+ private static final class TargetLevelTableModel extends AbstractTableModel implements ItemRemovable{
+ private final List<Item> myItems = new ArrayList<Item>();
+ @Override
+ public int getRowCount() {
+ return myItems.size();
+ }
+
+ @Override
+ public int getColumnCount() {
+ return 2;
+ }
+
+ @Override
+ public boolean isCellEditable(int rowIndex, int columnIndex) {
+ return columnIndex != 0;
+ }
+
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ final Item item = myItems.get(rowIndex);
+ return columnIndex == 0? item.module : item.targetLevel;
+ }
+
+ @Override
+ public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
+ final Item item = myItems.get(rowIndex);
+ item.targetLevel = ((String)aValue).trim();
+ fireTableCellUpdated(rowIndex, columnIndex);
+ }
+
+ //public void addItem(Module module) {
+ // final int size = myItems.size();
+ // myItems.add(new Item(module.getName()));
+ // fireTableRowsInserted(size, size);
+ //}
+
+ public void addItems(Collection<Module> modules) {
+ for (Module module : modules) {
+ myItems.add(new Item(module));
+ }
+ sorItems();
+ fireTableDataChanged();
+ }
+
+ private void sorItems() {
+ Collections.sort(myItems, new Comparator<Item>() {
+ @Override
+ public int compare(Item o1, Item o2) {
+ return o1.module.getName().compareTo(o2.module.getName());
+ }
+ });
+ }
+
+ public List<Item> getItems() {
+ return myItems;
+ }
+
+ @Override
+ public void removeRow(int idx) {
+ myItems.remove(idx);
+ }
+
+ public void setItems(Map<Module, String> items) {
+ myItems.clear();
+ for (Map.Entry<Module, String> entry : items.entrySet()) {
+ myItems.add(new Item(entry.getKey(), entry.getValue()));
+ }
+ sorItems();
+ fireTableDataChanged();
+ }
+
+ private static final class Item {
+ final Module module;
+ String targetLevel = "";
+
+ Item(Module module) {
+ this.module = module;
+ }
+
+ Item(Module module, String targetLevel) {
+ this.module = module;
+ this.targetLevel = targetLevel;
+ }
+ }
+ }
+
+ private static final class TargetLevelComboboxModel extends AbstractListModel implements ComboBoxModel{
+
+ private final List<String> myOptions = new ArrayList<String>();
+ private String mySelectedItem = "";
+
+ TargetLevelComboboxModel() {
+ //myOptions.add("");
+ for (int i = KNOWN_TARGETS.length - 1; i >= 0; i--) {
+ myOptions.add(KNOWN_TARGETS[i]);
+ }
+ }
+
+ @Override
+ public int getSize() {
+ return myOptions.size();
+ }
+
+ @Override
+ public void setSelectedItem(Object anItem) {
+ mySelectedItem = toModelItem((String)anItem);
+ fireContentsChanged(this, 0, myOptions.size());
+ }
+
+ @Override
+ public Object getSelectedItem() {
+ return mySelectedItem;
+ }
+
+ @Override
+ public Object getElementAt(int index) {
+ return myOptions.get(index);
+ }
+
+ private String toModelItem(String item) {
+ item = item.trim();
+ for (String option : myOptions) {
+ if (option.equals(item)) {
+ return option;
+ }
+ }
+ return item;
+ }
+ }
+
+ private static ComboBox createTargetOptionsCombo() {
+ final ComboBox combo = new ComboBox(new TargetLevelComboboxModel());
+ //combo.setRenderer(new DefaultListCellRenderer() {
+ // @Override
+ // public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
+ // try {
+ // return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+ // }
+ // finally {
+ // //if ("".equals(value)) {
+ // // setText(COMPILER_DEFAULT);
+ // //}
+ // }
+ // }
+ //});
+ combo.setEditable(true);
+ combo.setEditor(new BasicComboBoxEditor() {
+ @Override
+ protected JTextField createEditorComponent() {
+ return new HintTextField(COMPILER_DEFAULT, 10);
+ }
+ });
+ return combo;
+ }
+
+ private static class ModuleCellRenderer extends DefaultTableCellRenderer {
+ @Override
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
+ try {
+ return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
+ }
+ finally {
+ final Module module = (Module)value;
+ setText(module.getName());
+ setIcon(ModuleType.get(module).getIcon());
+ }
+ }
+ }
+
+ private static class TargetLevelCellEditor extends DefaultCellEditor {
+ private TargetLevelCellEditor() {
+ super(createTargetOptionsCombo());
+ setClickCountToStart(0);
+ }
+ }
+
+ private static class TargetLevelCellRenderer extends DefaultTableCellRenderer {
+ @Override
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
+ final Component component = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
+ if (component instanceof JLabel) {
+ final JLabel comp = (JLabel)component;
+ comp.setHorizontalAlignment(SwingConstants.CENTER);
+ if ("".equals(value)) {
+ comp.setForeground(JBColor.GRAY);
+ comp.setText(COMPILER_DEFAULT);
+ }
+ else {
+ comp.setForeground(table.getForeground());
+ }
+ }
+ return component;
+ }
+ }
+
+ static class HintTextField extends JTextField {
+ private final char[] myHint;
+
+ public HintTextField(final String hint) {
+ this(hint, 0);
+ }
+
+ public HintTextField(final String hint, final int columns) {
+ super(hint, columns);
+ myHint = hint.toCharArray();
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ final boolean isFocused = isFocusOwner();
+ if (!isFocused && getText().isEmpty()) {
+ final Color oldColor = g.getColor();
+ final Font oldFont = g.getFont();
+ try {
+ g.setColor(JBColor.GRAY);
+ //g.setFont(oldFont.deriveFont(Font.ITALIC));
+ final FontMetrics metrics = g.getFontMetrics();
+ int x = Math.abs(getWidth() - metrics.charsWidth(myHint, 0, myHint.length)) / 2;
+ int y = Math.abs(getHeight() - metrics.getHeight()) / 2 + metrics.getAscent();
+ g.drawChars(myHint, 0, myHint.length, x, y);
+ }
+ finally {
+ g.setColor(oldColor);
+ g.setFont(oldFont);
+ }
+ }
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/options/ValidationConfiguration.java b/java/compiler/impl/src/com/intellij/compiler/options/ValidationConfiguration.java
new file mode 100644
index 0000000..2b8a43e
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/options/ValidationConfiguration.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.options;
+
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.Compiler;
+import com.intellij.openapi.compiler.options.ExcludedEntriesConfiguration;
+import com.intellij.openapi.components.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.util.xmlb.XmlSerializerUtil;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Dmitry Avdeev
+ */
+@State(
+ name = "ValidationConfiguration",
+ storages = {
+ @Storage( file = StoragePathMacros.WORKSPACE_FILE),
+ @Storage( file = StoragePathMacros.PROJECT_CONFIG_DIR + "/validation.xml", scheme = StorageScheme.DIRECTORY_BASED)
+ }
+)
+public class ValidationConfiguration implements PersistentStateComponent<ValidationConfiguration> {
+
+ public boolean VALIDATE_ON_BUILD = false;
+ public Map<String, Boolean> VALIDATORS = new HashMap<String, Boolean>();
+
+ public static boolean shouldValidate(Compiler validator, CompileContext context) {
+ ValidationConfiguration configuration = getInstance(context.getProject());
+ return (configuration.VALIDATE_ON_BUILD) && configuration.isSelected(validator);
+ }
+
+ public boolean isSelected(Compiler validator) {
+ return isSelected(validator.getDescription());
+ }
+
+ public boolean isSelected(String validatorDescription) {
+ final Boolean selected = VALIDATORS.get(validatorDescription);
+ return selected == null || selected.booleanValue();
+ }
+
+ public void setSelected(Compiler validator, boolean selected) {
+ setSelected(validator.getDescription(), selected);
+ }
+
+ public void setSelected(String validatorDescription, boolean selected) {
+ VALIDATORS.put(validatorDescription, selected);
+ }
+
+ public static ValidationConfiguration getInstance(Project project) {
+ return ServiceManager.getService(project, ValidationConfiguration.class);
+ }
+
+ public static ExcludedEntriesConfiguration getExcludedEntriesConfiguration(Project project) {
+ return ServiceManager.getService(project, ExcludedFromValidationConfiguration.class);
+ }
+
+ public ValidationConfiguration getState() {
+ return this;
+ }
+
+ public void loadState(final ValidationConfiguration state) {
+ XmlSerializerUtil.copyBean(state, this);
+ }
+
+ @State(
+ name = "ExcludeFromValidation",
+ storages = {
+ @Storage( file = StoragePathMacros.PROJECT_FILE),
+ @Storage( file = StoragePathMacros.PROJECT_CONFIG_DIR + "/excludeFromValidation.xml", scheme = StorageScheme.DIRECTORY_BASED)
+ }
+ )
+ public static class ExcludedFromValidationConfiguration extends ExcludedEntriesConfiguration {}
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/progress/CompilerTask.java b/java/compiler/impl/src/com/intellij/compiler/progress/CompilerTask.java
new file mode 100644
index 0000000..527b627
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/progress/CompilerTask.java
@@ -0,0 +1,607 @@
+/*
+ * 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.
+ */
+
+/*
+ * @author: Eugene Zhuravlev
+ * Date: Jan 22, 2003
+ * Time: 2:25:31 PM
+ */
+package com.intellij.compiler.progress;
+
+import com.intellij.compiler.CompilerManagerImpl;
+import com.intellij.compiler.CompilerMessageImpl;
+import com.intellij.compiler.impl.CompilerErrorTreeView;
+import com.intellij.ide.errorTreeView.NewErrorTreeViewPanel;
+import com.intellij.ide.errorTreeView.impl.ErrorTreeViewConfiguration;
+import com.intellij.ide.impl.ProjectUtil;
+import com.intellij.openapi.application.Application;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ModalityState;
+import com.intellij.openapi.compiler.*;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.fileEditor.OpenFileDescriptor;
+import com.intellij.openapi.progress.EmptyProgressIndicator;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.Task;
+import com.intellij.openapi.progress.util.ProgressIndicatorBase;
+import com.intellij.openapi.project.DumbModeAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectManager;
+import com.intellij.openapi.project.ProjectManagerListener;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.wm.AppIconScheme;
+import com.intellij.openapi.wm.ToolWindow;
+import com.intellij.openapi.wm.ToolWindowId;
+import com.intellij.openapi.wm.ToolWindowManager;
+import com.intellij.openapi.wm.ex.ProgressIndicatorEx;
+import com.intellij.pom.Navigatable;
+import com.intellij.problems.WolfTheProblemSolver;
+import com.intellij.ui.AppIcon;
+import com.intellij.ui.content.*;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.messages.MessageBusConnection;
+import com.intellij.util.ui.MessageCategory;
+import com.intellij.util.ui.UIUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class CompilerTask extends Task.Backgroundable {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.progress.CompilerProgressIndicator");
+ private static final Key<Key<?>> CONTENT_ID_KEY = Key.create("CONTENT_ID");
+ private static final String APP_ICON_ID = "compiler";
+ private Key<Key<?>> myContentIdKey = CONTENT_ID_KEY;
+ private final Key<Key<?>> myContentId = Key.create("compile_content");
+ private NewErrorTreeViewPanel myErrorTreeView;
+ private final Object myMessageViewLock = new Object();
+ private final String myContentName;
+ private final boolean myHeadlessMode;
+ private final boolean myForceAsyncExecution;
+ private int myErrorCount = 0;
+ private int myWarningCount = 0;
+ private boolean myMessagesAutoActivated = false;
+
+ private volatile ProgressIndicator myIndicator = new EmptyProgressIndicator();
+ private Runnable myCompileWork;
+ private final AtomicBoolean myMessageViewWasPrepared = new AtomicBoolean(false);
+ private Runnable myRestartWork;
+
+ public CompilerTask(@NotNull Project project, String contentName, final boolean headlessMode, boolean forceAsync) {
+ super(project, contentName);
+ myContentName = contentName;
+ myHeadlessMode = headlessMode;
+ myForceAsyncExecution = forceAsync;
+ }
+
+ public void setContentIdKey(Key<Key<?>> contentIdKey) {
+ myContentIdKey = contentIdKey != null? contentIdKey : CONTENT_ID_KEY;
+ }
+
+ public String getProcessId() {
+ return "compilation";
+ }
+
+ @Override
+ public DumbModeAction getDumbModeAction() {
+ return DumbModeAction.WAIT;
+ }
+
+ public boolean shouldStartInBackground() {
+ return true;
+ }
+
+ public ProgressIndicator getIndicator() {
+ return myIndicator;
+ }
+
+ @Nullable
+ public NotificationInfo getNotificationInfo() {
+ return new NotificationInfo(myErrorCount > 0? "Compiler (errors)" : "Compiler (success)", "Compilation Finished", myErrorCount + " Errors, " + myWarningCount + " Warnings", true);
+ }
+
+ private CloseListener myCloseListener;
+
+ public void run(@NotNull final ProgressIndicator indicator) {
+ myIndicator = indicator;
+
+ final ProjectManager projectManager = ProjectManager.getInstance();
+ projectManager.addProjectManagerListener(myProject, myCloseListener = new CloseListener());
+
+ final Semaphore semaphore = ((CompilerManagerImpl)CompilerManager.getInstance(myProject)).getCompilationSemaphore();
+ boolean acquired = false;
+ try {
+
+ try {
+ while (!acquired) {
+ acquired = semaphore.tryAcquire(500, TimeUnit.MILLISECONDS);
+ if (indicator.isCanceled()) {
+ // give up obtaining the semaphore,
+ // let compile work begin in order to stop gracefuly on cancel event
+ break;
+ }
+ }
+ }
+ catch (InterruptedException ignored) {
+ }
+
+ if (!isHeadless()) {
+ addIndicatorDelegate();
+ }
+ myCompileWork.run();
+ }
+ finally {
+ try {
+ indicator.stop();
+ projectManager.removeProjectManagerListener(myProject, myCloseListener);
+ }
+ finally {
+ if (acquired) {
+ semaphore.release();
+ }
+ }
+ }
+ }
+
+ private void prepareMessageView() {
+ if (!myIndicator.isRunning()) {
+ return;
+ }
+ if (myMessageViewWasPrepared.getAndSet(true)) {
+ return;
+ }
+
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ public void run() {
+ if (myProject.isDisposed()) return;
+ synchronized (myMessageViewLock) {
+ // clear messages from the previous compilation
+ if (myErrorTreeView == null) {
+ // if message view != null, the contents has already been cleared
+ removeAllContents(myProject, null);
+ }
+ }
+ }
+ });
+ }
+
+ private void addIndicatorDelegate() {
+ ProgressIndicator indicator = myIndicator;
+ if (!(indicator instanceof ProgressIndicatorEx)) return;
+ ((ProgressIndicatorEx)indicator).addStateDelegate(new ProgressIndicatorBase() {
+
+ public void cancel() {
+ super.cancel();
+ closeUI();
+ stopAppIconProgress();
+ }
+
+ public void stop() {
+ super.stop();
+ if (!isCanceled()) {
+ closeUI();
+ }
+ stopAppIconProgress();
+ }
+
+ private void stopAppIconProgress() {
+ UIUtil.invokeLaterIfNeeded(new Runnable() {
+ public void run() {
+ AppIcon appIcon = AppIcon.getInstance();
+ if (appIcon.hideProgress(myProject, APP_ICON_ID)) {
+ if (myErrorCount > 0) {
+ appIcon.setErrorBadge(myProject, String.valueOf(myErrorCount));
+ appIcon.requestAttention(myProject, true);
+ } else {
+ appIcon.setOkBadge(myProject, true);
+ appIcon.requestAttention(myProject, false);
+ }
+ }
+ }
+ });
+ }
+
+ public void setText(final String text) {
+ super.setText(text);
+ updateProgressText();
+ }
+
+ public void setText2(final String text) {
+ super.setText2(text);
+ updateProgressText();
+ }
+
+ public void setFraction(final double fraction) {
+ super.setFraction(fraction);
+ updateProgressText();
+ UIUtil.invokeLaterIfNeeded(new Runnable() {
+ public void run() {
+ AppIcon.getInstance().setProgress(myProject, APP_ICON_ID, AppIconScheme.Progress.BUILD, fraction, true);
+ }
+ });
+ }
+
+ protected void onProgressChange() {
+ prepareMessageView();
+ }
+ });
+ }
+
+ public void cancel() {
+ if (!myIndicator.isCanceled()) {
+ myIndicator.cancel();
+ }
+ }
+
+ public void addMessage(final CompilerMessage message) {
+ prepareMessageView();
+
+ final CompilerMessageCategory messageCategory = message.getCategory();
+ if (CompilerMessageCategory.WARNING.equals(messageCategory)) {
+ myWarningCount += 1;
+ }
+ else if (CompilerMessageCategory.ERROR.equals(messageCategory)) {
+ myErrorCount += 1;
+ informWolf(message);
+ }
+
+ if (ApplicationManager.getApplication().isDispatchThread()) {
+ openMessageView();
+ doAddMessage(message);
+ }
+ else {
+ final Window window = getWindow();
+ final ModalityState modalityState = window != null ? ModalityState.stateForComponent(window) : ModalityState.NON_MODAL;
+ ApplicationManager.getApplication().invokeLater(new Runnable() {
+ public void run() {
+ if (!myProject.isDisposed()) {
+ openMessageView();
+ doAddMessage(message);
+ }
+ }
+ }, modalityState);
+ }
+ }
+
+ private void informWolf(final CompilerMessage message) {
+ WolfTheProblemSolver wolf = WolfTheProblemSolver.getInstance(myProject);
+ VirtualFile file = getVirtualFile(message);
+ wolf.queue(file);
+ }
+
+ private void doAddMessage(final CompilerMessage message) {
+ synchronized (myMessageViewLock) {
+ if (myErrorTreeView != null) {
+ final Navigatable navigatable = message.getNavigatable();
+ final VirtualFile file = message.getVirtualFile();
+ final CompilerMessageCategory category = message.getCategory();
+ final int type = translateCategory(category);
+ final String[] text = convertMessage(message);
+ if (navigatable != null) {
+ final String groupName = file != null? file.getPresentableUrl() : category.getPresentableText();
+ myErrorTreeView.addMessage(type, text, groupName, navigatable, message.getExportTextPrefix(), message.getRenderTextPrefix(), message.getVirtualFile());
+ }
+ else {
+ myErrorTreeView.addMessage(type, text, file, -1, -1, message.getVirtualFile());
+ }
+
+ final boolean shouldAutoActivate =
+ !myMessagesAutoActivated &&
+ (
+ CompilerMessageCategory.ERROR.equals(category) ||
+ (CompilerMessageCategory.WARNING.equals(category) && !ErrorTreeViewConfiguration.getInstance(myProject).isHideWarnings())
+ );
+ if (shouldAutoActivate) {
+ myMessagesAutoActivated = true;
+ activateMessageView();
+ }
+ }
+ }
+ }
+
+ private static String[] convertMessage(final CompilerMessage message) {
+ String text = message.getMessage();
+ if (!text.contains("\n")) {
+ return new String[]{text};
+ }
+ ArrayList<String> lines = new ArrayList<String>();
+ StringTokenizer tokenizer = new StringTokenizer(text, "\n", false);
+ while (tokenizer.hasMoreTokens()) {
+ lines.add(tokenizer.nextToken());
+ }
+ return ArrayUtil.toStringArray(lines);
+ }
+
+ public static int translateCategory(CompilerMessageCategory category) {
+ if (CompilerMessageCategory.ERROR.equals(category)) {
+ return MessageCategory.ERROR;
+ }
+ if (CompilerMessageCategory.WARNING.equals(category)) {
+ return MessageCategory.WARNING;
+ }
+ if (CompilerMessageCategory.STATISTICS.equals(category)) {
+ return MessageCategory.STATISTICS;
+ }
+ if (CompilerMessageCategory.INFORMATION.equals(category)) {
+ return MessageCategory.INFORMATION;
+ }
+ LOG.error("Unknown message category: " + category);
+ return 0;
+ }
+
+ public void start(Runnable compileWork, Runnable restartWork) {
+ myCompileWork = compileWork;
+ myRestartWork = restartWork;
+ queue();
+ }
+
+ private void updateProgressText() {
+ if (isHeadlessMode()) {
+ return;
+ }
+ }
+
+ // error tree view initialization must be invoked from event dispatch thread
+ private void openMessageView() {
+ if (isHeadlessMode()) {
+ return;
+ }
+ if (myIndicator.isCanceled()) {
+ return;
+ }
+
+ final JComponent component;
+ synchronized (myMessageViewLock) {
+ if (myErrorTreeView != null) {
+ return;
+ }
+ myErrorTreeView = new CompilerErrorTreeView(
+ myProject,
+ myRestartWork
+ );
+
+ myErrorTreeView.setProcessController(new NewErrorTreeViewPanel.ProcessController() {
+ public void stopProcess() {
+ cancel();
+ }
+
+ public boolean isProcessStopped() {
+ return !myIndicator.isRunning();
+ }
+ });
+ component = myErrorTreeView.getComponent();
+ }
+
+ final MessageView messageView = MessageView.SERVICE.getInstance(myProject);
+ final Content content = ContentFactory.SERVICE.getInstance().createContent(component, myContentName, true);
+ content.putUserData(myContentIdKey, myContentId);
+ messageView.getContentManager().addContent(content);
+ myCloseListener.setContent(content, messageView.getContentManager());
+ removeAllContents(myProject, content);
+ messageView.getContentManager().setSelectedContent(content);
+ }
+
+ public void showCompilerContent() {
+ synchronized (myMessageViewLock) {
+ if (myErrorTreeView != null) {
+ final MessageView messageView = MessageView.SERVICE.getInstance(myProject);
+ Content[] contents = messageView.getContentManager().getContents();
+ for (Content content : contents) {
+ if (content.getUserData(myContentIdKey) != null) {
+ messageView.getContentManager().setSelectedContent(content);
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ private void removeAllContents(Project project, Content notRemove) {
+ final MessageView messageView = MessageView.SERVICE.getInstance(project);
+ Content[] contents = messageView.getContentManager().getContents();
+ for (Content content : contents) {
+ if (content.isPinned()) {
+ continue;
+ }
+ if (content == notRemove) {
+ continue;
+ }
+ if (content.getUserData(myContentIdKey) != null) { // the content was added by me
+ messageView.getContentManager().removeContent(content, true);
+ }
+ }
+ }
+
+ private void activateMessageView() {
+ synchronized (myMessageViewLock) {
+ if (myErrorTreeView != null) {
+ final ToolWindow tw = ToolWindowManager.getInstance(myProject).getToolWindow(ToolWindowId.MESSAGES_WINDOW);
+ if (tw != null) {
+ tw.activate(null, false);
+ }
+ }
+ }
+ }
+
+ private void closeUI() {
+ if (isHeadlessMode()) {
+ return;
+ }
+ Window window = getWindow();
+ ModalityState modalityState = window != null ? ModalityState.stateForComponent(window) : ModalityState.NON_MODAL;
+ final Application application = ApplicationManager.getApplication();
+ application.invokeLater(new Runnable() {
+ public void run() {
+ synchronized (myMessageViewLock) {
+ if (myErrorTreeView != null) {
+ final boolean shouldRetainView = myErrorCount > 0 || myWarningCount > 0 && !myErrorTreeView.isHideWarnings();
+ if (shouldRetainView) {
+ addMessage(new CompilerMessageImpl(myProject, CompilerMessageCategory.STATISTICS, CompilerBundle.message("statistics.error.count", myErrorCount)));
+ addMessage(new CompilerMessageImpl(myProject, CompilerMessageCategory.STATISTICS, CompilerBundle.message("statistics.warnings.count", myWarningCount)));
+ //activateMessageView();
+ myErrorTreeView.selectFirstMessage();
+ }
+ else {
+ removeAllContents(myProject, null);
+ }
+ }
+ }
+ }
+ }, modalityState);
+ }
+
+ public Window getWindow(){
+ return null;
+ }
+
+ public boolean isHeadless() {
+ return myHeadlessMode && !myForceAsyncExecution;
+ }
+
+ private boolean isHeadlessMode() {
+ return myHeadlessMode;
+ }
+
+ private static VirtualFile getVirtualFile(final CompilerMessage message) {
+ VirtualFile virtualFile = message.getVirtualFile();
+ if (virtualFile == null) {
+ Navigatable navigatable = message.getNavigatable();
+ if (navigatable instanceof OpenFileDescriptor) {
+ virtualFile = ((OpenFileDescriptor)navigatable).getFile();
+ }
+ }
+ return virtualFile;
+ }
+
+ public static TextRange getTextRange(final CompilerMessage message) {
+ Navigatable navigatable = message.getNavigatable();
+ if (navigatable instanceof OpenFileDescriptor) {
+ int offset = ((OpenFileDescriptor)navigatable).getOffset();
+ return new TextRange(offset, offset);
+ }
+ return TextRange.EMPTY_RANGE;
+ }
+
+ private class CloseListener extends ContentManagerAdapter implements ProjectManagerListener {
+ private Content myContent;
+ private ContentManager myContentManager;
+ private boolean myIsApplicationExitingOrProjectClosing = false;
+ private boolean myUserAcceptedCancel = false;
+
+ public boolean canCloseProject(final Project project) {
+ assert project != null;
+ if (!project.equals(myProject)) {
+ return true;
+ }
+ if (shouldAskUser()) {
+ int result = Messages.showOkCancelDialog(
+ myProject,
+ CompilerBundle.message("warning.compiler.running.on.project.close"),
+ CompilerBundle.message("compiler.running.dialog.title"),
+ Messages.getQuestionIcon()
+ );
+ if (result != 0) {
+ return false; // veto closing
+ }
+ myUserAcceptedCancel = true;
+
+ final MessageBusConnection connection = project.getMessageBus().connect();
+ connection.subscribe(CompilerTopics.COMPILATION_STATUS, new CompilationStatusAdapter() {
+ public void compilationFinished(boolean aborted, int errors, int warnings, final CompileContext compileContext) {
+ connection.disconnect();
+ ProjectUtil.closeAndDispose(project);
+ }
+ });
+ cancel();
+ return false; // cancel compiler and let it finish, after compilation close the project, but currently - veto closing
+ }
+ return !myIndicator.isRunning();
+ }
+
+ public void setContent(Content content, ContentManager contentManager) {
+ myContent = content;
+ myContentManager = contentManager;
+ contentManager.addContentManagerListener(this);
+ }
+
+ public void contentRemoved(ContentManagerEvent event) {
+ if (event.getContent() == myContent) {
+ synchronized (myMessageViewLock) {
+ if (myErrorTreeView != null) {
+ myErrorTreeView.dispose();
+ myErrorTreeView = null;
+ if (myIndicator.isRunning()) {
+ cancel();
+ }
+ if (AppIcon.getInstance().hideProgress(myProject, "compiler")) {
+ AppIcon.getInstance().setErrorBadge(myProject, null);
+ }
+ }
+ }
+ myContentManager.removeContentManagerListener(this);
+ myContent.release();
+ myContent = null;
+ }
+ }
+
+ public void contentRemoveQuery(ContentManagerEvent event) {
+ if (event.getContent() == myContent) {
+ if (!myIndicator.isCanceled() && shouldAskUser()) {
+ int result = Messages.showOkCancelDialog(
+ myProject,
+ CompilerBundle.message("warning.compiler.running.on.toolwindow.close"),
+ CompilerBundle.message("compiler.running.dialog.title"),
+ Messages.getQuestionIcon()
+ );
+ if (result != 0) {
+ event.consume(); // veto closing
+ }
+ myUserAcceptedCancel = true;
+ }
+ }
+ }
+
+ private boolean shouldAskUser() {
+ // do not ask second time if user already accepted closing
+ return !myUserAcceptedCancel && !myIsApplicationExitingOrProjectClosing && myIndicator.isRunning();
+ }
+
+ public void projectOpened(Project project) {
+ }
+
+ public void projectClosed(Project project) {
+ if (project.equals(myProject) && myContent != null) {
+ myContentManager.removeContent(myContent, true);
+ }
+ }
+
+ public void projectClosing(Project project) {
+ if (project.equals(myProject)) {
+ myIsApplicationExitingOrProjectClosing = true;
+ }
+ }
+ }
+}
+
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/compiler/server/AutoMakeMessageHandler.java b/java/compiler/impl/src/com/intellij/compiler/server/AutoMakeMessageHandler.java
new file mode 100644
index 0000000..05bfe72
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/server/AutoMakeMessageHandler.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.server;
+
+import com.intellij.compiler.CompilerMessageImpl;
+import com.intellij.compiler.ProblemsView;
+import com.intellij.notification.Notification;
+import com.intellij.openapi.compiler.CompilationStatusListener;
+import com.intellij.openapi.compiler.CompilerManager;
+import com.intellij.openapi.compiler.CompilerMessageCategory;
+import com.intellij.openapi.compiler.CompilerTopics;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.MessageType;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.problems.Problem;
+import com.intellij.problems.WolfTheProblemSolver;
+import org.jetbrains.jps.api.CmdlineRemoteProto;
+
+import java.util.Collections;
+import java.util.UUID;
+
+/**
+* @author Eugene Zhuravlev
+* Date: 4/25/12
+*/
+class AutoMakeMessageHandler extends DefaultMessageHandler {
+ private static final Key<Notification> LAST_AUTO_MAKE_NOFITICATION = Key.create("LAST_AUTO_MAKE_NOFITICATION");
+ private CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.Status myBuildStatus;
+ private final Project myProject;
+ private final WolfTheProblemSolver myWolf;
+
+ public AutoMakeMessageHandler(Project project) {
+ super(project);
+ myProject = project;
+ myBuildStatus = CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.Status.SUCCESS;
+ myWolf = WolfTheProblemSolver.getInstance(project);
+ }
+
+ @Override
+ public void buildStarted(UUID sessionId) {
+ }
+
+ @Override
+ protected void handleBuildEvent(UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event) {
+ if (myProject.isDisposed()) {
+ return;
+ }
+ switch (event.getEventType()) {
+ case BUILD_COMPLETED:
+ if (event.hasCompletionStatus()) {
+ myBuildStatus = event.getCompletionStatus();
+ }
+ return;
+
+ case FILES_GENERATED:
+ final CompilationStatusListener publisher = myProject.getMessageBus().syncPublisher(CompilerTopics.COMPILATION_STATUS);
+ for (CmdlineRemoteProto.Message.BuilderMessage.BuildEvent.GeneratedFile generatedFile : event.getGeneratedFilesList()) {
+ final String root = FileUtil.toSystemIndependentName(generatedFile.getOutputRoot());
+ final String relativePath = FileUtil.toSystemIndependentName(generatedFile.getRelativePath());
+ publisher.fileGenerated(root, relativePath);
+ }
+ return;
+
+ default:
+ return;
+ }
+ }
+
+ @Override
+ protected void handleCompileMessage(final UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.CompileMessage message) {
+ if (myProject.isDisposed()) {
+ return;
+ }
+ final CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind kind = message.getKind();
+ if (kind == CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind.PROGRESS) {
+ final ProblemsView view = ProblemsView.SERVICE.getInstance(myProject);
+ if (message.hasDone()) {
+ view.setProgress(message.getText(), message.getDone());
+ }
+ else {
+ view.setProgress(message.getText());
+ }
+ }
+ else if (kind == CmdlineRemoteProto.Message.BuilderMessage.CompileMessage.Kind.ERROR) {
+ informWolf(myProject, message);
+
+ final String sourceFilePath = message.hasSourceFilePath() ? message.getSourceFilePath() : null;
+ final VirtualFile vFile = sourceFilePath != null? LocalFileSystem.getInstance().findFileByPath(FileUtil.toSystemIndependentName(sourceFilePath)) : null;
+ final long line = message.hasLine() ? message.getLine() : -1;
+ final long column = message.hasColumn() ? message.getColumn() : -1;
+ ProblemsView.SERVICE.getInstance(myProject).addMessage(new CompilerMessageImpl(myProject, CompilerMessageCategory.ERROR, message.getText(), vFile, (int)line, (int)column, null), sessionId);
+ }
+ }
+
+ @Override
+ public void handleFailure(UUID sessionId, CmdlineRemoteProto.Message.Failure failure) {
+ final String msg = "Auto make failure: " + failure.getDescription();
+ CompilerManager.NOTIFICATION_GROUP.createNotification(msg, MessageType.INFO);
+ ProblemsView.SERVICE.getInstance(myProject).addMessage(new CompilerMessageImpl(myProject, CompilerMessageCategory.ERROR, msg), sessionId);
+ }
+
+ @Override
+ public void sessionTerminated(UUID sessionId) {
+ String statusMessage = null/*"Auto make completed"*/;
+ switch (myBuildStatus) {
+ case SUCCESS:
+ //statusMessage = "Auto make completed successfully";
+ break;
+ case UP_TO_DATE:
+ //statusMessage = "All files are up-to-date";
+ break;
+ case ERRORS:
+ statusMessage = "Auto make completed with errors";
+ break;
+ case CANCELED:
+ //statusMessage = "Auto make has been canceled";
+ break;
+ }
+ if (statusMessage != null) {
+ final Notification notification = CompilerManager.NOTIFICATION_GROUP.createNotification(statusMessage, MessageType.INFO);
+ if (!myProject.isDisposed()) {
+ notification.notify(myProject);
+ }
+ myProject.putUserData(LAST_AUTO_MAKE_NOFITICATION, notification);
+ }
+ else {
+ Notification notification = myProject.getUserData(LAST_AUTO_MAKE_NOFITICATION);
+ if (notification != null) {
+ notification.expire();
+ myProject.putUserData(LAST_AUTO_MAKE_NOFITICATION, null);
+ }
+ }
+ final ProblemsView view = ProblemsView.SERVICE.getInstance(myProject);
+ view.clearProgress();
+ view.clearOldMessages(null, sessionId);
+ }
+
+ private void informWolf(Project project, CmdlineRemoteProto.Message.BuilderMessage.CompileMessage message) {
+ final String srcPath = message.getSourceFilePath();
+ if (srcPath != null && !project.isDisposed()) {
+ final VirtualFile vFile = LocalFileSystem.getInstance().findFileByPath(srcPath);
+ if (vFile != null) {
+ final int line = (int)message.getLine();
+ final int column = (int)message.getColumn();
+ if (line > 0 && column > 0) {
+ final Problem problem = myWolf.convertToProblem(vFile, line, column, new String[]{message.getText()});
+ myWolf.weHaveGotProblems(vFile, Collections.singletonList(problem));
+ }
+ else {
+ myWolf.queue(vFile);
+ }
+ }
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java b/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java
new file mode 100644
index 0000000..a819b47
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/server/BuildManager.java
@@ -0,0 +1,1100 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.server;
+
+import com.intellij.ProjectTopics;
+import com.intellij.application.options.PathMacrosImpl;
+import com.intellij.compiler.CompilerWorkspaceConfiguration;
+import com.intellij.compiler.impl.javaCompiler.javac.JavacConfiguration;
+import com.intellij.compiler.server.impl.CompileServerClasspathManager;
+import com.intellij.execution.ExecutionAdapter;
+import com.intellij.execution.ExecutionException;
+import com.intellij.execution.ExecutionManager;
+import com.intellij.execution.configurations.GeneralCommandLine;
+import com.intellij.execution.configurations.RunProfile;
+import com.intellij.execution.process.*;
+import com.intellij.execution.ui.RunContentDescriptor;
+import com.intellij.execution.ui.RunContentManager;
+import com.intellij.ide.PowerSaveMode;
+import com.intellij.openapi.Disposable;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.PathMacros;
+import com.intellij.openapi.application.PathManager;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.components.ApplicationComponent;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectCoreUtil;
+import com.intellij.openapi.project.ProjectManager;
+import com.intellij.openapi.project.ProjectManagerAdapter;
+import com.intellij.openapi.projectRoots.JavaSdk;
+import com.intellij.openapi.projectRoots.JavaSdkType;
+import com.intellij.openapi.projectRoots.JavaSdkVersion;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl;
+import com.intellij.openapi.roots.ModuleRootAdapter;
+import com.intellij.openapi.roots.ModuleRootEvent;
+import com.intellij.openapi.roots.ModuleRootManager;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.ShutDownTracker;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.registry.Registry;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.CharsetToolkit;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.intellij.openapi.vfs.newvfs.BulkFileListener;
+import com.intellij.openapi.vfs.newvfs.events.VFileEvent;
+import com.intellij.util.Alarm;
+import com.intellij.util.Function;
+import com.intellij.util.concurrency.SequentialTaskExecutor;
+import com.intellij.util.io.storage.HeavyProcessLatch;
+import com.intellij.util.messages.MessageBusConnection;
+import com.intellij.util.net.NetUtils;
+import gnu.trove.THashSet;
+import org.jboss.netty.bootstrap.ServerBootstrap;
+import org.jboss.netty.channel.*;
+import org.jboss.netty.channel.group.ChannelGroup;
+import org.jboss.netty.channel.group.ChannelGroupFuture;
+import org.jboss.netty.channel.group.DefaultChannelGroup;
+import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory;
+import org.jboss.netty.handler.codec.protobuf.ProtobufDecoder;
+import org.jboss.netty.handler.codec.protobuf.ProtobufEncoder;
+import org.jboss.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
+import org.jboss.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.TestOnly;
+import org.jetbrains.ide.PooledThreadExecutor;
+import org.jetbrains.jps.api.CmdlineProtoUtil;
+import org.jetbrains.jps.api.CmdlineRemoteProto;
+import org.jetbrains.jps.api.GlobalOptions;
+import org.jetbrains.jps.api.RequestFuture;
+import org.jetbrains.jps.cmdline.BuildMain;
+import org.jetbrains.jps.cmdline.ClasspathBootstrap;
+import org.jetbrains.jps.incremental.Utils;
+
+import javax.tools.*;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.nio.charset.Charset;
+import java.util.*;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.jetbrains.jps.api.CmdlineRemoteProto.Message.ControllerMessage.ParametersMessage.TargetTypeBuildScope;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 9/6/11
+ */
+public class BuildManager implements ApplicationComponent{
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.server.BuildManager");
+ private static final String SYSTEM_ROOT = "compile-server";
+ private static final String LOGGER_CONFIG = "log.xml";
+ private static final String DEFAULT_LOGGER_CONFIG = "defaultLogConfig.xml";
+ private static final int MAKE_TRIGGER_DELAY = 3 * 1000 /*3 seconds*/;
+ private final boolean IS_UNIT_TEST_MODE;
+ private static final String IWS_EXTENSION = ".iws";
+ private static final String IPR_EXTENSION = ".ipr";
+ private static final String IDEA_PROJECT_DIR_PATTERN = "/.idea/";
+ private static final Function<String, Boolean> PATH_FILTER =
+ SystemInfo.isFileSystemCaseSensitive?
+ new Function<String, Boolean>() {
+ @Override
+ public Boolean fun(String s) {
+ return !(s.contains(IDEA_PROJECT_DIR_PATTERN) || s.endsWith(IWS_EXTENSION) || s.endsWith(IPR_EXTENSION));
+ }
+ } :
+ new Function<String, Boolean>() {
+ @Override
+ public Boolean fun(String s) {
+ return !(StringUtil.endsWithIgnoreCase(s, IWS_EXTENSION) || StringUtil.endsWithIgnoreCase(s, IPR_EXTENSION) || StringUtil.containsIgnoreCase(s, IDEA_PROJECT_DIR_PATTERN));
+ }
+ };
+
+ private final File mySystemDirectory;
+ private final ProjectManager myProjectManager;
+
+ private final Map<RequestFuture, Project> myAutomakeFutures = new HashMap<RequestFuture, Project>();
+ private final Map<String, RequestFuture> myBuildsInProgress = Collections.synchronizedMap(new HashMap<String, RequestFuture>());
+ private final CompileServerClasspathManager myClasspathManager = new CompileServerClasspathManager();
+ private final Executor myPooledThreadExecutor = new PooledThreadExecutor();
+ private final SequentialTaskExecutor myRequestsProcessor = new SequentialTaskExecutor(myPooledThreadExecutor);
+ private final Map<String, ProjectData> myProjectDataMap = Collections.synchronizedMap(new HashMap<String, ProjectData>());
+
+ private final Alarm myAlarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
+ private final AtomicBoolean myAutoMakeInProgress = new AtomicBoolean(false);
+
+ private final ChannelGroup myAllOpenChannels = new DefaultChannelGroup("build-manager");
+ private final BuildMessageDispatcher myMessageDispatcher = new BuildMessageDispatcher();
+ private volatile int myListenPort = -1;
+ private volatile CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings myGlobals;
+ @Nullable
+ private final Charset mySystemCharset;
+
+ public BuildManager(final ProjectManager projectManager) {
+ IS_UNIT_TEST_MODE = ApplicationManager.getApplication().isUnitTestMode();
+ myProjectManager = projectManager;
+ mySystemCharset = CharsetToolkit.getDefaultSystemCharset();
+ final String systemPath = PathManager.getSystemPath();
+ File system = new File(systemPath);
+ try {
+ system = system.getCanonicalFile();
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ }
+ mySystemDirectory = system;
+
+ projectManager.addProjectManagerListener(new ProjectWatcher());
+
+ final MessageBusConnection conn = ApplicationManager.getApplication().getMessageBus().connect();
+ conn.subscribe(VirtualFileManager.VFS_CHANGES, new BulkFileListener.Adapter() {
+ @Override
+ public void after(@NotNull List<? extends VFileEvent> events) {
+ if (shouldTriggerMake(events)) {
+ scheduleAutoMake();
+ }
+ }
+
+ private boolean shouldTriggerMake(List<? extends VFileEvent> events) {
+ if (PowerSaveMode.isEnabled()) {
+ return false;
+ }
+ List<Project> activeProjects = null;
+ for (VFileEvent event : events) {
+ final VirtualFile eventFile = event.getFile();
+ if (eventFile == null || ProjectCoreUtil.isProjectOrWorkspaceFile(eventFile)) {
+ continue;
+ }
+
+ if (activeProjects == null) {
+ activeProjects = getActiveProjects();
+ if (activeProjects.isEmpty()) {
+ return false;
+ }
+ }
+ // todo: probably we do not need this excessive filtering
+ for (Project project : activeProjects) {
+ if (!project.isInitialized() || ProjectRootManager.getInstance(project).getFileIndex().isInContent(eventFile)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private List<Project> getActiveProjects() {
+ final Project[] projects = myProjectManager.getOpenProjects();
+ if (projects.length == 0) {
+ return Collections.emptyList();
+ }
+ final List<Project> projectList = new ArrayList<Project>();
+ for (Project project : projects) {
+ if (project.isDefault() || project.isDisposed()) {
+ continue;
+ }
+ projectList.add(project);
+ }
+ return projectList;
+ }
+ });
+
+ ShutDownTracker.getInstance().registerShutdownTask(new Runnable() {
+ @Override
+ public void run() {
+ stopListening();
+ }
+ });
+ }
+
+ public static BuildManager getInstance() {
+ return ApplicationManager.getApplication().getComponent(BuildManager.class);
+ }
+
+ public void notifyFilesChanged(final Collection<File> paths) {
+ doNotify(paths, false);
+ }
+
+ public void notifyFilesDeleted(Collection<File> paths) {
+ doNotify(paths, true);
+ }
+
+ public void runCommand(Runnable command) {
+ myRequestsProcessor.submit(command);
+ }
+
+ private void doNotify(final Collection<File> paths, final boolean notifyDeletion) {
+ // ensure events processed in the order they arrived
+ runCommand(new Runnable() {
+
+ @Override
+ public void run() {
+ final List<String> filtered = new ArrayList<String>(paths.size());
+ for (File file : paths) {
+ final String path = FileUtil.toSystemIndependentName(file.getPath());
+ if (PATH_FILTER.fun(path)) {
+ filtered.add(path);
+ }
+ }
+ if (filtered.isEmpty()) {
+ return;
+ }
+ synchronized (myProjectDataMap) {
+ if (IS_UNIT_TEST_MODE) {
+ if (notifyDeletion) {
+ LOG.info("Registering deleted paths: " + filtered);
+ }
+ else {
+ LOG.info("Registering changed paths: " + filtered);
+ }
+ }
+ for (Map.Entry<String, ProjectData> entry : myProjectDataMap.entrySet()) {
+ final ProjectData data = entry.getValue();
+ if (notifyDeletion) {
+ data.addDeleted(filtered);
+ }
+ else {
+ data.addChanged(filtered);
+ }
+ final RequestFuture future = myBuildsInProgress.get(entry.getKey());
+ if (future != null && !future.isCancelled() && !future.isDone()) {
+ final UUID sessionId = future.getRequestID();
+ final Channel channel = myMessageDispatcher.getConnectedChannel(sessionId);
+ if (channel != null) {
+ final CmdlineRemoteProto.Message.ControllerMessage message =
+ CmdlineRemoteProto.Message.ControllerMessage.newBuilder().setType(
+ CmdlineRemoteProto.Message.ControllerMessage.Type.FS_EVENT).setFsEvent(data.createNextEvent()).build();
+ Channels.write(channel, CmdlineProtoUtil.toMessage(sessionId, message));
+ }
+ }
+ }
+ }
+ }
+ });
+ }
+
+ public static void forceModelLoading(CompileContext context) {
+ context.getCompileScope().putUserData(BuildMain.FORCE_MODEL_LOADING_PARAMETER, Boolean.TRUE.toString());
+ }
+
+ public void clearState(Project project) {
+ myGlobals = null;
+ final String projectPath = getProjectPath(project);
+ synchronized (myProjectDataMap) {
+ final ProjectData data = myProjectDataMap.get(projectPath);
+ if (data != null) {
+ data.dropChanges();
+ }
+ }
+ scheduleAutoMake();
+ }
+
+ public boolean rescanRequired(Project project) {
+ final String projectPath = getProjectPath(project);
+ synchronized (myProjectDataMap) {
+ final ProjectData data = myProjectDataMap.get(projectPath);
+ return data == null || data.myNeedRescan;
+ }
+ }
+
+ @Nullable
+ public List<String> getFilesChangedSinceLastCompilation(Project project) {
+ String projectPath = getProjectPath(project);
+ synchronized (myProjectDataMap) {
+ ProjectData data = myProjectDataMap.get(projectPath);
+ if (data != null && !data.myNeedRescan) {
+ return new ArrayList<String>(data.myChanged);
+ }
+ return null;
+ }
+ }
+
+ @Nullable
+ private static String getProjectPath(final Project project) {
+ final String url = project.getPresentableUrl();
+ if (url == null) {
+ return null;
+ }
+ return VirtualFileManager.extractPath(url);
+ }
+
+ public void scheduleAutoMake() {
+ if (IS_UNIT_TEST_MODE || PowerSaveMode.isEnabled()) {
+ return;
+ }
+ addMakeRequest(new Runnable() {
+ @Override
+ public void run() {
+ if (!HeavyProcessLatch.INSTANCE.isRunning() && !myAutoMakeInProgress.getAndSet(true)) {
+ try {
+ ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ runAutoMake();
+ }
+ finally {
+ myAutoMakeInProgress.set(false);
+ }
+ }
+ });
+ }
+ catch (RejectedExecutionException ignored) {
+ // we were shut down
+ myAutoMakeInProgress.set(false);
+ }
+ catch (Throwable e) {
+ myAutoMakeInProgress.set(false);
+ throw new RuntimeException(e);
+ }
+ }
+ else {
+ addMakeRequest(this);
+ }
+ }
+ });
+ }
+
+ private void addMakeRequest(Runnable runnable) {
+ myAlarm.cancelAllRequests();
+ final int delay = Math.max(50, Registry.intValue("compiler.automake.trigger.delay", MAKE_TRIGGER_DELAY));
+ myAlarm.addRequest(runnable, delay);
+ }
+
+ private void runAutoMake() {
+ final Project[] openProjects = myProjectManager.getOpenProjects();
+ if (openProjects.length > 0) {
+ final List<RequestFuture> futures = new ArrayList<RequestFuture>();
+ for (final Project project : openProjects) {
+ if (project.isDefault() || project.isDisposed()) {
+ continue;
+ }
+ final CompilerWorkspaceConfiguration config = CompilerWorkspaceConfiguration.getInstance(project);
+ if (!config.useOutOfProcessBuild() || !config.MAKE_PROJECT_ON_SAVE) {
+ continue;
+ }
+ if (!config.allowAutoMakeWhileRunningApplication()) {
+ final RunContentManager contentManager = ExecutionManager.getInstance(project).getContentManager();
+ boolean hasRunningProcesses = false;
+ for (RunContentDescriptor descriptor : contentManager.getAllDescriptors()) {
+ final ProcessHandler handler = descriptor.getProcessHandler();
+ if (handler != null && !handler.isProcessTerminated()) { // active process
+ hasRunningProcesses = true;
+ break;
+ }
+ }
+ if (hasRunningProcesses) {
+ continue;
+ }
+ }
+
+ final List<String> emptyList = Collections.emptyList();
+ final RequestFuture future = scheduleBuild(
+ project, false, true, false, CmdlineProtoUtil.createAllModulesScopes(), emptyList, Collections.<String, String>emptyMap(), new AutoMakeMessageHandler(project)
+ );
+ if (future != null) {
+ futures.add(future);
+ synchronized (myAutomakeFutures) {
+ myAutomakeFutures.put(future, project);
+ }
+ }
+ }
+ try {
+ for (RequestFuture future : futures) {
+ future.waitFor();
+ }
+ }
+ finally {
+ synchronized (myAutomakeFutures) {
+ myAutomakeFutures.keySet().removeAll(futures);
+ }
+ }
+ }
+ }
+
+ public Collection<RequestFuture> cancelAutoMakeTasks(Project project) {
+ final Collection<RequestFuture> futures = new ArrayList<RequestFuture>();
+ synchronized (myAutomakeFutures) {
+ for (Map.Entry<RequestFuture, Project> entry : myAutomakeFutures.entrySet()) {
+ if (entry.getValue().equals(project)) {
+ final RequestFuture future = entry.getKey();
+ future.cancel(false);
+ futures.add(future);
+ }
+ }
+ }
+ return futures;
+ }
+
+ @Nullable
+ public RequestFuture scheduleBuild(
+ final Project project, final boolean isRebuild, final boolean isMake,
+ final boolean onlyCheckUpToDate, final List<TargetTypeBuildScope> scopes,
+ final Collection<String> paths,
+ final Map<String, String> userData, final DefaultMessageHandler handler) {
+
+ final String projectPath = getProjectPath(project);
+ final UUID sessionId = UUID.randomUUID();
+
+ // ensure server is listening
+ if (myListenPort < 0) {
+ try {
+ synchronized (this) {
+ if (myListenPort < 0) {
+ myListenPort = startListening();
+ }
+ }
+ }
+ catch (Exception e) {
+ handler.handleFailure(sessionId, CmdlineProtoUtil.createFailure(e.getMessage(), null));
+ handler.sessionTerminated(sessionId);
+ return null;
+ }
+ }
+
+ try {
+ final RequestFuture<BuilderMessageHandler> future = new RequestFuture<BuilderMessageHandler>(handler, sessionId, new RequestFuture.CancelAction<BuilderMessageHandler>() {
+ @Override
+ public void cancel(RequestFuture<BuilderMessageHandler> future) throws Exception {
+ myMessageDispatcher.cancelSession(future.getRequestID());
+ }
+ });
+ // by using the same queue that processes events we ensure that
+ // the build will be aware of all events that have happened before this request
+ runCommand(new Runnable() {
+ @Override
+ public void run() {
+ if (future.isCancelled() || project.isDisposed()) {
+ handler.sessionTerminated(sessionId);
+ future.setDone();
+ return;
+ }
+
+ CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings globals = myGlobals;
+ if (globals == null) {
+ globals = buildGlobalSettings();
+ myGlobals = globals;
+ }
+ CmdlineRemoteProto.Message.ControllerMessage.FSEvent currentFSChanges;
+ final SequentialTaskExecutor projectTaskQueue;
+ synchronized (myProjectDataMap) {
+ ProjectData data = myProjectDataMap.get(projectPath);
+ if (data == null) {
+ data = new ProjectData(new SequentialTaskExecutor(myPooledThreadExecutor));
+ myProjectDataMap.put(projectPath, data);
+ }
+ if (isRebuild) {
+ data.dropChanges();
+ }
+ if (IS_UNIT_TEST_MODE) {
+ LOG.info("Scheduling build for " +
+ projectPath +
+ "; CHANGED: " +
+ new HashSet<String>(data.myChanged) +
+ "; DELETED: " +
+ new HashSet<String>(data.myDeleted));
+ }
+ currentFSChanges = data.getAndResetRescanFlag() ? null : data.createNextEvent();
+ projectTaskQueue = data.taskQueue;
+ }
+
+ final CmdlineRemoteProto.Message.ControllerMessage params;
+ if (isRebuild) {
+ params = CmdlineProtoUtil.createRebuildRequest(projectPath, scopes, userData, globals);
+ }
+ else if (onlyCheckUpToDate) {
+ params = CmdlineProtoUtil.createUpToDateCheckRequest(projectPath, scopes, paths, userData, globals, currentFSChanges);
+ }
+ else {
+ params = isMake ?
+ CmdlineProtoUtil.createMakeRequest(projectPath, scopes, userData, globals, currentFSChanges) :
+ CmdlineProtoUtil.createForceCompileRequest(projectPath, scopes, paths, userData, globals, currentFSChanges);
+ }
+
+ myMessageDispatcher.registerBuildMessageHandler(sessionId, new BuilderMessageHandlerWrapper(handler) {
+ @Override
+ public void sessionTerminated(UUID sessionId) {
+ try {
+ super.sessionTerminated(sessionId);
+ }
+ finally {
+ future.setDone();
+ }
+ }
+ }, params);
+
+ try {
+ projectTaskQueue.submit(new Runnable() {
+ @Override
+ public void run() {
+ ExecutionException execFailure = null;
+ try {
+ if (project.isDisposed()) {
+ return;
+ }
+ myBuildsInProgress.put(projectPath, future);
+ final OSProcessHandler processHandler = launchBuildProcess(project, myListenPort, sessionId);
+ final StringBuilder stdErrOutput = new StringBuilder();
+ processHandler.addProcessListener(new ProcessAdapter() {
+ @Override
+ public void onTextAvailable(ProcessEvent event, Key outputType) {
+ // re-translate builder's output to idea.log
+ final String text = event.getText();
+ if (!StringUtil.isEmptyOrSpaces(text)) {
+ LOG.info("BUILDER_PROCESS [" + outputType.toString() + "]: " + text.trim());
+ if (stdErrOutput.length() < 1024 && ProcessOutputTypes.STDERR.equals(outputType)) {
+ stdErrOutput.append(text);
+ }
+ }
+ }
+ });
+ processHandler.startNotify();
+ final boolean terminated = processHandler.waitFor();
+ if (terminated) {
+ final int exitValue = processHandler.getProcess().exitValue();
+ if (exitValue != 0) {
+ final StringBuilder msg = new StringBuilder();
+ msg.append("Abnormal build process termination: ");
+ if (stdErrOutput.length() > 0) {
+ msg.append("\n").append(stdErrOutput);
+ }
+ else {
+ msg.append("unknown error");
+ }
+ handler.handleFailure(sessionId, CmdlineProtoUtil.createFailure(msg.toString(), null));
+ }
+ }
+ else {
+ handler.handleFailure(sessionId, CmdlineProtoUtil.createFailure("Disconnected from build process", null));
+ }
+ }
+ catch (ExecutionException e) {
+ execFailure = e;
+ }
+ finally {
+ myBuildsInProgress.remove(projectPath);
+ if (myMessageDispatcher.getAssociatedChannel(sessionId) == null) {
+ // either the connection has never been established (process not started or execution failed), or no messages were sent from the launched process.
+ // in this case the session cannot be unregistered by the message dispatcher
+ final BuilderMessageHandler unregistered = myMessageDispatcher.unregisterBuildMessageHandler(sessionId);
+ if (unregistered != null) {
+ if (execFailure != null) {
+ unregistered.handleFailure(sessionId, CmdlineProtoUtil.createFailure(execFailure.getMessage(), execFailure));
+ }
+ unregistered.sessionTerminated(sessionId);
+ }
+ }
+ }
+ }
+ });
+ }
+ catch (Throwable e) {
+ final BuilderMessageHandler unregistered = myMessageDispatcher.unregisterBuildMessageHandler(sessionId);
+ if (unregistered != null) {
+ unregistered.handleFailure(sessionId, CmdlineProtoUtil.createFailure(e.getMessage(), e));
+ unregistered.sessionTerminated(sessionId);
+ }
+ }
+ }
+ });
+
+ return future;
+ }
+ catch (Throwable e) {
+ handler.handleFailure(sessionId, CmdlineProtoUtil.createFailure(e.getMessage(), e));
+ handler.sessionTerminated(sessionId);
+ }
+
+ return null;
+ }
+
+ @Override
+ public void initComponent() {
+ }
+
+ @Override
+ public void disposeComponent() {
+ stopListening();
+ }
+
+ @NotNull
+ @Override
+ public String getComponentName() {
+ return "com.intellij.compiler.server.BuildManager";
+ }
+
+ private static CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings buildGlobalSettings() {
+ final Map<String, String> data = new HashMap<String, String>();
+
+ for (Map.Entry<String, String> entry : PathMacrosImpl.getGlobalSystemMacros().entrySet()) {
+ data.put(entry.getKey(), FileUtil.toSystemIndependentName(entry.getValue()));
+ }
+
+ final PathMacros pathVars = PathMacros.getInstance();
+ for (String name : pathVars.getAllMacroNames()) {
+ final String path = pathVars.getValue(name);
+ if (path != null) {
+ data.put(name, FileUtil.toSystemIndependentName(path));
+ }
+ }
+
+ final CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings.Builder cmdBuilder =
+ CmdlineRemoteProto.Message.ControllerMessage.GlobalSettings.newBuilder();
+
+ cmdBuilder.setGlobalOptionsPath(PathManager.getOptionsPath());
+
+ if (!data.isEmpty()) {
+ for (Map.Entry<String, String> entry : data.entrySet()) {
+ final String var = entry.getKey();
+ final String value = entry.getValue();
+ if (var != null && value != null) {
+ cmdBuilder.addPathVariable(CmdlineProtoUtil.createPair(var, value));
+ }
+ }
+ }
+
+ return cmdBuilder.build();
+ }
+
+ private OSProcessHandler launchBuildProcess(Project project, final int port, final UUID sessionId) throws ExecutionException {
+ // choosing sdk with which the build process should be run
+ Sdk projectJdk = null;
+ JavaSdkVersion sdkVersion = null;
+ int sdkMinorVersion = 0;
+
+ final Set<Sdk> candidates = new HashSet<Sdk>();
+ for (Module module : ModuleManager.getInstance(project).getModules()) {
+ final Sdk sdk = ModuleRootManager.getInstance(module).getSdk();
+ if (sdk != null && sdk.getSdkType() instanceof JavaSdk) {
+ candidates.add(sdk);
+ }
+ }
+ // now select the latest version from the sdks that are used in the project, but not older than the internal sdk version
+ for (Sdk candidate : candidates) {
+ final String vs = candidate.getVersionString();
+ if (vs != null) {
+ final JavaSdkVersion candidateVersion = ((JavaSdk)candidate.getSdkType()).getVersion(vs);
+ if (candidateVersion != null) {
+ final int candidateMinorVersion = getMinorVersion(vs);
+ if (projectJdk == null) {
+ sdkVersion = candidateVersion;
+ sdkMinorVersion = candidateMinorVersion;
+ projectJdk = candidate;
+ }
+ else {
+ final int result = candidateVersion.compareTo(sdkVersion);
+ if (result > 0 || (result == 0 && candidateMinorVersion > sdkMinorVersion)) {
+ sdkVersion = candidateVersion;
+ sdkMinorVersion = candidateMinorVersion;
+ projectJdk = candidate;
+ }
+ }
+ }
+ }
+ }
+
+ final Sdk internalJdk = JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk();
+ if (projectJdk == null || sdkVersion == null || !sdkVersion.isAtLeast(JavaSdkVersion.JDK_1_6)) {
+ projectJdk = internalJdk;
+ }
+
+ // validate tools.jar presence
+ final String compilerPath;
+ if (projectJdk.equals(internalJdk)) {
+ final JavaCompiler systemCompiler = ToolProvider.getSystemJavaCompiler();
+ if (systemCompiler == null) {
+ throw new ExecutionException("No system java compiler is provided by the JRE. Make sure tools.jar is present in IntelliJ IDEA classpath.");
+ }
+ compilerPath = ClasspathBootstrap.getResourcePath(systemCompiler.getClass());
+ }
+ else {
+ compilerPath = ((JavaSdk)projectJdk.getSdkType()).getToolsPath(projectJdk);
+ if (compilerPath == null) {
+ throw new ExecutionException("Cannot determine path to 'tools.jar' library for " + projectJdk.getName() + " (" + projectJdk.getHomePath() + ")");
+ }
+ }
+
+ final CompilerWorkspaceConfiguration config = CompilerWorkspaceConfiguration.getInstance(project);
+ final GeneralCommandLine cmdLine = new GeneralCommandLine();
+ final String vmExecutablePath = ((JavaSdkType)projectJdk.getSdkType()).getVMExecutablePath(projectJdk);
+ cmdLine.setExePath(vmExecutablePath);
+ //cmdLine.addParameter("-XX:MaxPermSize=150m");
+ //cmdLine.addParameter("-XX:ReservedCodeCacheSize=64m");
+ int heapSize = config.COMPILER_PROCESS_HEAP_SIZE;
+
+ // todo: remove when old make implementation is removed
+ if (heapSize == CompilerWorkspaceConfiguration.DEFAULT_COMPILE_PROCESS_HEAP_SIZE) {
+ // check if javac is set to use larger heap, and if so, use it.
+ heapSize = Math.max(heapSize, JavacConfiguration.getOptions(project, JavacConfiguration.class).MAXIMUM_HEAP_SIZE);
+ }
+
+ cmdLine.addParameter("-Xmx" + heapSize + "m");
+
+ if (SystemInfo.isMac && sdkVersion != null && JavaSdkVersion.JDK_1_6.equals(sdkVersion) && Registry.is("compiler.process.32bit.vm.on.mac")) {
+ // unfortunately -d32 is supported on jdk 1.6 only
+ cmdLine.addParameter("-d32");
+ }
+
+ cmdLine.addParameter("-Djava.awt.headless=true");
+ if (IS_UNIT_TEST_MODE) {
+ cmdLine.addParameter("-Dtest.mode=true");
+ }
+ cmdLine.addParameter("-Djdt.compiler.useSingleThread=true"); // always run eclipse compiler in single-threaded mode
+
+ final String shouldGenerateIndex = System.getProperty(GlobalOptions.GENERATE_CLASSPATH_INDEX_OPTION);
+ if (shouldGenerateIndex != null) {
+ cmdLine.addParameter("-D"+ GlobalOptions.GENERATE_CLASSPATH_INDEX_OPTION +"=" + shouldGenerateIndex);
+ }
+ cmdLine.addParameter("-D"+ GlobalOptions.COMPILE_PARALLEL_OPTION +"=" + Boolean.toString(config.PARALLEL_COMPILATION));
+
+ boolean isProfilingMode = false;
+ final String additionalOptions = config.COMPILER_PROCESS_ADDITIONAL_VM_OPTIONS;
+ if (!StringUtil.isEmpty(additionalOptions)) {
+ final StringTokenizer tokenizer = new StringTokenizer(additionalOptions, " ", false);
+ while (tokenizer.hasMoreTokens()) {
+ final String option = tokenizer.nextToken();
+ if ("-Dprofiling.mode=true".equals(option)) {
+ isProfilingMode = true;
+ }
+ cmdLine.addParameter(option);
+ }
+ }
+
+ // debugging
+ final int debugPort = Registry.intValue("compiler.process.debug.port");
+ if (debugPort > 0) {
+ cmdLine.addParameter("-XX:+HeapDumpOnOutOfMemoryError");
+ cmdLine.addParameter("-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=" + debugPort);
+ }
+
+ if (Registry.is("compiler.process.use.memory.temp.cache")) {
+ cmdLine.addParameter("-D"+ GlobalOptions.USE_MEMORY_TEMP_CACHE_OPTION);
+ }
+ if (Registry.is("compiler.process.use.external.javac")) {
+ cmdLine.addParameter("-D"+ GlobalOptions.USE_EXTERNAL_JAVAC_OPTION);
+ }
+
+ // javac's VM should use the same default locale that IDEA uses in order for javac to print messages in 'correct' language
+ if (mySystemCharset != null) {
+ cmdLine.setCharset(mySystemCharset);
+ cmdLine.addParameter("-D" + CharsetToolkit.FILE_ENCODING_PROPERTY + "=" + mySystemCharset.name());
+ }
+ for (String name : new String[]{"user.language", "user.country", "user.region", PathManager.PROPERTY_HOME_PATH}) {
+ final String value = System.getProperty(name);
+ if (value != null) {
+ cmdLine.addParameter("-D" + name + "=" + value);
+ }
+ }
+
+ final File workDirectory = getBuildSystemDirectory();
+ workDirectory.mkdirs();
+ ensureLogConfigExists(workDirectory);
+
+ final List<String> cp = ClasspathBootstrap.getBuildProcessApplicationClasspath();
+ cp.add(compilerPath);
+ cp.addAll(myClasspathManager.getCompileServerPluginsClasspath(project));
+ if (isProfilingMode) {
+ cp.add(new File(workDirectory, "yjp-controller-api-redist.jar").getPath());
+ cmdLine.addParameter("-agentlib:yjpagent=disablej2ee,disablealloc,sessionname=ExternalBuild");
+ }
+
+ cmdLine.addParameter("-classpath");
+ cmdLine.addParameter(classpathToString(cp));
+
+ cmdLine.addParameter(BuildMain.class.getName());
+ cmdLine.addParameter("127.0.0.1");
+ cmdLine.addParameter(Integer.toString(port));
+ cmdLine.addParameter(sessionId.toString());
+
+ cmdLine.addParameter(FileUtil.toSystemIndependentName(workDirectory.getPath()));
+
+ cmdLine.setWorkDirectory(workDirectory);
+
+ final Process process = cmdLine.createProcess();
+
+ return new OSProcessHandler(process, null, mySystemCharset) {
+ @Override
+ protected boolean shouldDestroyProcessRecursively() {
+ return true;
+ }
+ };
+ }
+
+ public File getBuildSystemDirectory() {
+ return new File(mySystemDirectory, SYSTEM_ROOT);
+ }
+
+ @Nullable
+ public File getProjectSystemDirectory(Project project) {
+ final String projectPath = getProjectPath(project);
+ return projectPath != null? Utils.getDataStorageRoot(getBuildSystemDirectory(), projectPath) : null;
+ }
+
+ private static int getMinorVersion(String vs) {
+ final int dashIndex = vs.lastIndexOf('_');
+ if (dashIndex >= 0) {
+ StringBuilder builder = new StringBuilder();
+ for (int idx = dashIndex + 1; idx < vs.length(); idx++) {
+ final char ch = vs.charAt(idx);
+ if (Character.isDigit(ch)) {
+ builder.append(ch);
+ }
+ else {
+ break;
+ }
+ }
+ if (builder.length() > 0) {
+ try {
+ return Integer.parseInt(builder.toString());
+ }
+ catch (NumberFormatException ignored) {
+ }
+ }
+ }
+ return 0;
+ }
+
+ private static void ensureLogConfigExists(File workDirectory) {
+ final File logConfig = new File(workDirectory, LOGGER_CONFIG);
+ if (!logConfig.exists()) {
+ FileUtil.createIfDoesntExist(logConfig);
+ try {
+ final InputStream in = BuildMain.class.getResourceAsStream("/" + DEFAULT_LOGGER_CONFIG);
+ if (in != null) {
+ try {
+ final FileOutputStream out = new FileOutputStream(logConfig);
+ try {
+ FileUtil.copy(in, out);
+ }
+ finally {
+ out.close();
+ }
+ }
+ finally {
+ in.close();
+ }
+ }
+ }
+ catch (IOException e) {
+ LOG.error(e);
+ }
+ }
+ }
+
+ public void stopListening() {
+ final ChannelGroupFuture closeFuture = myAllOpenChannels.close();
+ closeFuture.awaitUninterruptibly();
+ }
+
+ private int startListening() throws Exception {
+ final ChannelFactory channelFactory = new NioServerSocketChannelFactory(myPooledThreadExecutor, myPooledThreadExecutor, 1);
+ final SimpleChannelUpstreamHandler channelRegistrar = new SimpleChannelUpstreamHandler() {
+ public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
+ myAllOpenChannels.add(e.getChannel());
+ super.channelOpen(ctx, e);
+ }
+
+ @Override
+ public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
+ myAllOpenChannels.remove(e.getChannel());
+ super.channelClosed(ctx, e);
+ }
+ };
+ ChannelPipelineFactory pipelineFactory = new ChannelPipelineFactory() {
+ public ChannelPipeline getPipeline() throws Exception {
+ return Channels.pipeline(
+ channelRegistrar,
+ new ProtobufVarint32FrameDecoder(),
+ new ProtobufDecoder(CmdlineRemoteProto.Message.getDefaultInstance()),
+ new ProtobufVarint32LengthFieldPrepender(),
+ new ProtobufEncoder(),
+ myMessageDispatcher
+ );
+ }
+ };
+ final ServerBootstrap bootstrap = new ServerBootstrap(channelFactory);
+ bootstrap.setPipelineFactory(pipelineFactory);
+ bootstrap.setOption("child.tcpNoDelay", true);
+ bootstrap.setOption("child.keepAlive", true);
+ final int listenPort = NetUtils.findAvailableSocketPort();
+ final Channel serverChannel = bootstrap.bind(new InetSocketAddress(InetAddress.getByName("127.0.0.1"), listenPort));
+ myAllOpenChannels.add(serverChannel);
+ return listenPort;
+ }
+
+ @TestOnly
+ public void stopWatchingProject(Project project) {
+ myProjectDataMap.remove(getProjectPath(project));
+ }
+
+ private static String classpathToString(List<String> cp) {
+ StringBuilder builder = new StringBuilder();
+ for (String file : cp) {
+ if (builder.length() > 0) {
+ builder.append(File.pathSeparator);
+ }
+ builder.append(FileUtil.toCanonicalPath(file));
+ }
+ return builder.toString();
+ }
+
+ private static class BuilderMessageHandlerWrapper implements BuilderMessageHandler {
+ private final DefaultMessageHandler myHandler;
+
+ public BuilderMessageHandlerWrapper(DefaultMessageHandler handler) {
+ myHandler = handler;
+ }
+
+ @Override
+ public void buildStarted(UUID sessionId) {
+ myHandler.buildStarted(sessionId);
+ }
+
+ @Override
+ public void handleBuildMessage(Channel channel, UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage msg) {
+ myHandler.handleBuildMessage(channel, sessionId, msg);
+ }
+
+ @Override
+ public void handleFailure(UUID sessionId, CmdlineRemoteProto.Message.Failure failure) {
+ myHandler.handleFailure(sessionId, failure);
+ }
+
+ @Override
+ public void sessionTerminated(UUID sessionId) {
+ myHandler.sessionTerminated(sessionId);
+ }
+ }
+
+ private class ProjectWatcher extends ProjectManagerAdapter {
+ private final Map<Project, MessageBusConnection> myConnections = new HashMap<Project, MessageBusConnection>();
+
+ @Override
+ public void projectOpened(final Project project) {
+ final MessageBusConnection conn = project.getMessageBus().connect();
+ myConnections.put(project, conn);
+ conn.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() {
+ @Override
+ public void rootsChanged(final ModuleRootEvent event) {
+ final Object source = event.getSource();
+ if (source instanceof Project) {
+ clearState((Project)source);
+ }
+ }
+ });
+ conn.subscribe(ExecutionManager.EXECUTION_TOPIC, new ExecutionAdapter() {
+ @Override
+ public void processTerminated(@NotNull RunProfile runProfile, @NotNull ProcessHandler handler) {
+ scheduleAutoMake();
+ }
+ });
+ final String projectPath = getProjectPath(project);
+ Disposer.register(project, new Disposable() {
+ @Override
+ public void dispose() {
+ myProjectDataMap.remove(projectPath);
+ }
+ });
+ scheduleAutoMake(); // run automake on project opening
+ }
+
+ @Override
+ public boolean canCloseProject(Project project) {
+ cancelAutoMakeTasks(project);
+ return super.canCloseProject(project);
+ }
+
+ @Override
+ public void projectClosing(Project project) {
+ for (RequestFuture future : cancelAutoMakeTasks(project)) {
+ future.waitFor(500, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ @Override
+ public void projectClosed(Project project) {
+ myProjectDataMap.remove(getProjectPath(project));
+ final MessageBusConnection conn = myConnections.remove(project);
+ if (conn != null) {
+ conn.disconnect();
+ }
+ }
+ }
+
+ private static class ProjectData {
+ final SequentialTaskExecutor taskQueue;
+ private final Set<String> myChanged = new THashSet<String>(FileUtil.PATH_HASHING_STRATEGY);
+ private final Set<String> myDeleted = new THashSet<String>(FileUtil.PATH_HASHING_STRATEGY);
+ private long myNextEventOrdinal = 0L;
+ private boolean myNeedRescan = true;
+
+ private ProjectData(SequentialTaskExecutor taskQueue) {
+ this.taskQueue = taskQueue;
+ }
+
+ public void addChanged(Collection<String> paths) {
+ if (!myNeedRescan) {
+ myDeleted.removeAll(paths);
+ myChanged.addAll(paths);
+ }
+ }
+
+ public void addDeleted(Collection<String> paths) {
+ if (!myNeedRescan) {
+ myChanged.removeAll(paths);
+ myDeleted.addAll(paths);
+ }
+ }
+
+ public CmdlineRemoteProto.Message.ControllerMessage.FSEvent createNextEvent() {
+ final CmdlineRemoteProto.Message.ControllerMessage.FSEvent.Builder builder =
+ CmdlineRemoteProto.Message.ControllerMessage.FSEvent.newBuilder();
+ builder.setOrdinal(++myNextEventOrdinal);
+ builder.addAllChangedPaths(myChanged);
+ myChanged.clear();
+ builder.addAllDeletedPaths(myDeleted);
+ myDeleted.clear();
+ return builder.build();
+ }
+
+ public boolean getAndResetRescanFlag() {
+ final boolean rescan = myNeedRescan;
+ myNeedRescan = false;
+ return rescan;
+ }
+
+ public void dropChanges() {
+ myNeedRescan = true;
+ myNextEventOrdinal = 0L;
+ myChanged.clear();
+ myDeleted.clear();
+ }
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/server/BuildMessageDispatcher.java b/java/compiler/impl/src/com/intellij/compiler/server/BuildMessageDispatcher.java
new file mode 100644
index 0000000..0c49759
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/server/BuildMessageDispatcher.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.server;
+
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.util.containers.ConcurrentHashSet;
+import org.jboss.netty.channel.*;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.jps.api.CmdlineProtoUtil;
+import org.jetbrains.jps.api.CmdlineRemoteProto;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+* @author Eugene Zhuravlev
+* Date: 4/25/12
+*/
+class BuildMessageDispatcher extends SimpleChannelHandler {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.server.BuildMessageDispatcher");
+ private final Map<UUID, SessionData> myMessageHandlers = new ConcurrentHashMap<UUID, SessionData>();
+ private final Set<UUID> myCanceledSessions = new ConcurrentHashSet<UUID>();
+
+ public void registerBuildMessageHandler(UUID sessionId,
+ BuilderMessageHandler handler,
+ CmdlineRemoteProto.Message.ControllerMessage params) {
+ myMessageHandlers.put(sessionId, new SessionData(sessionId, handler, params));
+ }
+
+ @Nullable
+ public BuilderMessageHandler unregisterBuildMessageHandler(UUID sessionId) {
+ myCanceledSessions.remove(sessionId);
+ final SessionData data = myMessageHandlers.remove(sessionId);
+ return data != null? data.handler : null;
+ }
+
+ public void cancelSession(UUID sessionId) {
+ if (myCanceledSessions.add(sessionId)) {
+ final Channel channel = getConnectedChannel(sessionId);
+ if (channel != null) {
+ Channels.write(channel, CmdlineProtoUtil.toMessage(sessionId, CmdlineProtoUtil.createCancelCommand()));
+ }
+ }
+ }
+
+ @Nullable
+ public Channel getConnectedChannel(final UUID sessionId) {
+ final Channel channel = getAssociatedChannel(sessionId);
+ return channel != null && channel.isConnected()? channel : null;
+ }
+
+ @Nullable
+ public Channel getAssociatedChannel(final UUID sessionId) {
+ final SessionData data = myMessageHandlers.get(sessionId);
+ return data != null? data.channel : null;
+ }
+
+
+ @Override
+ public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
+ final CmdlineRemoteProto.Message message = (CmdlineRemoteProto.Message)e.getMessage();
+
+ SessionData sessionData = (SessionData)ctx.getAttachment();
+
+ UUID sessionId;
+ if (sessionData == null) {
+ // this is the first message for this session, so fill session data with missing info
+ final CmdlineRemoteProto.Message.UUID id = message.getSessionId();
+ sessionId = new UUID(id.getMostSigBits(), id.getLeastSigBits());
+
+ sessionData = myMessageHandlers.get(sessionId);
+ if (sessionData != null) {
+ sessionData.channel = ctx.getChannel();
+ ctx.setAttachment(sessionData);
+ }
+ if (myCanceledSessions.contains(sessionId)) {
+ Channels.write(ctx.getChannel(), CmdlineProtoUtil.toMessage(sessionId, CmdlineProtoUtil.createCancelCommand()));
+ }
+ }
+ else {
+ sessionId = sessionData.sessionId;
+ }
+
+ final BuilderMessageHandler handler = sessionData != null? sessionData.handler : null;
+ if (handler == null) {
+ // todo
+ LOG.info("No message handler registered for session " + sessionId);
+ return;
+ }
+
+ final CmdlineRemoteProto.Message.Type messageType = message.getType();
+ switch (messageType) {
+ case FAILURE:
+ handler.handleFailure(sessionId, message.getFailure());
+ break;
+
+ case BUILDER_MESSAGE:
+ final CmdlineRemoteProto.Message.BuilderMessage builderMessage = message.getBuilderMessage();
+ final CmdlineRemoteProto.Message.BuilderMessage.Type msgType = builderMessage.getType();
+ if (msgType == CmdlineRemoteProto.Message.BuilderMessage.Type.PARAM_REQUEST) {
+ final CmdlineRemoteProto.Message.ControllerMessage params = sessionData.params;
+ if (params != null) {
+ handler.buildStarted(sessionId);
+ sessionData.params = null;
+ Channels.write(ctx.getChannel(), CmdlineProtoUtil.toMessage(sessionId, params));
+ }
+ else {
+ cancelSession(sessionId);
+ }
+ }
+ else {
+ handler.handleBuildMessage(ctx.getChannel(), sessionId, builderMessage);
+ }
+ break;
+
+ default:
+ LOG.info("Unsupported message type " + messageType);
+ break;
+ }
+ }
+
+ @Override
+ public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
+ try {
+ super.channelClosed(ctx, e);
+ }
+ finally {
+ final SessionData sessionData = (SessionData)ctx.getAttachment();
+ if (sessionData != null) {
+ final BuilderMessageHandler handler = unregisterBuildMessageHandler(sessionData.sessionId);
+ if (handler != null) {
+ // notify the handler only if it has not been notified yet
+ handler.sessionTerminated(sessionData.sessionId);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
+ final Throwable cause = e.getCause();
+ if (cause != null) {
+ LOG.info(cause);
+ }
+ ctx.sendUpstream(e);
+ }
+
+ @Override
+ public void channelDisconnected(ChannelHandlerContext ctx, ChannelStateEvent e) throws Exception {
+ try {
+ super.channelDisconnected(ctx, e);
+ }
+ finally {
+ final SessionData sessionData = (SessionData)ctx.getAttachment();
+ if (sessionData != null) { // this is the context corresponding to some session
+ final Channel channel = e.getChannel();
+ ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
+ @Override
+ public void run() {
+ channel.close();
+ }
+ });
+ }
+ }
+ }
+
+ private static final class SessionData {
+ final UUID sessionId;
+ final BuilderMessageHandler handler;
+ volatile CmdlineRemoteProto.Message.ControllerMessage params;
+ volatile Channel channel;
+
+ private SessionData(UUID sessionId, BuilderMessageHandler handler, CmdlineRemoteProto.Message.ControllerMessage params) {
+ this.sessionId = sessionId;
+ this.handler = handler;
+ this.params = params;
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/server/BuilderMessageHandler.java b/java/compiler/impl/src/com/intellij/compiler/server/BuilderMessageHandler.java
new file mode 100644
index 0000000..fadf63b
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/server/BuilderMessageHandler.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.server;
+
+import org.jboss.netty.channel.Channel;
+import org.jetbrains.jps.api.CmdlineRemoteProto;
+
+import java.util.UUID;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 4/25/12
+ */
+public interface BuilderMessageHandler {
+ void buildStarted(UUID sessionId);
+
+ void handleBuildMessage(Channel channel, UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage msg);
+
+ void handleFailure(UUID sessionId, CmdlineRemoteProto.Message.Failure failure);
+
+ void sessionTerminated(UUID sessionId);
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/server/CompileServerPathProvider.java b/java/compiler/impl/src/com/intellij/compiler/server/CompileServerPathProvider.java
new file mode 100644
index 0000000..f4f2bb4
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/server/CompileServerPathProvider.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.server;
+
+import com.intellij.openapi.extensions.ExtensionPointName;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * Project-level extension points to dynamically vary classpath of external build process.
+ */
+public interface CompileServerPathProvider {
+ ExtensionPointName<CompileServerPathProvider> EP_NAME = ExtensionPointName.create("com.intellij.compileServer.pathProvider");
+
+ @NotNull List<String> getClassPath();
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/server/CompileServerPlugin.java b/java/compiler/impl/src/com/intellij/compiler/server/CompileServerPlugin.java
new file mode 100644
index 0000000..2040b6c
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/server/CompileServerPlugin.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.server;
+
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.openapi.extensions.PluginAware;
+import com.intellij.openapi.extensions.PluginDescriptor;
+import com.intellij.util.xmlb.annotations.Attribute;
+
+/**
+ * @author nik
+ */
+public class CompileServerPlugin implements PluginAware {
+ public static final ExtensionPointName<CompileServerPlugin> EP_NAME = ExtensionPointName.create("com.intellij.compileServer.plugin");
+
+ private PluginDescriptor myPluginDescriptor;
+ private String myClasspath;
+
+ /**
+ * <p>Specifies semicolon-separated list of paths which should be added to the classpath of the compile server.
+ * The paths are relative to the plugin 'lib' directory.</p>
+ *
+ * <p>In the development mode the name of each file without extension is treated as a module name and the output directory of the module
+ * is added to the classpath. If such file doesn't exists the jar is searched under 'lib' directory of the plugin sources home directory.</p>
+ */
+ @Attribute("classpath")
+ public String getClasspath() {
+ return myClasspath;
+ }
+
+ @SuppressWarnings("UnusedDeclaration")
+ public void setClasspath(String classpath) {
+ myClasspath = classpath;
+ }
+
+ public PluginDescriptor getPluginDescriptor() {
+ return myPluginDescriptor;
+ }
+
+ @Override
+ public final void setPluginDescriptor(PluginDescriptor pluginDescriptor) {
+ myPluginDescriptor = pluginDescriptor;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/server/CustomBuilderMessageHandler.java b/java/compiler/impl/src/com/intellij/compiler/server/CustomBuilderMessageHandler.java
new file mode 100644
index 0000000..74bf679
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/server/CustomBuilderMessageHandler.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.server;
+
+import com.intellij.util.messages.Topic;
+
+import java.util.EventListener;
+
+/**
+ * @author nik
+ */
+public interface CustomBuilderMessageHandler extends EventListener {
+ Topic<CustomBuilderMessageHandler> TOPIC = Topic.create("custom builder message", CustomBuilderMessageHandler.class);
+
+ void messageReceived(String builderId, String messageType, String messageText);
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/server/DefaultMessageHandler.java b/java/compiler/impl/src/com/intellij/compiler/server/DefaultMessageHandler.java
new file mode 100644
index 0000000..add20bd
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/server/DefaultMessageHandler.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.server;
+
+import com.intellij.compiler.make.CachingSearcher;
+import com.intellij.lang.StdLanguages;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.openapi.project.DumbService;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.util.registry.Registry;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.*;
+import com.intellij.psi.search.*;
+import com.intellij.psi.util.PsiUtil;
+import com.intellij.util.cls.ClsUtil;
+import com.intellij.util.concurrency.SequentialTaskExecutor;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.Channels;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.jps.api.CmdlineProtoUtil;
+import org.jetbrains.jps.api.CmdlineRemoteProto;
+
+import java.util.*;
+import java.util.concurrent.Executor;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 4/18/12
+ */
+public abstract class DefaultMessageHandler implements BuilderMessageHandler {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.server.DefaultMessageHandler");
+ private final int MAX_CONSTANT_SEARCHES = Registry.intValue("compiler.max.static.constants.searches");
+ private final Project myProject;
+ private int myConstantSearchesCount = 0;
+ private final CachingSearcher mySearcher;
+ private final SequentialTaskExecutor myTaskExecutor = new SequentialTaskExecutor(new Executor() {
+ @Override
+ public void execute(Runnable command) {
+ ApplicationManager.getApplication().executeOnPooledThread(command);
+ }
+ });
+
+ protected DefaultMessageHandler(Project project) {
+ myProject = project;
+ mySearcher = new CachingSearcher(project);
+ }
+
+ @Override
+ public void buildStarted(UUID sessionId) {
+ }
+
+ @Override
+ public final void handleBuildMessage(final Channel channel, final UUID sessionId, final CmdlineRemoteProto.Message.BuilderMessage msg) {
+ switch (msg.getType()) {
+ case BUILD_EVENT:
+ handleBuildEvent(sessionId, msg.getBuildEvent());
+ break;
+ case COMPILE_MESSAGE:
+ handleCompileMessage(sessionId, msg.getCompileMessage());
+ break;
+ case CONSTANT_SEARCH_TASK:
+ final CmdlineRemoteProto.Message.BuilderMessage.ConstantSearchTask task = msg.getConstantSearchTask();
+ myTaskExecutor.submit(new Runnable() {
+ @Override
+ public void run() {
+ handleConstantSearchTask(channel, sessionId, task);
+ }
+ });
+ break;
+ }
+ }
+
+ protected abstract void handleCompileMessage(UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.CompileMessage message);
+
+ protected abstract void handleBuildEvent(UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.BuildEvent event);
+
+ private void handleConstantSearchTask(Channel channel, UUID sessionId, CmdlineRemoteProto.Message.BuilderMessage.ConstantSearchTask task) {
+ final String ownerClassName = task.getOwnerClassName();
+ final String fieldName = task.getFieldName();
+ final int accessFlags = task.getAccessFlags();
+ final boolean accessChanged = task.getIsAccessChanged();
+ final boolean isRemoved = task.getIsFieldRemoved();
+ final Ref<Boolean> isSuccess = Ref.create(Boolean.TRUE);
+ final Set<String> affectedPaths = Collections.synchronizedSet(new HashSet<String>()); // PsiSearchHelper runs multiple threads
+ try {
+ if (isDumbMode()) {
+ // do not wait until dumb mode finishes
+ isSuccess.set(Boolean.FALSE);
+ LOG.debug("Constant search task: cannot search in dumb mode");
+ }
+ else {
+ ApplicationManager.getApplication().runReadAction(new Runnable() {
+ public void run() {
+ try {
+ String qualifiedName = ownerClassName.replace('$', '.');
+ final PsiClass[] classes = JavaPsiFacade.getInstance(myProject).findClasses(qualifiedName, GlobalSearchScope.allScope(myProject));
+ if (isRemoved) {
+ if (classes.length > 0) {
+ for (PsiClass aClass : classes) {
+ final boolean success = performRemovedConstantSearch(aClass, fieldName, accessFlags, affectedPaths);
+ if (!success) {
+ isSuccess.set(Boolean.FALSE);
+ break;
+ }
+ }
+ }
+ else {
+ isSuccess.set(
+ performRemovedConstantSearch(null, fieldName, accessFlags, affectedPaths)
+ );
+ }
+ }
+ else {
+ if (classes.length > 0) {
+ boolean foundAtLeastOne = false;
+ for (PsiClass aClass : classes) {
+ PsiField changedField = null;
+ for (PsiField psiField : aClass.getFields()) {
+ if (fieldName.equals(psiField.getName())) {
+ changedField = psiField;
+ break;
+ }
+ }
+ if (changedField == null) {
+ continue;
+ }
+ foundAtLeastOne = true;
+ final boolean sucess = performChangedConstantSearch(aClass, changedField, accessFlags, accessChanged, affectedPaths);
+ if (!sucess) {
+ isSuccess.set(Boolean.FALSE);
+ break;
+ }
+ }
+ if (!foundAtLeastOne) {
+ isSuccess.set(Boolean.FALSE);
+ LOG.debug("Constant search task: field " + fieldName + " not found in classes " + qualifiedName);
+ }
+ }
+ else {
+ isSuccess.set(Boolean.FALSE);
+ LOG.debug("Constant search task: class " + qualifiedName + " not found");
+ }
+ }
+ }
+ catch (Throwable e) {
+ isSuccess.set(Boolean.FALSE);
+ LOG.debug("Constant search task: failed with message " + e.getMessage());
+ }
+ }
+ });
+ }
+ }
+ finally {
+ final CmdlineRemoteProto.Message.ControllerMessage.ConstantSearchResult.Builder builder = CmdlineRemoteProto.Message.ControllerMessage.ConstantSearchResult.newBuilder();
+ builder.setOwnerClassName(ownerClassName);
+ builder.setFieldName(fieldName);
+ if (isSuccess.get()) {
+ builder.setIsSuccess(true);
+ builder.addAllPath(affectedPaths);
+ LOG.debug("Constant search task: " + affectedPaths.size() + " affected files found");
+ }
+ else {
+ builder.setIsSuccess(false);
+ LOG.debug("Constant search task: unsuccessful");
+ }
+ Channels.write(channel, CmdlineProtoUtil.toMessage(sessionId, CmdlineRemoteProto.Message.ControllerMessage.newBuilder().setType(
+ CmdlineRemoteProto.Message.ControllerMessage.Type.CONSTANT_SEARCH_RESULT).setConstantSearchResult(builder.build()).build()
+ ));
+ }
+ }
+
+ private boolean isDumbMode() {
+ final DumbService dumbService = DumbService.getInstance(myProject);
+ boolean isDumb = dumbService.isDumb();
+ if (isDumb) {
+ // wait some time
+ for (int idx = 0; idx < 5; idx++) {
+ try {
+ Thread.sleep(10L);
+ }
+ catch (InterruptedException ignored) {
+ }
+ isDumb = dumbService.isDumb();
+ if (!isDumb) {
+ break;
+ }
+ }
+ }
+ return isDumb;
+ }
+
+ private boolean performChangedConstantSearch(PsiClass aClass, PsiField field, int accessFlags, boolean isAccessibilityChange, Set<String> affectedPaths) {
+ if (!isAccessibilityChange && ClsUtil.isPrivate(accessFlags)) {
+ return true; // optimization: don't need to search, cause may be used only in this class
+ }
+ final Set<PsiElement> usages = new HashSet<PsiElement>();
+ try {
+ addUsages(field, usages, isAccessibilityChange);
+ for (final PsiElement usage : usages) {
+ affect(usage, affectedPaths);
+ //final PsiClass ownerClass = getOwnerClass(usage);
+ //if (ownerClass != null && !ownerClass.equals(aClass)) {
+ // affect(ownerClass, affectedPaths);
+ //}
+ //else if (ownerClass == null) {
+ // affect(usage, affectedPaths);
+ //}
+ }
+ }
+ catch (PsiInvalidElementAccessException e) {
+ LOG.debug("Constant search task: PIEAE thrown while searching of usages of changed constant");
+ return false;
+ }
+ catch (ProcessCanceledException e) {
+ LOG.debug("Constant search task: PCE thrown while searching of usages of changed constant");
+ return false;
+ }
+ return true;
+ }
+
+
+ private boolean performRemovedConstantSearch(@Nullable final PsiClass aClass, String fieldName, int accessFlags, final Set<String> affectedPaths) {
+ SearchScope searchScope = GlobalSearchScope.projectScope(myProject);
+ if (aClass != null && ClsUtil.isPackageLocal(accessFlags)) {
+ final PsiFile containingFile = aClass.getContainingFile();
+ if (containingFile instanceof PsiJavaFile) {
+ final String packageName = ((PsiJavaFile)containingFile).getPackageName();
+ final PsiPackage aPackage = JavaPsiFacade.getInstance(myProject).findPackage(packageName);
+ if (aPackage != null) {
+ searchScope = PackageScope.packageScope(aPackage, false);
+ searchScope = searchScope.intersectWith(aClass.getUseScope());
+ }
+ }
+ }
+ final PsiSearchHelper psiSearchHelper = PsiSearchHelper.SERVICE.getInstance(myProject);
+
+ final Ref<Boolean> result = new Ref<Boolean>(Boolean.TRUE);
+ processIdentifiers(psiSearchHelper, new PsiElementProcessor<PsiIdentifier>() {
+ @Override
+ public boolean execute(@NotNull PsiIdentifier identifier) {
+ try {
+ final PsiElement parent = identifier.getParent();
+ if (parent instanceof PsiReferenceExpression) {
+ final PsiClass ownerClass = getOwnerClass(parent);
+ if (ownerClass != null /*&& !ownerClass.equals(aClass)*/) {
+ if (ownerClass.getQualifiedName() != null) {
+ affect(ownerClass, affectedPaths);
+ }
+ }
+ }
+ return true;
+ }
+ catch (PsiInvalidElementAccessException e) {
+ result.set(Boolean.FALSE);
+ LOG.debug("Constant search task: PIEAE thrown while searching of usages of removed constant");
+ return false;
+ }
+ }
+ }, fieldName, searchScope, UsageSearchContext.IN_CODE);
+
+ return result.get();
+ }
+
+ private static void affect(PsiElement ownerClass, Set<String> affectedPaths) {
+ final PsiFile containingPsi = ownerClass.getContainingFile();
+ if (containingPsi != null) {
+ final VirtualFile vFile = containingPsi.getOriginalFile().getVirtualFile();
+ if (vFile != null) {
+ affectedPaths.add(vFile.getPath());
+ }
+ }
+ }
+
+ private static boolean processIdentifiers(PsiSearchHelper helper, @NotNull final PsiElementProcessor<PsiIdentifier> processor, @NotNull final String identifier, @NotNull SearchScope searchScope, short searchContext) {
+ TextOccurenceProcessor processor1 = new TextOccurenceProcessor() {
+ public boolean execute(PsiElement element, int offsetInElement) {
+ return !(element instanceof PsiIdentifier) || processor.execute((PsiIdentifier)element);
+ }
+ };
+ return helper.processElementsWithWord(processor1, searchScope, identifier, searchContext, true);
+ }
+
+ private void addUsages(PsiField psiField, Collection<PsiElement> usages, final boolean ignoreAccessScope) throws ProcessCanceledException {
+ final int count = myConstantSearchesCount;
+ if (count > MAX_CONSTANT_SEARCHES) {
+ throw new ProcessCanceledException();
+ }
+ Collection<PsiReference> references = mySearcher.findReferences(psiField, ignoreAccessScope)/*doFindReferences(searchHelper, psiField)*/;
+
+ myConstantSearchesCount++;
+
+ for (final PsiReference ref : references) {
+ if (!(ref instanceof PsiReferenceExpression)) {
+ continue;
+ }
+ PsiElement e = ref.getElement();
+ usages.add(e);
+ PsiField ownerField = getOwnerField(e);
+ if (ownerField != null) {
+ if (ownerField.hasModifierProperty(PsiModifier.FINAL)) {
+ PsiExpression initializer = ownerField.getInitializer();
+ if (initializer != null && PsiUtil.isConstantExpression(initializer)) {
+ // if the field depends on the compile-time-constant expression and is itself final
+ addUsages(ownerField, usages, ignoreAccessScope);
+ }
+ }
+ }
+ }
+ }
+
+ @Nullable
+ private static PsiClass getOwnerClass(PsiElement element) {
+ while (!(element instanceof PsiFile)) {
+ if (element instanceof PsiClass && element.getParent() instanceof PsiJavaFile) { // top-level class
+ final PsiClass psiClass = (PsiClass)element;
+ if (JspPsiUtil.isInJspFile(psiClass)) {
+ return null;
+ }
+ final PsiFile containingFile = psiClass.getContainingFile();
+ if (containingFile == null) {
+ return null;
+ }
+ return StdLanguages.JAVA.equals(containingFile.getLanguage())? psiClass : null;
+ }
+ element = element.getParent();
+ }
+ return null;
+ }
+
+ @Nullable
+ private static PsiField getOwnerField(PsiElement element) {
+ while (!(element instanceof PsiFile)) {
+ if (element instanceof PsiClass) {
+ break;
+ }
+ if (element instanceof PsiField) { // top-level class
+ return (PsiField)element;
+ }
+ element = element.getParent();
+ }
+ return null;
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/compiler/server/impl/CompileServerClasspathManager.java b/java/compiler/impl/src/com/intellij/compiler/server/impl/CompileServerClasspathManager.java
new file mode 100644
index 0000000..f95d151
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/compiler/server/impl/CompileServerClasspathManager.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.compiler.server.impl;
+
+import com.intellij.compiler.server.CompileServerPathProvider;
+import com.intellij.compiler.server.CompileServerPlugin;
+import com.intellij.ide.plugins.IdeaPluginDescriptor;
+import com.intellij.ide.plugins.PluginManager;
+import com.intellij.openapi.application.PathManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.extensions.PluginId;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.util.PathUtil;
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public class CompileServerClasspathManager {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.server.impl.CompileServerClasspathManager");
+
+ private List<String> myCompileServerPluginsClasspath;
+
+ public List<String> getCompileServerPluginsClasspath(Project project) {
+ List<String> staticClasspath = getStaticClasspath();
+ List<String> dynamicClasspath = getDynamicClasspath(project);
+
+ if (dynamicClasspath.isEmpty()) {
+ return staticClasspath;
+ }
+ else {
+ dynamicClasspath.addAll(staticClasspath);
+ return dynamicClasspath;
+ }
+ }
+
+ private List<String> getStaticClasspath() {
+ if (myCompileServerPluginsClasspath == null) {
+ myCompileServerPluginsClasspath = computeCompileServerPluginsClasspath();
+ }
+ return myCompileServerPluginsClasspath;
+ }
+
+ private static List<String> computeCompileServerPluginsClasspath() {
+ final List<String> classpath = ContainerUtil.newArrayList();
+ for (CompileServerPlugin serverPlugin : CompileServerPlugin.EP_NAME.getExtensions()) {
+ final PluginId pluginId = serverPlugin.getPluginDescriptor().getPluginId();
+ final IdeaPluginDescriptor plugin = PluginManager.getPlugin(pluginId);
+ LOG.assertTrue(plugin != null, pluginId);
+ final File baseFile = plugin.getPath();
+ if (baseFile.isFile()) {
+ classpath.add(baseFile.getPath());
+ }
+ else if (baseFile.isDirectory()) {
+ for (String relativePath : StringUtil.split(serverPlugin.getClasspath(), ";")) {
+ final File jarFile = new File(new File(baseFile, "lib"), relativePath);
+ File classesDir = new File(baseFile, "classes");
+ if (jarFile.exists()) {
+ classpath.add(jarFile.getPath());
+ }
+ else if (classesDir.isDirectory()) {
+ //'plugin run configuration': all module output are copied to 'classes' folder
+ classpath.add(classesDir.getPath());
+ }
+ else {
+ //development mode: add directory out/classes/production/<jar-name> to classpath, assuming that jar-name is equal to module name
+ final String moduleName = FileUtil.getNameWithoutExtension(PathUtil.getFileName(relativePath));
+ final File dir = new File(baseFile.getParentFile(), moduleName);
+ if (dir.exists()) {
+ classpath.add(dir.getPath());
+ }
+ else {
+ //looks like <jar-name> refers to a library, try to find it under <plugin-dir>/lib
+ File pluginDir = getPluginDir(plugin);
+ if (pluginDir != null) {
+ File libraryFile = new File(pluginDir, "lib" + File.separator + PathUtil.getFileName(relativePath));
+ if (libraryFile.exists()) {
+ classpath.add(libraryFile.getPath());
+ }
+ else {
+ LOG.error("Cannot add plugin '" + plugin.getName() + "' to external compiler classpath: " +
+ "library " + libraryFile.getAbsolutePath() + " not found");
+ }
+ }
+ else {
+ LOG.error("Cannot add plugin '" + plugin.getName() + "' to external compiler classpath: home directory of plugin not found");
+ }
+ }
+ }
+ }
+ }
+ }
+ return classpath;
+ }
+
+ @Nullable
+ private static File getPluginDir(IdeaPluginDescriptor plugin) {
+ String pluginDirName = StringUtil.getShortName(plugin.getPluginId().getIdString());
+ String[] roots = {PathManager.getHomePath(), PathManager.getHomePath() + File.separator + "community"};
+ for (String root : roots) {
+ File pluginDir = new File(root, "plugins" + File.separator + pluginDirName);
+ if (pluginDir.isDirectory()) {
+ return pluginDir;
+ }
+ }
+ return null;
+ }
+
+ private static List<String> getDynamicClasspath(Project project) {
+ List<String> classpath = ContainerUtil.newArrayList();
+ for (CompileServerPathProvider provider : project.getExtensions(CompileServerPathProvider.EP_NAME)) {
+ classpath.addAll(provider.getClassPath());
+ }
+ return classpath;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/openapi/compiler/ex/CompileContextEx.java b/java/compiler/impl/src/com/intellij/openapi/compiler/ex/CompileContextEx.java
new file mode 100644
index 0000000..8f8f4e3
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/openapi/compiler/ex/CompileContextEx.java
@@ -0,0 +1,59 @@
+/*
+ * 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 com.intellij.openapi.compiler.ex;
+
+import com.intellij.compiler.make.DependencyCache;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.CompileScope;
+import com.intellij.openapi.compiler.CompilerMessage;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+import java.util.Set;
+
+public interface CompileContextEx extends CompileContext {
+ DependencyCache getDependencyCache();
+
+ @Nullable
+ VirtualFile getSourceFileByOutputFile(VirtualFile outputFile);
+
+ void addMessage(CompilerMessage message);
+
+ @NotNull
+ Set<VirtualFile> getTestOutputDirectories();
+
+ /**
+ * the same as FileIndex.isInTestSourceContent(), but takes into account generated output dirs
+ */
+ boolean isInTestSourceContent(@NotNull VirtualFile fileOrDir);
+
+ boolean isInSourceContent(@NotNull VirtualFile fileOrDir);
+
+ void addScope(CompileScope additionalScope);
+
+ long getStartCompilationStamp();
+
+ void recalculateOutputDirs();
+
+ void markGenerated(Collection<VirtualFile> files);
+
+ boolean isGenerated(VirtualFile file);
+
+ void assignModule(@NotNull VirtualFile root, @NotNull Module module, boolean isTestSource, @Nullable com.intellij.openapi.compiler.Compiler compiler);
+}
diff --git a/java/compiler/impl/src/com/intellij/openapi/compiler/generic/BuildTarget.java b/java/compiler/impl/src/com/intellij/openapi/compiler/generic/BuildTarget.java
new file mode 100644
index 0000000..27f16ad
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/openapi/compiler/generic/BuildTarget.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.openapi.compiler.generic;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author nik
+ */
+public abstract class BuildTarget {
+ public static final BuildTarget DEFAULT = new BuildTarget() {
+ @NotNull
+ @Override
+ public String getId() {
+ return "<default>";
+ }
+ };
+
+ @NotNull
+ public abstract String getId();
+
+ @Override
+ public String toString() {
+ return "Build Target: " + getId();
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/openapi/compiler/generic/CompileItem.java b/java/compiler/impl/src/com/intellij/openapi/compiler/generic/CompileItem.java
new file mode 100644
index 0000000..3bd9972
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/openapi/compiler/generic/CompileItem.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.openapi.compiler.generic;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author nik
+ */
+public abstract class CompileItem<Key, SourceState, OutputState> {
+ @NotNull
+ public abstract Key getKey();
+
+ public abstract boolean isSourceUpToDate(@NotNull SourceState state);
+
+ @NotNull
+ public abstract SourceState computeSourceState();
+
+
+ public abstract boolean isOutputUpToDate(@NotNull OutputState state);
+
+ @NotNull
+ public abstract OutputState computeOutputState();
+
+ public boolean isExcluded() {
+ return false;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/openapi/compiler/generic/DummyPersistentState.java b/java/compiler/impl/src/com/intellij/openapi/compiler/generic/DummyPersistentState.java
new file mode 100644
index 0000000..b3d66ce
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/openapi/compiler/generic/DummyPersistentState.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.openapi.compiler.generic;
+
+import com.intellij.util.io.DataExternalizer;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+ * @author nik
+ */
+public class DummyPersistentState {
+ public static final DummyPersistentState INSTANCE = new DummyPersistentState();
+ public static final DataExternalizer<DummyPersistentState> EXTERNALIZER = new DummyPersistentStateExternalizer();
+
+ private DummyPersistentState() {
+ }
+
+ private static class DummyPersistentStateExternalizer implements DataExternalizer<DummyPersistentState> {
+ @Override
+ public void save(DataOutput out, DummyPersistentState value) throws IOException {
+ }
+
+ @Override
+ public DummyPersistentState read(DataInput in) throws IOException {
+ return INSTANCE;
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/openapi/compiler/generic/GenericCompiler.java b/java/compiler/impl/src/com/intellij/openapi/compiler/generic/GenericCompiler.java
new file mode 100644
index 0000000..b09c8e5
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/openapi/compiler/generic/GenericCompiler.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.openapi.compiler.generic;
+
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.CompileScope;
+import com.intellij.openapi.compiler.Compiler;
+import com.intellij.util.io.DataExternalizer;
+import com.intellij.util.io.EnumeratorStringDescriptor;
+import com.intellij.util.io.KeyDescriptor;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author nik
+ */
+public abstract class GenericCompiler<Key, SourceState, OutputState> implements Compiler {
+ protected static final KeyDescriptor<String> STRING_KEY_DESCRIPTOR = new EnumeratorStringDescriptor();
+ private final String myId;
+ private final int myVersion;
+ private final CompileOrderPlace myOrderPlace;
+
+ protected GenericCompiler(@NotNull String id, int version, @NotNull CompileOrderPlace orderPlace) {
+ myId = id;
+ myVersion = version;
+ myOrderPlace = orderPlace;
+ }
+
+ @NotNull
+ public abstract KeyDescriptor<Key> getItemKeyDescriptor();
+ @NotNull
+ public abstract DataExternalizer<SourceState> getSourceStateExternalizer();
+ @NotNull
+ public abstract DataExternalizer<OutputState> getOutputStateExternalizer();
+
+ @NotNull
+ public abstract GenericCompilerInstance<?, ? extends CompileItem<Key, SourceState, OutputState>, Key, SourceState, OutputState> createInstance(@NotNull CompileContext context);
+
+ public final String getId() {
+ return myId;
+ }
+
+ public final int getVersion() {
+ return myVersion;
+ }
+
+ @Override
+ public boolean validateConfiguration(CompileScope scope) {
+ return true;
+ }
+
+ public CompileOrderPlace getOrderPlace() {
+ return myOrderPlace;
+ }
+
+ public static enum CompileOrderPlace {
+ CLASS_INSTRUMENTING, CLASS_POST_PROCESSING, PACKAGING, VALIDATING
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/openapi/compiler/generic/GenericCompilerCacheState.java b/java/compiler/impl/src/com/intellij/openapi/compiler/generic/GenericCompilerCacheState.java
new file mode 100644
index 0000000..bfc4468
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/openapi/compiler/generic/GenericCompilerCacheState.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.openapi.compiler.generic;
+
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author nik
+ */
+public class GenericCompilerCacheState<Key, SourceState, OutputState> {
+ private final Key myKey;
+ private final SourceState mySourceState;
+ private final OutputState myOutputState;
+
+ public GenericCompilerCacheState(@NotNull Key key, @NotNull SourceState sourceState, @NotNull OutputState outputState) {
+ myKey = key;
+ mySourceState = sourceState;
+ myOutputState = outputState;
+ }
+
+ @NotNull
+ public Key getKey() {
+ return myKey;
+ }
+
+ @NotNull
+ public SourceState getSourceState() {
+ return mySourceState;
+ }
+
+ @NotNull
+ public OutputState getOutputState() {
+ return myOutputState;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/openapi/compiler/generic/GenericCompilerInstance.java b/java/compiler/impl/src/com/intellij/openapi/compiler/generic/GenericCompilerInstance.java
new file mode 100644
index 0000000..6bb9274
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/openapi/compiler/generic/GenericCompilerInstance.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.openapi.compiler.generic;
+
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.project.Project;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public abstract class GenericCompilerInstance<T extends BuildTarget, Item extends CompileItem<Key, SourceState, OutputState>, Key, SourceState, OutputState> {
+ protected final CompileContext myContext;
+
+ protected GenericCompilerInstance(CompileContext context) {
+ myContext = context;
+ }
+
+ protected Project getProject() {
+ return myContext.getProject();
+ }
+
+ @NotNull
+ public abstract List<T> getAllTargets();
+
+ @NotNull
+ public abstract List<T> getSelectedTargets();
+
+ public abstract void processObsoleteTarget(@NotNull String targetId, @NotNull List<GenericCompilerCacheState<Key, SourceState, OutputState>> obsoleteItems);
+
+
+ @NotNull
+ public abstract List<Item> getItems(@NotNull T target);
+
+ public abstract void processItems(@NotNull T target, @NotNull List<GenericCompilerProcessingItem<Item, SourceState, OutputState>> changedItems, @NotNull List<GenericCompilerCacheState<Key, SourceState, OutputState>> obsoleteItems,
+ @NotNull OutputConsumer<Item> consumer);
+
+ public interface OutputConsumer<Item extends CompileItem<?,?,?>> {
+ void addFileToRefresh(@NotNull File file);
+
+ void addDirectoryToRefresh(@NotNull File dir);
+
+ void addProcessedItem(@NotNull Item sourceItem);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/openapi/compiler/generic/GenericCompilerProcessingItem.java b/java/compiler/impl/src/com/intellij/openapi/compiler/generic/GenericCompilerProcessingItem.java
new file mode 100644
index 0000000..e62a5b3
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/openapi/compiler/generic/GenericCompilerProcessingItem.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.openapi.compiler.generic;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author nik
+ */
+public class GenericCompilerProcessingItem<Item extends CompileItem<?, SourceState, OutputState>, SourceState, OutputState> {
+ private final Item myItem;
+ private final SourceState myCachedSourceState;
+ private final OutputState myCachedOutputState;
+
+ public GenericCompilerProcessingItem(@NotNull Item item, @Nullable SourceState cachedSourceState, @Nullable OutputState cachedOutputState) {
+ myItem = item;
+ myCachedSourceState = cachedSourceState;
+ myCachedOutputState = cachedOutputState;
+ }
+
+ @NotNull
+ public Item getItem() {
+ return myItem;
+ }
+
+ @Nullable
+ public SourceState getCachedSourceState() {
+ return myCachedSourceState;
+ }
+
+ @Nullable
+ public OutputState getCachedOutputState() {
+ return myCachedOutputState;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/openapi/compiler/generic/VirtualFileCompileItem.java b/java/compiler/impl/src/com/intellij/openapi/compiler/generic/VirtualFileCompileItem.java
new file mode 100644
index 0000000..b1e5394
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/openapi/compiler/generic/VirtualFileCompileItem.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.openapi.compiler.generic;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author nik
+ */
+public abstract class VirtualFileCompileItem<OutputState> extends CompileItem<String, VirtualFilePersistentState, OutputState> {
+ protected final VirtualFile myFile;
+
+ public VirtualFileCompileItem(@NotNull VirtualFile file) {
+ myFile = file;
+ }
+
+ @NotNull
+ public VirtualFile getFile() {
+ return myFile;
+ }
+
+ @NotNull
+ @Override
+ public VirtualFilePersistentState computeSourceState() {
+ return new VirtualFilePersistentState(myFile.getTimeStamp());
+ }
+
+ @Override
+ public boolean isSourceUpToDate(@NotNull VirtualFilePersistentState state) {
+ return myFile.getTimeStamp() == state.getSourceTimestamp();
+ }
+
+ @NotNull
+ @Override
+ public String getKey() {
+ return myFile.getUrl();
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/openapi/compiler/generic/VirtualFilePersistentState.java b/java/compiler/impl/src/com/intellij/openapi/compiler/generic/VirtualFilePersistentState.java
new file mode 100644
index 0000000..e9ea970
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/openapi/compiler/generic/VirtualFilePersistentState.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.openapi.compiler.generic;
+
+import com.intellij.util.io.DataExternalizer;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+* @author nik
+*/
+public class VirtualFilePersistentState {
+ public static DataExternalizer<VirtualFilePersistentState> EXTERNALIZER = new VirtualFileStateExternalizer();
+ private final long mySourceTimestamp;
+
+ public VirtualFilePersistentState(long sourceTimestamp) {
+ mySourceTimestamp = sourceTimestamp;
+ }
+
+ public final long getSourceTimestamp() {
+ return mySourceTimestamp;
+ }
+
+ private static class VirtualFileStateExternalizer implements DataExternalizer<VirtualFilePersistentState> {
+ @Override
+ public void save(DataOutput out, VirtualFilePersistentState value) throws IOException {
+ out.writeLong(value.getSourceTimestamp());
+ }
+
+ @Override
+ public VirtualFilePersistentState read(DataInput in) throws IOException {
+ return new VirtualFilePersistentState(in.readLong());
+ }
+
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/openapi/compiler/generic/VirtualFileSetState.java b/java/compiler/impl/src/com/intellij/openapi/compiler/generic/VirtualFileSetState.java
new file mode 100644
index 0000000..a22e80d
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/openapi/compiler/generic/VirtualFileSetState.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.openapi.compiler.generic;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.io.DataExternalizer;
+import com.intellij.util.io.IOUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author nik
+ */
+public class VirtualFileSetState {
+ public static final DataExternalizer<VirtualFileSetState> EXTERNALIZER = new VirtualFileWithDependenciesExternalizer();
+ private Map<String, Long> myTimestamps = new HashMap<String, Long>();
+
+ public VirtualFileSetState() {
+ }
+
+ public VirtualFileSetState(Collection<? extends VirtualFile> files) {
+ for (VirtualFile file : files) {
+ addFile(file);
+ }
+ }
+
+ public void addFile(@NotNull VirtualFile file) {
+ myTimestamps.put(file.getUrl(), file.getTimeStamp());
+ }
+
+ public boolean isUpToDate(Set<? extends VirtualFile> files) {
+ if (files.size() != myTimestamps.size()) {
+ return false;
+ }
+
+ for (VirtualFile file : files) {
+ final Long timestamp = myTimestamps.get(file.getUrl());
+ if (timestamp == null || timestamp != file.getTimeStamp()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ private static class VirtualFileWithDependenciesExternalizer implements DataExternalizer<VirtualFileSetState> {
+ private byte[] myBuffer = IOUtil.allocReadWriteUTFBuffer();
+
+ @Override
+ public void save(DataOutput out, VirtualFileSetState value) throws IOException {
+ final Map<String, Long> dependencies = value.myTimestamps;
+ out.writeInt(dependencies.size());
+ for (Map.Entry<String, Long> entry : dependencies.entrySet()) {
+ IOUtil.writeUTFFast(myBuffer, out, entry.getKey());
+ out.writeLong(entry.getValue());
+ }
+ }
+
+ @Override
+ public VirtualFileSetState read(DataInput in) throws IOException {
+ final VirtualFileSetState state = new VirtualFileSetState();
+ int size = in.readInt();
+ while (size-- > 0) {
+ final String url = IOUtil.readUTFFast(myBuffer, in);
+ final long timestamp = in.readLong();
+ state.myTimestamps.put(url, timestamp);
+ }
+ return state;
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/openapi/compiler/generic/VirtualFileWithDependenciesState.java b/java/compiler/impl/src/com/intellij/openapi/compiler/generic/VirtualFileWithDependenciesState.java
new file mode 100644
index 0000000..9f4a815
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/openapi/compiler/generic/VirtualFileWithDependenciesState.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.openapi.compiler.generic;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.intellij.util.io.DataExternalizer;
+import com.intellij.util.io.IOUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author nik
+ */
+public class VirtualFileWithDependenciesState {
+ public static final DataExternalizer<VirtualFileWithDependenciesState> EXTERNALIZER = new VirtualFileWithDependenciesExternalizer();
+ private long mySourceTimestamp;
+ private Map<String, Long> myDependencies = new HashMap<String, Long>();
+
+ public VirtualFileWithDependenciesState(long sourceTimestamp) {
+ mySourceTimestamp = sourceTimestamp;
+ }
+
+ public void addDependency(@NotNull VirtualFile file) {
+ myDependencies.put(file.getUrl(), file.getTimeStamp());
+ }
+
+ public boolean isUpToDate(@NotNull VirtualFile sourceFile) {
+ if (sourceFile.getTimeStamp() != mySourceTimestamp) {
+ return false;
+ }
+
+ VirtualFileManager manager = VirtualFileManager.getInstance();
+ for (Map.Entry<String, Long> entry : myDependencies.entrySet()) {
+ final VirtualFile file = manager.findFileByUrl(entry.getKey());
+ if (file == null || file.getTimeStamp() != entry.getValue()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ private static class VirtualFileWithDependenciesExternalizer implements DataExternalizer<VirtualFileWithDependenciesState> {
+ private byte[] myBuffer = IOUtil.allocReadWriteUTFBuffer();
+
+ @Override
+ public void save(DataOutput out, VirtualFileWithDependenciesState value) throws IOException {
+ out.writeLong(value.mySourceTimestamp);
+ final Map<String, Long> dependencies = value.myDependencies;
+ out.writeInt(dependencies.size());
+ for (Map.Entry<String, Long> entry : dependencies.entrySet()) {
+ IOUtil.writeUTFFast(myBuffer, out, entry.getKey());
+ out.writeLong(entry.getValue());
+ }
+ }
+
+ @Override
+ public VirtualFileWithDependenciesState read(DataInput in) throws IOException {
+ final VirtualFileWithDependenciesState state = new VirtualFileWithDependenciesState(in.readLong());
+ int size = in.readInt();
+ while (size-- > 0) {
+ final String url = IOUtil.readUTFFast(myBuffer, in);
+ final long timestamp = in.readLong();
+ state.myDependencies.put(url, timestamp);
+ }
+ return state;
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/openapi/compiler/util/InspectionValidatorUtil.java b/java/compiler/impl/src/com/intellij/openapi/compiler/util/InspectionValidatorUtil.java
new file mode 100644
index 0000000..b41b5b0
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/openapi/compiler/util/InspectionValidatorUtil.java
@@ -0,0 +1,69 @@
+/*
+ * 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 com.intellij.openapi.compiler.util;
+
+import com.intellij.compiler.impl.FileSetCompileScope;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.ex.CompileContextEx;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.roots.ProjectFileIndex;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiFile;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.descriptors.ConfigFile;
+import gnu.trove.THashSet;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+
+/**
+ * @author peter
+ */
+public class InspectionValidatorUtil {
+ private InspectionValidatorUtil() {
+ }
+
+ public static void addDescriptor(@NotNull final Collection<VirtualFile> result, @Nullable final ConfigFile configFile) {
+ if (configFile != null) {
+ ContainerUtil.addIfNotNull(configFile.getVirtualFile(), result);
+ }
+ }
+
+ public static void addFile(@NotNull final Collection<VirtualFile> result, @Nullable final PsiFile psiFile) {
+ if (psiFile != null) {
+ ContainerUtil.addIfNotNull(psiFile.getVirtualFile(), result);
+ }
+ }
+
+
+ public static Collection<VirtualFile> expandCompileScopeIfNeeded(final Collection<VirtualFile> result, final CompileContext context) {
+ final ProjectFileIndex index = ProjectRootManager.getInstance(context.getProject()).getFileIndex();
+ final THashSet<VirtualFile> set = new THashSet<VirtualFile>();
+ final THashSet<Module> modules = new THashSet<Module>();
+ for (VirtualFile file : result) {
+ if (index.getSourceRootForFile(file) == null) {
+ set.add(file);
+ ContainerUtil.addIfNotNull(index.getModuleForFile(file), modules);
+ }
+ }
+ if (!set.isEmpty()) {
+ ((CompileContextEx)context).addScope(new FileSetCompileScope(set, modules.toArray(new Module[modules.size()])));
+ }
+ return result;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/openapi/compiler/util/InspectionValidatorWrapper.java b/java/compiler/impl/src/com/intellij/openapi/compiler/util/InspectionValidatorWrapper.java
new file mode 100644
index 0000000..1b84ca3
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/openapi/compiler/util/InspectionValidatorWrapper.java
@@ -0,0 +1,340 @@
+/*
+ * Copyright (c) 2000-2007 JetBrains s.r.o. All Rights Reserved.
+ */
+package com.intellij.openapi.compiler.util;
+
+import com.intellij.codeHighlighting.HighlightDisplayLevel;
+import com.intellij.codeInsight.daemon.HighlightDisplayKey;
+import com.intellij.codeInsight.daemon.impl.AnnotationHolderImpl;
+import com.intellij.codeInsight.daemon.impl.HighlightInfo;
+import com.intellij.codeInspection.*;
+import com.intellij.compiler.options.ValidationConfiguration;
+import com.intellij.lang.ExternalLanguageAnnotators;
+import com.intellij.lang.StdLanguages;
+import com.intellij.lang.annotation.Annotation;
+import com.intellij.lang.annotation.AnnotationSession;
+import com.intellij.lang.annotation.ExternalAnnotator;
+import com.intellij.lang.annotation.HighlightSeverity;
+import com.intellij.openapi.application.AccessToken;
+import com.intellij.openapi.application.ReadAction;
+import com.intellij.openapi.application.ReadActionProcessor;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.compiler.*;
+import com.intellij.openapi.compiler.options.ExcludedEntriesConfiguration;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
+import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiManager;
+import com.intellij.psi.xml.XmlFile;
+import com.intellij.util.Processor;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.hash.LinkedHashMap;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.DataInput;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author peter
+ */
+public class InspectionValidatorWrapper implements Validator {
+ private final InspectionValidator myValidator;
+ private final PsiManager myPsiManager;
+ private final CompilerManager myCompilerManager;
+ private final InspectionManager myInspectionManager;
+ private final InspectionProjectProfileManager myProfileManager;
+ private final PsiDocumentManager myPsiDocumentManager;
+ private static final ThreadLocal<Boolean> ourCompilationThreads = new ThreadLocal<Boolean>() {
+ protected Boolean initialValue() {
+ return Boolean.FALSE;
+ }
+ };
+
+ public InspectionValidatorWrapper(final CompilerManager compilerManager, final InspectionManager inspectionManager,
+ final InspectionProjectProfileManager profileManager, final PsiDocumentManager psiDocumentManager,
+ final PsiManager psiManager, final InspectionValidator validator) {
+ myCompilerManager = compilerManager;
+ myInspectionManager = inspectionManager;
+ myProfileManager = profileManager;
+ myPsiDocumentManager = psiDocumentManager;
+ myPsiManager = psiManager;
+ myValidator = validator;
+ }
+
+ public static boolean isCompilationThread() {
+ return ourCompilationThreads.get().booleanValue();
+ }
+
+ private class MyValidatorProcessingItem implements ProcessingItem {
+ private final VirtualFile myVirtualFile;
+ private final PsiFile myPsiFile;
+ private PsiElementsValidityState myValidityState;
+
+ public MyValidatorProcessingItem(@NotNull final PsiFile psiFile) {
+ myPsiFile = psiFile;
+ myVirtualFile = psiFile.getVirtualFile();
+ }
+
+ @NotNull
+ public VirtualFile getFile() {
+ return myVirtualFile;
+ }
+
+ @Nullable
+ public ValidityState getValidityState() {
+ if (myValidityState == null) {
+ myValidityState = computeValidityState();
+ }
+ return myValidityState;
+ }
+
+ private PsiElementsValidityState computeValidityState() {
+ final PsiElementsValidityState state = new PsiElementsValidityState();
+ for (PsiElement psiElement : myValidator.getDependencies(myPsiFile)) {
+ state.addDependency(psiElement);
+ }
+ return state;
+ }
+
+ public PsiFile getPsiFile() {
+ return myPsiFile;
+ }
+ }
+
+ @NotNull
+ public ProcessingItem[] getProcessingItems(final CompileContext context) {
+ final Project project = context.getProject();
+ if (project.isDefault() || !ValidationConfiguration.shouldValidate(this, context)) {
+ return ProcessingItem.EMPTY_ARRAY;
+ }
+ final ExcludedEntriesConfiguration excludedEntriesConfiguration = ValidationConfiguration.getExcludedEntriesConfiguration(project);
+ final List<ProcessingItem> items = new ReadAction<List<ProcessingItem>>() {
+ protected void run(final Result<List<ProcessingItem>> result) {
+ final CompileScope compileScope = context.getCompileScope();
+ if (!myValidator.isAvailableOnScope(compileScope)) return;
+
+ final ArrayList<ProcessingItem> items = new ArrayList<ProcessingItem>();
+
+ final Processor<VirtualFile> processor = new ReadActionProcessor<VirtualFile>() {
+ @Override
+ public boolean processInReadAction(VirtualFile file) {
+ if (!file.isValid()) {
+ return true;
+ }
+
+ if (myCompilerManager.isExcludedFromCompilation(file) ||
+ excludedEntriesConfiguration.isExcluded(file)) {
+ return true;
+ }
+
+ final Module module = context.getModuleByFile(file);
+ if (module != null) {
+ final PsiFile psiFile = myPsiManager.findFile(file);
+ if (psiFile != null) {
+ items.add(new MyValidatorProcessingItem(psiFile));
+ }
+ }
+ return true;
+ }
+ };
+ ContainerUtil.process(myValidator.getFilesToProcess(myPsiManager.getProject(), context), processor);
+
+ result.setResult(items);
+ }
+ }.execute().getResultObject();
+ if (items == null) return ProcessingItem.EMPTY_ARRAY;
+
+ return items.toArray(new ProcessingItem[items.size()]);
+ }
+
+ public ProcessingItem[] process(final CompileContext context, final ProcessingItem[] items) {
+ context.getProgressIndicator().setText(myValidator.getProgressIndicatorText());
+
+ final List<ProcessingItem> processedItems = new ArrayList<ProcessingItem>();
+ final List<LocalInspectionTool> inspections = new ArrayList<LocalInspectionTool>();
+ for (final Class aClass : myValidator.getInspectionToolClasses(context)) {
+ try {
+ inspections.add((LocalInspectionTool)aClass.newInstance());
+ }
+ catch (RuntimeException e) {
+ throw e;
+ }
+ catch (Exception e) {
+ throw new Error(e);
+ }
+ }
+ for (int i = 0; i < items.length; i++) {
+ final MyValidatorProcessingItem item = (MyValidatorProcessingItem)items[i];
+ context.getProgressIndicator().checkCanceled();
+ context.getProgressIndicator().setFraction((double)i / items.length);
+
+ try {
+ ourCompilationThreads.set(Boolean.TRUE);
+
+ if (checkFile(inspections, item.getPsiFile(), context)) {
+ processedItems.add(item);
+ }
+ }
+ finally {
+ ourCompilationThreads.set(Boolean.FALSE);
+ }
+ }
+
+ return processedItems.toArray(new ProcessingItem[processedItems.size()]);
+ }
+
+ private boolean checkFile(List<LocalInspectionTool> inspections, final PsiFile file, final CompileContext context) {
+ if (!checkUnderReadAction(file, context, new Computable<Map<ProblemDescriptor, HighlightDisplayLevel>>() {
+ @Override
+ public Map<ProblemDescriptor, HighlightDisplayLevel> compute() {
+ return myValidator.checkAdditionally(file);
+ }
+ })) {
+ return false;
+ }
+
+ if (!checkUnderReadAction(file, context, new Computable<Map<ProblemDescriptor, HighlightDisplayLevel>>() {
+ @Override
+ public Map<ProblemDescriptor, HighlightDisplayLevel> compute() {
+ if (file instanceof XmlFile) {
+ return runXmlFileSchemaValidation((XmlFile)file);
+ }
+ return Collections.emptyMap();
+ }
+ })) return false;
+
+
+ final InspectionProfile inspectionProfile = myProfileManager.getInspectionProfile();
+ for (final LocalInspectionTool inspectionTool : inspections) {
+ if (!checkUnderReadAction(file, context, new Computable<Map<ProblemDescriptor, HighlightDisplayLevel>>() {
+ @Override
+ public Map<ProblemDescriptor, HighlightDisplayLevel> compute() {
+ if (getHighlightDisplayLevel(inspectionTool, inspectionProfile, file) != HighlightDisplayLevel.DO_NOT_SHOW) {
+ return runInspectionTool(file, inspectionTool, getHighlightDisplayLevel(inspectionTool, inspectionProfile, file)
+ );
+ }
+ return Collections.emptyMap();
+ }
+ })) return false;
+ }
+ return true;
+ }
+
+ private boolean checkUnderReadAction(PsiFile file, CompileContext context, Computable<Map<ProblemDescriptor, HighlightDisplayLevel>> runnable) {
+ AccessToken token = ReadAction.start();
+ try {
+ if (!file.isValid()) return false;
+
+ final Document document = myPsiDocumentManager.getCachedDocument(file);
+ if (document != null && myPsiDocumentManager.isUncommited(document)) {
+ final String url = file.getViewProvider().getVirtualFile().getUrl();
+ context.addMessage(CompilerMessageCategory.WARNING, CompilerBundle.message("warning.text.file.has.been.changed"), url, -1, -1);
+ return false;
+ }
+
+ if (reportProblems(context, runnable.compute())) return false;
+ }
+ finally {
+ token.finish();
+ }
+ return true;
+ }
+
+ private boolean reportProblems(CompileContext context, Map<ProblemDescriptor, HighlightDisplayLevel> problemsMap) {
+ if (problemsMap.isEmpty()) {
+ return false;
+ }
+
+ for (Map.Entry<ProblemDescriptor, HighlightDisplayLevel> entry : problemsMap.entrySet()) {
+ ProblemDescriptor problemDescriptor = entry.getKey();
+ final PsiElement element = problemDescriptor.getPsiElement();
+ final PsiFile psiFile = element.getContainingFile();
+ if (psiFile == null) continue;
+
+ final VirtualFile virtualFile = psiFile.getVirtualFile();
+ if (virtualFile == null) continue;
+
+ final CompilerMessageCategory category = myValidator.getCategoryByHighlightDisplayLevel(entry.getValue(), virtualFile, context);
+ final Document document = myPsiDocumentManager.getDocument(psiFile);
+
+ final int offset = problemDescriptor.getStartElement().getTextOffset();
+ assert document != null;
+ final int line = document.getLineNumber(offset);
+ final int column = offset - document.getLineStartOffset(line);
+ context.addMessage(category, problemDescriptor.getDescriptionTemplate(), virtualFile.getUrl(), line + 1, column + 1);
+ }
+ return true;
+ }
+
+ private static Map<ProblemDescriptor, HighlightDisplayLevel> runInspectionTool(final PsiFile file,
+ final LocalInspectionTool inspectionTool,
+ final HighlightDisplayLevel level) {
+ Map<ProblemDescriptor, HighlightDisplayLevel> problemsMap = new LinkedHashMap<ProblemDescriptor, HighlightDisplayLevel>();
+ for (CommonProblemDescriptor descriptor : InspectionRunningUtil.runInspectionOnFile(file, inspectionTool)) {
+ if (descriptor instanceof ProblemDescriptor) {
+ problemsMap.put((ProblemDescriptor)descriptor, level);
+ }
+ }
+ return problemsMap;
+ }
+
+ private static HighlightDisplayLevel getHighlightDisplayLevel(final LocalInspectionTool inspectionTool,
+ final InspectionProfile inspectionProfile, PsiElement file) {
+ final HighlightDisplayKey key = HighlightDisplayKey.find(inspectionTool.getShortName());
+ return inspectionProfile.isToolEnabled(key, file) ? inspectionProfile.getErrorLevel(key, file) : HighlightDisplayLevel.DO_NOT_SHOW;
+ }
+
+ private Map<ProblemDescriptor, HighlightDisplayLevel> runXmlFileSchemaValidation(@NotNull XmlFile xmlFile) {
+ final AnnotationHolderImpl holder = new AnnotationHolderImpl(new AnnotationSession(xmlFile));
+
+ final List<ExternalAnnotator> annotators = ExternalLanguageAnnotators.allForFile(StdLanguages.XML, xmlFile);
+ for (ExternalAnnotator annotator : annotators) {
+ annotator.annotate(xmlFile, holder);
+ }
+
+ if (!holder.hasAnnotations()) return Collections.emptyMap();
+
+ Map<ProblemDescriptor, HighlightDisplayLevel> problemsMap = new LinkedHashMap<ProblemDescriptor, HighlightDisplayLevel>();
+ for (final Annotation annotation : holder) {
+ final HighlightInfo info = HighlightInfo.fromAnnotation(annotation);
+ if (info.getSeverity() == HighlightSeverity.INFORMATION) continue;
+
+ final PsiElement startElement = xmlFile.findElementAt(info.startOffset);
+ final PsiElement endElement = info.startOffset == info.endOffset ? startElement : xmlFile.findElementAt(info.endOffset - 1);
+ if (startElement == null || endElement == null) continue;
+
+ final ProblemDescriptor descriptor =
+ myInspectionManager.createProblemDescriptor(startElement, endElement, info.description, ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
+ false);
+ final HighlightDisplayLevel level = info.getSeverity() == HighlightSeverity.ERROR? HighlightDisplayLevel.ERROR: HighlightDisplayLevel.WARNING;
+ problemsMap.put(descriptor, level);
+ }
+ return problemsMap;
+ }
+
+
+ @NotNull
+ public String getDescription() {
+ return myValidator.getDescription();
+ }
+
+ public boolean validateConfiguration(final CompileScope scope) {
+ return true;
+ }
+
+ public ValidityState createValidityState(final DataInput in) throws IOException {
+ return PsiElementsValidityState.load(in);
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/openapi/compiler/util/PsiElementsValidityState.java b/java/compiler/impl/src/com/intellij/openapi/compiler/util/PsiElementsValidityState.java
new file mode 100644
index 0000000..ce79281
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/openapi/compiler/util/PsiElementsValidityState.java
@@ -0,0 +1,76 @@
+/*
+ * 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 com.intellij.openapi.compiler.util;
+
+import com.intellij.compiler.CompilerIOUtil;
+import com.intellij.openapi.compiler.ValidityState;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.util.io.IOUtil;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+public class PsiElementsValidityState implements ValidityState {
+ private final Map<String, Long> myDependencies = new HashMap<String, Long>();
+
+ public PsiElementsValidityState() {
+ }
+
+ public void addDependency(final String key, final Long value) {
+ myDependencies.put(key, value);
+ }
+
+ public boolean equalsTo(ValidityState otherState) {
+ return otherState instanceof PsiElementsValidityState &&
+ myDependencies.equals(((PsiElementsValidityState)otherState).myDependencies);
+ }
+
+ public void save(DataOutput out) throws IOException {
+ final Set<Map.Entry<String, Long>> entries = myDependencies.entrySet();
+ out.writeInt(entries.size());
+ for (Map.Entry<String, Long> entry : entries) {
+ IOUtil.writeString(entry.getKey(), out);
+ out.writeLong(entry.getValue().longValue());
+ }
+ }
+
+ public static PsiElementsValidityState load(DataInput input) throws IOException {
+ int size = input.readInt();
+ final PsiElementsValidityState state = new PsiElementsValidityState();
+ while (size-- > 0) {
+ final String s = CompilerIOUtil.readString(input);
+ final long timestamp = input.readLong();
+ state.addDependency(s, timestamp);
+ }
+ return state;
+ }
+
+ public void addDependency(final PsiElement element) {
+ final PsiFile psiFile = element.getContainingFile();
+ if (psiFile != null) {
+ VirtualFile file = psiFile.getVirtualFile();
+ if (file != null) {
+ addDependency(file.getUrl(), file.getTimeStamp());
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/openapi/deployment/DeploymentUtilImpl.java b/java/compiler/impl/src/com/intellij/openapi/deployment/DeploymentUtilImpl.java
new file mode 100644
index 0000000..d36d28f
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/openapi/deployment/DeploymentUtilImpl.java
@@ -0,0 +1,177 @@
+/*
+ * 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 com.intellij.openapi.deployment;
+
+import com.intellij.compiler.impl.packagingCompiler.BuildRecipeImpl;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.compiler.CompilerMessageCategory;
+import com.intellij.openapi.compiler.make.BuildRecipe;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleType;
+import com.intellij.openapi.util.SystemInfo;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.xml.XmlDocument;
+import com.intellij.psi.xml.XmlFile;
+import com.intellij.util.descriptors.ConfigFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.util.Set;
+
+/**
+ * @author Alexey Kudravtsev
+ */
+public class DeploymentUtilImpl extends DeploymentUtil {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.deployment.MakeUtilImpl");
+
+ public void copyFile(@NotNull final File fromFile,
+ @NotNull final File toFile,
+ @NotNull CompileContext context,
+ @Nullable Set<String> writtenPaths,
+ @Nullable FileFilter fileFilter) throws IOException {
+ if (fileFilter != null && !fileFilter.accept(fromFile)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Skipping " + fromFile.getAbsolutePath() + ": it wasn't accepted by filter " + fileFilter);
+ }
+ return;
+ }
+ checkPathDoNotNavigatesUpFromFile(fromFile);
+ checkPathDoNotNavigatesUpFromFile(toFile);
+ if (fromFile.isDirectory()) {
+ final File[] fromFiles = fromFile.listFiles();
+ toFile.mkdirs();
+ for (File file : fromFiles) {
+ copyFile(file, new File(toFile, file.getName()), context, writtenPaths, fileFilter);
+ }
+ return;
+ }
+ if (toFile.isDirectory()) {
+ context.addMessage(CompilerMessageCategory.ERROR,
+ CompilerBundle.message("message.text.destination.is.directory", createCopyErrorMessage(fromFile, toFile)), null, -1, -1);
+ return;
+ }
+ if (FileUtil.filesEqual(fromFile, toFile) || writtenPaths != null && !writtenPaths.add(toFile.getPath())) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Skipping " + fromFile.getAbsolutePath() + ": " + toFile.getAbsolutePath() + " is already written");
+ }
+ return;
+ }
+ if (!FileUtil.isFilePathAcceptable(toFile, fileFilter)) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Skipping " + fromFile.getAbsolutePath() + ": " + toFile.getAbsolutePath() + " wasn't accepted by filter " + fileFilter);
+ }
+ return;
+ }
+ context.getProgressIndicator().setText("Copying files");
+ context.getProgressIndicator().setText2(fromFile.getPath());
+ try {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Copy file '" + fromFile + "' to '"+toFile+"'");
+ }
+ if (toFile.exists() && !SystemInfo.isFileSystemCaseSensitive) {
+ File canonicalFile = toFile.getCanonicalFile();
+ if (!canonicalFile.getAbsolutePath().equals(toFile.getAbsolutePath())) {
+ FileUtil.delete(toFile);
+ }
+ }
+ FileUtil.copy(fromFile, toFile);
+ }
+ catch (IOException e) {
+ context.addMessage(CompilerMessageCategory.ERROR, createCopyErrorMessage(fromFile, toFile) + ": "+e.getLocalizedMessage(), null, -1, -1);
+ }
+ }
+
+ // OS X is sensitive for that
+ private static void checkPathDoNotNavigatesUpFromFile(File file) {
+ String path = file.getPath();
+ int i = path.indexOf("..");
+ if (i != -1) {
+ String filepath = path.substring(0,i-1);
+ File filepart = new File(filepath);
+ if (filepart.exists() && !filepart.isDirectory()) {
+ LOG.error("Incorrect file path: '" + path + '\'');
+ }
+ }
+ }
+
+ private static String createCopyErrorMessage(final File fromFile, final File toFile) {
+ return CompilerBundle.message("message.text.error.copying.file.to.file", FileUtil.toSystemDependentName(fromFile.getPath()),
+ FileUtil.toSystemDependentName(toFile.getPath()));
+ }
+
+ public final boolean addItemsRecursively(@NotNull BuildRecipe items, @NotNull File root, String outputRelativePath) {
+ if (outputRelativePath == null) outputRelativePath = "";
+ outputRelativePath = trimForwardSlashes(outputRelativePath);
+
+ items.addFileCopyInstruction(root, true, outputRelativePath);
+ return true;
+ }
+
+ public void reportDeploymentDescriptorDoesNotExists(ConfigFile descriptor, CompileContext context, Module module) {
+ final String description = ModuleType.get(module).getName() + " '" + module.getName() + '\'';
+ String descriptorPath = VfsUtil.urlToPath(descriptor.getUrl());
+ final String message =
+ CompilerBundle.message("message.text.compiling.item.deployment.descriptor.could.not.be.found", description, descriptorPath);
+ context.addMessage(CompilerMessageCategory.ERROR, message, null, -1, -1);
+ }
+
+ public void checkConfigFile(final ConfigFile descriptor, final CompileContext compileContext, final Module module) {
+ if (new File(VfsUtil.urlToPath(descriptor.getUrl())).exists()) {
+ String message = getConfigFileErrorMessage(descriptor);
+ if (message != null) {
+ final String moduleDescription = ModuleType.get(module).getName() + " '" + module.getName() + '\'';
+ compileContext.addMessage(CompilerMessageCategory.ERROR,
+ CompilerBundle.message("message.text.compiling.module.message", moduleDescription, message),
+ descriptor.getUrl(), -1, -1);
+ }
+ }
+ else {
+ DeploymentUtil.getInstance().reportDeploymentDescriptorDoesNotExists(descriptor, compileContext, module);
+ }
+ }
+
+ public BuildRecipe createBuildRecipe() {
+ return new BuildRecipeImpl();
+ }
+
+ @Nullable
+ public String getConfigFileErrorMessage(final ConfigFile configFile) {
+ if (configFile.getVirtualFile() == null) {
+ String path = FileUtil.toSystemDependentName(VfsUtil.urlToPath(configFile.getUrl()));
+ return CompilerBundle.message("mesage.text.deployment.descriptor.file.not.exist", path);
+ }
+ PsiFile psiFile = configFile.getPsiFile();
+ if (psiFile == null || !psiFile.isValid()) {
+ return CompilerBundle.message("message.text.deployment.description.invalid.file");
+ }
+
+ if (psiFile instanceof XmlFile) {
+ XmlDocument document = ((XmlFile)psiFile).getDocument();
+ if (document == null || document.getRootTag() == null) {
+ return CompilerBundle.message("message.text.xml.file.invalid", FileUtil.toSystemDependentName(VfsUtil.urlToPath(configFile.getUrl())));
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/openapi/projectRoots/impl/MockJdkWrapper.java b/java/compiler/impl/src/com/intellij/openapi/projectRoots/impl/MockJdkWrapper.java
new file mode 100644
index 0000000..c5b6c66
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/openapi/projectRoots/impl/MockJdkWrapper.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2000-2012 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.
+ */
+
+/**
+ * @author cdr
+ */
+package com.intellij.openapi.projectRoots.impl;
+
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.projectRoots.SdkAdditionalData;
+import com.intellij.openapi.projectRoots.SdkModificator;
+import com.intellij.openapi.projectRoots.SdkTypeId;
+import com.intellij.openapi.roots.RootProvider;
+import com.intellij.openapi.util.Key;
+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.VirtualFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+
+/**
+ * used to override JdkHome location in order to provide correct paths
+ */
+public final class MockJdkWrapper implements Sdk {
+ private final String myHomePath;
+ private final Sdk myDelegate;
+
+ public MockJdkWrapper(String homePath, @NotNull Sdk delegate) {
+ myHomePath = homePath;
+ myDelegate = delegate;
+ }
+
+ public VirtualFile getHomeDirectory() {
+ return LocalFileSystem.getInstance().findFileByIoFile(new File(getHomePath()));
+ }
+
+ public String getHomePath() {
+ final String homePath = FileUtil.toSystemDependentName(myHomePath == null ? myDelegate.getHomePath() : myHomePath);
+ return StringUtil.trimEnd(homePath, File.separator);
+ }
+
+ @NotNull
+ public SdkTypeId getSdkType() {
+ return myDelegate.getSdkType();
+ }
+
+ @NotNull
+ public String getName() {
+ return myDelegate.getName();
+ }
+
+ public String getVersionString() {
+ return myDelegate.getVersionString();
+ }
+
+ @NotNull
+ public RootProvider getRootProvider() {
+ return myDelegate.getRootProvider();
+ }
+
+ public <T> T getUserData(@NotNull Key<T> key) {
+ return myDelegate.getUserData(key);
+ }
+
+ public <T> void putUserData(@NotNull Key<T> key, @Nullable T value) {
+ myDelegate.putUserData(key, value);
+ }
+
+ @NotNull
+ public Object clone() throws CloneNotSupportedException {
+ throw new CloneNotSupportedException();
+ }
+
+ public SdkAdditionalData getSdkAdditionalData() {
+ return null;
+ }
+
+ @NotNull
+ public SdkModificator getSdkModificator() {
+ return null;
+ }
+
+ public Sdk getDelegate() {
+ return myDelegate;
+ }
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactBySourceFileFinder.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactBySourceFileFinder.java
new file mode 100644
index 0000000..5a2211b
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactBySourceFileFinder.java
@@ -0,0 +1,35 @@
+/*
+ * 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.Artifact;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+
+/**
+ * @author nik
+ */
+public abstract class ArtifactBySourceFileFinder {
+ public static ArtifactBySourceFileFinder getInstance(@NotNull Project project) {
+ return ServiceManager.getService(project, ArtifactBySourceFileFinder.class);
+ }
+
+ public abstract Collection<? extends Artifact> findArtifacts(@NotNull VirtualFile sourceFile);
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactBySourceFileFinderImpl.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactBySourceFileFinderImpl.java
new file mode 100644
index 0000000..0083ead
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactBySourceFileFinderImpl.java
@@ -0,0 +1,118 @@
+/*
+ * 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.ModificationTracker;
+import com.intellij.openapi.util.MultiValuesMap;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.artifacts.ArtifactManager;
+import com.intellij.packaging.elements.ComplexPackagingElementType;
+import com.intellij.packaging.elements.PackagingElement;
+import com.intellij.packaging.elements.PackagingElementFactory;
+import com.intellij.packaging.elements.PackagingElementResolvingContext;
+import com.intellij.packaging.impl.elements.FileOrDirectoryCopyPackagingElement;
+import com.intellij.packaging.impl.elements.ModuleOutputPackagingElement;
+import com.intellij.psi.util.CachedValue;
+import com.intellij.psi.util.CachedValueProvider;
+import com.intellij.psi.util.CachedValuesManager;
+import com.intellij.util.SmartList;
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public class ArtifactBySourceFileFinderImpl extends ArtifactBySourceFileFinder {
+ private CachedValue<MultiValuesMap<VirtualFile, Artifact>> myFile2Artifacts;
+ private final Project myProject;
+
+ public ArtifactBySourceFileFinderImpl(Project project) {
+ myProject = project;
+ }
+
+ public CachedValue<MultiValuesMap<VirtualFile, Artifact>> getFileToArtifactsMap() {
+ if (myFile2Artifacts == null) {
+ myFile2Artifacts =
+ CachedValuesManager.getManager(myProject).createCachedValue(new CachedValueProvider<MultiValuesMap<VirtualFile, Artifact>>() {
+ public Result<MultiValuesMap<VirtualFile, Artifact>> compute() {
+ MultiValuesMap<VirtualFile, Artifact> result = computeFileToArtifactsMap();
+ List<ModificationTracker> trackers = new ArrayList<ModificationTracker>();
+ trackers.add(ArtifactManager.getInstance(myProject).getModificationTracker());
+ for (ComplexPackagingElementType<?> type : PackagingElementFactory.getInstance().getComplexElementTypes()) {
+ ContainerUtil.addIfNotNull(type.getAllSubstitutionsModificationTracker(myProject), trackers);
+ }
+ return Result.create(result, trackers.toArray(new ModificationTracker[trackers.size()]));
+ }
+ }, false);
+ }
+ return myFile2Artifacts;
+ }
+
+ private MultiValuesMap<VirtualFile, Artifact> computeFileToArtifactsMap() {
+ final MultiValuesMap<VirtualFile, Artifact> result = new MultiValuesMap<VirtualFile, Artifact>();
+ final ArtifactManager artifactManager = ArtifactManager.getInstance(myProject);
+ for (final Artifact artifact : artifactManager.getArtifacts()) {
+ final PackagingElementResolvingContext context = artifactManager.getResolvingContext();
+ ArtifactUtil.processPackagingElements(artifact, null, new PackagingElementProcessor<PackagingElement<?>>() {
+ @Override
+ public boolean process(@NotNull PackagingElement<?> element, @NotNull PackagingElementPath path) {
+ if (element instanceof FileOrDirectoryCopyPackagingElement<?>) {
+ final VirtualFile root = ((FileOrDirectoryCopyPackagingElement)element).findFile();
+ if (root != null) {
+ result.put(root, artifact);
+ }
+ }
+ else if (element instanceof ModuleOutputPackagingElement) {
+ for (VirtualFile sourceRoot : ((ModuleOutputPackagingElement)element).getSourceRoots(context)) {
+ result.put(sourceRoot, artifact);
+ }
+ }
+ return true;
+ }
+ }, context, true);
+ }
+ return result;
+ }
+
+ @Override
+ public Collection<? extends Artifact> findArtifacts(@NotNull VirtualFile sourceFile) {
+ final MultiValuesMap<VirtualFile, Artifact> map = getFileToArtifactsMap().getValue();
+ if (map.isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ List<Artifact> result = null;
+ VirtualFile file = sourceFile;
+ while (file != null) {
+ final Collection<Artifact> artifacts = map.get(file);
+ if (artifacts != null) {
+ if (result == null) {
+ result = new SmartList<Artifact>();
+ }
+ result.addAll(artifacts);
+ }
+ file = file.getParent();
+ }
+ return result != null ? result : Collections.<Artifact>emptyList();
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactImpl.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactImpl.java
new file mode 100644
index 0000000..65763c0
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactImpl.java
@@ -0,0 +1,188 @@
+/*
+ * 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.openapi.util.UserDataHolderBase;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.*;
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.packaging.impl.elements.ArchivePackagingElement;
+import com.intellij.util.EventDispatcher;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author nik
+ */
+public class ArtifactImpl extends UserDataHolderBase implements ModifiableArtifact {
+ private CompositePackagingElement<?> myRootElement;
+ private String myName;
+ private boolean myBuildOnMake;
+ private String myOutputPath;
+ private final EventDispatcher<ArtifactListener> myDispatcher;
+ private ArtifactType myArtifactType;
+ private Map<ArtifactPropertiesProvider, ArtifactProperties<?>> myProperties;
+
+ public ArtifactImpl(@NotNull String name, @NotNull ArtifactType artifactType, boolean buildOnMake, @NotNull CompositePackagingElement<?> rootElement,
+ String outputPath) {
+ this(name, artifactType, buildOnMake, rootElement, outputPath, null);
+ }
+ public ArtifactImpl(@NotNull String name, @NotNull ArtifactType artifactType, boolean buildOnMake, @NotNull CompositePackagingElement<?> rootElement,
+ String outputPath,
+ EventDispatcher<ArtifactListener> dispatcher) {
+ myName = name;
+ myArtifactType = artifactType;
+ myBuildOnMake = buildOnMake;
+ myRootElement = rootElement;
+ myOutputPath = outputPath;
+ myDispatcher = dispatcher;
+ myProperties = new HashMap<ArtifactPropertiesProvider, ArtifactProperties<?>>();
+ resetProperties();
+ }
+
+ private void resetProperties() {
+ myProperties.clear();
+ for (ArtifactPropertiesProvider provider : ArtifactPropertiesProvider.getProviders()) {
+ if (provider.isAvailableFor(myArtifactType)) {
+ myProperties.put(provider, provider.createProperties(myArtifactType));
+ }
+ }
+ }
+
+ @NotNull
+ public ArtifactType getArtifactType() {
+ return myArtifactType;
+ }
+
+ public String getName() {
+ return myName;
+ }
+
+ public boolean isBuildOnMake() {
+ return myBuildOnMake;
+ }
+
+ @NotNull
+ public CompositePackagingElement<?> getRootElement() {
+ return myRootElement;
+ }
+
+ public String getOutputPath() {
+ return myOutputPath;
+ }
+
+ public Collection<? extends ArtifactPropertiesProvider> getPropertiesProviders() {
+ return Collections.unmodifiableCollection(myProperties.keySet());
+ }
+
+ public ArtifactImpl createCopy(EventDispatcher<ArtifactListener> dispatcher) {
+ final ArtifactImpl artifact = new ArtifactImpl(myName, myArtifactType, myBuildOnMake, myRootElement, myOutputPath, dispatcher);
+ for (Map.Entry<ArtifactPropertiesProvider, ArtifactProperties<?>> entry : myProperties.entrySet()) {
+ final ArtifactProperties newProperties = artifact.myProperties.get(entry.getKey());
+ //noinspection unchecked
+ newProperties.loadState(entry.getValue().getState());
+ }
+ return artifact;
+ }
+
+ public void setName(@NotNull String name) {
+ String oldName = myName;
+ myName = name;
+ if (myDispatcher != null) {
+ myDispatcher.getMulticaster().artifactChanged(this, oldName);
+ }
+ }
+
+ @NonNls @Override
+ public String toString() {
+ return "artifact:" + myName;
+ }
+
+ public void setRootElement(CompositePackagingElement<?> root) {
+ myRootElement = root;
+ }
+
+ public void setProperties(ArtifactPropertiesProvider provider, ArtifactProperties<?> properties) {
+ if (properties != null) {
+ myProperties.put(provider, properties);
+ }
+ else {
+ myProperties.remove(provider);
+ }
+ }
+
+ public void setArtifactType(@NotNull ArtifactType selected) {
+ myArtifactType = selected;
+ resetProperties();
+ }
+
+ public void setBuildOnMake(boolean buildOnMake) {
+ myBuildOnMake = buildOnMake;
+ }
+
+ public void setOutputPath(String outputPath) {
+ myOutputPath = outputPath;
+ }
+
+ public ArtifactProperties<?> getProperties(@NotNull ArtifactPropertiesProvider provider) {
+ return myProperties.get(provider);
+ }
+
+ @Override
+ public VirtualFile getOutputFile() {
+ String filePath = getOutputFilePath();
+ return !StringUtil.isEmpty(filePath) ? LocalFileSystem.getInstance().findFileByPath(filePath) : null;
+ }
+
+ @Override
+ public String getOutputFilePath() {
+ if (StringUtil.isEmpty(myOutputPath)) return null;
+
+ String filePath;
+ if (myRootElement instanceof ArchivePackagingElement) {
+ filePath = myOutputPath + "/" + ((ArchivePackagingElement)myRootElement).getArchiveFileName();
+ }
+ else {
+ filePath = myOutputPath;
+ }
+ return filePath;
+ }
+
+ @Nullable
+ public String getOutputDirectoryPathToCleanOnRebuild() {
+ if (myRootElement instanceof ArchivePackagingElement || StringUtil.isEmpty(myOutputPath)) {
+ return null;
+ }
+ return myOutputPath;
+ }
+
+ public void copyFrom(ArtifactImpl modified) {
+ myName = modified.getName();
+ myOutputPath = modified.getOutputPath();
+ myBuildOnMake = modified.isBuildOnMake();
+ myRootElement = modified.getRootElement();
+ myProperties = modified.myProperties;
+ myArtifactType = modified.getArtifactType();
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactLoadingErrorDescription.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactLoadingErrorDescription.java
new file mode 100644
index 0000000..2b990dc
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactLoadingErrorDescription.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.application.WriteAction;
+import com.intellij.openapi.module.ConfigurationErrorDescription;
+import com.intellij.openapi.module.ConfigurationErrorType;
+import com.intellij.openapi.project.Project;
+import com.intellij.packaging.artifacts.ArtifactManager;
+import com.intellij.packaging.artifacts.ModifiableArtifactModel;
+
+/**
+ * @author nik
+ */
+public class ArtifactLoadingErrorDescription extends ConfigurationErrorDescription {
+ private static final ConfigurationErrorType INVALID_ARTIFACT = new ConfigurationErrorType("artifact", false);
+ private final Project myProject;
+ private final InvalidArtifact myArtifact;
+
+ public ArtifactLoadingErrorDescription(Project project, InvalidArtifact artifact) {
+ super(artifact.getName(), artifact.getErrorMessage(), INVALID_ARTIFACT);
+ myProject = project;
+ myArtifact = artifact;
+ }
+
+ @Override
+ public void ignoreInvalidElement() {
+ final ModifiableArtifactModel model = ArtifactManager.getInstance(myProject).createModifiableModel();
+ model.removeArtifact(myArtifact);
+ new WriteAction() {
+ protected void run(final Result result) {
+ model.commit();
+ }
+ }.execute();
+ }
+
+ @Override
+ public String getIgnoreConfirmationMessage() {
+ return "Would you like to remove artifact '" + myArtifact.getName() + "?";
+ }
+
+ @Override
+ public boolean isValid() {
+ return ArtifactManager.getInstance(myProject).getAllArtifactsIncludingInvalid().contains(myArtifact);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactManagerImpl.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactManagerImpl.java
new file mode 100644
index 0000000..c3896a8
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactManagerImpl.java
@@ -0,0 +1,450 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.compiler.server.BuildManager;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.application.WriteAction;
+import com.intellij.openapi.components.*;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.module.ProjectLoadingErrorsNotifier;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ex.ProjectRootManagerEx;
+import com.intellij.openapi.util.ModificationTracker;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.intellij.packaging.artifacts.*;
+import com.intellij.packaging.elements.*;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.xmlb.SkipDefaultValuesSerializationFilters;
+import com.intellij.util.xmlb.XmlSerializer;
+import gnu.trove.THashSet;
+import org.jdom.Element;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.jps.model.serialization.artifact.ArtifactManagerState;
+import org.jetbrains.jps.model.serialization.artifact.ArtifactPropertiesState;
+import org.jetbrains.jps.model.serialization.artifact.ArtifactState;
+
+import java.util.*;
+
+/**
+ * @author nik
+ */
+@State(
+ name = ArtifactManagerImpl.COMPONENT_NAME,
+ storages = {
+ @Storage( file = StoragePathMacros.PROJECT_FILE),
+ @Storage( file = StoragePathMacros.PROJECT_CONFIG_DIR + "/artifacts/", scheme = StorageScheme.DIRECTORY_BASED, stateSplitter = ArtifactManagerStateSplitter.class)
+ }
+)
+public class ArtifactManagerImpl extends ArtifactManager implements ProjectComponent, PersistentStateComponent<ArtifactManagerState> {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.packaging.impl.artifacts.ArtifactManagerImpl");
+ @NonNls public static final String COMPONENT_NAME = "ArtifactManager";
+ @NonNls public static final String PACKAGING_ELEMENT_NAME = "element";
+ @NonNls public static final String TYPE_ID_ATTRIBUTE = "id";
+ private final ArtifactManagerModel myModel;
+ private final Project myProject;
+ private final DefaultPackagingElementResolvingContext myResolvingContext;
+ private boolean myInsideCommit = false;
+ private boolean myLoaded;
+ private long myModificationCount;
+ private final ModificationTracker myModificationTracker = new ModificationTracker() {
+ public long getModificationCount() {
+ return myModificationCount;
+ }
+ };
+ private final Map<String, LocalFileSystem.WatchRequest> myWatchedOutputs = new HashMap<String, LocalFileSystem.WatchRequest>();
+
+ public ArtifactManagerImpl(Project project) {
+ myProject = project;
+ myModel = new ArtifactManagerModel();
+ myResolvingContext = new DefaultPackagingElementResolvingContext(myProject);
+ ((ArtifactPointerManagerImpl)ArtifactPointerManager.getInstance(project)).setArtifactManager(this);
+ }
+
+ @NotNull
+ public Artifact[] getArtifacts() {
+ return myModel.getArtifacts();
+ }
+
+ public Artifact findArtifact(@NotNull String name) {
+ return myModel.findArtifact(name);
+ }
+
+ @NotNull
+ public Artifact getArtifactByOriginal(@NotNull Artifact artifact) {
+ return myModel.getArtifactByOriginal(artifact);
+ }
+
+ @NotNull
+ public Artifact getOriginalArtifact(@NotNull Artifact artifact) {
+ return myModel.getOriginalArtifact(artifact);
+ }
+
+ public Collection<? extends Artifact> getArtifactsByType(@NotNull ArtifactType type) {
+ return myModel.getArtifactsByType(type);
+ }
+
+ @Override
+ public List<? extends Artifact> getAllArtifactsIncludingInvalid() {
+ return myModel.getAllArtifactsIncludingInvalid();
+ }
+
+ public ArtifactManagerState getState() {
+ final ArtifactManagerState state = new ArtifactManagerState();
+ for (Artifact artifact : getAllArtifactsIncludingInvalid()) {
+ final ArtifactState artifactState;
+ if (artifact instanceof InvalidArtifact) {
+ artifactState = ((InvalidArtifact)artifact).getState();
+ }
+ else {
+ artifactState = new ArtifactState();
+ artifactState.setBuildOnMake(artifact.isBuildOnMake());
+ artifactState.setName(artifact.getName());
+ artifactState.setOutputPath(artifact.getOutputPath());
+ artifactState.setRootElement(serializePackagingElement(artifact.getRootElement()));
+ artifactState.setArtifactType(artifact.getArtifactType().getId());
+ for (ArtifactPropertiesProvider provider : artifact.getPropertiesProviders()) {
+ final ArtifactPropertiesState propertiesState = serializeProperties(provider, artifact.getProperties(provider));
+ if (propertiesState != null) {
+ artifactState.getPropertiesList().add(propertiesState);
+ }
+ }
+ Collections.sort(artifactState.getPropertiesList(), new Comparator<ArtifactPropertiesState>() {
+ public int compare(ArtifactPropertiesState o1, ArtifactPropertiesState o2) {
+ return o1.getId().compareTo(o2.getId());
+ }
+ });
+ }
+ state.getArtifacts().add(artifactState);
+ }
+ return state;
+ }
+
+ @Nullable
+ private static <S> ArtifactPropertiesState serializeProperties(ArtifactPropertiesProvider provider, ArtifactProperties<S> properties) {
+ final ArtifactPropertiesState state = new ArtifactPropertiesState();
+ state.setId(provider.getId());
+ final Element options = new Element("options");
+ XmlSerializer.serializeInto(properties.getState(), options, new SkipDefaultValuesSerializationFilters());
+ if (options.getContent().isEmpty() && options.getAttributes().isEmpty()) return null;
+ state.setOptions(options);
+ return state;
+ }
+
+ private static Element serializePackagingElement(PackagingElement<?> packagingElement) {
+ Element element = new Element(PACKAGING_ELEMENT_NAME);
+ element.setAttribute(TYPE_ID_ATTRIBUTE, packagingElement.getType().getId());
+ final Object bean = packagingElement.getState();
+ if (bean != null) {
+ XmlSerializer.serializeInto(bean, element, new SkipDefaultValuesSerializationFilters());
+ }
+ if (packagingElement instanceof CompositePackagingElement) {
+ for (PackagingElement<?> child : ((CompositePackagingElement<?>)packagingElement).getChildren()) {
+ element.addContent(serializePackagingElement(child));
+ }
+ }
+ return element;
+ }
+
+ private <T> PackagingElement<T> deserializeElement(Element element) throws UnknownPackagingElementTypeException {
+ final String id = element.getAttributeValue(TYPE_ID_ATTRIBUTE);
+ PackagingElementType<?> type = PackagingElementFactory.getInstance().findElementType(id);
+ if (type == null) {
+ throw new UnknownPackagingElementTypeException(id);
+ }
+
+ PackagingElement<T> packagingElement = (PackagingElement<T>)type.createEmpty(myProject);
+ T state = packagingElement.getState();
+ if (state != null) {
+ XmlSerializer.deserializeInto(state, element);
+ packagingElement.loadState(state);
+ }
+ final List children = element.getChildren(PACKAGING_ELEMENT_NAME);
+ //noinspection unchecked
+ for (Element child : (List<? extends Element>)children) {
+ ((CompositePackagingElement<?>)packagingElement).addOrFindChild(deserializeElement(child));
+ }
+ return packagingElement;
+ }
+
+ public void loadState(ArtifactManagerState managerState) {
+ final List<ArtifactImpl> artifacts = new ArrayList<ArtifactImpl>();
+ for (ArtifactState state : managerState.getArtifacts()) {
+ artifacts.add(loadArtifact(state));
+ }
+
+ if (myLoaded) {
+ final ArtifactModelImpl model = new ArtifactModelImpl(this, artifacts);
+ doCommit(model);
+ }
+ else {
+ myModel.setArtifactsList(artifacts);
+ myLoaded = true;
+ }
+ }
+
+ private ArtifactImpl loadArtifact(ArtifactState state) {
+ ArtifactType type = ArtifactType.findById(state.getArtifactType());
+ if (type == null) {
+ return createInvalidArtifact(state, "Unknown artifact type: " + state.getArtifactType());
+ }
+
+ final Element element = state.getRootElement();
+ final String artifactName = state.getName();
+ final CompositePackagingElement<?> rootElement;
+ if (element != null) {
+ try {
+ rootElement = (CompositePackagingElement<?>)deserializeElement(element);
+ }
+ catch (UnknownPackagingElementTypeException e) {
+ return createInvalidArtifact(state, "Unknown element: " + e.getTypeId());
+ }
+ }
+ else {
+ rootElement = type.createRootElement(artifactName);
+ }
+
+ final ArtifactImpl artifact = new ArtifactImpl(artifactName, type, state.isBuildOnMake(), rootElement, state.getOutputPath());
+ final List<ArtifactPropertiesState> propertiesList = state.getPropertiesList();
+ for (ArtifactPropertiesState propertiesState : propertiesList) {
+ final ArtifactPropertiesProvider provider = ArtifactPropertiesProvider.findById(propertiesState.getId());
+ if (provider != null) {
+ deserializeProperties(artifact.getProperties(provider), propertiesState);
+ }
+ else {
+ return createInvalidArtifact(state, "Unknown artifact properties: " + propertiesState.getId());
+ }
+ }
+ return artifact;
+ }
+
+ private InvalidArtifact createInvalidArtifact(ArtifactState state, String errorMessage) {
+ final InvalidArtifact artifact = new InvalidArtifact(state, errorMessage);
+ ProjectLoadingErrorsNotifier.getInstance(myProject).registerError(new ArtifactLoadingErrorDescription(myProject, artifact));
+ return artifact;
+ }
+
+ private static <S> void deserializeProperties(ArtifactProperties<S> artifactProperties, ArtifactPropertiesState propertiesState) {
+ final Element options = propertiesState.getOptions();
+ if (artifactProperties == null || options == null) {
+ return;
+ }
+ final S state = artifactProperties.getState();
+ if (state != null) {
+ XmlSerializer.deserializeInto(state, options);
+ artifactProperties.loadState(state);
+ }
+ }
+
+ public void disposeComponent() {
+ LocalFileSystem.getInstance().removeWatchedRoots(myWatchedOutputs.values());
+ }
+
+ @NotNull
+ public String getComponentName() {
+ return COMPONENT_NAME;
+ }
+
+ public void initComponent() {
+ VirtualFileManager.getInstance().addVirtualFileListener(new ArtifactVirtualFileListener(myProject, this), myProject);
+ updateWatchedRoots();
+ }
+
+ private void updateWatchedRoots() {
+ Set<String> pathsToRemove = new HashSet<String>(myWatchedOutputs.keySet());
+ Set<String> toAdd = new HashSet<String>();
+ for (Artifact artifact : getArtifacts()) {
+ final String path = artifact.getOutputPath();
+ if (path != null && path.length() > 0) {
+ pathsToRemove.remove(path);
+ if (!myWatchedOutputs.containsKey(path)) {
+ toAdd.add(path);
+ }
+ }
+ }
+
+ List<LocalFileSystem.WatchRequest> requestsToRemove = new ArrayList<LocalFileSystem.WatchRequest>();
+ for (String path : pathsToRemove) {
+ final LocalFileSystem.WatchRequest request = myWatchedOutputs.remove(path);
+ ContainerUtil.addIfNotNull(request, requestsToRemove);
+ }
+
+ Set<LocalFileSystem.WatchRequest> newRequests = LocalFileSystem.getInstance().replaceWatchedRoots(requestsToRemove, toAdd, null);
+ for (LocalFileSystem.WatchRequest request : newRequests) {
+ myWatchedOutputs.put(request.getRootPath(), request);
+ }
+ }
+
+ public void projectOpened() {
+ }
+
+ public void projectClosed() {
+ }
+
+ @Override
+ public Artifact[] getSortedArtifacts() {
+ return myModel.getSortedArtifacts();
+ }
+
+ @Override
+ public ModifiableArtifactModel createModifiableModel() {
+ return new ArtifactModelImpl(this, getArtifactsList());
+ }
+
+ @Override
+ public PackagingElementResolvingContext getResolvingContext() {
+ return myResolvingContext;
+ }
+
+ public List<ArtifactImpl> getArtifactsList() {
+ return myModel.myArtifactsList;
+ }
+
+ public void commit(ArtifactModelImpl artifactModel) {
+ ApplicationManager.getApplication().assertWriteAccessAllowed();
+
+ doCommit(artifactModel);
+ }
+
+ private void doCommit(ArtifactModelImpl artifactModel) {
+ boolean hasChanges;
+ LOG.assertTrue(!myInsideCommit, "Recursive commit");
+ myInsideCommit = true;
+ try {
+
+ final List<ArtifactImpl> allArtifacts = artifactModel.getOriginalArtifacts();
+
+ final Set<ArtifactImpl> removed = new THashSet<ArtifactImpl>(myModel.myArtifactsList);
+ final List<ArtifactImpl> added = new ArrayList<ArtifactImpl>();
+ final List<Pair<ArtifactImpl, String>> changed = new ArrayList<Pair<ArtifactImpl, String>>();
+
+ for (ArtifactImpl artifact : allArtifacts) {
+ final boolean isAdded = !removed.remove(artifact);
+ final ArtifactImpl modifiableCopy = artifactModel.getModifiableCopy(artifact);
+ if (isAdded) {
+ added.add(artifact);
+ }
+ else if (modifiableCopy != null && !modifiableCopy.equals(artifact)) {
+ final String oldName = artifact.getName();
+ artifact.copyFrom(modifiableCopy);
+ changed.add(Pair.create(artifact, oldName));
+ }
+ }
+
+ myModel.setArtifactsList(allArtifacts);
+ myModificationCount++;
+ final ArtifactListener publisher = myProject.getMessageBus().syncPublisher(TOPIC);
+ hasChanges = !removed.isEmpty() || !added.isEmpty() || !changed.isEmpty();
+ ProjectRootManagerEx.getInstanceEx(myProject).mergeRootsChangesDuring(new Runnable() {
+ public void run() {
+ for (ArtifactImpl artifact : removed) {
+ publisher.artifactRemoved(artifact);
+ }
+ //it's important to send 'removed' events before 'added'. Otherwise when artifacts are reloaded from xml artifact pointers will be damaged
+ for (ArtifactImpl artifact : added) {
+ publisher.artifactAdded(artifact);
+ }
+ for (Pair<ArtifactImpl, String> pair : changed) {
+ publisher.artifactChanged(pair.getFirst(), pair.getSecond());
+ }
+ }
+ });
+ }
+ finally {
+ myInsideCommit = false;
+ }
+ updateWatchedRoots();
+ if (hasChanges) {
+ BuildManager.getInstance().clearState(myProject);
+ }
+ }
+
+ public Project getProject() {
+ return myProject;
+ }
+
+ @NotNull
+ public Artifact addArtifact(@NotNull final String name, @NotNull final ArtifactType type, final CompositePackagingElement<?> root) {
+ return new WriteAction<Artifact>() {
+ protected void run(final Result<Artifact> result) {
+ final ModifiableArtifactModel model = createModifiableModel();
+ final ModifiableArtifact artifact = model.addArtifact(name, type);
+ if (root != null) {
+ artifact.setRootElement(root);
+ }
+ model.commit();
+ result.setResult(artifact);
+ }
+ }.execute().getResultObject();
+ }
+
+ @Override
+ public void addElementsToDirectory(@NotNull Artifact artifact, @NotNull String relativePath, @NotNull PackagingElement<?> element) {
+ addElementsToDirectory(artifact, relativePath, Collections.singletonList(element));
+ }
+
+ @Override
+ public void addElementsToDirectory(@NotNull Artifact artifact, @NotNull String relativePath,
+ @NotNull Collection<? extends PackagingElement<?>> elements) {
+ final ModifiableArtifactModel model = createModifiableModel();
+ final CompositePackagingElement<?> root = model.getOrCreateModifiableArtifact(artifact).getRootElement();
+ PackagingElementFactory.getInstance().getOrCreateDirectory(root, relativePath).addOrFindChildren(elements);
+ new WriteAction() {
+ protected void run(final Result result) {
+ model.commit();
+ }
+ }.execute();
+ }
+
+ @Override
+ public ModificationTracker getModificationTracker() {
+ return myModificationTracker;
+ }
+
+ private static class ArtifactManagerModel extends ArtifactModelBase {
+ private List<ArtifactImpl> myArtifactsList = new ArrayList<ArtifactImpl>();
+ private Artifact[] mySortedArtifacts;
+
+ public void setArtifactsList(List<ArtifactImpl> artifactsList) {
+ myArtifactsList = artifactsList;
+ artifactsChanged();
+ }
+
+ @Override
+ protected void artifactsChanged() {
+ super.artifactsChanged();
+ mySortedArtifacts = null;
+ }
+
+ protected List<? extends Artifact> getArtifactsList() {
+ return myArtifactsList;
+ }
+
+ public Artifact[] getSortedArtifacts() {
+ if (mySortedArtifacts == null) {
+ mySortedArtifacts = getArtifacts().clone();
+ Arrays.sort(mySortedArtifacts, ARTIFACT_COMPARATOR);
+ }
+ return mySortedArtifacts;
+ }
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactManagerStateSplitter.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactManagerStateSplitter.java
new file mode 100644
index 0000000..90ee240
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactManagerStateSplitter.java
@@ -0,0 +1,51 @@
+/*
+ * 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.openapi.components.StateSplitter;
+import com.intellij.openapi.util.JDOMUtil;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.util.text.UniqueNameGenerator;
+import org.jdom.Element;
+import org.jetbrains.jps.model.serialization.artifact.ArtifactState;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public class ArtifactManagerStateSplitter implements StateSplitter {
+ public List<Pair<Element, String>> splitState(Element e) {
+ final UniqueNameGenerator generator = new UniqueNameGenerator();
+
+ List<Pair<Element, String>> result = new ArrayList<Pair<Element, String>>();
+
+ for (Element element : JDOMUtil.getElements(e)) {
+ final String name = generator.generateUniqueName(FileUtil.sanitizeFileName(element.getAttributeValue(ArtifactState.NAME_ATTRIBUTE))) + ".xml";
+ result.add(new Pair<Element, String>(element, name));
+ }
+
+ return result;
+ }
+
+ public void mergeStatesInto(Element target, Element[] elements) {
+ for (Element e : elements) {
+ target.addContent(e);
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactModelBase.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactModelBase.java
new file mode 100644
index 0000000..9dbfa2a
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactModelBase.java
@@ -0,0 +1,90 @@
+/*
+ * 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.openapi.util.Condition;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.artifacts.ArtifactModel;
+import com.intellij.packaging.artifacts.ArtifactType;
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+
+/**
+ * @author nik
+ */
+public abstract class ArtifactModelBase implements ArtifactModel {
+ private Map<String, Artifact> myArtifactsMap;
+ private Artifact[] myArtifactsArray;
+ public static final Condition<Artifact> VALID_ARTIFACT_CONDITION = new Condition<Artifact>() {
+ @Override
+ public boolean value(Artifact artifact) {
+ return !(artifact instanceof InvalidArtifact);
+ }
+ };
+
+ protected abstract List<? extends Artifact> getArtifactsList();
+
+ @NotNull
+ public Artifact[] getArtifacts() {
+ if (myArtifactsArray == null) {
+ final List<? extends Artifact> validArtifacts = ContainerUtil.findAll(getArtifactsList(), VALID_ARTIFACT_CONDITION);
+ myArtifactsArray = validArtifacts.toArray(new Artifact[validArtifacts.size()]);
+ }
+ return myArtifactsArray;
+ }
+
+ @Override
+ public List<? extends Artifact> getAllArtifactsIncludingInvalid() {
+ return Collections.unmodifiableList(getArtifactsList());
+ }
+
+ public Artifact findArtifact(@NotNull String name) {
+ if (myArtifactsMap == null) {
+ myArtifactsMap = new HashMap<String, Artifact>();
+ for (Artifact artifact : getArtifactsList()) {
+ myArtifactsMap.put(artifact.getName(), artifact);
+ }
+ }
+ return myArtifactsMap.get(name);
+ }
+
+ @NotNull
+ public Artifact getArtifactByOriginal(@NotNull Artifact artifact) {
+ return artifact;
+ }
+
+ @NotNull
+ public Artifact getOriginalArtifact(@NotNull Artifact artifact) {
+ return artifact;
+ }
+
+ public Collection<? extends Artifact> getArtifactsByType(@NotNull ArtifactType type) {
+ final List<Artifact> result = new ArrayList<Artifact>();
+ for (Artifact artifact : getArtifacts()) {
+ if (artifact.getArtifactType().equals(type)) {
+ result.add(artifact);
+ }
+ }
+ return result;
+ }
+
+ protected void artifactsChanged() {
+ myArtifactsMap = null;
+ myArtifactsArray = null;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactModelImpl.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactModelImpl.java
new file mode 100644
index 0000000..38f46db
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactModelImpl.java
@@ -0,0 +1,175 @@
+/*
+ * 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.packaging.artifacts.*;
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.util.EventDispatcher;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author nik
+ */
+public class ArtifactModelImpl extends ArtifactModelBase implements ModifiableArtifactModel {
+ private final List<ArtifactImpl> myOriginalArtifacts;
+ private final ArtifactManagerImpl myArtifactManager;
+ private final Map<ArtifactImpl, ArtifactImpl> myArtifact2ModifiableCopy = new HashMap<ArtifactImpl, ArtifactImpl>();
+ private final Map<ArtifactImpl, ArtifactImpl> myModifiable2Original = new HashMap<ArtifactImpl, ArtifactImpl>();
+ private final EventDispatcher<ArtifactListener> myDispatcher = EventDispatcher.create(ArtifactListener.class);
+
+ public ArtifactModelImpl(ArtifactManagerImpl artifactManager, List<ArtifactImpl> originalArtifacts) {
+ myArtifactManager = artifactManager;
+ myOriginalArtifacts = new ArrayList<ArtifactImpl>(originalArtifacts);
+ addListener(new ArtifactAdapter() {
+ @Override
+ public void artifactChanged(@NotNull Artifact artifact, @NotNull String oldName) {
+ artifactsChanged();
+ }
+ });
+ }
+
+ protected List<? extends Artifact> getArtifactsList() {
+ final List<ArtifactImpl> list = new ArrayList<ArtifactImpl>();
+ for (ArtifactImpl artifact : myOriginalArtifacts) {
+ final ArtifactImpl copy = myArtifact2ModifiableCopy.get(artifact);
+ if (copy != null) {
+ list.add(copy);
+ }
+ else {
+ list.add(artifact);
+ }
+ }
+ return list;
+ }
+
+ @NotNull
+ public ModifiableArtifact addArtifact(@NotNull final String name, @NotNull ArtifactType artifactType) {
+ return addArtifact(name, artifactType, artifactType.createRootElement(name));
+ }
+
+ @NotNull
+ public ModifiableArtifact addArtifact(@NotNull String name, @NotNull ArtifactType artifactType, CompositePackagingElement<?> rootElement) {
+ final String uniqueName = generateUniqueName(name);
+ final String outputPath = ArtifactUtil.getDefaultArtifactOutputPath(uniqueName, myArtifactManager.getProject());
+ final ArtifactImpl artifact = new ArtifactImpl(uniqueName, artifactType, false, rootElement, outputPath, myDispatcher);
+ myOriginalArtifacts.add(artifact);
+ myArtifact2ModifiableCopy.put(artifact, artifact);
+ myModifiable2Original.put(artifact, artifact);
+
+ artifactsChanged();
+ myDispatcher.getMulticaster().artifactAdded(artifact);
+ return artifact;
+ }
+
+ private String generateUniqueName(String baseName) {
+ String name = baseName;
+ int i = 2;
+ while (true) {
+ if (findArtifact(name) == null) {
+ return name;
+ }
+ name = baseName + i++;
+ }
+ }
+
+ public void addListener(@NotNull ArtifactListener listener) {
+ myDispatcher.addListener(listener);
+ }
+
+ public void removeListener(@NotNull ArtifactListener listener) {
+ myDispatcher.addListener(listener);
+ }
+
+ public void removeArtifact(@NotNull Artifact artifact) {
+ final ArtifactImpl artifactImpl = (ArtifactImpl)artifact;
+ ArtifactImpl original = myModifiable2Original.remove(artifactImpl);
+ if (original != null) {
+ myOriginalArtifacts.remove(original);
+ }
+ else {
+ original = artifactImpl;
+ }
+ myArtifact2ModifiableCopy.remove(original);
+ myOriginalArtifacts.remove(original);
+ artifactsChanged();
+ myDispatcher.getMulticaster().artifactRemoved(original);
+ }
+
+ @NotNull
+ public ModifiableArtifact getOrCreateModifiableArtifact(@NotNull Artifact artifact) {
+ final ArtifactImpl artifactImpl = (ArtifactImpl)artifact;
+ if (myModifiable2Original.containsKey(artifactImpl)) {
+ return artifactImpl;
+ }
+
+ ArtifactImpl modifiableCopy = myArtifact2ModifiableCopy.get(artifactImpl);
+ if (modifiableCopy == null) {
+ modifiableCopy = artifactImpl.createCopy(myDispatcher);
+ myDispatcher.getMulticaster().artifactChanged(modifiableCopy, artifact.getName());
+ myArtifact2ModifiableCopy.put(artifactImpl, modifiableCopy);
+ myModifiable2Original.put(modifiableCopy, artifactImpl);
+ artifactsChanged();
+ }
+ return modifiableCopy;
+ }
+
+ @NotNull
+ public Artifact getOriginalArtifact(@NotNull Artifact artifact) {
+ final ArtifactImpl original = myModifiable2Original.get(artifact);
+ return original != null ? original : artifact;
+ }
+
+ @NotNull
+ public ArtifactImpl getArtifactByOriginal(@NotNull Artifact artifact) {
+ final ArtifactImpl artifactImpl = (ArtifactImpl)artifact;
+ final ArtifactImpl copy = myArtifact2ModifiableCopy.get(artifactImpl);
+ return copy != null ? copy : artifactImpl;
+ }
+
+ public boolean isModified() {
+ return !myOriginalArtifacts.equals(myArtifactManager.getArtifactsList()) || !myArtifact2ModifiableCopy.isEmpty();
+ }
+
+ public void commit() {
+ myArtifactManager.commit(this);
+ }
+
+ public void dispose() {
+ List<Artifact> artifacts = new ArrayList<Artifact>();
+ for (ArtifactImpl artifact : myModifiable2Original.keySet()) {
+ if (myModifiable2Original.get(artifact).equals(artifact)) {
+ artifacts.add(artifact);
+ }
+ }
+ ((ArtifactPointerManagerImpl)ArtifactPointerManager.getInstance(myArtifactManager.getProject())).disposePointers(artifacts);
+ }
+
+ @Nullable
+ public ArtifactImpl getModifiableCopy(Artifact artifact) {
+ //noinspection SuspiciousMethodCalls
+ return myArtifact2ModifiableCopy.get(artifact);
+ }
+
+ public List<ArtifactImpl> getOriginalArtifacts() {
+ return myOriginalArtifacts;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactPointerImpl.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactPointerImpl.java
new file mode 100644
index 0000000..b2b60bb
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactPointerImpl.java
@@ -0,0 +1,70 @@
+/*
+ * 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.artifacts.ArtifactModel;
+import com.intellij.packaging.artifacts.ArtifactPointer;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author nik
+ */
+public class ArtifactPointerImpl implements ArtifactPointer {
+ private String myName;
+ private Artifact myArtifact;
+
+ public ArtifactPointerImpl(@NotNull String name) {
+ myName = name;
+ }
+
+ public ArtifactPointerImpl(@NotNull Artifact artifact) {
+ myArtifact = artifact;
+ myName = artifact.getName();
+ }
+
+ @NotNull
+ public String getArtifactName() {
+ return myName;
+ }
+
+ public Artifact getArtifact() {
+ return myArtifact;
+ }
+
+ @NotNull
+ public String getArtifactName(@NotNull ArtifactModel artifactModel) {
+ if (myArtifact != null) {
+ return artifactModel.getArtifactByOriginal(myArtifact).getName();
+ }
+ return myName;
+ }
+
+ public Artifact findArtifact(@NotNull ArtifactModel artifactModel) {
+ if (myArtifact != null) {
+ return artifactModel.getArtifactByOriginal(myArtifact);
+ }
+ return artifactModel.findArtifact(myName);
+ }
+
+ void setArtifact(Artifact artifact) {
+ myArtifact = artifact;
+ }
+
+ void setName(String name) {
+ myName = name;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactPointerManagerImpl.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactPointerManagerImpl.java
new file mode 100644
index 0000000..cc9d5db
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactPointerManagerImpl.java
@@ -0,0 +1,117 @@
+/*
+ * 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.packaging.artifacts.*;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author nik
+ */
+public class ArtifactPointerManagerImpl extends ArtifactPointerManager {
+ private final Map<String, ArtifactPointerImpl> myUnresolvedPointers = new HashMap<String, ArtifactPointerImpl>();
+ private final Map<Artifact, ArtifactPointerImpl> myPointers = new HashMap<Artifact, ArtifactPointerImpl>();
+ private ArtifactManager myArtifactManager;
+
+ public ArtifactPointerManagerImpl(Project project) {
+ project.getMessageBus().connect().subscribe(ArtifactManager.TOPIC, new ArtifactAdapter() {
+ @Override
+ public void artifactRemoved(@NotNull Artifact artifact) {
+ disposePointer(artifact);
+ }
+
+ @Override
+ public void artifactAdded(@NotNull Artifact artifact) {
+ final ArtifactPointerImpl pointer = myPointers.get(artifact);
+ if (pointer != null) {
+ pointer.setName(artifact.getName());
+ }
+
+ final ArtifactPointerImpl unresolved = myUnresolvedPointers.remove(artifact.getName());
+ if (unresolved != null) {
+ unresolved.setArtifact(artifact);
+ if (pointer == null) {
+ myPointers.put(artifact, unresolved);
+ }
+ }
+ }
+
+ @Override
+ public void artifactChanged(@NotNull Artifact artifact, @NotNull String oldName) {
+ artifactAdded(artifact);
+ }
+ });
+ }
+
+ public void setArtifactManager(ArtifactManager artifactManager) {
+ myArtifactManager = artifactManager;
+ }
+
+ private void disposePointer(Artifact artifact) {
+ final ArtifactPointerImpl pointer = myPointers.remove(artifact);
+ if (pointer != null) {
+ pointer.setArtifact(null);
+ myUnresolvedPointers.put(pointer.getArtifactName(), pointer);
+ }
+ }
+
+ public ArtifactPointer createPointer(@NotNull String name) {
+ if (myArtifactManager != null) {
+ final Artifact artifact = myArtifactManager.findArtifact(name);
+ if (artifact != null) {
+ return createPointer(artifact);
+ }
+ }
+
+ ArtifactPointerImpl pointer = myUnresolvedPointers.get(name);
+ if (pointer == null) {
+ pointer = new ArtifactPointerImpl(name);
+ myUnresolvedPointers.put(name, pointer);
+ }
+ return pointer;
+ }
+
+ public ArtifactPointer createPointer(@NotNull Artifact artifact) {
+ ArtifactPointerImpl pointer = myPointers.get(artifact);
+ if (pointer == null) {
+ pointer = myUnresolvedPointers.get(artifact.getName());
+ if (pointer != null) {
+ pointer.setArtifact(artifact);
+ }
+ else {
+ pointer = new ArtifactPointerImpl(artifact);
+ }
+ myPointers.put(artifact, pointer);
+ }
+ return pointer;
+ }
+
+ @Override
+ public ArtifactPointer createPointer(@NotNull Artifact artifact, @NotNull ArtifactModel artifactModel) {
+ return createPointer(artifactModel.getOriginalArtifact(artifact));
+ }
+
+ public void disposePointers(List<Artifact> artifacts) {
+ for (Artifact artifact : artifacts) {
+ disposePointer(artifact);
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactSortingUtil.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactSortingUtil.java
new file mode 100644
index 0000000..b3fe783
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactSortingUtil.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.openapi.project.Project;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author nik
+ */
+public abstract class ArtifactSortingUtil {
+ public static ArtifactSortingUtil getInstance(@NotNull Project project) {
+ return ServiceManager.getService(project, ArtifactSortingUtil.class);
+ }
+
+ public abstract Map<String, String> getArtifactToSelfIncludingNameMap();
+
+ public abstract List<String> getArtifactsSortedByInclusion();
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactSortingUtilImpl.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactSortingUtilImpl.java
new file mode 100644
index 0000000..e06f9de
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactSortingUtilImpl.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.artifacts.ArtifactManager;
+import com.intellij.packaging.elements.PackagingElementResolvingContext;
+import com.intellij.packaging.impl.elements.ArtifactElementType;
+import com.intellij.packaging.impl.elements.ArtifactPackagingElement;
+import com.intellij.psi.util.CachedValue;
+import com.intellij.psi.util.CachedValueProvider;
+import com.intellij.psi.util.CachedValuesManager;
+import com.intellij.util.graph.CachingSemiGraph;
+import com.intellij.util.graph.DFSTBuilder;
+import com.intellij.util.graph.GraphGenerator;
+import gnu.trove.TIntArrayList;
+import gnu.trove.TIntProcedure;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.*;
+
+/**
+ * @author nik
+ */
+public class ArtifactSortingUtilImpl extends ArtifactSortingUtil {
+ private final Project myProject;
+ private CachedValue<Map<String, String>> myArtifactToSelfIncludingName;
+ private CachedValue<List<String>> mySortedArtifacts;
+
+ public ArtifactSortingUtilImpl(Project project) {
+ myProject = project;
+ }
+
+ @Override
+ public Map<String, String> getArtifactToSelfIncludingNameMap() {
+ if (myArtifactToSelfIncludingName == null) {
+ myArtifactToSelfIncludingName = CachedValuesManager.getManager(myProject).createCachedValue(new CachedValueProvider<Map<String, String>>() {
+ public Result<Map<String, String>> compute() {
+ return Result.create(computeArtifactToSelfIncludingNameMap(), ArtifactManager.getInstance(myProject).getModificationTracker());
+ }
+ }, false);
+ }
+ return myArtifactToSelfIncludingName.getValue();
+ }
+
+ @Override
+ public List<String> getArtifactsSortedByInclusion() {
+ if (mySortedArtifacts == null) {
+ mySortedArtifacts = CachedValuesManager.getManager(myProject).createCachedValue(new CachedValueProvider<List<String>>() {
+ @Override
+ public Result<List<String>> compute() {
+ return Result.create(doGetSortedArtifacts(), ArtifactManager.getInstance(myProject).getModificationTracker());
+ }
+ }, false);
+ }
+ return mySortedArtifacts.getValue();
+ }
+
+ private List<String> doGetSortedArtifacts() {
+ GraphGenerator<String> graph = createArtifactsGraph();
+ DFSTBuilder<String> builder = new DFSTBuilder<String>(graph);
+ builder.buildDFST();
+ List<String> names = new ArrayList<String>();
+ names.addAll(graph.getNodes());
+ Collections.sort(names, builder.comparator());
+ return names;
+ }
+
+ private Map<String, String> computeArtifactToSelfIncludingNameMap() {
+ final Map<String, String> result = new HashMap<String, String>();
+ final GraphGenerator<String> graph = createArtifactsGraph();
+ for (String artifactName : graph.getNodes()) {
+ final Iterator<String> in = graph.getIn(artifactName);
+ while (in.hasNext()) {
+ String next = in.next();
+ if (next.equals(artifactName)) {
+ result.put(artifactName, artifactName);
+ break;
+ }
+ }
+ }
+
+ final DFSTBuilder<String> builder = new DFSTBuilder<String>(graph);
+ builder.buildDFST();
+ if (builder.isAcyclic() && result.isEmpty()) return Collections.emptyMap();
+
+ final TIntArrayList sccs = builder.getSCCs();
+ sccs.forEach(new TIntProcedure() {
+ int myTNumber = 0;
+ public boolean execute(int size) {
+ if (size > 1) {
+ for (int j = 0; j < size; j++) {
+ final String artifactName = builder.getNodeByTNumber(myTNumber + j);
+ result.put(artifactName, artifactName);
+ }
+ }
+ myTNumber += size;
+ return true;
+ }
+ });
+
+ for (int i = 0; i < graph.getNodes().size(); i++) {
+ final String artifactName = builder.getNodeByTNumber(i);
+ if (!result.containsKey(artifactName)) {
+ final Iterator<String> in = graph.getIn(artifactName);
+ while (in.hasNext()) {
+ final String name = result.get(in.next());
+ if (name != null) {
+ result.put(artifactName, name);
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private GraphGenerator<String> createArtifactsGraph() {
+ final ArtifactManager artifactManager = ArtifactManager.getInstance(myProject);
+ return GraphGenerator.create(CachingSemiGraph.create(new ArtifactsGraph(artifactManager)));
+ }
+
+ private class ArtifactsGraph implements GraphGenerator.SemiGraph<String> {
+ private final ArtifactManager myArtifactManager;
+ private final Set<String> myArtifactNames;
+
+ public ArtifactsGraph(ArtifactManager artifactManager) {
+ myArtifactManager = artifactManager;
+ myArtifactNames = new LinkedHashSet<String>();
+ for (Artifact artifact : myArtifactManager.getSortedArtifacts()) {
+ myArtifactNames.add(artifact.getName());
+ }
+ }
+
+ @Override
+ public Collection<String> getNodes() {
+ return myArtifactNames;
+ }
+
+ @Override
+ public Iterator<String> getIn(String name) {
+ final Set<String> included = new LinkedHashSet<String>();
+ final PackagingElementResolvingContext context = myArtifactManager.getResolvingContext();
+ final Artifact artifact = context.getArtifactModel().findArtifact(name);
+ if (artifact != null) {
+ ArtifactUtil.processPackagingElements(artifact, ArtifactElementType.ARTIFACT_ELEMENT_TYPE, new PackagingElementProcessor<ArtifactPackagingElement>() {
+ @Override
+ public boolean process(@NotNull ArtifactPackagingElement element,
+ @NotNull PackagingElementPath path) {
+ final String artifactName = element.getArtifactName();
+ if (myArtifactNames.contains(artifactName)) {
+ included.add(artifactName);
+ }
+ return true;
+ }
+ }, context, false);
+ }
+ return included.iterator();
+ }
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactUtil.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactUtil.java
new file mode 100644
index 0000000..0a272e8
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactUtil.java
@@ -0,0 +1,565 @@
+/*
+ * 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.compiler.CompilerConfiguration;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.CompilerProjectExtension;
+import com.intellij.openapi.util.Condition;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.Trinity;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.artifacts.ArtifactManager;
+import com.intellij.packaging.artifacts.ArtifactProperties;
+import com.intellij.packaging.artifacts.ArtifactType;
+import com.intellij.packaging.elements.*;
+import com.intellij.packaging.impl.elements.*;
+import com.intellij.util.PathUtil;
+import com.intellij.util.Processor;
+import com.intellij.util.SmartList;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.FList;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+/**
+ * @author nik
+ */
+public class ArtifactUtil {
+ private ArtifactUtil() {
+ }
+
+ public static CompositePackagingElement<?> copyFromRoot(@NotNull CompositePackagingElement<?> oldRoot, @NotNull Project project) {
+ final CompositePackagingElement<?> newRoot = (CompositePackagingElement<?>)copyElement(oldRoot, project);
+ copyChildren(oldRoot, newRoot, project);
+ return newRoot;
+ }
+
+
+ public static void copyChildren(CompositePackagingElement<?> oldParent, CompositePackagingElement<?> newParent, @NotNull Project project) {
+ for (PackagingElement<?> child : oldParent.getChildren()) {
+ newParent.addOrFindChild(copyWithChildren(child, project));
+ }
+ }
+
+ @NotNull
+ public static <S> PackagingElement<S> copyWithChildren(@NotNull PackagingElement<S> element, @NotNull Project project) {
+ final PackagingElement<S> copy = copyElement(element, project);
+ if (element instanceof CompositePackagingElement<?>) {
+ copyChildren((CompositePackagingElement<?>)element, (CompositePackagingElement<?>)copy, project);
+ }
+ return copy;
+ }
+
+ @NotNull
+ private static <S> PackagingElement<S> copyElement(@NotNull PackagingElement<S> element, @NotNull Project project) {
+ //noinspection unchecked
+ final PackagingElement<S> copy = (PackagingElement<S>)element.getType().createEmpty(project);
+ copy.loadState(element.getState());
+ return copy;
+ }
+
+ public static <E extends PackagingElement<?>> boolean processPackagingElements(@NotNull Artifact artifact, @Nullable PackagingElementType<E> type,
+ @NotNull final Processor<? super E> processor,
+ final @NotNull PackagingElementResolvingContext resolvingContext,
+ final boolean processSubstitutions) {
+ return processPackagingElements(artifact, type, new PackagingElementProcessor<E>() {
+ @Override
+ public boolean process(@NotNull E e, @NotNull PackagingElementPath path) {
+ return processor.process(e);
+ }
+ }, resolvingContext, processSubstitutions);
+ }
+
+ public static <E extends PackagingElement<?>> boolean processPackagingElements(@NotNull Artifact artifact, @Nullable PackagingElementType<E> type,
+ @NotNull PackagingElementProcessor<? super E> processor,
+ final @NotNull PackagingElementResolvingContext resolvingContext,
+ final boolean processSubstitutions) {
+ return processPackagingElements(artifact.getRootElement(), type, processor, resolvingContext, processSubstitutions, artifact.getArtifactType());
+ }
+
+ public static <E extends PackagingElement<?>> boolean processPackagingElements(final PackagingElement<?> rootElement, @Nullable PackagingElementType<E> type,
+ @NotNull PackagingElementProcessor<? super E> processor,
+ final @NotNull PackagingElementResolvingContext resolvingContext,
+ final boolean processSubstitutions,
+ final ArtifactType artifactType) {
+ return processElementRecursively(rootElement, type, processor, resolvingContext, processSubstitutions, artifactType,
+ PackagingElementPath.EMPTY, new HashSet<PackagingElement<?>>());
+ }
+
+ private static <E extends PackagingElement<?>> boolean processElementsRecursively(final List<? extends PackagingElement<?>> elements,
+ @Nullable PackagingElementType<E> type,
+ @NotNull PackagingElementProcessor<? super E> processor,
+ final @NotNull PackagingElementResolvingContext resolvingContext,
+ final boolean processSubstitutions, ArtifactType artifactType,
+ @NotNull PackagingElementPath path,
+ Set<PackagingElement<?>> processed) {
+ for (PackagingElement<?> element : elements) {
+ if (!processElementRecursively(element, type, processor, resolvingContext, processSubstitutions, artifactType, path, processed)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static <E extends PackagingElement<?>> boolean processElementRecursively(@NotNull PackagingElement<?> element, @Nullable PackagingElementType<E> type,
+ @NotNull PackagingElementProcessor<? super E> processor,
+ @NotNull PackagingElementResolvingContext resolvingContext,
+ final boolean processSubstitutions,
+ ArtifactType artifactType,
+ @NotNull PackagingElementPath path, Set<PackagingElement<?>> processed) {
+ if (!processor.shouldProcess(element) || !processed.add(element)) {
+ return true;
+ }
+ if (type == null || element.getType().equals(type)) {
+ if (!processor.process((E)element, path)) {
+ return false;
+ }
+ }
+ if (element instanceof CompositePackagingElement<?>) {
+ final CompositePackagingElement<?> composite = (CompositePackagingElement<?>)element;
+ return processElementsRecursively(composite.getChildren(), type, processor, resolvingContext, processSubstitutions, artifactType,
+ path.appendComposite(composite), processed);
+ }
+ else if (element instanceof ComplexPackagingElement<?> && processSubstitutions) {
+ final ComplexPackagingElement<?> complexElement = (ComplexPackagingElement<?>)element;
+ if (processor.shouldProcessSubstitution(complexElement)) {
+ final List<? extends PackagingElement<?>> substitution = complexElement.getSubstitution(resolvingContext, artifactType);
+ if (substitution != null) {
+ return processElementsRecursively(substitution, type, processor, resolvingContext, processSubstitutions, artifactType,
+ path.appendComplex(complexElement), processed);
+ }
+ }
+ }
+ return true;
+ }
+
+ public static void removeDuplicates(@NotNull CompositePackagingElement<?> parent) {
+ List<PackagingElement<?>> prevChildren = new ArrayList<PackagingElement<?>>();
+
+ List<PackagingElement<?>> toRemove = new ArrayList<PackagingElement<?>>();
+ for (PackagingElement<?> child : parent.getChildren()) {
+ if (child instanceof CompositePackagingElement<?>) {
+ removeDuplicates((CompositePackagingElement<?>)child);
+ }
+ boolean merged = false;
+ for (PackagingElement<?> prevChild : prevChildren) {
+ if (child.isEqualTo(prevChild)) {
+ if (child instanceof CompositePackagingElement<?>) {
+ for (PackagingElement<?> childElement : ((CompositePackagingElement<?>)child).getChildren()) {
+ ((CompositePackagingElement<?>)prevChild).addOrFindChild(childElement);
+ }
+ }
+ merged = true;
+ break;
+ }
+ }
+ if (merged) {
+ toRemove.add(child);
+ }
+ else {
+ prevChildren.add(child);
+ }
+ }
+
+ for (PackagingElement<?> child : toRemove) {
+ parent.removeChild(child);
+ }
+ }
+
+ public static <S> void copyProperties(ArtifactProperties<?> from, ArtifactProperties<S> to) {
+ //noinspection unchecked
+ to.loadState((S)from.getState());
+ }
+
+ @Nullable
+ public static String getDefaultArtifactOutputPath(@NotNull String artifactName, final @NotNull Project project) {
+ final CompilerProjectExtension extension = CompilerProjectExtension.getInstance(project);
+ if (extension == null) return null;
+ String outputUrl = extension.getCompilerOutputUrl();
+ if (outputUrl == null || outputUrl.length() == 0) {
+ final VirtualFile baseDir = project.getBaseDir();
+ if (baseDir == null) return null;
+ outputUrl = baseDir.getUrl() + "/out";
+ }
+ return VfsUtil.urlToPath(outputUrl) + "/artifacts/" + FileUtil.sanitizeFileName(artifactName);
+ }
+
+ public static <E extends PackagingElement<?>> boolean processElementsWithSubstitutions(@NotNull List<? extends PackagingElement<?>> elements,
+ @NotNull PackagingElementResolvingContext context,
+ @NotNull ArtifactType artifactType,
+ @NotNull PackagingElementPath parentPath,
+ @NotNull PackagingElementProcessor<E> processor) {
+ return processElementsWithSubstitutions(elements, context, artifactType, parentPath, processor, new HashSet<PackagingElement<?>>());
+ }
+
+ private static <E extends PackagingElement<?>> boolean processElementsWithSubstitutions(@NotNull List<? extends PackagingElement<?>> elements,
+ @NotNull PackagingElementResolvingContext context,
+ @NotNull ArtifactType artifactType,
+ @NotNull PackagingElementPath parentPath,
+ @NotNull PackagingElementProcessor<E> processor,
+ final Set<PackagingElement<?>> processed) {
+ for (PackagingElement<?> element : elements) {
+ if (!processed.add(element)) {
+ continue;
+ }
+
+ if (element instanceof ComplexPackagingElement<?> && processor.shouldProcessSubstitution((ComplexPackagingElement)element)) {
+ final ComplexPackagingElement<?> complexElement = (ComplexPackagingElement<?>)element;
+ final List<? extends PackagingElement<?>> substitution = complexElement.getSubstitution(context, artifactType);
+ if (substitution != null &&
+ !processElementsWithSubstitutions(substitution, context, artifactType, parentPath.appendComplex(complexElement), processor, processed)) {
+ return false;
+ }
+ }
+ else if (!processor.process((E)element, parentPath)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static List<PackagingElement<?>> findByRelativePath(@NotNull CompositePackagingElement<?> parent, @NotNull String relativePath,
+ @NotNull PackagingElementResolvingContext context, @NotNull ArtifactType artifactType) {
+ final List<PackagingElement<?>> result = new ArrayList<PackagingElement<?>>();
+ processElementsByRelativePath(parent, relativePath, context, artifactType, PackagingElementPath.EMPTY, new PackagingElementProcessor<PackagingElement<?>>() {
+ @Override
+ public boolean process(@NotNull PackagingElement<?> packagingElement, @NotNull PackagingElementPath path) {
+ result.add(packagingElement);
+ return true;
+ }
+ });
+ return result;
+ }
+
+ public static boolean processElementsByRelativePath(@NotNull final CompositePackagingElement<?> parent, @NotNull String relativePath,
+ @NotNull final PackagingElementResolvingContext context, @NotNull final ArtifactType artifactType,
+ @NotNull PackagingElementPath parentPath,
+ @NotNull final PackagingElementProcessor<PackagingElement<?>> processor) {
+ relativePath = StringUtil.trimStart(relativePath, "/");
+ if (relativePath.length() == 0) {
+ return true;
+ }
+
+ int i = relativePath.indexOf('/');
+ final String firstName = i != -1 ? relativePath.substring(0, i) : relativePath;
+ final String tail = i != -1 ? relativePath.substring(i+1) : "";
+
+ return processElementsWithSubstitutions(parent.getChildren(), context, artifactType, parentPath.appendComposite(parent), new PackagingElementProcessor<PackagingElement<?>>() {
+ @Override
+ public boolean process(@NotNull PackagingElement<?> element, @NotNull PackagingElementPath path) {
+ boolean process = false;
+ if (element instanceof CompositePackagingElement && firstName.equals(((CompositePackagingElement<?>)element).getName())) {
+ process = true;
+ }
+ else if (element instanceof FileCopyPackagingElement) {
+ final FileCopyPackagingElement fileCopy = (FileCopyPackagingElement)element;
+ if (firstName.equals(fileCopy.getOutputFileName())) {
+ process = true;
+ }
+ }
+
+ if (process) {
+ if (tail.length() == 0) {
+ if (!processor.process(element, path)) return false;
+ }
+ else if (element instanceof CompositePackagingElement<?>) {
+ return processElementsByRelativePath((CompositePackagingElement)element, tail, context, artifactType, path, processor);
+ }
+ }
+ return true;
+ }
+ });
+ }
+
+ public static boolean processDirectoryChildren(@NotNull CompositePackagingElement<?> parent,
+ @NotNull PackagingElementPath pathToParent,
+ @NotNull String relativePath,
+ @NotNull final PackagingElementResolvingContext context,
+ @NotNull final ArtifactType artifactType,
+ @NotNull final PackagingElementProcessor<PackagingElement<?>> processor) {
+ return processElementsByRelativePath(parent, relativePath, context, artifactType, pathToParent, new PackagingElementProcessor<PackagingElement<?>>() {
+ @Override
+ public boolean process(@NotNull PackagingElement<?> element, @NotNull PackagingElementPath path) {
+ if (element instanceof DirectoryPackagingElement) {
+ final List<PackagingElement<?>> children = ((DirectoryPackagingElement)element).getChildren();
+ if (!processElementsWithSubstitutions(children, context, artifactType, path.appendComposite((DirectoryPackagingElement)element), processor)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ });
+ }
+
+ public static void processFileOrDirectoryCopyElements(Artifact artifact,
+ PackagingElementProcessor<FileOrDirectoryCopyPackagingElement<?>> processor,
+ PackagingElementResolvingContext context,
+ boolean processSubstitutions) {
+ processPackagingElements(artifact, PackagingElementFactoryImpl.FILE_COPY_ELEMENT_TYPE, processor, context, processSubstitutions);
+ processPackagingElements(artifact, PackagingElementFactoryImpl.DIRECTORY_COPY_ELEMENT_TYPE, processor, context, processSubstitutions);
+ processPackagingElements(artifact, PackagingElementFactoryImpl.EXTRACTED_DIRECTORY_ELEMENT_TYPE, processor, context, processSubstitutions);
+ }
+
+ public static Collection<Trinity<Artifact, PackagingElementPath, String>> findContainingArtifactsWithOutputPaths(@NotNull final VirtualFile file,
+ @NotNull Project project,
+ final Artifact[] artifacts) {
+ final boolean isResourceFile = CompilerConfiguration.getInstance(project).isResourceFile(file);
+ final List<Trinity<Artifact, PackagingElementPath, String>> result = new ArrayList<Trinity<Artifact, PackagingElementPath, String>>();
+ final PackagingElementResolvingContext context = ArtifactManager.getInstance(project).getResolvingContext();
+ for (final Artifact artifact : artifacts) {
+ processPackagingElements(artifact, null, new PackagingElementProcessor<PackagingElement<?>>() {
+ @Override
+ public boolean process(@NotNull PackagingElement<?> element, @NotNull PackagingElementPath path) {
+ if (element instanceof FileOrDirectoryCopyPackagingElement<?>) {
+ final VirtualFile root = ((FileOrDirectoryCopyPackagingElement)element).findFile();
+ if (root != null && VfsUtil.isAncestor(root, file, false)) {
+ final String relativePath;
+ if (root.equals(file) && element instanceof FileCopyPackagingElement) {
+ relativePath = ((FileCopyPackagingElement)element).getOutputFileName();
+ }
+ else {
+ relativePath = VfsUtilCore.getRelativePath(file, root, '/');
+ }
+ result.add(Trinity.create(artifact, path, relativePath));
+ return false;
+ }
+ }
+ else if (isResourceFile && element instanceof ModuleOutputPackagingElement) {
+ final String relativePath = getRelativePathInSources(file, (ModuleOutputPackagingElement)element, context);
+ if (relativePath != null) {
+ result.add(Trinity.create(artifact, path, relativePath));
+ return false;
+ }
+ }
+ return true;
+ }
+ }, context, true);
+ }
+ return result;
+ }
+
+ @Nullable
+ private static String getRelativePathInSources(@NotNull VirtualFile file, final @NotNull ModuleOutputPackagingElement moduleElement,
+ @NotNull PackagingElementResolvingContext context) {
+ for (VirtualFile sourceRoot : moduleElement.getSourceRoots(context)) {
+ if (VfsUtil.isAncestor(sourceRoot, file, true)) {
+ return VfsUtilCore.getRelativePath(file, sourceRoot, '/');
+ }
+ }
+ return null;
+ }
+
+ @Nullable
+ public static VirtualFile findSourceFileByOutputPath(Artifact artifact, String outputPath, PackagingElementResolvingContext context) {
+ final List<VirtualFile> files = findSourceFilesByOutputPath(artifact.getRootElement(), outputPath, context, artifact.getArtifactType());
+ return files.isEmpty() ? null : files.get(0);
+ }
+
+ @Nullable
+ public static VirtualFile findSourceFileByOutputPath(CompositePackagingElement<?> parent, String outputPath,
+ PackagingElementResolvingContext context, ArtifactType artifactType) {
+ final List<VirtualFile> files = findSourceFilesByOutputPath(parent, outputPath, context, artifactType);
+ return files.isEmpty() ? null : files.get(0);
+ }
+
+ public static List<VirtualFile> findSourceFilesByOutputPath(CompositePackagingElement<?> parent, final String outputPath,
+ final PackagingElementResolvingContext context, final ArtifactType artifactType) {
+ final String path = StringUtil.trimStart(outputPath, "/");
+ if (path.length() == 0) {
+ return Collections.emptyList();
+ }
+
+ int i = path.indexOf('/');
+ final String firstName = i != -1 ? path.substring(0, i) : path;
+ final String tail = i != -1 ? path.substring(i+1) : "";
+
+ final List<VirtualFile> result = new SmartList<VirtualFile>();
+ processElementsWithSubstitutions(parent.getChildren(), context, artifactType, PackagingElementPath.EMPTY, new PackagingElementProcessor<PackagingElement<?>>() {
+ @Override
+ public boolean process(@NotNull PackagingElement<?> element, @NotNull PackagingElementPath elementPath) {
+ //todo[nik] replace by method findSourceFile() in PackagingElement
+ if (element instanceof CompositePackagingElement) {
+ final CompositePackagingElement<?> compositeElement = (CompositePackagingElement<?>)element;
+ if (firstName.equals(compositeElement.getName())) {
+ result.addAll(findSourceFilesByOutputPath(compositeElement, tail, context, artifactType));
+ }
+ }
+ else if (element instanceof FileCopyPackagingElement) {
+ final FileCopyPackagingElement fileCopyElement = (FileCopyPackagingElement)element;
+ if (firstName.equals(fileCopyElement.getOutputFileName()) && tail.length() == 0) {
+ ContainerUtil.addIfNotNull(fileCopyElement.findFile(), result);
+ }
+ }
+ else if (element instanceof DirectoryCopyPackagingElement || element instanceof ExtractedDirectoryPackagingElement) {
+ final VirtualFile sourceRoot = ((FileOrDirectoryCopyPackagingElement<?>)element).findFile();
+ if (sourceRoot != null) {
+ ContainerUtil.addIfNotNull(sourceRoot.findFileByRelativePath(path), result);
+ }
+ }
+ else if (element instanceof ModuleOutputPackagingElement) {
+ final CompilerConfiguration compilerConfiguration = CompilerConfiguration.getInstance(context.getProject());
+ for (VirtualFile sourceRoot : ((ModuleOutputPackagingElement)element).getSourceRoots(context)) {
+ final VirtualFile sourceFile = sourceRoot.findFileByRelativePath(path);
+ if (sourceFile != null && compilerConfiguration.isResourceFile(sourceFile)) {
+ result.add(sourceFile);
+ }
+ }
+ }
+ return true;
+ }
+ });
+
+ return result;
+ }
+
+ public static boolean processParents(@NotNull Artifact artifact,
+ @NotNull PackagingElementResolvingContext context,
+ @NotNull ParentElementProcessor processor,
+ int maxLevel) {
+ return processParents(artifact, context, processor, FList.<Pair<Artifact, CompositePackagingElement<?>>>emptyList(), maxLevel,
+ new HashSet<Artifact>());
+ }
+
+ private static boolean processParents(@NotNull final Artifact artifact, @NotNull final PackagingElementResolvingContext context,
+ @NotNull final ParentElementProcessor processor, FList<Pair<Artifact, CompositePackagingElement<?>>> pathToElement,
+ final int maxLevel, final Set<Artifact> processed) {
+ if (!processed.add(artifact)) return true;
+
+ final FList<Pair<Artifact, CompositePackagingElement<?>>> pathFromRoot;
+ final CompositePackagingElement<?> rootElement = artifact.getRootElement();
+ if (rootElement instanceof ArtifactRootElement<?>) {
+ pathFromRoot = pathToElement;
+ }
+ else {
+ if (!processor.process(rootElement, pathToElement, artifact)) {
+ return false;
+ }
+ pathFromRoot = pathToElement.prepend(new Pair<Artifact, CompositePackagingElement<?>>(artifact, rootElement));
+ }
+ if (pathFromRoot.size() > maxLevel) return true;
+
+ for (final Artifact anArtifact : context.getArtifactModel().getArtifacts()) {
+ if (processed.contains(anArtifact)) continue;
+
+ final PackagingElementProcessor<ArtifactPackagingElement> elementProcessor =
+ new PackagingElementProcessor<ArtifactPackagingElement>() {
+ @Override
+ public boolean shouldProcessSubstitution(ComplexPackagingElement<?> element) {
+ return !(element instanceof ArtifactPackagingElement);
+ }
+
+ @Override
+ public boolean process(@NotNull ArtifactPackagingElement element, @NotNull PackagingElementPath path) {
+ if (artifact.getName().equals(element.getArtifactName())) {
+ FList<Pair<Artifact, CompositePackagingElement<?>>> currentPath = pathFromRoot;
+ final List<CompositePackagingElement<?>> parents = path.getParents();
+ for (int i = 0, parentsSize = parents.size(); i < parentsSize - 1; i++) {
+ CompositePackagingElement<?> parent = parents.get(i);
+ if (!processor.process(parent, currentPath, anArtifact)) {
+ return false;
+ }
+ currentPath = currentPath.prepend(new Pair<Artifact, CompositePackagingElement<?>>(anArtifact, parent));
+ if (currentPath.size() > maxLevel) {
+ return true;
+ }
+ }
+
+ if (!parents.isEmpty()) {
+ CompositePackagingElement<?> lastParent = parents.get(parents.size() - 1);
+ if (lastParent instanceof ArtifactRootElement<?> && !processor.process(lastParent, currentPath, anArtifact)) {
+ return false;
+ }
+ }
+ return processParents(anArtifact, context, processor, currentPath, maxLevel, processed);
+ }
+ return true;
+ }
+ };
+ if (!processPackagingElements(anArtifact, ArtifactElementType.ARTIFACT_ELEMENT_TYPE, elementProcessor, context, true)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static boolean isArchiveName(String name) {
+ return name.length() >= 4 && name.charAt(name.length() - 4) == '.' && StringUtil.endsWithIgnoreCase(name, "ar");
+ }
+
+ public static void removeChildrenRecursively(@NotNull CompositePackagingElement<?> element, @NotNull Condition<PackagingElement<?>> condition) {
+ List<PackagingElement<?>> toRemove = new ArrayList<PackagingElement<?>>();
+ for (PackagingElement<?> child : element.getChildren()) {
+ if (child instanceof CompositePackagingElement<?>) {
+ final CompositePackagingElement<?> compositeChild = (CompositePackagingElement<?>)child;
+ removeChildrenRecursively(compositeChild, condition);
+ if (compositeChild.getChildren().isEmpty()) {
+ toRemove.add(child);
+ }
+ }
+ else if (condition.value(child)) {
+ toRemove.add(child);
+ }
+ }
+
+ element.removeChildren(toRemove);
+ }
+
+ public static boolean shouldClearArtifactOutputBeforeRebuild(Artifact artifact) {
+ final String outputPath = artifact.getOutputPath();
+ return !StringUtil.isEmpty(outputPath) && artifact.getRootElement() instanceof ArtifactRootElement<?>;
+ }
+
+ public static Set<Module> getModulesIncludedInArtifacts(final @NotNull Collection<? extends Artifact> artifacts, final @NotNull Project project) {
+ final Set<Module> modules = new HashSet<Module>();
+ final PackagingElementResolvingContext resolvingContext = ArtifactManager.getInstance(project).getResolvingContext();
+ for (Artifact artifact : artifacts) {
+ processPackagingElements(artifact, null, new Processor<PackagingElement<?>>() {
+ @Override
+ public boolean process(PackagingElement<?> element) {
+ if (element instanceof ModuleOutputPackagingElement) {
+ ContainerUtil.addIfNotNull(modules, ((ModuleOutputPackagingElement)element).findModule(resolvingContext));
+ }
+ return true;
+ }
+ }, resolvingContext, true);
+ }
+ return modules;
+ }
+
+ public static List<Artifact> getArtifactWithOutputPaths(Project project) {
+ final List<Artifact> result = new ArrayList<Artifact>();
+ for (Artifact artifact : ArtifactManager.getInstance(project).getSortedArtifacts()) {
+ if (!StringUtil.isEmpty(artifact.getOutputPath())) {
+ result.add(artifact);
+ }
+ }
+ return result;
+ }
+
+ public static String suggestArtifactFileName(String artifactName) {
+ return PathUtil.suggestFileName(artifactName, true, true);
+ }
+}
+
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactVirtualFileListener.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactVirtualFileListener.java
new file mode 100644
index 0000000..641bae7
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ArtifactVirtualFileListener.java
@@ -0,0 +1,109 @@
+/*
+ * 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.MultiValuesMap;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileAdapter;
+import com.intellij.openapi.vfs.VirtualFileMoveEvent;
+import com.intellij.openapi.vfs.VirtualFilePropertyEvent;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.artifacts.ModifiableArtifactModel;
+import com.intellij.packaging.impl.elements.FileOrDirectoryCopyPackagingElement;
+import com.intellij.psi.util.CachedValue;
+import com.intellij.psi.util.CachedValueProvider;
+import com.intellij.psi.util.CachedValuesManager;
+import com.intellij.util.PathUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+
+/**
+ * @author nik
+ */
+public class ArtifactVirtualFileListener extends VirtualFileAdapter {
+ private final CachedValue<MultiValuesMap<String, Artifact>> myParentPathsToArtifacts;
+ private final ArtifactManagerImpl myArtifactManager;
+
+ public ArtifactVirtualFileListener(Project project, final ArtifactManagerImpl artifactManager) {
+ myArtifactManager = artifactManager;
+ myParentPathsToArtifacts =
+ CachedValuesManager.getManager(project).createCachedValue(new CachedValueProvider<MultiValuesMap<String, Artifact>>() {
+ public Result<MultiValuesMap<String, Artifact>> compute() {
+ MultiValuesMap<String, Artifact> result = computeParentPathToArtifactMap();
+ return Result.createSingleDependency(result, artifactManager.getModificationTracker());
+ }
+ }, false);
+ }
+
+ private MultiValuesMap<String, Artifact> computeParentPathToArtifactMap() {
+ final MultiValuesMap<String, Artifact> result = new MultiValuesMap<String, Artifact>();
+ for (final Artifact artifact : myArtifactManager.getArtifacts()) {
+ ArtifactUtil.processFileOrDirectoryCopyElements(artifact, new PackagingElementProcessor<FileOrDirectoryCopyPackagingElement<?>>() {
+ @Override
+ public boolean process(@NotNull FileOrDirectoryCopyPackagingElement<?> element, @NotNull PackagingElementPath pathToElement) {
+ String path = element.getFilePath();
+ while (path.length() > 0) {
+ result.put(path, artifact);
+ path = PathUtil.getParentPath(path);
+ }
+ return true;
+ }
+ }, myArtifactManager.getResolvingContext(), false);
+ }
+ return result;
+ }
+
+
+ @Override
+ public void fileMoved(VirtualFileMoveEvent event) {
+ final String oldPath = event.getOldParent().getPath() + "/" + event.getFileName();
+ filePathChanged(oldPath, event.getNewParent().getPath() + "/" + event.getFileName());
+ }
+
+ private void filePathChanged(@NotNull final String oldPath, @NotNull final String newPath) {
+ final Collection<Artifact> artifacts = myParentPathsToArtifacts.getValue().get(oldPath);
+ if (artifacts != null) {
+ final ModifiableArtifactModel model = myArtifactManager.createModifiableModel();
+ for (Artifact artifact : artifacts) {
+ final Artifact copy = model.getOrCreateModifiableArtifact(artifact);
+ ArtifactUtil.processFileOrDirectoryCopyElements(copy, new PackagingElementProcessor<FileOrDirectoryCopyPackagingElement<?>>() {
+ @Override
+ public boolean process(@NotNull FileOrDirectoryCopyPackagingElement<?> element, @NotNull PackagingElementPath pathToElement) {
+ final String path = element.getFilePath();
+ if (FileUtil.startsWith(path, oldPath)) {
+ element.setFilePath(newPath + path.substring(oldPath.length()));
+ }
+ return true;
+ }
+ }, myArtifactManager.getResolvingContext(), false);
+ }
+ model.commit();
+ }
+ }
+
+ @Override
+ public void propertyChanged(VirtualFilePropertyEvent event) {
+ if (VirtualFile.PROP_NAME.equals(event.getPropertyName())) {
+ final VirtualFile parent = event.getParent();
+ if (parent != null) {
+ filePathChanged(parent.getPath() + "/" + event.getOldValue(), parent.getPath() + "/" + event.getNewValue());
+ }
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/DefaultManifestFileProvider.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/DefaultManifestFileProvider.java
new file mode 100644
index 0000000..c80617b
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/DefaultManifestFileProvider.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2000-2011 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.ArtifactType;
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.packaging.elements.ManifestFileProvider;
+import com.intellij.packaging.elements.PackagingElementResolvingContext;
+import com.intellij.packaging.impl.elements.ManifestFileUtil;
+import com.intellij.packaging.ui.ManifestFileConfiguration;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+* @author nik
+*/
+public class DefaultManifestFileProvider implements ManifestFileProvider {
+ private final PackagingElementResolvingContext myContext;
+
+ public DefaultManifestFileProvider(PackagingElementResolvingContext context) {
+ myContext = context;
+ }
+
+ @Override
+ public List<String> getClasspathFromManifest(@NotNull CompositePackagingElement<?> archiveRoot, @NotNull ArtifactType artifactType) {
+ final VirtualFile manifestFile = ManifestFileUtil.findManifestFile(archiveRoot, myContext, artifactType);
+ if (manifestFile == null) {
+ return null;
+ }
+
+ ManifestFileConfiguration configuration = ManifestFileUtil.createManifestFileConfiguration(manifestFile);
+ return configuration.getClasspath();
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/DefaultPackagingElementResolvingContext.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/DefaultPackagingElementResolvingContext.java
new file mode 100644
index 0000000..16000b4
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/DefaultPackagingElementResolvingContext.java
@@ -0,0 +1,80 @@
+/*
+ * 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.facet.impl.DefaultFacetsProvider;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.roots.libraries.LibraryTable;
+import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
+import com.intellij.openapi.roots.ui.configuration.DefaultModulesProvider;
+import com.intellij.openapi.roots.ui.configuration.FacetsProvider;
+import com.intellij.openapi.roots.ui.configuration.ModulesProvider;
+import com.intellij.packaging.artifacts.ArtifactManager;
+import com.intellij.packaging.artifacts.ArtifactModel;
+import com.intellij.packaging.elements.ManifestFileProvider;
+import com.intellij.packaging.elements.PackagingElementResolvingContext;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+* @author nik
+*/
+public class DefaultPackagingElementResolvingContext implements PackagingElementResolvingContext {
+ private final Project myProject;
+ private final DefaultModulesProvider myModulesProvider;
+
+ public DefaultPackagingElementResolvingContext(Project project) {
+ myProject = project;
+ myModulesProvider = new DefaultModulesProvider(myProject);
+ }
+
+ @NotNull
+ public Project getProject() {
+ return myProject;
+ }
+
+ @NotNull
+ public ArtifactModel getArtifactModel() {
+ return ArtifactManager.getInstance(myProject);
+ }
+
+ @NotNull
+ public ModulesProvider getModulesProvider() {
+ return myModulesProvider;
+ }
+
+ @NotNull
+ public FacetsProvider getFacetsProvider() {
+ return DefaultFacetsProvider.INSTANCE;
+ }
+
+ public Library findLibrary(@NotNull String level, @NotNull String libraryName) {
+ return findLibrary(myProject, level, libraryName);
+ }
+
+ @NotNull
+ @Override
+ public ManifestFileProvider getManifestFileProvider() {
+ return new DefaultManifestFileProvider(this);
+ }
+
+ @Nullable
+ public static Library findLibrary(Project project, String level, String libraryName) {
+ LibraryTable table = LibraryTablesRegistrar.getInstance().getLibraryTableByLevel(level, project);
+ return table != null ? table.getLibraryByName(libraryName) : null;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/InvalidArtifact.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/InvalidArtifact.java
new file mode 100644
index 0000000..c6df02e
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/InvalidArtifact.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.packaging.elements.PackagingElementFactory;
+import org.jetbrains.jps.model.serialization.artifact.ArtifactState;
+
+/**
+ * @author nik
+ */
+public class InvalidArtifact extends ArtifactImpl {
+ private ArtifactState myState;
+ private final String myErrorMessage;
+
+ public InvalidArtifact(ArtifactState state, String errorMessage) {
+ super(state.getName(), InvalidArtifactType.getInstance(), false, PackagingElementFactory.getInstance().createArtifactRootElement(), "");
+ myState = state;
+ myErrorMessage = errorMessage;
+ }
+
+ public String getErrorMessage() {
+ return myErrorMessage;
+ }
+
+ public ArtifactState getState() {
+ return myState;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/InvalidArtifactType.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/InvalidArtifactType.java
new file mode 100644
index 0000000..30a0010
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/InvalidArtifactType.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.components.ServiceManager;
+import com.intellij.packaging.artifacts.ArtifactType;
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.packaging.elements.PackagingElementFactory;
+import com.intellij.packaging.elements.PackagingElementOutputKind;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+
+/**
+ * @author nik
+ */
+public class InvalidArtifactType extends ArtifactType {
+
+ public static InvalidArtifactType getInstance() {
+ return ServiceManager.getService(InvalidArtifactType.class);
+ }
+
+ public InvalidArtifactType() {
+ super("invalid", "Invalid");
+ }
+
+ @NotNull
+ @Override
+ public Icon getIcon() {
+ return AllIcons.FileTypes.Unknown;
+ }
+
+ @Override
+ public String getDefaultPathFor(@NotNull PackagingElementOutputKind kind) {
+ return "";
+ }
+
+ @NotNull
+ @Override
+ public CompositePackagingElement<?> createRootElement(@NotNull String artifactName) {
+ return PackagingElementFactory.getInstance().createArtifactRootElement();
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/JarArtifactFromModulesDialog.form b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/JarArtifactFromModulesDialog.form
new file mode 100644
index 0000000..eed2b88
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/JarArtifactFromModulesDialog.form
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.packaging.impl.artifacts.JarArtifactFromModulesDialog">
+ <grid id="27dc6" binding="myMainPanel" layout-manager="GridLayoutManager" row-count="7" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <xy x="20" y="20" width="521" height="400"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="c89c9" class="javax.swing.JLabel" binding="myMainClassLabel">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="Main &Class:"/>
+ </properties>
+ </component>
+ <vspacer id="8cb38">
+ <constraints>
+ <grid row="6" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
+ </constraints>
+ </vspacer>
+ <component id="535a8" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myMainClassField">
+ <constraints>
+ <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="2fb86" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <labelFor value="4853e"/>
+ <text value="&Module:"/>
+ </properties>
+ </component>
+ <component id="4853e" class="javax.swing.JComboBox" binding="myModuleComboBox">
+ <constraints>
+ <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false">
+ <preferred-size width="400" height="-1"/>
+ </grid>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="8e551" class="javax.swing.JLabel" binding="myManifestDirLabel">
+ <constraints>
+ <grid row="3" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="&Directory for META-INF/MANIFEST.MF:"/>
+ </properties>
+ </component>
+ <component id="1ea2d" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myManifestDirField">
+ <constraints>
+ <grid row="4" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="6" anchor="0" fill="1" indent="2" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ <grid id="53c3f" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <grid row="2" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <clientProperties>
+ <BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithIndent"/>
+ </clientProperties>
+ <border type="none" title="Jar files from libraries"/>
+ <children>
+ <component id="f9a17" class="javax.swing.JRadioButton" binding="myExtractJarsRadioButton">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <selected value="true"/>
+ <text value="&extract to the target jar"/>
+ </properties>
+ </component>
+ <hspacer id="90c4f">
+ <constraints>
+ <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ </hspacer>
+ <component id="b3972" class="javax.swing.JRadioButton" binding="myCopyJarsRadioButton">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="copy &to the output directory and link via manifest"/>
+ </properties>
+ </component>
+ </children>
+ </grid>
+ <component id="d5a56" class="javax.swing.JCheckBox" binding="myIncludeTestsCheckBox">
+ <constraints>
+ <grid row="5" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="&Include tests"/>
+ </properties>
+ </component>
+ </children>
+ </grid>
+ <buttonGroups>
+ <group name="jarFiles">
+ <member id="f9a17"/>
+ <member id="b3972"/>
+ </group>
+ </buttonGroups>
+</form>
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/JarArtifactFromModulesDialog.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/JarArtifactFromModulesDialog.java
new file mode 100644
index 0000000..02d9e42
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/JarArtifactFromModulesDialog.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleType;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ui.configuration.ModulesAlphaComparator;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.ui.TextFieldWithBrowseButton;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.elements.PackagingElementResolvingContext;
+import com.intellij.packaging.impl.elements.ManifestFileUtil;
+import com.intellij.ui.ComboboxSpeedSearch;
+import com.intellij.ui.DocumentAdapter;
+import com.intellij.ui.ListCellRendererWrapper;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import javax.swing.event.DocumentEvent;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.Arrays;
+
+/**
+ * @author nik
+ */
+public class JarArtifactFromModulesDialog extends DialogWrapper {
+ private JPanel myMainPanel;
+ private TextFieldWithBrowseButton myMainClassField;
+ private JComboBox myModuleComboBox;
+ private JLabel myMainClassLabel;
+ private TextFieldWithBrowseButton myManifestDirField;
+ private JLabel myManifestDirLabel;
+ private JRadioButton myExtractJarsRadioButton;
+ private JRadioButton myCopyJarsRadioButton;
+ private JCheckBox myIncludeTestsCheckBox;
+ private PackagingElementResolvingContext myContext;
+
+ public JarArtifactFromModulesDialog(PackagingElementResolvingContext context) {
+ super(context.getProject());
+ myContext = context;
+ setTitle("Create Jar from Modules");
+ myMainClassLabel.setLabelFor(myMainClassField.getTextField());
+ myManifestDirLabel.setLabelFor(myManifestDirField.getTextField());
+
+ final Project project = myContext.getProject();
+ ManifestFileUtil.setupMainClassField(project, myMainClassField);
+ myMainClassField.getTextField().getDocument().addDocumentListener(new DocumentAdapter() {
+ @Override
+ protected void textChanged(DocumentEvent e) {
+ updateManifestDirField();
+ }
+ });
+ final ActionListener actionListener = new ActionListener() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ updateManifestDirField();
+ }
+ };
+ myExtractJarsRadioButton.addActionListener(actionListener);
+ myCopyJarsRadioButton.addActionListener(actionListener);
+
+ updateManifestDirField();
+ myManifestDirField.addBrowseFolderListener(null, null, project, ManifestFileUtil.createDescriptorForManifestDirectory());
+
+ setupModulesCombobox(context);
+ init();
+ }
+
+ private void setupModulesCombobox(PackagingElementResolvingContext context) {
+ final Module[] modules = context.getModulesProvider().getModules().clone();
+ Arrays.sort(modules, ModulesAlphaComparator.INSTANCE);
+ if (modules.length > 1) {
+ myModuleComboBox.addItem(null);
+ }
+ for (Module module : modules) {
+ myModuleComboBox.addItem(module);
+ }
+ myModuleComboBox.setRenderer(new ModuleListRenderer(myModuleComboBox));
+ new ComboboxSpeedSearch(myModuleComboBox) {
+ @Override
+ protected String getElementText(Object element) {
+ return element instanceof Module ? ((Module)element).getName() : "";
+ }
+ };
+ }
+
+ private void updateManifestDirField() {
+ final boolean enable = !myMainClassField.getText().isEmpty() || !myExtractJarsRadioButton.isSelected();
+ setManifestDirFieldEnabled(enable);
+ if (enable && myManifestDirField.getText().isEmpty()) {
+ final VirtualFile file = ManifestFileUtil.suggestManifestFileDirectory(myContext.getProject(), getSelectedModule());
+ if (file != null) {
+ myManifestDirField.setText(FileUtil.toSystemDependentName(file.getPath()));
+ }
+ }
+ }
+
+ @Nullable
+ private Module getSelectedModule() {
+ return (Module)myModuleComboBox.getSelectedItem();
+ }
+
+ @NotNull
+ public Module[] getSelectedModules() {
+ final Module module = getSelectedModule();
+ if (module != null) {
+ return new Module[]{module};
+ }
+ return myContext.getModulesProvider().getModules();
+ }
+
+ @NotNull
+ public String getDirectoryForManifest() {
+ return FileUtil.toSystemIndependentName(myManifestDirField.getText());
+ }
+
+ public boolean isExtractLibrariesToJar() {
+ return myExtractJarsRadioButton.isSelected();
+ }
+
+ public boolean isIncludeTests() {
+ return myIncludeTestsCheckBox.isSelected();
+ }
+
+ public String getMainClassName() {
+ return myMainClassField.getText();
+ }
+
+ private void setManifestDirFieldEnabled(boolean enabled) {
+ myManifestDirLabel.setEnabled(enabled);
+ myManifestDirField.setEnabled(enabled);
+ }
+
+ @Override
+ protected JComponent createCenterPanel() {
+ return myMainPanel;
+ }
+
+ @Override
+ protected String getHelpId() {
+ return "reference.project.structure.artifacts.jar.from.module";
+ }
+
+ private static class ModuleListRenderer extends ListCellRendererWrapper<Module> {
+ public ModuleListRenderer(JComboBox comboBox) {
+ super();
+ }
+
+ @Override
+ public void customize(JList list, Module value, int index, boolean selected, boolean hasFocus) {
+ if (value != null) {
+ setIcon(ModuleType.get(value).getIcon());
+ setText(value.getName());
+ }
+ else {
+ setText("<All Modules>");
+ setIcon(null);
+ }
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/JarArtifactType.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/JarArtifactType.java
new file mode 100644
index 0000000..19571e4
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/JarArtifactType.java
@@ -0,0 +1,65 @@
+/*
+ * 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.packaging.artifacts.ArtifactTemplate;
+import com.intellij.packaging.artifacts.ArtifactType;
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.packaging.elements.PackagingElementOutputKind;
+import com.intellij.packaging.elements.PackagingElementResolvingContext;
+import com.intellij.packaging.impl.elements.ArchivePackagingElement;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public class JarArtifactType extends ArtifactType {
+ public JarArtifactType() {
+ super("jar", "Jar");
+ }
+
+ public static JarArtifactType getInstance() {
+ return EP_NAME.findExtension(JarArtifactType.class);
+ }
+
+ @NotNull
+ @Override
+ public Icon getIcon() {
+ return AllIcons.Nodes.Artifact;
+ }
+
+ @Override
+ public String getDefaultPathFor(@NotNull PackagingElementOutputKind kind) {
+ return "/";
+ }
+
+ @NotNull
+ @Override
+ public CompositePackagingElement<?> createRootElement(@NotNull String artifactName) {
+ return new ArchivePackagingElement(ArtifactUtil.suggestArtifactFileName(artifactName) + ".jar");
+ }
+
+ @NotNull
+ @Override
+ public List<? extends ArtifactTemplate> getNewArtifactTemplates(@NotNull PackagingElementResolvingContext context) {
+ return Collections.singletonList(new JarFromModulesTemplate(context));
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/JarFromModulesTemplate.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/JarFromModulesTemplate.java
new file mode 100644
index 0000000..308a211
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/JarFromModulesTemplate.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.CommonBundle;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.OrderEnumerator;
+import com.intellij.openapi.roots.OrderRootType;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.roots.ui.configuration.ModulesProvider;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.ArtifactTemplate;
+import com.intellij.packaging.elements.*;
+import com.intellij.packaging.impl.elements.LibraryPackagingElement;
+import com.intellij.packaging.impl.elements.ManifestFileUtil;
+import com.intellij.packaging.impl.elements.ProductionModuleOutputElementType;
+import com.intellij.packaging.impl.elements.TestModuleOutputElementType;
+import com.intellij.util.CommonProcessors;
+import com.intellij.util.PathUtil;
+import com.intellij.util.Processor;
+import gnu.trove.THashSet;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * @author nik
+ */
+public class JarFromModulesTemplate extends ArtifactTemplate {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.packaging.impl.artifacts.JarFromModulesTemplate");
+
+ private PackagingElementResolvingContext myContext;
+
+ public JarFromModulesTemplate(PackagingElementResolvingContext context) {
+ myContext = context;
+ }
+
+ @Override
+ public NewArtifactConfiguration createArtifact() {
+ JarArtifactFromModulesDialog dialog = new JarArtifactFromModulesDialog(myContext);
+ dialog.show();
+ if (!dialog.isOK()) {
+ return null;
+ }
+
+ return doCreateArtifact(dialog.getSelectedModules(), dialog.getMainClassName(), dialog.getDirectoryForManifest(),
+ dialog.isExtractLibrariesToJar(), dialog.isIncludeTests());
+ }
+
+ @Nullable
+ public NewArtifactConfiguration doCreateArtifact(final Module[] modules, final String mainClassName,
+ final String directoryForManifest,
+ final boolean extractLibrariesToJar,
+ final boolean includeTests) {
+ VirtualFile manifestFile = null;
+ final Project project = myContext.getProject();
+ if (mainClassName != null && !mainClassName.isEmpty() || !extractLibrariesToJar) {
+ final VirtualFile directory;
+ try {
+ directory = VfsUtil.createDirectoryIfMissing(directoryForManifest);
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ Messages.showErrorDialog(project, "Cannot create directory '" + directoryForManifest + "': " + e.getMessage(),
+ CommonBundle.getErrorTitle());
+ return null;
+ }
+ if (directory == null) return null;
+
+ manifestFile = ManifestFileUtil.createManifestFile(directory, project);
+ if (manifestFile == null) {
+ return null;
+ }
+ ManifestFileUtil.updateManifest(manifestFile, mainClassName, null, true);
+ }
+
+ String name = modules.length == 1 ? modules[0].getName() : project.getName();
+
+ final PackagingElementFactory factory = PackagingElementFactory.getInstance();
+ final CompositePackagingElement<?> archive = factory.createArchive(ArtifactUtil.suggestArtifactFileName(name) + ".jar");
+
+ OrderEnumerator orderEnumerator = ProjectRootManager.getInstance(project).orderEntries(Arrays.asList(modules));
+
+ final Set<Library> libraries = new THashSet<Library>();
+ if (!includeTests) {
+ orderEnumerator = orderEnumerator.productionOnly();
+ }
+ final ModulesProvider modulesProvider = myContext.getModulesProvider();
+ final OrderEnumerator enumerator = orderEnumerator.using(modulesProvider).withoutSdk().runtimeOnly().recursively();
+ enumerator.forEachLibrary(new CommonProcessors.CollectProcessor<Library>(libraries));
+ enumerator.forEachModule(new Processor<Module>() {
+ @Override
+ public boolean process(Module module) {
+ if (ProductionModuleOutputElementType.ELEMENT_TYPE.isSuitableModule(modulesProvider, module)) {
+ archive.addOrFindChild(factory.createModuleOutput(module));
+ }
+ if (includeTests && TestModuleOutputElementType.ELEMENT_TYPE.isSuitableModule(modulesProvider, module)) {
+ archive.addOrFindChild(factory.createTestModuleOutput(module));
+ }
+ return true;
+ }
+ });
+
+ final JarArtifactType jarArtifactType = JarArtifactType.getInstance();
+ if (manifestFile != null && !manifestFile.equals(ManifestFileUtil.findManifestFile(archive, myContext, jarArtifactType))) {
+ archive.addFirstChild(factory.createFileCopyWithParentDirectories(manifestFile.getPath(), ManifestFileUtil.MANIFEST_DIR_NAME));
+ }
+
+ final String artifactName = name + ":jar";
+ if (extractLibrariesToJar) {
+ addExtractedLibrariesToJar(archive, factory, libraries);
+ return new NewArtifactConfiguration(archive, artifactName, jarArtifactType);
+ }
+ else {
+ final ArtifactRootElement<?> root = factory.createArtifactRootElement();
+ List<String> classpath = new ArrayList<String>();
+ root.addOrFindChild(archive);
+ addLibraries(libraries, root, archive, classpath);
+ ManifestFileUtil.updateManifest(manifestFile, mainClassName, classpath, true);
+ return new NewArtifactConfiguration(root, artifactName, PlainArtifactType.getInstance());
+ }
+ }
+
+ private void addLibraries(Set<Library> libraries, ArtifactRootElement<?> root, CompositePackagingElement<?> archive,
+ List<String> classpath) {
+ PackagingElementFactory factory = PackagingElementFactory.getInstance();
+ for (Library library : libraries) {
+ if (LibraryPackagingElement.getKindForLibrary(library).containsDirectoriesWithClasses()) {
+ for (VirtualFile classesRoot : library.getFiles(OrderRootType.CLASSES)) {
+ if (classesRoot.isInLocalFileSystem()) {
+ archive.addOrFindChild(factory.createDirectoryCopyWithParentDirectories(classesRoot.getPath(), "/"));
+ }
+ else {
+ final PackagingElement<?> child = factory.createFileCopyWithParentDirectories(PathUtil.getLocalFile(classesRoot).getPath(), "/");
+ root.addOrFindChild(child);
+ classpath.addAll(ManifestFileUtil.getClasspathForElements(Collections.singletonList(child), myContext, PlainArtifactType.getInstance()));
+ }
+ }
+
+ }
+ else {
+ final List<? extends PackagingElement<?>> children = factory.createLibraryElements(library);
+ classpath.addAll(ManifestFileUtil.getClasspathForElements(children, myContext, PlainArtifactType.getInstance()));
+ root.addOrFindChildren(children);
+ }
+ }
+ }
+
+ private static void addExtractedLibrariesToJar(CompositePackagingElement<?> archive, PackagingElementFactory factory, Set<Library> libraries) {
+ for (Library library : libraries) {
+ if (LibraryPackagingElement.getKindForLibrary(library).containsJarFiles()) {
+ for (VirtualFile classesRoot : library.getFiles(OrderRootType.CLASSES)) {
+ if (classesRoot.isInLocalFileSystem()) {
+ archive.addOrFindChild(factory.createDirectoryCopyWithParentDirectories(classesRoot.getPath(), "/"));
+ }
+ else {
+ archive.addOrFindChild(factory.createExtractedDirectory(classesRoot));
+ }
+ }
+
+ }
+ else {
+ archive.addOrFindChildren(factory.createLibraryElements(library));
+ }
+ }
+ }
+
+ @Override
+ public String getPresentableName() {
+ return "From modules with dependencies...";
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/PackagingElementPath.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/PackagingElementPath.java
new file mode 100644
index 0000000..dda0981
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/PackagingElementPath.java
@@ -0,0 +1,138 @@
+/*
+ * 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.elements.ComplexPackagingElement;
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.packaging.elements.PackagingElement;
+import com.intellij.packaging.elements.PackagingElementResolvingContext;
+import com.intellij.packaging.impl.elements.ArtifactPackagingElement;
+import com.intellij.util.SmartList;
+import com.intellij.util.StringBuilderSpinAllocator;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public class PackagingElementPath {
+ public static final PackagingElementPath EMPTY = new PackagingElementPath(null, null);
+ private final PackagingElementPath myParentPath;
+ private final PackagingElement<?> myLastElement;
+
+ private PackagingElementPath(PackagingElementPath parentPath, PackagingElement<?> lastElement) {
+ myParentPath = parentPath;
+ myLastElement = lastElement;
+ }
+
+ public PackagingElementPath appendComplex(ComplexPackagingElement<?> element) {
+ return new PackagingElementPath(this, element);
+ }
+
+ public PackagingElementPath appendComposite(CompositePackagingElement<?> element) {
+ return new PackagingElementPath(this, element);
+ }
+
+ @NotNull
+ public String getPathString() {
+ return getPathString("/");
+ }
+
+ @NotNull
+ public String getPathString(String separator) {
+ return getPathStringFrom(separator, null);
+ }
+
+ @NotNull
+ public String getPathStringFrom(String separator, @Nullable CompositePackagingElement<?> ancestor) {
+ final StringBuilder builder = StringBuilderSpinAllocator.alloc();
+ try {
+ final List<CompositePackagingElement<?>> parents = getParentsFrom(ancestor);
+ for (int i = parents.size() - 1; i >= 0; i--) {
+ builder.append(parents.get(i).getName());
+ if (i > 0) {
+ builder.append(separator);
+ }
+ }
+ return builder.toString();
+ }
+ finally {
+ StringBuilderSpinAllocator.dispose(builder);
+ }
+ }
+
+ public List<CompositePackagingElement<?>> getParents() {
+ return getParentsFrom(null);
+ }
+
+ public List<CompositePackagingElement<?>> getParentsFrom(@Nullable CompositePackagingElement<?> ancestor) {
+ List<CompositePackagingElement<?>> result = new SmartList<CompositePackagingElement<?>>();
+ PackagingElementPath path = this;
+ while (path != EMPTY && path.myLastElement != ancestor) {
+ if (path.myLastElement instanceof CompositePackagingElement<?>) {
+ result.add((CompositePackagingElement)path.myLastElement);
+ }
+ path = path.myParentPath;
+ }
+ return result;
+ }
+
+ public List<PackagingElement<?>> getAllElements() {
+ List<PackagingElement<?>> result = new SmartList<PackagingElement<?>>();
+ PackagingElementPath path = this;
+ while (path != EMPTY) {
+ result.add(path.myLastElement);
+ path = path.myParentPath;
+ }
+ return result;
+ }
+
+ @Nullable
+ public CompositePackagingElement<?> getLastParent() {
+ PackagingElementPath path = this;
+ while (path != EMPTY) {
+ if (path.myLastElement instanceof CompositePackagingElement<?>) {
+ return (CompositePackagingElement)path.myLastElement;
+ }
+ path = path.myParentPath;
+ }
+ return null;
+ }
+
+ @Nullable
+ public Artifact findLastArtifact(PackagingElementResolvingContext context) {
+ PackagingElementPath path = this;
+ while (path != EMPTY) {
+ final PackagingElement<?> element = path.myLastElement;
+ if (element instanceof ArtifactPackagingElement) {
+ return ((ArtifactPackagingElement)element).findArtifact(context);
+ }
+ path = path.myParentPath;
+ }
+ return null;
+ }
+
+ public static PackagingElementPath createPath(@NotNull List<PackagingElement<?>> elements) {
+ PackagingElementPath path = EMPTY;
+ for (PackagingElement<?> element : elements) {
+ path = new PackagingElementPath(path, element);
+ }
+ return path;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/PackagingElementProcessor.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/PackagingElementProcessor.java
new file mode 100644
index 0000000..048217f
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/PackagingElementProcessor.java
@@ -0,0 +1,35 @@
+/*
+ * 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.packaging.elements.ComplexPackagingElement;
+import com.intellij.packaging.elements.PackagingElement;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author nik
+ */
+public abstract class PackagingElementProcessor<E extends PackagingElement<?>> {
+ public boolean shouldProcessSubstitution(ComplexPackagingElement<?> element) {
+ return true;
+ }
+
+ public boolean shouldProcess(PackagingElement<?> element) {
+ return true;
+ }
+
+ public abstract boolean process(@NotNull E element, @NotNull PackagingElementPath path);
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ParentElementProcessor.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ParentElementProcessor.java
new file mode 100644
index 0000000..6f09d18
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/ParentElementProcessor.java
@@ -0,0 +1,33 @@
+/*
+ * 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.openapi.util.Pair;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public abstract class ParentElementProcessor {
+
+ public abstract boolean process(@NotNull CompositePackagingElement<?> element, @NotNull List<Pair<Artifact,CompositePackagingElement<?>>> parents,
+ @NotNull Artifact artifact);
+
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/PlainArtifactType.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/PlainArtifactType.java
new file mode 100644
index 0000000..9337db5
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/PlainArtifactType.java
@@ -0,0 +1,58 @@
+/*
+ * 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 com.intellij.packaging.impl.artifacts;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.packaging.artifacts.ArtifactType;
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.packaging.elements.PackagingElementOutputKind;
+import com.intellij.packaging.impl.elements.ArtifactRootElementImpl;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+
+/**
+ * @author nik
+ */
+public class PlainArtifactType extends ArtifactType {
+ @NonNls public static final String ID = "plain";
+
+ public static PlainArtifactType getInstance() {
+ return EP_NAME.findExtension(PlainArtifactType.class);
+ }
+
+ public PlainArtifactType() {
+ super(ID, CompilerBundle.message("artifact.type.plain"));
+ }
+
+ @NotNull
+ @Override
+ public Icon getIcon() {
+ return AllIcons.Nodes.Artifact;
+ }
+
+ @Override
+ public String getDefaultPathFor(@NotNull PackagingElementOutputKind kind) {
+ return "/";
+ }
+
+ @NotNull
+ public CompositePackagingElement<?> createRootElement(@NotNull String artifactName) {
+ return new ArtifactRootElementImpl();
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/UnknownPackagingElementTypeException.java b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/UnknownPackagingElementTypeException.java
new file mode 100644
index 0000000..9856fec
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/artifacts/UnknownPackagingElementTypeException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.packaging.impl.artifacts;
+
+/**
+ * @author nik
+ */
+class UnknownPackagingElementTypeException extends Exception {
+ private final String myTypeId;
+
+ public UnknownPackagingElementTypeException(String typeId) {
+ myTypeId = typeId;
+ }
+
+ public String getTypeId() {
+ return myTypeId;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactAdditionalCompileScopeProvider.java b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactAdditionalCompileScopeProvider.java
new file mode 100644
index 0000000..ceb8bb4
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactAdditionalCompileScopeProvider.java
@@ -0,0 +1,49 @@
+/*
+ * 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 com.intellij.packaging.impl.compiler;
+
+import com.intellij.compiler.impl.AdditionalCompileScopeProvider;
+import com.intellij.openapi.application.ReadAction;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.compiler.CompileScope;
+import com.intellij.openapi.compiler.CompilerFilter;
+import com.intellij.openapi.project.Project;
+import com.intellij.packaging.artifacts.Artifact;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Set;
+
+/**
+ * @author nik
+ */
+public class ArtifactAdditionalCompileScopeProvider extends AdditionalCompileScopeProvider {
+ @Override
+ public CompileScope getAdditionalScope(@NotNull final CompileScope baseScope, @NotNull CompilerFilter filter, @NotNull final Project project) {
+ if (ArtifactCompileScope.getArtifacts(baseScope) != null) {
+ return null;
+ }
+ final ArtifactsCompiler compiler = ArtifactsCompiler.getInstance(project);
+ if (compiler == null || !filter.acceptCompiler(compiler)) {
+ return null;
+ }
+ return new ReadAction<CompileScope>() {
+ protected void run(final Result<CompileScope> result) {
+ final Set<Artifact> artifacts = ArtifactCompileScope.getArtifactsToBuild(project, baseScope, false);
+ result.setResult(ArtifactCompileScope.createScopeForModulesInArtifacts(project, artifacts));
+ }
+ }.execute().getResultObject();
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactAwareCompiler.java b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactAwareCompiler.java
new file mode 100644
index 0000000..1d10459
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactAwareCompiler.java
@@ -0,0 +1,30 @@
+/*
+ * 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 com.intellij.packaging.impl.compiler;
+
+import com.intellij.packaging.artifacts.Artifact;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+
+/**
+ * @author nik
+ */
+public interface ArtifactAwareCompiler {
+
+ boolean shouldRun(@NotNull Collection<? extends Artifact> changedArtifacts);
+
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactBuildTarget.java b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactBuildTarget.java
new file mode 100644
index 0000000..4a29afc
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactBuildTarget.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.packaging.impl.compiler;
+
+import com.intellij.openapi.compiler.generic.BuildTarget;
+import com.intellij.packaging.artifacts.Artifact;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author nik
+ */
+public class ArtifactBuildTarget extends BuildTarget {
+ private Artifact myArtifact;
+
+ public ArtifactBuildTarget(Artifact artifact) {
+ myArtifact = artifact;
+ }
+
+ public Artifact getArtifact() {
+ return myArtifact;
+ }
+
+ @NotNull
+ @Override
+ public String getId() {
+ return myArtifact.getName();
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactBuildTargetScopeProvider.java b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactBuildTargetScopeProvider.java
new file mode 100644
index 0000000..c6af259
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactBuildTargetScopeProvider.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.packaging.impl.compiler;
+
+import com.intellij.compiler.impl.BuildTargetScopeProvider;
+import com.intellij.compiler.impl.CompileScopeUtil;
+import com.intellij.openapi.application.ReadAction;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.compiler.CompileScope;
+import com.intellij.openapi.compiler.CompilerFilter;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.impl.artifacts.ArtifactUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.jps.api.CmdlineRemoteProto.Message.ControllerMessage.ParametersMessage.TargetTypeBuildScope;
+import org.jetbrains.jps.incremental.artifacts.ArtifactBuildTargetType;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author nik
+ */
+public class ArtifactBuildTargetScopeProvider extends BuildTargetScopeProvider {
+ @NotNull
+ @Override
+ public List<TargetTypeBuildScope> getBuildTargetScopes(@NotNull final CompileScope baseScope, @NotNull CompilerFilter filter,
+ @NotNull final Project project) {
+ final ArtifactsCompiler compiler = ArtifactsCompiler.getInstance(project);
+ if (compiler == null || !filter.acceptCompiler(compiler)) {
+ return Collections.emptyList();
+ }
+ final List<TargetTypeBuildScope> scopes = new ArrayList<TargetTypeBuildScope>();
+ new ReadAction() {
+ protected void run(final Result result) {
+ final Set<Artifact> artifacts = ArtifactCompileScope.getArtifactsToBuild(project, baseScope, false);
+ if (ArtifactCompileScope.getArtifacts(baseScope) == null) {
+ Set<Module> modules = ArtifactUtil.getModulesIncludedInArtifacts(artifacts, project);
+ CompileScopeUtil.addScopesForModules(modules, scopes);
+ }
+ if (!artifacts.isEmpty()) {
+ TargetTypeBuildScope.Builder builder =
+ TargetTypeBuildScope.newBuilder().setTypeId(ArtifactBuildTargetType.INSTANCE.getTypeId());
+ for (Artifact artifact : artifacts) {
+ builder.addTargetId(artifact.getName());
+ }
+ scopes.add(builder.build());
+ }
+ }
+ }.execute();
+
+ return scopes;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactCompileScope.java b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactCompileScope.java
new file mode 100644
index 0000000..b434eac
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactCompileScope.java
@@ -0,0 +1,145 @@
+/*
+ * 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 com.intellij.packaging.impl.compiler;
+
+import com.intellij.compiler.impl.ModuleCompileScope;
+import com.intellij.openapi.compiler.CompileScope;
+import com.intellij.openapi.compiler.CompilerManager;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.artifacts.ArtifactManager;
+import com.intellij.packaging.elements.PackagingElementResolvingContext;
+import com.intellij.packaging.impl.artifacts.ArtifactUtil;
+import com.intellij.packaging.impl.elements.ArtifactElementType;
+import com.intellij.packaging.impl.elements.ArtifactPackagingElement;
+import com.intellij.packaging.impl.elements.ModuleOutputPackagingElement;
+import com.intellij.packaging.impl.elements.ProductionModuleOutputElementType;
+import com.intellij.util.Processor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+
+/**
+ * @author nik
+ */
+public class ArtifactCompileScope {
+ private static final Key<Artifact[]> ARTIFACTS_KEY = Key.create("artifacts");
+ private static final Key<Set<Artifact>> CACHED_ARTIFACTS_KEY = Key.create("cached_artifacts");
+ private static final Key<Key<?>> ARTIFACTS_CONTENT_ID_KEY = Key.create("build_artifacts_task");
+
+ private ArtifactCompileScope() {
+ }
+
+ public static ModuleCompileScope createScopeForModulesInArtifacts(@NotNull Project project, @NotNull Collection<? extends Artifact> artifacts) {
+ final Set<Module> modules = ArtifactUtil.getModulesIncludedInArtifacts(artifacts, project);
+ return new ModuleCompileScope(project, modules.toArray(new Module[modules.size()]), true);
+ }
+
+ public static CompileScope createArtifactsScope(@NotNull Project project, @NotNull Collection<Artifact> artifacts) {
+ return createScopeWithArtifacts(createScopeForModulesInArtifacts(project, artifacts), artifacts, true);
+ }
+
+ public static CompileScope createScopeWithArtifacts(final CompileScope baseScope, @NotNull Collection<Artifact> artifacts, boolean useCustomContentId) {
+ baseScope.putUserData(ARTIFACTS_KEY, artifacts.toArray(new Artifact[artifacts.size()]));
+ if (useCustomContentId) {
+ baseScope.putUserData(CompilerManager.CONTENT_ID_KEY, ARTIFACTS_CONTENT_ID_KEY);
+ }
+ return baseScope;
+ }
+
+ public static Set<Artifact> getArtifactsToBuild(final Project project,
+ final CompileScope compileScope,
+ final boolean addIncludedArtifactsWithOutputPathsOnly) {
+ final Artifact[] artifactsFromScope = getArtifacts(compileScope);
+ final ArtifactManager artifactManager = ArtifactManager.getInstance(project);
+ PackagingElementResolvingContext context = artifactManager.getResolvingContext();
+ if (artifactsFromScope != null) {
+ return addIncludedArtifacts(Arrays.asList(artifactsFromScope), context, addIncludedArtifactsWithOutputPathsOnly);
+ }
+
+ final Set<Artifact> cached = compileScope.getUserData(CACHED_ARTIFACTS_KEY);
+ if (cached != null) {
+ return cached;
+ }
+
+ Set<Artifact> artifacts = new HashSet<Artifact>();
+ final Set<Module> modules = new HashSet<Module>(Arrays.asList(compileScope.getAffectedModules()));
+ final List<Module> allModules = Arrays.asList(ModuleManager.getInstance(project).getModules());
+ for (Artifact artifact : artifactManager.getArtifacts()) {
+ if (artifact.isBuildOnMake()) {
+ if (modules.containsAll(allModules)
+ || containsModuleOutput(artifact, modules, context)) {
+ artifacts.add(artifact);
+ }
+ }
+ }
+ Set<Artifact> result = addIncludedArtifacts(artifacts, context, addIncludedArtifactsWithOutputPathsOnly);
+ compileScope.putUserData(CACHED_ARTIFACTS_KEY, result);
+ return result;
+ }
+
+ @Nullable
+ public static Artifact[] getArtifacts(CompileScope compileScope) {
+ return compileScope.getUserData(ARTIFACTS_KEY);
+ }
+
+ private static boolean containsModuleOutput(Artifact artifact, final Set<Module> modules, final PackagingElementResolvingContext context) {
+ return !ArtifactUtil.processPackagingElements(artifact, ProductionModuleOutputElementType.ELEMENT_TYPE,
+ new Processor<ModuleOutputPackagingElement>() {
+ public boolean process(ModuleOutputPackagingElement moduleOutputPackagingElement) {
+ final Module module = moduleOutputPackagingElement.findModule(context);
+ return module == null || !modules.contains(module);
+ }
+ }, context, true);
+ }
+
+ @NotNull
+ private static Set<Artifact> addIncludedArtifacts(@NotNull Collection<Artifact> artifacts,
+ @NotNull PackagingElementResolvingContext context,
+ final boolean withOutputPathOnly) {
+ Set<Artifact> result = new HashSet<Artifact>();
+ for (Artifact artifact : artifacts) {
+ collectIncludedArtifacts(artifact, context, new HashSet<Artifact>(), result, withOutputPathOnly);
+ }
+ return result;
+ }
+
+ private static void collectIncludedArtifacts(Artifact artifact, final PackagingElementResolvingContext context,
+ final Set<Artifact> processed, final Set<Artifact> result, final boolean withOutputPathOnly) {
+ if (!processed.add(artifact)) {
+ return;
+ }
+ if (!withOutputPathOnly || !StringUtil.isEmpty(artifact.getOutputPath())) {
+ result.add(artifact);
+ }
+
+ ArtifactUtil.processPackagingElements(artifact, ArtifactElementType.ARTIFACT_ELEMENT_TYPE, new Processor<ArtifactPackagingElement>() {
+ @Override
+ public boolean process(ArtifactPackagingElement element) {
+ Artifact included = element.findArtifact(context);
+ if (included != null) {
+ collectIncludedArtifacts(included, context, processed, result, withOutputPathOnly);
+ }
+ return true;
+ }
+ }, context, false);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactCompilerCompileItem.java b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactCompilerCompileItem.java
new file mode 100644
index 0000000..b4e1865
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactCompilerCompileItem.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.packaging.impl.compiler;
+
+import com.intellij.openapi.compiler.generic.VirtualFileCompileItem;
+import com.intellij.compiler.impl.packagingCompiler.DestinationInfo;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.SmartList;
+import com.intellij.util.io.DataExternalizer;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public class ArtifactCompilerCompileItem extends VirtualFileCompileItem<ArtifactPackagingItemOutputState> {
+ public static final DataExternalizer<ArtifactPackagingItemOutputState> OUTPUT_EXTERNALIZER = new ArtifactPackagingItemExternalizer();
+ private final List<DestinationInfo> myDestinations = new SmartList<DestinationInfo>();
+
+ public ArtifactCompilerCompileItem(VirtualFile file) {
+ super(file);
+ }
+
+ public void addDestination(DestinationInfo info) {
+ myDestinations.add(info);
+ }
+
+ public List<DestinationInfo> getDestinations() {
+ return myDestinations;
+ }
+
+ @NotNull
+ @Override
+ public ArtifactPackagingItemOutputState computeOutputState() {
+ final SmartList<Pair<String, Long>> pairs = new SmartList<Pair<String, Long>>();
+ for (DestinationInfo destination : myDestinations) {
+ destination.update();
+ final VirtualFile outputFile = destination.getOutputFile();
+ long timestamp = outputFile != null ? outputFile.getTimeStamp() : -1;
+ pairs.add(Pair.create(destination.getOutputPath(), timestamp));
+ }
+ return new ArtifactPackagingItemOutputState(pairs);
+ }
+
+ @Override
+ public boolean isOutputUpToDate(@NotNull ArtifactPackagingItemOutputState state) {
+ final SmartList<Pair<String, Long>> cachedDestinations = state.myDestinations;
+ if (cachedDestinations.size() != myDestinations.size()) {
+ return false;
+ }
+
+ for (DestinationInfo info : myDestinations) {
+ final VirtualFile outputFile = info.getOutputFile();
+ long timestamp = outputFile != null ? outputFile.getTimeStamp() : -1;
+ final String path = info.getOutputPath();
+ boolean found = false;
+ //todo[nik] use map if list contains many items
+ for (Pair<String, Long> cachedDestination : cachedDestinations) {
+ if (cachedDestination.first.equals(path)) {
+ if (cachedDestination.second != timestamp) return false;
+ found = true;
+ break;
+ }
+ }
+ if (!found) return false;
+ }
+
+ return true;
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactCompilerUtil.java b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactCompilerUtil.java
new file mode 100644
index 0000000..0de7e05
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactCompilerUtil.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.packaging.impl.compiler;
+
+import com.intellij.facet.Facet;
+import com.intellij.facet.FacetManager;
+import com.intellij.facet.FacetRootsProvider;
+import com.intellij.openapi.application.ReadAction;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.CompilerMessageCategory;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.JarFileSystem;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.artifacts.ArtifactManager;
+import com.intellij.packaging.elements.ComplexPackagingElement;
+import com.intellij.packaging.elements.PackagingElement;
+import com.intellij.packaging.elements.PackagingElementResolvingContext;
+import com.intellij.packaging.impl.artifacts.ArtifactUtil;
+import com.intellij.packaging.impl.artifacts.PackagingElementPath;
+import com.intellij.packaging.impl.artifacts.PackagingElementProcessor;
+import com.intellij.packaging.impl.elements.ArtifactPackagingElement;
+import com.intellij.packaging.impl.elements.FileOrDirectoryCopyPackagingElement;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.containers.MultiMap;
+import gnu.trove.THashMap;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.jps.api.CmdlineRemoteProto.Message.ControllerMessage.ParametersMessage.TargetTypeBuildScope;
+import org.jetbrains.jps.incremental.artifacts.ArtifactBuildTargetType;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * @author nik
+ */
+public class ArtifactCompilerUtil {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.packaging.impl.compiler.ArtifactCompilerUtil");
+
+ private ArtifactCompilerUtil() {
+ }
+
+ @Nullable
+ public static BufferedInputStream getJarEntryInputStream(VirtualFile sourceFile, final CompileContext context) throws IOException {
+ final String fullPath = sourceFile.getPath();
+ final int jarEnd = fullPath.indexOf(JarFileSystem.JAR_SEPARATOR);
+ LOG.assertTrue(jarEnd != -1, fullPath);
+ String pathInJar = fullPath.substring(jarEnd + JarFileSystem.JAR_SEPARATOR.length());
+ String jarPath = fullPath.substring(0, jarEnd);
+ final ZipFile jarFile = new ZipFile(new File(FileUtil.toSystemDependentName(jarPath)));
+ final ZipEntry entry = jarFile.getEntry(pathInJar);
+ if (entry == null) {
+ context.addMessage(CompilerMessageCategory.ERROR, "Cannot extract '" + pathInJar + "' from '" + jarFile.getName() + "': entry not found", null, -1, -1);
+ return null;
+ }
+
+ return new BufferedInputStream(jarFile.getInputStream(entry)) {
+ @Override
+ public void close() throws IOException {
+ super.close();
+ jarFile.close();
+ }
+ };
+ }
+
+ public static File getJarFile(VirtualFile jarEntry) {
+ String fullPath = jarEntry.getPath();
+ return new File(FileUtil.toSystemDependentName(fullPath.substring(fullPath.indexOf(JarFileSystem.JAR_SEPARATOR))));
+ }
+
+
+ @NotNull
+ public static Set<VirtualFile> getArtifactOutputsContainingSourceFiles(final @NotNull Project project) {
+ final List<VirtualFile> allOutputs = new ArrayList<VirtualFile>();
+ for (Artifact artifact : ArtifactManager.getInstance(project).getArtifacts()) {
+ ContainerUtil.addIfNotNull(artifact.getOutputFile(), allOutputs);
+ }
+
+ final Set<VirtualFile> roots = new HashSet<VirtualFile>();
+ final PackagingElementResolvingContext context = ArtifactManager.getInstance(project).getResolvingContext();
+ for (Artifact artifact : ArtifactManager.getInstance(project).getArtifacts()) {
+ ArtifactUtil.processPackagingElements(artifact, null, new PackagingElementProcessor<PackagingElement<?>>() {
+ @Override
+ public boolean shouldProcessSubstitution(ComplexPackagingElement<?> element) {
+ return !(element instanceof ArtifactPackagingElement);
+ }
+
+ @Override
+ public boolean process(@NotNull PackagingElement<?> element, @NotNull PackagingElementPath path) {
+ if (element instanceof FileOrDirectoryCopyPackagingElement<?>) {
+ final VirtualFile file = ((FileOrDirectoryCopyPackagingElement)element).findFile();
+ if (file != null) {
+ roots.add(file);
+ }
+ }
+ return true;
+ }
+ }, context, true);
+ }
+
+ final Module[] modules = ModuleManager.getInstance(project).getModules();
+ for (Module module : modules) {
+ final Facet[] facets = FacetManager.getInstance(module).getAllFacets();
+ for (Facet facet : facets) {
+ if (facet instanceof FacetRootsProvider) {
+ roots.addAll(((FacetRootsProvider)facet).getFacetRoots());
+ }
+ }
+ }
+
+ final Set<VirtualFile> affectedOutputPaths = new HashSet<VirtualFile>();
+ for (VirtualFile output : allOutputs) {
+ for (VirtualFile root : roots) {
+ if (VfsUtilCore.isAncestor(output, root, false)) {
+ affectedOutputPaths.add(output);
+ }
+ }
+ }
+ return affectedOutputPaths;
+ }
+
+ public static boolean containsArtifacts(List<TargetTypeBuildScope> scopes) {
+ for (TargetTypeBuildScope scope : scopes) {
+ if (ArtifactBuildTargetType.INSTANCE.getTypeId().equals(scope.getTypeId())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static MultiMap<String, Artifact> createOutputToArtifactMap(final Project project) {
+ final MultiMap<String, Artifact> result = new MultiMap<String, Artifact>() {
+ @Override
+ protected Map<String, Collection<Artifact>> createMap() {
+ return new THashMap<String, Collection<Artifact>>(FileUtil.PATH_HASHING_STRATEGY);
+ }
+ };
+ new ReadAction() {
+ protected void run(final Result r) {
+ for (Artifact artifact : ArtifactManager.getInstance(project).getArtifacts()) {
+ String outputPath = artifact.getOutputFilePath();
+ if (!StringUtil.isEmpty(outputPath)) {
+ result.putValue(outputPath, artifact);
+ }
+ }
+ }
+ }.execute();
+
+
+ return result;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactPackagingItemExternalizer.java b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactPackagingItemExternalizer.java
new file mode 100644
index 0000000..030e948
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactPackagingItemExternalizer.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.packaging.impl.compiler;
+
+import com.intellij.openapi.util.Pair;
+import com.intellij.util.SmartList;
+import com.intellij.util.io.DataExternalizer;
+import com.intellij.util.io.IOUtil;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+/**
+* @author nik
+*/
+public class ArtifactPackagingItemExternalizer implements DataExternalizer<ArtifactPackagingItemOutputState> {
+ private byte[] myBuffer = IOUtil.allocReadWriteUTFBuffer();
+
+ @Override
+ public void save(DataOutput out, ArtifactPackagingItemOutputState value) throws IOException {
+ out.writeInt(value.myDestinations.size());
+ for (Pair<String, Long> pair : value.myDestinations) {
+ IOUtil.writeUTFFast(myBuffer, out, pair.getFirst());
+ out.writeLong(pair.getSecond());
+ }
+ }
+
+ @Override
+ public ArtifactPackagingItemOutputState read(DataInput in) throws IOException {
+ int size = in.readInt();
+ SmartList<Pair<String, Long>> destinations = new SmartList<Pair<String, Long>>();
+ while (size-- > 0) {
+ String path = IOUtil.readUTFFast(myBuffer, in);
+ long outputTimestamp = in.readLong();
+ destinations.add(Pair.create(path, outputTimestamp));
+ }
+ return new ArtifactPackagingItemOutputState(destinations);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactPackagingItemOutputState.java b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactPackagingItemOutputState.java
new file mode 100644
index 0000000..29ac0ae
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactPackagingItemOutputState.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.packaging.impl.compiler;
+
+import com.intellij.openapi.util.Pair;
+import com.intellij.util.SmartList;
+
+/**
+* @author nik
+*/
+public class ArtifactPackagingItemOutputState {
+ public final SmartList<Pair<String, Long>> myDestinations;
+
+ public ArtifactPackagingItemOutputState(SmartList<Pair<String, Long>> destinations) {
+ myDestinations = destinations;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactsCompiler.java b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactsCompiler.java
new file mode 100644
index 0000000..9875816
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactsCompiler.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.packaging.impl.compiler;
+
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.CompilerManager;
+import com.intellij.openapi.compiler.generic.CompileItem;
+import com.intellij.openapi.compiler.generic.GenericCompiler;
+import com.intellij.openapi.compiler.generic.GenericCompilerInstance;
+import com.intellij.openapi.compiler.generic.VirtualFilePersistentState;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Key;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.util.io.DataExternalizer;
+import com.intellij.util.io.KeyDescriptor;
+import gnu.trove.THashSet;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Set;
+
+/**
+ * @author nik
+ */
+public class ArtifactsCompiler extends GenericCompiler<String, VirtualFilePersistentState, ArtifactPackagingItemOutputState> {
+ private static final Key<Set<String>> WRITTEN_PATHS_KEY = Key.create("artifacts_written_paths");
+ private static final Key<Set<Artifact>> CHANGED_ARTIFACTS = Key.create("affected_artifacts");
+
+ public ArtifactsCompiler() {
+ super("artifacts_compiler", 0, GenericCompiler.CompileOrderPlace.PACKAGING);
+ }
+
+ @Nullable
+ public static ArtifactsCompiler getInstance(@NotNull Project project) {
+ final ArtifactsCompiler[] compilers = CompilerManager.getInstance(project).getCompilers(ArtifactsCompiler.class);
+ return compilers.length == 1 ? compilers[0] : null;
+ }
+
+ public static void addChangedArtifact(final CompileContext context, Artifact artifact) {
+ Set<Artifact> artifacts = context.getUserData(CHANGED_ARTIFACTS);
+ if (artifacts == null) {
+ artifacts = new THashSet<Artifact>();
+ context.putUserData(CHANGED_ARTIFACTS, artifacts);
+ }
+ artifacts.add(artifact);
+ }
+
+ public static void addWrittenPaths(final CompileContext context, Set<String> writtenPaths) {
+ Set<String> paths = context.getUserData(WRITTEN_PATHS_KEY);
+ if (paths == null) {
+ paths = new THashSet<String>();
+ context.putUserData(WRITTEN_PATHS_KEY, paths);
+ }
+ paths.addAll(writtenPaths);
+ }
+
+ @NotNull
+ @Override
+ public KeyDescriptor<String> getItemKeyDescriptor() {
+ return STRING_KEY_DESCRIPTOR;
+ }
+
+ @NotNull
+ @Override
+ public DataExternalizer<VirtualFilePersistentState> getSourceStateExternalizer() {
+ return VirtualFilePersistentState.EXTERNALIZER;
+ }
+
+ @NotNull
+ @Override
+ public DataExternalizer<ArtifactPackagingItemOutputState> getOutputStateExternalizer() {
+ return new ArtifactPackagingItemExternalizer();
+ }
+
+ @NotNull
+ @Override
+ public GenericCompilerInstance<ArtifactBuildTarget, ? extends CompileItem<String, VirtualFilePersistentState, ArtifactPackagingItemOutputState>, String, VirtualFilePersistentState, ArtifactPackagingItemOutputState> createInstance(
+ @NotNull CompileContext context) {
+ return new ArtifactsCompilerInstance(context);
+ }
+
+ @NotNull
+ public String getDescription() {
+ return "Artifacts Packaging Compiler";
+ }
+
+ @Nullable
+ public static Set<Artifact> getChangedArtifacts(final CompileContext compileContext) {
+ return compileContext.getUserData(CHANGED_ARTIFACTS);
+ }
+
+ @Nullable
+ public static Set<String> getWrittenPaths(@NotNull CompileContext context) {
+ return context.getUserData(WRITTEN_PATHS_KEY);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactsCompilerInstance.java b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactsCompilerInstance.java
new file mode 100644
index 0000000..1edb774
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactsCompilerInstance.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.packaging.impl.compiler;
+
+import com.intellij.compiler.CompilerManagerImpl;
+import com.intellij.compiler.impl.CompilerUtil;
+import com.intellij.compiler.impl.packagingCompiler.*;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ReadAction;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.compiler.CompilerMessageCategory;
+import com.intellij.openapi.compiler.generic.GenericCompilerCacheState;
+import com.intellij.openapi.compiler.generic.GenericCompilerInstance;
+import com.intellij.openapi.compiler.generic.GenericCompilerProcessingItem;
+import com.intellij.openapi.compiler.generic.VirtualFilePersistentState;
+import com.intellij.openapi.compiler.make.BuildParticipant;
+import com.intellij.openapi.compiler.make.BuildParticipantProvider;
+import com.intellij.openapi.deployment.DeploymentUtil;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.progress.ProcessCanceledException;
+import com.intellij.openapi.project.DumbService;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.*;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.artifacts.ArtifactManager;
+import com.intellij.packaging.artifacts.ArtifactProperties;
+import com.intellij.packaging.artifacts.ArtifactPropertiesProvider;
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.packaging.elements.PackagingElementResolvingContext;
+import com.intellij.packaging.impl.artifacts.ArtifactSortingUtil;
+import com.intellij.util.ThrowableRunnable;
+import com.intellij.util.containers.ContainerUtil;
+import gnu.trove.THashSet;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * @author nik
+ */
+public class ArtifactsCompilerInstance extends GenericCompilerInstance<ArtifactBuildTarget, ArtifactCompilerCompileItem,
+ String, VirtualFilePersistentState, ArtifactPackagingItemOutputState> {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.packaging.impl.compiler.ArtifactsCompilerInstance");
+ public static final Logger FULL_LOG = Logger.getInstance("#com.intellij.full-artifacts-compiler-log");
+ private ArtifactsProcessingItemsBuilderContext myBuilderContext;
+
+ public ArtifactsCompilerInstance(CompileContext context) {
+ super(context);
+ }
+
+ @NotNull
+ @Override
+ public List<ArtifactBuildTarget> getAllTargets() {
+ return getArtifactTargets(false);
+ }
+
+ @NotNull
+ @Override
+ public List<ArtifactBuildTarget> getSelectedTargets() {
+ return getArtifactTargets(true);
+ }
+
+ private List<ArtifactBuildTarget> getArtifactTargets(final boolean selectedOnly) {
+ final List<ArtifactBuildTarget> targets = new ArrayList<ArtifactBuildTarget>();
+ new ReadAction() {
+ protected void run(final Result result) {
+ final Set<Artifact> artifacts;
+ if (selectedOnly) {
+ artifacts = ArtifactCompileScope.getArtifactsToBuild(getProject(), myContext.getCompileScope(), true);
+ }
+ else {
+ artifacts = new HashSet<Artifact>(Arrays.asList(ArtifactManager.getInstance(getProject()).getArtifacts()));
+ }
+
+ Map<String, Artifact> artifactsMap = new HashMap<String, Artifact>();
+ for (Artifact artifact : artifacts) {
+ artifactsMap.put(artifact.getName(), artifact);
+ }
+ for (String name : ArtifactSortingUtil.getInstance(getProject()).getArtifactsSortedByInclusion()) {
+ Artifact artifact = artifactsMap.get(name);
+ if (artifact != null) {
+ targets.add(new ArtifactBuildTarget(artifact));
+ }
+ }
+
+ for (BuildParticipantProvider provider : BuildParticipantProvider.EXTENSION_POINT_NAME.getExtensions()) {
+ for (Module module : ModuleManager.getInstance(getProject()).getModules()) {
+ final Collection<? extends BuildParticipant> participants = provider.getParticipants(module);
+ for (BuildParticipant participant : participants) {
+ Artifact artifact = participant.createArtifact(myContext);
+ if (artifact != null) {
+ LOG.debug("additional artifact to build: " + artifact);
+ targets.add(new ArtifactBuildTarget(artifact));
+ }
+ }
+ }
+ }
+ }
+ }.execute();
+ return targets;
+ }
+
+ @Override
+ public void processObsoleteTarget(@NotNull String targetId,
+ @NotNull List<GenericCompilerCacheState<String, VirtualFilePersistentState, ArtifactPackagingItemOutputState>> obsoleteItems) {
+ deleteFiles(obsoleteItems, Collections.<GenericCompilerProcessingItem<ArtifactCompilerCompileItem, VirtualFilePersistentState, ArtifactPackagingItemOutputState>>emptyList());
+ }
+
+ @NotNull
+ @Override
+ public List<ArtifactCompilerCompileItem> getItems(@NotNull ArtifactBuildTarget target) {
+ myBuilderContext = new ArtifactsProcessingItemsBuilderContext(myContext);
+ final Artifact artifact = target.getArtifact();
+
+ final Map<String, String> selfIncludingArtifacts = new ReadAction<Map<String, String>>() {
+ protected void run(final Result<Map<String, String>> result) {
+ result.setResult(ArtifactSortingUtil.getInstance(getProject()).getArtifactToSelfIncludingNameMap());
+ }
+ }.execute().getResultObject();
+ final String selfIncludingName = selfIncludingArtifacts.get(artifact.getName());
+ if (selfIncludingName != null) {
+ String name = selfIncludingName.equals(artifact.getName()) ? "it" : "'" + selfIncludingName + "' artifact";
+ myContext.addMessage(CompilerMessageCategory.ERROR, "Cannot build '" + artifact.getName() + "' artifact: " + name + " includes itself in the output layout", null, -1, -1);
+ return Collections.emptyList();
+ }
+
+ final String outputPath = artifact.getOutputPath();
+ if (outputPath == null || outputPath.length() == 0) {
+ myContext.addMessage(CompilerMessageCategory.ERROR, "Cannot build '" + artifact.getName() + "' artifact: output path is not specified",
+ null, -1, -1);
+ return Collections.emptyList();
+ }
+
+ DumbService.getInstance(getProject()).waitForSmartMode();
+ new ReadAction() {
+ protected void run(final Result result) {
+ collectItems(artifact, outputPath);
+ }
+ }.execute();
+ return new ArrayList<ArtifactCompilerCompileItem>(myBuilderContext.getProcessingItems());
+ }
+
+ private void collectItems(@NotNull Artifact artifact, @NotNull String outputPath) {
+ final CompositePackagingElement<?> rootElement = artifact.getRootElement();
+ final VirtualFile outputFile = LocalFileSystem.getInstance().findFileByPath(outputPath);
+ final CopyToDirectoryInstructionCreator instructionCreator = new CopyToDirectoryInstructionCreator(myBuilderContext, outputPath, outputFile);
+ final PackagingElementResolvingContext resolvingContext = ArtifactManager.getInstance(getProject()).getResolvingContext();
+ FULL_LOG.debug("Collecting items for " + artifact.getName());
+ rootElement.computeIncrementalCompilerInstructions(instructionCreator, resolvingContext, myBuilderContext, artifact.getArtifactType());
+ }
+
+ private boolean doBuild(@NotNull Artifact artifact,
+ final List<GenericCompilerProcessingItem<ArtifactCompilerCompileItem, VirtualFilePersistentState, ArtifactPackagingItemOutputState>> changedItems,
+ final Set<ArtifactCompilerCompileItem> processedItems,
+ final @NotNull Set<String> writtenPaths,
+ final Set<String> deletedJars) {
+ FULL_LOG.debug("Building " + artifact.getName());
+ final boolean testMode = ApplicationManager.getApplication().isUnitTestMode();
+
+ final DeploymentUtil deploymentUtil = DeploymentUtil.getInstance();
+ final FileFilter fileFilter = new IgnoredFileFilter();
+ final Set<JarInfo> changedJars = new THashSet<JarInfo>();
+ for (String deletedJar : deletedJars) {
+ ContainerUtil.addIfNotNull(myBuilderContext.getJarInfo(deletedJar), changedJars);
+ }
+
+ try {
+ onBuildStartedOrFinished(artifact, false);
+ if (myContext.getMessageCount(CompilerMessageCategory.ERROR) > 0) {
+ return false;
+ }
+
+ int i = 0;
+ for (final GenericCompilerProcessingItem<ArtifactCompilerCompileItem, VirtualFilePersistentState, ArtifactPackagingItemOutputState> item : changedItems) {
+ final ArtifactCompilerCompileItem sourceItem = item.getItem();
+ myContext.getProgressIndicator().checkCanceled();
+
+ final Ref<IOException> exception = Ref.create(null);
+ new ReadAction() {
+ protected void run(final Result result) {
+ final VirtualFile sourceFile = sourceItem.getFile();
+ for (DestinationInfo destination : sourceItem.getDestinations()) {
+ if (destination instanceof ExplodedDestinationInfo) {
+ final ExplodedDestinationInfo explodedDestination = (ExplodedDestinationInfo)destination;
+ File toFile = new File(FileUtil.toSystemDependentName(explodedDestination.getOutputPath()));
+ try {
+ if (sourceFile.isInLocalFileSystem()) {
+ final File ioFromFile = VfsUtilCore.virtualToIoFile(sourceFile);
+ if (ioFromFile.exists()) {
+ deploymentUtil.copyFile(ioFromFile, toFile, myContext, writtenPaths, fileFilter);
+ }
+ else {
+ LOG.debug("Cannot copy " + ioFromFile.getAbsolutePath() + ": file doesn't exist");
+ }
+ }
+ else {
+ extractFile(sourceFile, toFile, writtenPaths, fileFilter);
+ }
+ }
+ catch (IOException e) {
+ exception.set(e);
+ return;
+ }
+ }
+ else {
+ changedJars.add(((JarDestinationInfo)destination).getJarInfo());
+ }
+ }
+ }
+ }.execute();
+ if (exception.get() != null) {
+ throw exception.get();
+ }
+
+ myContext.getProgressIndicator().setFraction(++i * 1.0 / changedItems.size());
+ processedItems.add(sourceItem);
+ if (testMode) {
+ CompilerManagerImpl.addRecompiledPath(FileUtil.toSystemDependentName(sourceItem.getFile().getPath()));
+ }
+ }
+
+ JarsBuilder builder = new JarsBuilder(changedJars, fileFilter, myContext);
+ final boolean processed = builder.buildJars(writtenPaths);
+ if (!processed) {
+ return false;
+ }
+
+ Set<VirtualFile> recompiledSources = new HashSet<VirtualFile>();
+ for (JarInfo info : builder.getJarsToBuild()) {
+ for (Pair<String, VirtualFile> pair : info.getPackedFiles()) {
+ recompiledSources.add(pair.getSecond());
+ }
+ }
+ for (VirtualFile source : recompiledSources) {
+ ArtifactCompilerCompileItem item = myBuilderContext.getItemBySource(source);
+ LOG.assertTrue(item != null, source);
+ processedItems.add(item);
+ if (testMode) {
+ CompilerManagerImpl.addRecompiledPath(FileUtil.toSystemDependentName(item.getFile().getPath()));
+ }
+ }
+
+ onBuildStartedOrFinished(artifact, true);
+ }
+ catch (ProcessCanceledException e) {
+ throw e;
+ }
+ catch (Exception e) {
+ LOG.info(e);
+ myContext.addMessage(CompilerMessageCategory.ERROR, e.getLocalizedMessage(), null, -1, -1);
+ return false;
+ }
+ return true;
+ }
+
+ private void extractFile(VirtualFile sourceFile, File toFile, Set<String> writtenPaths, FileFilter fileFilter) throws IOException {
+ if (!writtenPaths.add(toFile.getPath())) {
+ return;
+ }
+
+ if (!FileUtil.createParentDirs(toFile)) {
+ myContext.addMessage(CompilerMessageCategory.ERROR, "Cannot create directory for '" + toFile.getAbsolutePath() + "' file", null, -1, -1);
+ return;
+ }
+
+ final BufferedInputStream input = ArtifactCompilerUtil.getJarEntryInputStream(sourceFile, myContext);
+ if (input == null) return;
+ final BufferedOutputStream output = new BufferedOutputStream(new FileOutputStream(toFile));
+ try {
+ FileUtil.copy(input, output);
+ }
+ finally {
+ input.close();
+ output.close();
+ }
+ }
+
+ private void onBuildStartedOrFinished(@NotNull Artifact artifact, final boolean finished) throws Exception {
+ for (ArtifactPropertiesProvider provider : artifact.getPropertiesProviders()) {
+ final ArtifactProperties<?> properties = artifact.getProperties(provider);
+ if (finished) {
+ properties.onBuildFinished(artifact, myContext);
+ }
+ else {
+ properties.onBuildStarted(artifact, myContext);
+ }
+ }
+ }
+
+ private static THashSet<String> createPathsHashSet() {
+ return new THashSet<String>(FileUtil.PATH_HASHING_STRATEGY);
+ }
+
+ @Override
+ public void processItems(@NotNull final ArtifactBuildTarget target,
+ @NotNull final List<GenericCompilerProcessingItem<ArtifactCompilerCompileItem,VirtualFilePersistentState,ArtifactPackagingItemOutputState>> changedItems,
+ @NotNull List<GenericCompilerCacheState<String, VirtualFilePersistentState, ArtifactPackagingItemOutputState>> obsoleteItems,
+ @NotNull OutputConsumer<ArtifactCompilerCompileItem> consumer) {
+
+ final THashSet<String> deletedJars = deleteFiles(obsoleteItems, changedItems);
+
+ final Set<String> writtenPaths = createPathsHashSet();
+ final Ref<Boolean> built = Ref.create(false);
+ final Set<ArtifactCompilerCompileItem> processedItems = new HashSet<ArtifactCompilerCompileItem>();
+ CompilerUtil.runInContext(myContext, "Copying files", new ThrowableRunnable<RuntimeException>() {
+ public void run() throws RuntimeException {
+ built.set(doBuild(target.getArtifact(), changedItems, processedItems, writtenPaths, deletedJars));
+ }
+ });
+ if (!built.get()) {
+ return;
+ }
+
+ myContext.getProgressIndicator().setText(CompilerBundle.message("packaging.compiler.message.updating.caches"));
+ myContext.getProgressIndicator().setText2("");
+ for (String path : writtenPaths) {
+ consumer.addFileToRefresh(new File(path));
+ }
+ for (ArtifactCompilerCompileItem item : processedItems) {
+ consumer.addProcessedItem(item);
+ }
+ ArtifactsCompiler.addWrittenPaths(myContext, writtenPaths);
+ ArtifactsCompiler.addChangedArtifact(myContext, target.getArtifact());
+ }
+
+ private THashSet<String> deleteFiles(List<GenericCompilerCacheState<String, VirtualFilePersistentState, ArtifactPackagingItemOutputState>> obsoleteItems,
+ List<GenericCompilerProcessingItem<ArtifactCompilerCompileItem, VirtualFilePersistentState, ArtifactPackagingItemOutputState>> changedItems) {
+ myContext.getProgressIndicator().setText(CompilerBundle.message("packaging.compiler.message.deleting.outdated.files"));
+
+ final boolean testMode = ApplicationManager.getApplication().isUnitTestMode();
+ final THashSet<String> deletedJars = new THashSet<String>();
+ final THashSet<String> notDeletedJars = new THashSet<String>();
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Deleting outdated files...");
+ }
+
+ Set<String> pathToDelete = new THashSet<String>();
+ for (GenericCompilerProcessingItem<ArtifactCompilerCompileItem, VirtualFilePersistentState, ArtifactPackagingItemOutputState> item : changedItems) {
+ final ArtifactPackagingItemOutputState cached = item.getCachedOutputState();
+ if (cached != null) {
+ for (Pair<String, Long> destination : cached.myDestinations) {
+ pathToDelete.add(destination.getFirst());
+ }
+ }
+ }
+ for (GenericCompilerProcessingItem<ArtifactCompilerCompileItem, VirtualFilePersistentState, ArtifactPackagingItemOutputState> item : changedItems) {
+ for (DestinationInfo destination : item.getItem().getDestinations()) {
+ pathToDelete.remove(destination.getOutputPath());
+ }
+ }
+ for (GenericCompilerCacheState<String, VirtualFilePersistentState, ArtifactPackagingItemOutputState> item : obsoleteItems) {
+ for (Pair<String, Long> destination : item.getOutputState().myDestinations) {
+ pathToDelete.add(destination.getFirst());
+ }
+ }
+
+ int notDeletedFilesCount = 0;
+ List<File> filesToRefresh = new ArrayList<File>();
+
+ for (String fullPath : pathToDelete) {
+ int end = fullPath.indexOf(JarFileSystem.JAR_SEPARATOR);
+ boolean isJar = end != -1;
+ String filePath = isJar ? fullPath.substring(0, end) : fullPath;
+ boolean deleted = false;
+ if (isJar) {
+ if (notDeletedJars.contains(filePath)) {
+ continue;
+ }
+ deleted = deletedJars.contains(filePath);
+ }
+
+ File file = new File(FileUtil.toSystemDependentName(filePath));
+ if (!deleted) {
+ filesToRefresh.add(file);
+ deleted = FileUtil.delete(file);
+ }
+
+ if (deleted) {
+ if (isJar) {
+ deletedJars.add(filePath);
+ }
+ if (testMode) {
+ CompilerManagerImpl.addDeletedPath(file.getAbsolutePath());
+ }
+ }
+ else {
+ if (isJar) {
+ notDeletedJars.add(filePath);
+ }
+ if (notDeletedFilesCount++ > 50) {
+ myContext.addMessage(CompilerMessageCategory.WARNING, "Deletion of outdated files stopped because too many files cannot be deleted", null, -1, -1);
+ break;
+ }
+ myContext.addMessage(CompilerMessageCategory.WARNING, "Cannot delete file '" + filePath + "'", null, -1, -1);
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Cannot delete file " + file);
+ }
+ }
+ }
+
+ CompilerUtil.refreshIOFiles(filesToRefresh);
+ return deletedJars;
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactsProcessingItemsBuilderContext.java b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactsProcessingItemsBuilderContext.java
new file mode 100644
index 0000000..a96663c
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactsProcessingItemsBuilderContext.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.packaging.impl.compiler;
+
+import com.intellij.compiler.impl.packagingCompiler.DestinationInfo;
+import com.intellij.compiler.impl.packagingCompiler.ExplodedDestinationInfo;
+import com.intellij.compiler.impl.packagingCompiler.JarInfo;
+import com.intellij.openapi.compiler.CompileContext;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.elements.ArtifactIncrementalCompilerContext;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author nik
+ */
+public class ArtifactsProcessingItemsBuilderContext implements ArtifactIncrementalCompilerContext {
+ protected final Map<VirtualFile, ArtifactCompilerCompileItem> myItemsBySource;
+ private final Map<String, VirtualFile> mySourceByOutput;
+ private final Map<String, JarInfo> myJarByPath;
+ private final CompileContext myCompileContext;
+ private final boolean myPrintToLog;
+
+ public ArtifactsProcessingItemsBuilderContext(CompileContext compileContext) {
+ myCompileContext = compileContext;
+ myItemsBySource = new HashMap<VirtualFile, ArtifactCompilerCompileItem>();
+ mySourceByOutput = new HashMap<String, VirtualFile>();
+ myJarByPath = new HashMap<String, JarInfo>();
+ myPrintToLog = ArtifactsCompilerInstance.FULL_LOG.isDebugEnabled();
+ }
+
+ public boolean addDestination(@NotNull VirtualFile sourceFile, @NotNull DestinationInfo destinationInfo) {
+ if (destinationInfo instanceof ExplodedDestinationInfo && sourceFile.equals(destinationInfo.getOutputFile())) {
+ return false;
+ }
+
+ if (checkOutputPath(destinationInfo.getOutputPath(), sourceFile)) {
+ if (myPrintToLog) {
+ ArtifactsCompilerInstance.FULL_LOG.debug(" " + sourceFile.getPath() + " -> " + destinationInfo);
+ }
+ getOrCreateProcessingItem(sourceFile).addDestination(destinationInfo);
+ return true;
+ }
+ return false;
+ }
+
+ public Collection<ArtifactCompilerCompileItem> getProcessingItems() {
+ return myItemsBySource.values();
+ }
+
+ public boolean checkOutputPath(final String outputPath, final VirtualFile sourceFile) {
+ VirtualFile old = mySourceByOutput.get(outputPath);
+ if (old == null) {
+ mySourceByOutput.put(outputPath, sourceFile);
+ return true;
+ }
+ //todo[nik] show warning?
+ return false;
+ }
+
+ public ArtifactCompilerCompileItem getItemBySource(VirtualFile source) {
+ return myItemsBySource.get(source);
+ }
+
+ public boolean registerJarFile(@NotNull JarInfo jarInfo, @NotNull String outputPath) {
+ if (mySourceByOutput.containsKey(outputPath) || myJarByPath.containsKey(outputPath)) {
+ return false;
+ }
+ myJarByPath.put(outputPath, jarInfo);
+ return true;
+ }
+
+ @Nullable
+ public JarInfo getJarInfo(String outputPath) {
+ return myJarByPath.get(outputPath);
+ }
+
+ @Nullable
+ public VirtualFile getSourceByOutput(String outputPath) {
+ return mySourceByOutput.get(outputPath);
+ }
+
+ public CompileContext getCompileContext() {
+ return myCompileContext;
+ }
+
+ public ArtifactCompilerCompileItem getOrCreateProcessingItem(VirtualFile sourceFile) {
+ ArtifactCompilerCompileItem item = myItemsBySource.get(sourceFile);
+ if (item == null) {
+ item = new ArtifactCompilerCompileItem(sourceFile);
+ myItemsBySource.put(sourceFile, item);
+ }
+ return item;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactsWorkspaceSettings.java b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactsWorkspaceSettings.java
new file mode 100644
index 0000000..a25f4d0
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/ArtifactsWorkspaceSettings.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.packaging.impl.compiler;
+
+import com.intellij.openapi.components.*;
+import com.intellij.openapi.components.StoragePathMacros;
+import com.intellij.openapi.project.Project;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.artifacts.ArtifactManager;
+import com.intellij.util.containers.ContainerUtil;
+import com.intellij.util.xmlb.annotations.AbstractCollection;
+import com.intellij.util.xmlb.annotations.Tag;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+@State(name = "ArtifactsWorkspaceSettings",
+ storages = {
+ @Storage( file = StoragePathMacros.WORKSPACE_FILE)
+ })
+public class ArtifactsWorkspaceSettings implements PersistentStateComponent<ArtifactsWorkspaceSettings.ArtifactsWorkspaceSettingsState> {
+ private ArtifactsWorkspaceSettingsState myState = new ArtifactsWorkspaceSettingsState();
+ private final Project myProject;
+
+ public ArtifactsWorkspaceSettings(Project project) {
+ myProject = project;
+ }
+
+ public static ArtifactsWorkspaceSettings getInstance(@NotNull Project project) {
+ return ServiceManager.getService(project, ArtifactsWorkspaceSettings.class);
+ }
+
+ public List<Artifact> getArtifactsToBuild() {
+ final List<Artifact> result = new ArrayList<Artifact>();
+ final ArtifactManager artifactManager = ArtifactManager.getInstance(myProject);
+ for (String name : myState.myArtifactsToBuild) {
+ ContainerUtil.addIfNotNull(artifactManager.findArtifact(name), result);
+ }
+ return result;
+ }
+
+ public void setArtifactsToBuild(@NotNull Collection<? extends Artifact> artifacts) {
+ myState.myArtifactsToBuild.clear();
+ for (Artifact artifact : artifacts) {
+ myState.myArtifactsToBuild.add(artifact.getName());
+ }
+ Collections.sort(myState.myArtifactsToBuild);
+ }
+
+ public ArtifactsWorkspaceSettingsState getState() {
+ return myState;
+ }
+
+ public void loadState(ArtifactsWorkspaceSettingsState state) {
+ myState = state;
+ }
+
+ public static class ArtifactsWorkspaceSettingsState {
+ @Tag("artifacts-to-build")
+ @AbstractCollection(surroundWithTag = false, elementTag = "artifact", elementValueAttribute = "name")
+ public List<String> myArtifactsToBuild = new ArrayList<String>();
+
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/CopyToDirectoryInstructionCreator.java b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/CopyToDirectoryInstructionCreator.java
new file mode 100644
index 0000000..3043468
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/CopyToDirectoryInstructionCreator.java
@@ -0,0 +1,63 @@
+/*
+ * 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 com.intellij.packaging.impl.compiler;
+
+import com.intellij.compiler.impl.packagingCompiler.ExplodedDestinationInfo;
+import com.intellij.compiler.impl.packagingCompiler.JarInfo;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.elements.IncrementalCompilerInstructionCreator;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author nik
+ */
+public class CopyToDirectoryInstructionCreator extends IncrementalCompilerInstructionCreatorBase {
+ private final String myOutputPath;
+ private final @Nullable VirtualFile myOutputFile;
+
+ public CopyToDirectoryInstructionCreator(ArtifactsProcessingItemsBuilderContext context, String outputPath,
+ @Nullable VirtualFile outputFile) {
+ super(context);
+ myOutputPath = outputPath;
+ myOutputFile = outputFile;
+ }
+
+ public void addFileCopyInstruction(@NotNull VirtualFile file, @NotNull String outputFileName) {
+ myContext.addDestination(file, new ExplodedDestinationInfo(myOutputPath + "/" + outputFileName, outputChild(outputFileName)));
+ }
+
+ public CopyToDirectoryInstructionCreator subFolder(@NotNull String directoryName) {
+ return new CopyToDirectoryInstructionCreator(myContext, myOutputPath + "/" + directoryName, outputChild(directoryName));
+ }
+
+ public IncrementalCompilerInstructionCreator archive(@NotNull String archiveFileName) {
+ String jarOutputPath = myOutputPath + "/" + archiveFileName;
+ final JarInfo jarInfo = new JarInfo();
+ if (!myContext.registerJarFile(jarInfo, jarOutputPath)) {
+ return new SkipAllInstructionCreator(myContext);
+ }
+ VirtualFile outputFile = outputChild(archiveFileName);
+ final ExplodedDestinationInfo destination = new ExplodedDestinationInfo(jarOutputPath, outputFile);
+ jarInfo.addDestination(destination);
+ return new PackIntoArchiveInstructionCreator(myContext, jarInfo, "", destination);
+ }
+
+ @Nullable
+ private VirtualFile outputChild(String name) {
+ return myOutputFile != null ? myOutputFile.findChild(name) : null;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/IncrementalCompilerInstructionCreatorBase.java b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/IncrementalCompilerInstructionCreatorBase.java
new file mode 100644
index 0000000..9837b9f
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/IncrementalCompilerInstructionCreatorBase.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.packaging.impl.compiler;
+
+import com.intellij.openapi.fileTypes.FileTypeManager;
+import com.intellij.openapi.roots.ProjectFileIndex;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VfsUtilCore;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileVisitor;
+import com.intellij.packaging.elements.IncrementalCompilerInstructionCreator;
+import com.intellij.packaging.elements.PackagingFileFilter;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public abstract class IncrementalCompilerInstructionCreatorBase implements IncrementalCompilerInstructionCreator {
+ protected final ArtifactsProcessingItemsBuilderContext myContext;
+
+ public IncrementalCompilerInstructionCreatorBase(ArtifactsProcessingItemsBuilderContext context) {
+ myContext = context;
+ }
+
+ public void addDirectoryCopyInstructions(@NotNull VirtualFile directory) {
+ addDirectoryCopyInstructions(directory, null);
+ }
+
+ public void addDirectoryCopyInstructions(@NotNull VirtualFile directory, @Nullable PackagingFileFilter filter) {
+ final ProjectFileIndex index = ProjectRootManager.getInstance(myContext.getCompileContext().getProject()).getFileIndex();
+ final boolean copyExcluded = index.isIgnored(directory);
+ collectInstructionsRecursively(directory, this, filter, index, copyExcluded);
+ }
+
+ private static void collectInstructionsRecursively(VirtualFile directory,
+ final IncrementalCompilerInstructionCreatorBase creator,
+ @Nullable final PackagingFileFilter filter,
+ final ProjectFileIndex index,
+ final boolean copyExcluded) {
+ final FileTypeManager fileTypeManager = FileTypeManager.getInstance();
+ VfsUtilCore.visitChildrenRecursively(directory, new VirtualFileVisitor<IncrementalCompilerInstructionCreatorBase>(VirtualFileVisitor.SKIP_ROOT) {
+ { setValueForChildren(creator); }
+
+ @Override
+ public boolean visitFile(@NotNull VirtualFile child) {
+ if (copyExcluded) {
+ if (fileTypeManager.isFileIgnored(child)) return false;
+ }
+ else {
+ if (index.isIgnored(child)) return false;
+ }
+
+ final IncrementalCompilerInstructionCreatorBase creator = getCurrentValue();
+ if (filter != null && !filter.accept(child, creator.myContext.getCompileContext())) {
+ return false;
+ }
+
+ if (!child.isDirectory()) {
+ creator.addFileCopyInstruction(child, child.getName());
+ }
+ else {
+ setValueForChildren(creator.subFolder(child.getName()));
+ }
+
+ return true;
+ }
+ });
+ }
+
+ @Override
+ public abstract IncrementalCompilerInstructionCreatorBase subFolder(@NotNull String directoryName);
+
+ public IncrementalCompilerInstructionCreator subFolderByRelativePath(@NotNull String relativeDirectoryPath) {
+ final List<String> folders = StringUtil.split(relativeDirectoryPath, "/");
+ IncrementalCompilerInstructionCreator current = this;
+ for (String folder : folders) {
+ current = current.subFolder(folder);
+ }
+ return current;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/PackIntoArchiveInstructionCreator.java b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/PackIntoArchiveInstructionCreator.java
new file mode 100644
index 0000000..c51b8e7
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/PackIntoArchiveInstructionCreator.java
@@ -0,0 +1,66 @@
+/*
+ * 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 com.intellij.packaging.impl.compiler;
+
+import com.intellij.compiler.impl.packagingCompiler.DestinationInfo;
+import com.intellij.compiler.impl.packagingCompiler.JarDestinationInfo;
+import com.intellij.compiler.impl.packagingCompiler.JarInfo;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.elements.IncrementalCompilerInstructionCreator;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author nik
+ */
+public class PackIntoArchiveInstructionCreator extends IncrementalCompilerInstructionCreatorBase {
+ private final DestinationInfo myJarDestination;
+ private final JarInfo myJarInfo;
+ private final String myPathInJar;
+
+ public PackIntoArchiveInstructionCreator(ArtifactsProcessingItemsBuilderContext context, JarInfo jarInfo,
+ String pathInJar, DestinationInfo jarDestination) {
+ super(context);
+ myJarInfo = jarInfo;
+ myPathInJar = pathInJar;
+ myJarDestination = jarDestination;
+ }
+
+ public void addFileCopyInstruction(@NotNull VirtualFile file, @NotNull String outputFileName) {
+ final String pathInJar = childPathInJar(outputFileName);
+ if (myContext.addDestination(file, new JarDestinationInfo(pathInJar, myJarInfo, myJarDestination))) {
+ myJarInfo.addContent(pathInJar, file);
+ }
+ }
+
+ private String childPathInJar(String fileName) {
+ return myPathInJar.length() == 0 ? fileName : myPathInJar + "/" + fileName;
+ }
+
+ public PackIntoArchiveInstructionCreator subFolder(@NotNull String directoryName) {
+ return new PackIntoArchiveInstructionCreator(myContext, myJarInfo, childPathInJar(directoryName), myJarDestination);
+ }
+
+ public IncrementalCompilerInstructionCreator archive(@NotNull String archiveFileName) {
+ final JarInfo jarInfo = new JarInfo();
+ final String outputPath = myJarDestination.getOutputPath() + "/" + archiveFileName;
+ if (!myContext.registerJarFile(jarInfo, outputPath)) {
+ return new SkipAllInstructionCreator(myContext);
+ }
+ final JarDestinationInfo destination = new JarDestinationInfo(childPathInJar(archiveFileName), myJarInfo, myJarDestination);
+ jarInfo.addDestination(destination);
+ return new PackIntoArchiveInstructionCreator(myContext, jarInfo, "", destination);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/compiler/SkipAllInstructionCreator.java b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/SkipAllInstructionCreator.java
new file mode 100644
index 0000000..b7d409f
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/compiler/SkipAllInstructionCreator.java
@@ -0,0 +1,40 @@
+/*
+ * 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 com.intellij.packaging.impl.compiler;
+
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.elements.IncrementalCompilerInstructionCreator;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author nik
+ */
+public class SkipAllInstructionCreator extends IncrementalCompilerInstructionCreatorBase {
+ public SkipAllInstructionCreator(ArtifactsProcessingItemsBuilderContext context) {
+ super(context);
+ }
+
+ public void addFileCopyInstruction(@NotNull VirtualFile file, @NotNull String outputFileName) {
+ }
+
+ public SkipAllInstructionCreator subFolder(@NotNull String directoryName) {
+ return this;
+ }
+
+ public IncrementalCompilerInstructionCreator archive(@NotNull String archiveFileName) {
+ return this;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/ArchiveElementType.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ArchiveElementType.java
new file mode 100644
index 0000000..0dca4cb
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ArchiveElementType.java
@@ -0,0 +1,68 @@
+/*
+ * 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.packaging.elements.CompositePackagingElementType;
+import com.intellij.packaging.impl.artifacts.ArtifactUtil;
+import com.intellij.packaging.impl.ui.properties.ArchiveElementPropertiesPanel;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import com.intellij.packaging.ui.PackagingElementPropertiesPanel;
+import com.intellij.util.PlatformIcons;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+
+/**
+* @author nik
+*/
+class ArchiveElementType extends CompositePackagingElementType<ArchivePackagingElement> {
+ ArchiveElementType() {
+ super("archive", CompilerBundle.message("element.type.name.archive"));
+ }
+
+ @Override
+ public Icon getCreateElementIcon() {
+ return PlatformIcons.JAR_ICON;
+ }
+
+ @NotNull
+ @Override
+ public ArchivePackagingElement createEmpty(@NotNull Project project) {
+ return new ArchivePackagingElement();
+ }
+
+ @Override
+ public PackagingElementPropertiesPanel createElementPropertiesPanel(@NotNull ArchivePackagingElement element,
+ @NotNull ArtifactEditorContext context) {
+ final String name = element.getArchiveFileName();
+ if (ArtifactUtil.isArchiveName(name)) {
+ return new ArchiveElementPropertiesPanel(element, context);
+ }
+ return null;
+ }
+
+ public CompositePackagingElement<?> createComposite(CompositePackagingElement<?> parent, @Nullable String baseName, @NotNull ArtifactEditorContext context) {
+ final String initialValue = PackagingElementFactoryImpl.suggestFileName(parent, baseName != null ? baseName : "archive", ".jar");
+ final String path = Messages.showInputDialog(context.getProject(), "Enter archive name: ", "New Archive", null, initialValue, new FilePathValidator());
+ if (path == null) return null;
+ return PackagingElementFactoryImpl.createDirectoryOrArchiveWithParents(path, true);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/ArchivePackagingElement.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ArchivePackagingElement.java
new file mode 100644
index 0000000..a144df7
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ArchivePackagingElement.java
@@ -0,0 +1,118 @@
+/*
+ * 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.compiler.ant.BuildProperties;
+import com.intellij.compiler.ant.Generator;
+import com.intellij.compiler.ant.Tag;
+import com.intellij.compiler.ant.artifacts.ArchiveAntCopyInstructionCreator;
+import com.intellij.compiler.ant.taskdefs.Jar;
+import com.intellij.compiler.ant.taskdefs.Zip;
+import com.intellij.packaging.artifacts.ArtifactType;
+import com.intellij.packaging.elements.*;
+import com.intellij.packaging.impl.ui.ArchiveElementPresentation;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import com.intellij.packaging.ui.PackagingElementPresentation;
+import com.intellij.util.xmlb.XmlSerializerUtil;
+import com.intellij.util.xmlb.annotations.Attribute;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public class ArchivePackagingElement extends CompositeElementWithManifest<ArchivePackagingElement> {
+ @NonNls public static final String NAME_ATTRIBUTE = "name";
+ private String myArchiveFileName;
+
+ public ArchivePackagingElement() {
+ super(PackagingElementFactoryImpl.ARCHIVE_ELEMENT_TYPE);
+ }
+
+ public ArchivePackagingElement(@NotNull String archiveFileName) {
+ super(PackagingElementFactoryImpl.ARCHIVE_ELEMENT_TYPE);
+ myArchiveFileName = archiveFileName;
+ }
+
+ public PackagingElementPresentation createPresentation(@NotNull ArtifactEditorContext context) {
+ return new ArchiveElementPresentation(this);
+ }
+
+ @Override
+ public List<? extends Generator> computeAntInstructions(@NotNull PackagingElementResolvingContext resolvingContext, @NotNull AntCopyInstructionCreator creator,
+ @NotNull ArtifactAntGenerationContext generationContext,
+ @NotNull ArtifactType artifactType) {
+ final String tempJarProperty = generationContext.createNewTempFileProperty("temp.jar.path." + myArchiveFileName, myArchiveFileName);
+ String jarPath = BuildProperties.propertyRef(tempJarProperty);
+ final Tag jar;
+ if (myArchiveFileName.endsWith(".jar")) {
+ jar = new Jar(jarPath, "preserve", true);
+ }
+ else {
+ jar = new Zip(jarPath);
+ }
+ for (Generator generator : computeChildrenGenerators(resolvingContext, new ArchiveAntCopyInstructionCreator(""), generationContext, artifactType)) {
+ jar.add(generator);
+ }
+ generationContext.runBeforeCurrentArtifact(jar);
+ return Collections.singletonList(creator.createFileCopyInstruction(jarPath, myArchiveFileName));
+ }
+
+ @Override
+ public void computeIncrementalCompilerInstructions(@NotNull IncrementalCompilerInstructionCreator creator,
+ @NotNull PackagingElementResolvingContext resolvingContext,
+ @NotNull ArtifactIncrementalCompilerContext compilerContext, @NotNull ArtifactType artifactType) {
+ computeChildrenInstructions(creator.archive(myArchiveFileName), resolvingContext, compilerContext, artifactType);
+ }
+
+ @Attribute(NAME_ATTRIBUTE)
+ public String getArchiveFileName() {
+ return myArchiveFileName;
+ }
+
+ @NonNls @Override
+ public String toString() {
+ return "archive:" + myArchiveFileName;
+ }
+
+ public ArchivePackagingElement getState() {
+ return this;
+ }
+
+ public void setArchiveFileName(String archiveFileName) {
+ myArchiveFileName = archiveFileName;
+ }
+
+ public String getName() {
+ return myArchiveFileName;
+ }
+
+ public void rename(@NotNull String newName) {
+ myArchiveFileName = newName;
+ }
+
+ @Override
+ public boolean isEqualTo(@NotNull PackagingElement<?> element) {
+ return element instanceof ArchivePackagingElement && ((ArchivePackagingElement)element).getArchiveFileName().equals(myArchiveFileName);
+ }
+
+ public void loadState(ArchivePackagingElement state) {
+ XmlSerializerUtil.copyBean(state, this);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/ArtifactElementType.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ArtifactElementType.java
new file mode 100644
index 0000000..ed599a8
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ArtifactElementType.java
@@ -0,0 +1,107 @@
+/*
+ * 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.project.Project;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.artifacts.ArtifactManager;
+import com.intellij.packaging.artifacts.ArtifactPointerManager;
+import com.intellij.packaging.elements.ComplexPackagingElementType;
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.packaging.impl.artifacts.ArtifactUtil;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import com.intellij.util.Processor;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.util.*;
+
+/**
+* @author nik
+*/
+public class ArtifactElementType extends ComplexPackagingElementType<ArtifactPackagingElement> {
+ public static final ArtifactElementType ARTIFACT_ELEMENT_TYPE = new ArtifactElementType();
+
+ ArtifactElementType() {
+ super("artifact", CompilerBundle.message("element.type.name.artifact"));
+ }
+
+ @Override
+ public Icon getCreateElementIcon() {
+ return AllIcons.Nodes.Artifact;
+ }
+
+ @Override
+ public boolean canCreate(@NotNull ArtifactEditorContext context, @NotNull Artifact artifact) {
+ return !getAvailableArtifacts(context, artifact, false).isEmpty();
+ }
+
+ @NotNull
+ public List<? extends ArtifactPackagingElement> chooseAndCreate(@NotNull ArtifactEditorContext context, @NotNull Artifact artifact,
+ @NotNull CompositePackagingElement<?> parent) {
+ final Project project = context.getProject();
+ List<Artifact> artifacts = context.chooseArtifacts(getAvailableArtifacts(context, artifact, false), CompilerBundle.message("dialog.title.choose.artifacts"));
+ final List<ArtifactPackagingElement> elements = new ArrayList<ArtifactPackagingElement>();
+ for (Artifact selected : artifacts) {
+ elements.add(new ArtifactPackagingElement(project, ArtifactPointerManager.getInstance(project).createPointer(selected, context.getArtifactModel())));
+ }
+ return elements;
+ }
+
+ @NotNull
+ public static List<? extends Artifact> getAvailableArtifacts(@NotNull final ArtifactEditorContext context,
+ @NotNull final Artifact artifact,
+ final boolean notIncludedOnly) {
+ final Set<Artifact> result = new HashSet<Artifact>(Arrays.asList(context.getArtifactModel().getArtifacts()));
+ if (notIncludedOnly) {
+ ArtifactUtil.processPackagingElements(artifact, ARTIFACT_ELEMENT_TYPE, new Processor<ArtifactPackagingElement>() {
+ public boolean process(ArtifactPackagingElement artifactPackagingElement) {
+ result.remove(artifactPackagingElement.findArtifact(context));
+ return true;
+ }
+ }, context, true);
+ }
+ result.remove(artifact);
+ final Iterator<Artifact> iterator = result.iterator();
+ while (iterator.hasNext()) {
+ Artifact another = iterator.next();
+ final boolean notContainThis =
+ ArtifactUtil.processPackagingElements(another, ARTIFACT_ELEMENT_TYPE, new Processor<ArtifactPackagingElement>() {
+ public boolean process(ArtifactPackagingElement element) {
+ return !artifact.getName().equals(element.getArtifactName());
+ }
+ }, context, true);
+ if (!notContainThis) {
+ iterator.remove();
+ }
+ }
+ final ArrayList<Artifact> list = new ArrayList<Artifact>(result);
+ Collections.sort(list, ArtifactManager.ARTIFACT_COMPARATOR);
+ return list;
+ }
+
+ @NotNull
+ public ArtifactPackagingElement createEmpty(@NotNull Project project) {
+ return new ArtifactPackagingElement(project);
+ }
+
+ @Override
+ public String getShowContentActionText() {
+ return "Show Content of Included Artifacts";
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/ArtifactPackagingElement.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ArtifactPackagingElement.java
new file mode 100644
index 0000000..7d507b6
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ArtifactPackagingElement.java
@@ -0,0 +1,190 @@
+/*
+ * 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.compiler.ant.BuildProperties;
+import com.intellij.compiler.ant.Generator;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.artifacts.ArtifactPointer;
+import com.intellij.packaging.artifacts.ArtifactPointerManager;
+import com.intellij.packaging.artifacts.ArtifactType;
+import com.intellij.packaging.elements.*;
+import com.intellij.packaging.impl.ui.ArtifactElementPresentation;
+import com.intellij.packaging.impl.ui.DelegatedPackagingElementPresentation;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import com.intellij.packaging.ui.PackagingElementPresentation;
+import com.intellij.util.xmlb.annotations.Attribute;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public class ArtifactPackagingElement extends ComplexPackagingElement<ArtifactPackagingElement.ArtifactPackagingElementState> {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.packaging.impl.elements.ArtifactPackagingElement");
+ private final Project myProject;
+ private ArtifactPointer myArtifactPointer;
+ @NonNls public static final String ARTIFACT_NAME_ATTRIBUTE = "artifact-name";
+
+ public ArtifactPackagingElement(@NotNull Project project) {
+ super(ArtifactElementType.ARTIFACT_ELEMENT_TYPE);
+ myProject = project;
+ }
+
+ public ArtifactPackagingElement(@NotNull Project project, @NotNull ArtifactPointer artifactPointer) {
+ this(project);
+ myArtifactPointer = artifactPointer;
+ }
+
+ public List<? extends PackagingElement<?>> getSubstitution(@NotNull PackagingElementResolvingContext context, @NotNull ArtifactType artifactType) {
+ final Artifact artifact = findArtifact(context);
+ if (artifact != null) {
+ final ArtifactType type = artifact.getArtifactType();
+ List<? extends PackagingElement<?>> substitution = type.getSubstitution(artifact, context, artifactType);
+ if (substitution != null) {
+ return substitution;
+ }
+
+ final List<PackagingElement<?>> elements = new ArrayList<PackagingElement<?>>();
+ final CompositePackagingElement<?> rootElement = artifact.getRootElement();
+ if (rootElement instanceof ArtifactRootElement<?>) {
+ elements.addAll(rootElement.getChildren());
+ }
+ else {
+ elements.add(rootElement);
+ }
+ return elements;
+ }
+ return null;
+ }
+
+ @Override
+ public List<? extends Generator> computeAntInstructions(@NotNull PackagingElementResolvingContext resolvingContext, @NotNull AntCopyInstructionCreator creator,
+ @NotNull ArtifactAntGenerationContext generationContext,
+ @NotNull ArtifactType artifactType) {
+ final Artifact artifact = findArtifact(resolvingContext);
+ if (artifact != null) {
+ if (artifact.getArtifactType().getSubstitution(artifact, resolvingContext, artifactType) != null) {
+ return super.computeAntInstructions(resolvingContext, creator, generationContext, artifactType);
+ }
+ final String outputPath = BuildProperties.propertyRef(generationContext.getArtifactOutputProperty(artifact));
+ return Collections.singletonList(creator.createDirectoryContentCopyInstruction(outputPath));
+ }
+ return Collections.emptyList();
+ }
+
+ @Override
+ public void computeIncrementalCompilerInstructions(@NotNull IncrementalCompilerInstructionCreator creator,
+ @NotNull PackagingElementResolvingContext resolvingContext,
+ @NotNull ArtifactIncrementalCompilerContext compilerContext,
+ @NotNull ArtifactType artifactType) {
+ Artifact artifact = findArtifact(resolvingContext);
+ if (artifact == null) return;
+
+ if (StringUtil.isEmpty(artifact.getOutputPath())
+ || artifact.getArtifactType().getSubstitution(artifact, resolvingContext, artifactType) != null) {
+ super.computeIncrementalCompilerInstructions(creator, resolvingContext, compilerContext, artifactType);
+ return;
+ }
+
+ VirtualFile outputFile = artifact.getOutputFile();
+ if (outputFile == null) {
+ LOG.debug("Output file for " + artifact + " not found");
+ return;
+ }
+ if (!outputFile.isValid()) {
+ LOG.debug("Output file for " + artifact + "(" + outputFile + ") is not valid");
+ return;
+ }
+
+ if (outputFile.isDirectory()) {
+ creator.addDirectoryCopyInstructions(outputFile);
+ }
+ else {
+ creator.addFileCopyInstruction(outputFile, outputFile.getName());
+ }
+ }
+
+ @Override
+ protected ArtifactType getArtifactTypeForSubstitutedElements(PackagingElementResolvingContext resolvingContext, ArtifactType artifactType) {
+ final Artifact artifact = findArtifact(resolvingContext);
+ if (artifact != null) {
+ return artifact.getArtifactType();
+ }
+ return artifactType;
+ }
+
+ public PackagingElementPresentation createPresentation(@NotNull ArtifactEditorContext context) {
+ return new DelegatedPackagingElementPresentation(new ArtifactElementPresentation(myArtifactPointer, context));
+ }
+
+ public ArtifactPackagingElementState getState() {
+ final ArtifactPackagingElementState state = new ArtifactPackagingElementState();
+ if (myArtifactPointer != null) {
+ state.setArtifactName(myArtifactPointer.getArtifactName());
+ }
+ return state;
+ }
+
+ public void loadState(ArtifactPackagingElementState state) {
+ final String name = state.getArtifactName();
+ myArtifactPointer = name != null ? ArtifactPointerManager.getInstance(myProject).createPointer(name) : null;
+ }
+
+ @Override
+ public String toString() {
+ return "artifact:" + getArtifactName();
+ }
+
+ @Override
+ public boolean isEqualTo(@NotNull PackagingElement<?> element) {
+ return element instanceof ArtifactPackagingElement && myArtifactPointer != null
+ && myArtifactPointer.equals(((ArtifactPackagingElement)element).myArtifactPointer);
+ }
+
+ @Nullable
+ public Artifact findArtifact(@NotNull PackagingElementResolvingContext context) {
+ return myArtifactPointer != null ? myArtifactPointer.findArtifact(context.getArtifactModel()) : null;
+ }
+
+ @Nullable
+ public String getArtifactName() {
+ return myArtifactPointer != null ? myArtifactPointer.getArtifactName() : null;
+ }
+
+
+ public static class ArtifactPackagingElementState {
+ private String myArtifactName;
+
+ @Attribute(ARTIFACT_NAME_ATTRIBUTE)
+ public String getArtifactName() {
+ return myArtifactName;
+ }
+
+ public void setArtifactName(String artifactName) {
+ myArtifactName = artifactName;
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/ArtifactRootElementImpl.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ArtifactRootElementImpl.java
new file mode 100644
index 0000000..5fcba6f
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ArtifactRootElementImpl.java
@@ -0,0 +1,96 @@
+/*
+ * 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.compiler.ant.Generator;
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.packaging.artifacts.ArtifactType;
+import com.intellij.packaging.elements.*;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import com.intellij.packaging.ui.PackagingElementPresentation;
+import com.intellij.ui.SimpleTextAttributes;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public class ArtifactRootElementImpl extends ArtifactRootElement<Object> {
+ public ArtifactRootElementImpl() {
+ super(PackagingElementFactoryImpl.ARTIFACT_ROOT_ELEMENT_TYPE);
+ }
+
+ public PackagingElementPresentation createPresentation(@NotNull ArtifactEditorContext context) {
+ return new PackagingElementPresentation() {
+ @Override
+ public String getPresentableName() {
+ return CompilerBundle.message("packaging.element.text.output.root");
+ }
+
+ @Override
+ public void render(@NotNull PresentationData presentationData, SimpleTextAttributes mainAttributes,
+ SimpleTextAttributes commentAttributes) {
+ presentationData.setIcon(AllIcons.Nodes.Artifact);
+ presentationData.addText(getPresentableName(), mainAttributes);
+ }
+
+ @Override
+ public int getWeight() {
+ return 0;
+ }
+ };
+ }
+
+ public Object getState() {
+ return null;
+ }
+
+ public void loadState(Object state) {
+ }
+
+ @Override
+ public boolean canBeRenamed() {
+ return false;
+ }
+
+ public void rename(@NotNull String newName) {
+ }
+
+ public List<? extends Generator> computeAntInstructions(@NotNull PackagingElementResolvingContext resolvingContext, @NotNull AntCopyInstructionCreator creator,
+ @NotNull ArtifactAntGenerationContext generationContext,
+ @NotNull ArtifactType artifactType) {
+ return computeChildrenGenerators(resolvingContext, creator, generationContext, artifactType);
+ }
+
+ @Override
+ public void computeIncrementalCompilerInstructions(@NotNull IncrementalCompilerInstructionCreator creator,
+ @NotNull PackagingElementResolvingContext resolvingContext,
+ @NotNull ArtifactIncrementalCompilerContext compilerContext, @NotNull ArtifactType artifactType) {
+ computeChildrenInstructions(creator, resolvingContext, compilerContext, artifactType);
+ }
+
+ public String getName() {
+ return "";
+ }
+
+ @Override
+ public String toString() {
+ return "<root>";
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/CompositeElementWithManifest.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/CompositeElementWithManifest.java
new file mode 100644
index 0000000..fcaf889
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/CompositeElementWithManifest.java
@@ -0,0 +1,28 @@
+/*
+ * 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.packaging.elements.PackagingElementType;
+
+/**
+ * @author nik
+ */
+public abstract class CompositeElementWithManifest<T> extends CompositePackagingElement<T> {
+ protected CompositeElementWithManifest(PackagingElementType type) {
+ super(type);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/DirectoryCopyElementType.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/DirectoryCopyElementType.java
new file mode 100644
index 0000000..bc7258d
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/DirectoryCopyElementType.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.fileChooser.FileChooser;
+import com.intellij.openapi.fileChooser.FileChooserDescriptor;
+import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.packaging.elements.PackagingElementType;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+* @author nik
+*/
+public class DirectoryCopyElementType extends PackagingElementType<DirectoryCopyPackagingElement> {
+
+ DirectoryCopyElementType() {
+ super("dir-copy", "Directory Content");
+ }
+
+ @Override
+ public Icon getCreateElementIcon() {
+ return AllIcons.Nodes.CopyOfFolder;
+ }
+
+ @Override
+ public boolean canCreate(@NotNull ArtifactEditorContext context, @NotNull Artifact artifact) {
+ return true;
+ }
+
+ @NotNull
+ public List<? extends DirectoryCopyPackagingElement> chooseAndCreate(@NotNull ArtifactEditorContext context, @NotNull Artifact artifact,
+ @NotNull CompositePackagingElement<?> parent) {
+ final FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createMultipleFoldersDescriptor();
+ final VirtualFile[] files = FileChooser.chooseFiles(descriptor, context.getProject(), null);
+ final List<DirectoryCopyPackagingElement> list = new ArrayList<DirectoryCopyPackagingElement>();
+ for (VirtualFile file : files) {
+ list.add(new DirectoryCopyPackagingElement(file.getPath()));
+ }
+ return list;
+ }
+
+ @NotNull
+ public DirectoryCopyPackagingElement createEmpty(@NotNull Project project) {
+ return new DirectoryCopyPackagingElement();
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/DirectoryCopyPackagingElement.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/DirectoryCopyPackagingElement.java
new file mode 100644
index 0000000..c5c10dc
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/DirectoryCopyPackagingElement.java
@@ -0,0 +1,80 @@
+/*
+ * 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.compiler.ant.Generator;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.ArtifactType;
+import com.intellij.packaging.elements.*;
+import com.intellij.packaging.impl.ui.DirectoryCopyPresentation;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import com.intellij.packaging.ui.PackagingElementPresentation;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public class DirectoryCopyPackagingElement extends FileOrDirectoryCopyPackagingElement<DirectoryCopyPackagingElement> {
+ public DirectoryCopyPackagingElement() {
+ super(PackagingElementFactoryImpl.DIRECTORY_COPY_ELEMENT_TYPE);
+ }
+
+ public DirectoryCopyPackagingElement(String directoryPath) {
+ this();
+ myFilePath = directoryPath;
+ }
+
+ @Override
+ public PackagingElementPresentation createPresentation(@NotNull ArtifactEditorContext context) {
+ return new DirectoryCopyPresentation(myFilePath);
+ }
+
+ @Override
+ public List<? extends Generator> computeAntInstructions(@NotNull PackagingElementResolvingContext resolvingContext,
+ @NotNull AntCopyInstructionCreator creator,
+ @NotNull ArtifactAntGenerationContext generationContext,
+ @NotNull ArtifactType artifactType) {
+ final String path = generationContext.getSubstitutedPath(myFilePath);
+ return Collections.singletonList(creator.createDirectoryContentCopyInstruction(path));
+ }
+
+ @Override
+ public void computeIncrementalCompilerInstructions(@NotNull IncrementalCompilerInstructionCreator creator,
+ @NotNull PackagingElementResolvingContext resolvingContext,
+ @NotNull ArtifactIncrementalCompilerContext compilerContext,
+ @NotNull ArtifactType artifactType) {
+ final VirtualFile file = findFile();
+ if (file != null && file.isValid() && file.isDirectory()) {
+ creator.addDirectoryCopyInstructions(file);
+ }
+ }
+
+ public DirectoryCopyPackagingElement getState() {
+ return this;
+ }
+
+ public void loadState(DirectoryCopyPackagingElement state) {
+ myFilePath = state.getFilePath();
+ }
+
+ @Override
+ public String toString() {
+ return "dir:" + myFilePath;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/DirectoryElementType.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/DirectoryElementType.java
new file mode 100644
index 0000000..5732faf
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/DirectoryElementType.java
@@ -0,0 +1,67 @@
+/*
+ * 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.packaging.elements.CompositePackagingElementType;
+import com.intellij.packaging.impl.artifacts.ArtifactUtil;
+import com.intellij.packaging.impl.ui.properties.DirectoryElementPropertiesPanel;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import com.intellij.packaging.ui.PackagingElementPropertiesPanel;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+
+/**
+* @author nik
+*/
+class DirectoryElementType extends CompositePackagingElementType<DirectoryPackagingElement> {
+
+ DirectoryElementType() {
+ super("directory", CompilerBundle.message("element.type.name.directory"));
+ }
+
+ @Override
+ public Icon getCreateElementIcon() {
+ return AllIcons.Actions.NewFolder;
+ }
+
+ @NotNull
+ public DirectoryPackagingElement createEmpty(@NotNull Project project) {
+ return new DirectoryPackagingElement();
+ }
+
+ @Override
+ public PackagingElementPropertiesPanel createElementPropertiesPanel(@NotNull DirectoryPackagingElement element,
+ @NotNull ArtifactEditorContext context) {
+ if (ArtifactUtil.isArchiveName(element.getDirectoryName())) {
+ return new DirectoryElementPropertiesPanel(element, context);
+ }
+ return null;
+ }
+
+ public CompositePackagingElement<?> createComposite(CompositePackagingElement<?> parent, String baseName, @NotNull ArtifactEditorContext context) {
+ final String initialValue = PackagingElementFactoryImpl.suggestFileName(parent, baseName != null ? baseName : "folder", "");
+ String path = Messages.showInputDialog(context.getProject(), "Enter directory name: ", "New Directory", null, initialValue, new FilePathValidator());
+ if (path == null) return null;
+ return PackagingElementFactoryImpl.createDirectoryOrArchiveWithParents(path, false);
+ }
+
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/DirectoryPackagingElement.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/DirectoryPackagingElement.java
new file mode 100644
index 0000000..0fd3a34
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/DirectoryPackagingElement.java
@@ -0,0 +1,109 @@
+/*
+ * 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.compiler.ant.Generator;
+import com.intellij.packaging.elements.*;
+import com.intellij.packaging.impl.ui.DirectoryElementPresentation;
+import com.intellij.packaging.ui.PackagingElementPresentation;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import com.intellij.packaging.artifacts.ArtifactType;
+import com.intellij.util.xmlb.annotations.Attribute;
+import com.intellij.util.xmlb.XmlSerializerUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.NonNls;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author nik
+ *
+ * classpath is used for exploded WAR and EJB directories under exploded EAR
+ */
+public class DirectoryPackagingElement extends CompositeElementWithManifest<DirectoryPackagingElement> {
+ @NonNls public static final String NAME_ATTRIBUTE = "name";
+ private String myDirectoryName;
+
+ public DirectoryPackagingElement() {
+ super(PackagingElementFactoryImpl.DIRECTORY_ELEMENT_TYPE);
+ }
+
+ public DirectoryPackagingElement(String directoryName) {
+ super(PackagingElementFactoryImpl.DIRECTORY_ELEMENT_TYPE);
+ myDirectoryName = directoryName;
+ }
+
+ public PackagingElementPresentation createPresentation(@NotNull ArtifactEditorContext context) {
+ return new DirectoryElementPresentation(this);
+ }
+
+ @Override
+ public List<? extends Generator> computeAntInstructions(@NotNull PackagingElementResolvingContext resolvingContext, @NotNull AntCopyInstructionCreator creator,
+ @NotNull ArtifactAntGenerationContext generationContext,
+ @NotNull ArtifactType artifactType) {
+
+ final List<Generator> children = new ArrayList<Generator>();
+ final Generator command = creator.createSubFolderCommand(myDirectoryName);
+ if (command != null) {
+ children.add(command);
+ }
+ children.addAll(computeChildrenGenerators(resolvingContext, creator.subFolder(myDirectoryName), generationContext, artifactType));
+ return children;
+ }
+
+ @Override
+ public void computeIncrementalCompilerInstructions(@NotNull IncrementalCompilerInstructionCreator creator,
+ @NotNull PackagingElementResolvingContext resolvingContext,
+ @NotNull ArtifactIncrementalCompilerContext compilerContext, @NotNull ArtifactType artifactType) {
+ computeChildrenInstructions(creator.subFolder(myDirectoryName), resolvingContext, compilerContext, artifactType);
+ }
+
+ public DirectoryPackagingElement getState() {
+ return this;
+ }
+
+ @NonNls @Override
+ public String toString() {
+ return "dir:" + myDirectoryName;
+ }
+
+ @Attribute(NAME_ATTRIBUTE)
+ public String getDirectoryName() {
+ return myDirectoryName;
+ }
+
+ public void setDirectoryName(String directoryName) {
+ myDirectoryName = directoryName;
+ }
+
+ public void rename(@NotNull String newName) {
+ myDirectoryName = newName;
+ }
+
+ public String getName() {
+ return myDirectoryName;
+ }
+
+ @Override
+ public boolean isEqualTo(@NotNull PackagingElement<?> element) {
+ return element instanceof DirectoryPackagingElement && ((DirectoryPackagingElement)element).getDirectoryName().equals(myDirectoryName);
+ }
+
+ public void loadState(DirectoryPackagingElement state) {
+ XmlSerializerUtil.copyBean(state, this);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/ExtractedDirectoryElementType.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ExtractedDirectoryElementType.java
new file mode 100644
index 0000000..8f487f3
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ExtractedDirectoryElementType.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.fileChooser.FileChooser;
+import com.intellij.openapi.fileChooser.FileChooserDescriptor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.packaging.elements.PackagingElement;
+import com.intellij.packaging.elements.PackagingElementFactory;
+import com.intellij.packaging.elements.PackagingElementType;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+* @author nik
+*/
+public class ExtractedDirectoryElementType extends PackagingElementType<ExtractedDirectoryPackagingElement> {
+
+ ExtractedDirectoryElementType() {
+ super("extracted-dir", "Extracted Directory");
+ }
+
+ @Override
+ public Icon getCreateElementIcon() {
+ return AllIcons.Nodes.ExtractedFolder;
+ }
+
+ @Override
+ public boolean canCreate(@NotNull ArtifactEditorContext context, @NotNull Artifact artifact) {
+ return true;
+ }
+
+ @NotNull
+ public List<? extends PackagingElement<?>> chooseAndCreate(@NotNull ArtifactEditorContext context, @NotNull Artifact artifact,
+ @NotNull CompositePackagingElement<?> parent) {
+ final FileChooserDescriptor descriptor = new FileChooserDescriptor(false, true, true, false, true, true) {
+ @Override
+ public boolean isFileSelectable(VirtualFile file) {
+ if (file.isInLocalFileSystem() && file.isDirectory()) return false;
+ return super.isFileSelectable(file);
+ }
+ };
+ final VirtualFile[] files = FileChooser.chooseFiles(descriptor, context.getProject(), null);
+ final List<PackagingElement<?>> list = new ArrayList<PackagingElement<?>>();
+ final PackagingElementFactory factory = PackagingElementFactory.getInstance();
+ for (VirtualFile file : files) {
+ list.add(factory.createExtractedDirectory(file));
+ }
+ return list;
+ }
+
+ @NotNull
+ public ExtractedDirectoryPackagingElement createEmpty(@NotNull Project project) {
+ return new ExtractedDirectoryPackagingElement();
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/ExtractedDirectoryPackagingElement.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ExtractedDirectoryPackagingElement.java
new file mode 100644
index 0000000..a0b4dda
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ExtractedDirectoryPackagingElement.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.compiler.ant.BuildProperties;
+import com.intellij.compiler.ant.Generator;
+import com.intellij.compiler.ant.taskdefs.Include;
+import com.intellij.compiler.ant.taskdefs.Mkdir;
+import com.intellij.compiler.ant.taskdefs.PatternSet;
+import com.intellij.compiler.ant.taskdefs.Unzip;
+import com.intellij.openapi.util.Comparing;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.JarFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.ArtifactType;
+import com.intellij.packaging.elements.*;
+import com.intellij.packaging.impl.ui.ExtractedDirectoryPresentation;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import com.intellij.packaging.ui.PackagingElementPresentation;
+import com.intellij.util.PathUtil;
+import com.intellij.util.xmlb.annotations.Attribute;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public class ExtractedDirectoryPackagingElement extends FileOrDirectoryCopyPackagingElement<ExtractedDirectoryPackagingElement> {
+ private String myPathInJar;
+
+ public ExtractedDirectoryPackagingElement() {
+ super(PackagingElementFactoryImpl.EXTRACTED_DIRECTORY_ELEMENT_TYPE);
+ }
+
+ public ExtractedDirectoryPackagingElement(String jarPath, String pathInJar) {
+ super(PackagingElementFactoryImpl.EXTRACTED_DIRECTORY_ELEMENT_TYPE, jarPath);
+ myPathInJar = pathInJar;
+ if (!StringUtil.startsWithChar(myPathInJar, '/')) {
+ myPathInJar = "/" + myPathInJar;
+ }
+ if (!StringUtil.endsWithChar(myPathInJar, '/')) {
+ myPathInJar += "/";
+ }
+ }
+
+ @Override
+ public PackagingElementPresentation createPresentation(@NotNull ArtifactEditorContext context) {
+ return new ExtractedDirectoryPresentation(this);
+ }
+
+ @Override
+ public String toString() {
+ return "extracted:" + myFilePath + "!" + myPathInJar;
+ }
+
+ @Override
+ public VirtualFile findFile() {
+ final VirtualFile jarFile = super.findFile();
+ if (jarFile == null) return null;
+
+ final VirtualFile jarRoot = JarFileSystem.getInstance().getJarRootForLocalFile(jarFile);
+ if ("/".equals(myPathInJar)) return jarRoot;
+ return jarRoot != null ? jarRoot.findFileByRelativePath(myPathInJar) : null;
+ }
+
+ @Override
+ public List<? extends Generator> computeAntInstructions(@NotNull PackagingElementResolvingContext resolvingContext,
+ @NotNull AntCopyInstructionCreator creator,
+ @NotNull ArtifactAntGenerationContext generationContext,
+ @NotNull ArtifactType artifactType) {
+ final String jarPath = generationContext.getSubstitutedPath(myFilePath);
+ final String pathInJar = StringUtil.trimStart(myPathInJar, "/");
+ if (pathInJar.length() == 0) {
+ return Collections.singletonList(creator.createExtractedDirectoryInstruction(jarPath));
+ }
+
+ final String archiveName = PathUtil.getFileName(myFilePath);
+ final String tempDirProperty = generationContext.createNewTempFileProperty("temp.unpacked.path." + archiveName, archiveName);
+ final String tempDirPath = BuildProperties.propertyRef(tempDirProperty);
+ generationContext.runBeforeCurrentArtifact(new Mkdir(tempDirPath));
+
+ final Unzip unzip = new Unzip(jarPath, tempDirPath);
+ final PatternSet patterns = new PatternSet(null);
+ patterns.add(new Include(pathInJar + "**"));
+ unzip.add(patterns);
+ generationContext.runBeforeCurrentArtifact(unzip);
+
+ return Collections.singletonList(creator.createDirectoryContentCopyInstruction(tempDirPath + "/" + pathInJar));
+ }
+
+
+ @Override
+ public void computeIncrementalCompilerInstructions(@NotNull IncrementalCompilerInstructionCreator creator,
+ @NotNull PackagingElementResolvingContext resolvingContext,
+ @NotNull ArtifactIncrementalCompilerContext compilerContext,
+ @NotNull ArtifactType artifactType) {
+ final VirtualFile file = findFile();
+ if (file != null && file.isValid() && file.isDirectory()) {
+ creator.addDirectoryCopyInstructions(file);
+ }
+ }
+
+ @Override
+ public boolean isEqualTo(@NotNull PackagingElement<?> element) {
+ return element instanceof ExtractedDirectoryPackagingElement && super.isEqualTo(element)
+ && Comparing.equal(myPathInJar, ((ExtractedDirectoryPackagingElement)element).getPathInJar());
+ }
+
+ @Override
+ public ExtractedDirectoryPackagingElement getState() {
+ return this;
+ }
+
+ @Override
+ public void loadState(ExtractedDirectoryPackagingElement state) {
+ myFilePath = state.getFilePath();
+ myPathInJar = state.getPathInJar();
+ }
+
+ @Attribute("path-in-jar")
+ public String getPathInJar() {
+ return myPathInJar;
+ }
+
+ public void setPathInJar(String pathInJar) {
+ myPathInJar = pathInJar;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/FacetBasedPackagingElement.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/FacetBasedPackagingElement.java
new file mode 100644
index 0000000..a34fdaf
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/FacetBasedPackagingElement.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2000-2011 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.facet.Facet;
+import com.intellij.packaging.elements.PackagingElementResolvingContext;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author nik
+ */
+public interface FacetBasedPackagingElement {
+ @Nullable
+ Facet findFacet(@NotNull PackagingElementResolvingContext context);
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/FacetBasedPackagingElementType.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/FacetBasedPackagingElementType.java
new file mode 100644
index 0000000..dc0383f
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/FacetBasedPackagingElementType.java
@@ -0,0 +1,112 @@
+/*
+ * 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.facet.Facet;
+import com.intellij.facet.FacetTypeId;
+import com.intellij.facet.FacetTypeRegistry;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.ide.util.ChooseElementsDialog;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.packaging.elements.PackagingElement;
+import com.intellij.packaging.elements.PackagingElementType;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public abstract class FacetBasedPackagingElementType<E extends PackagingElement<?>, F extends Facet> extends PackagingElementType<E> {
+ private final FacetTypeId<F> myFacetType;
+
+ protected FacetBasedPackagingElementType(@NotNull @NonNls String id, @NotNull String presentableName, FacetTypeId<F> facetType) {
+ super(id, presentableName);
+ myFacetType = facetType;
+ }
+
+ @Override
+ public boolean canCreate(@NotNull ArtifactEditorContext context, @NotNull Artifact artifact) {
+ return !getFacets(context).isEmpty();
+ }
+
+ @Override
+ public Icon getCreateElementIcon() {
+ return FacetTypeRegistry.getInstance().findFacetType(myFacetType).getIcon();
+ }
+
+ @NotNull
+ @Override
+ public List<? extends E> chooseAndCreate(@NotNull ArtifactEditorContext context, @NotNull Artifact artifact, @NotNull CompositePackagingElement<?> parent) {
+ final List<F> facets = getFacets(context);
+ ChooseFacetsDialog dialog = new ChooseFacetsDialog(context.getProject(), facets, getDialogTitle(), getDialogDescription());
+ dialog.show();
+ if (dialog.isOK()) {
+ final List<E> elements = new ArrayList<E>();
+ for (F facet : dialog.getChosenElements()) {
+ elements.add(createElement(context.getProject(), facet));
+ }
+ return elements;
+ }
+ return Collections.emptyList();
+ }
+
+ private List<F> getFacets(ArtifactEditorContext context) {
+ final Module[] modules = context.getModulesProvider().getModules();
+ final List<F> facets = new ArrayList<F>();
+ for (Module module : modules) {
+ facets.addAll(context.getFacetsProvider().getFacetsByType(module, myFacetType));
+ }
+ return facets;
+ }
+
+ protected abstract E createElement(Project project, F facet);
+
+ protected abstract String getDialogTitle();
+
+ protected abstract String getDialogDescription();
+
+ protected abstract String getItemText(F item);
+
+ @Nullable
+ protected Icon getIcon(F item) {
+ return FacetTypeRegistry.getInstance().findFacetType(myFacetType).getIcon();
+ }
+
+ private class ChooseFacetsDialog extends ChooseElementsDialog<F> {
+ private ChooseFacetsDialog(Project project, List<? extends F> items, String title, String description) {
+ super(project, items, title, description, true);
+ }
+
+ @Override
+ protected String getItemText(F item) {
+ return FacetBasedPackagingElementType.this.getItemText(item);
+ }
+
+ @Override
+ protected Icon getItemIcon(F item) {
+ return FacetBasedPackagingElementType.this.getIcon(item);
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/FileCopyElementType.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/FileCopyElementType.java
new file mode 100644
index 0000000..ee3f52a
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/FileCopyElementType.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.fileChooser.FileChooser;
+import com.intellij.openapi.fileChooser.FileChooserDescriptor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.packaging.elements.PackagingElementType;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+* @author nik
+*/
+public class FileCopyElementType extends PackagingElementType<FileCopyPackagingElement> {
+
+ FileCopyElementType() {
+ super("file-copy", "File");
+ }
+
+ @Override
+ public Icon getCreateElementIcon() {
+ return AllIcons.FileTypes.Text;
+ }
+
+ @Override
+ public boolean canCreate(@NotNull ArtifactEditorContext context, @NotNull Artifact artifact) {
+ return true;
+ }
+
+ @NotNull
+ public List<? extends FileCopyPackagingElement> chooseAndCreate(@NotNull ArtifactEditorContext context, @NotNull Artifact artifact,
+ @NotNull CompositePackagingElement<?> parent) {
+ final FileChooserDescriptor descriptor = new FileChooserDescriptor(true, false, true, true, false, true);
+ final VirtualFile[] files = FileChooser.chooseFiles(descriptor, context.getProject(), null);
+ final List<FileCopyPackagingElement> list = new ArrayList<FileCopyPackagingElement>();
+ for (VirtualFile file : files) {
+ list.add(new FileCopyPackagingElement(file.getPath()));
+ }
+ return list;
+ }
+
+ @NotNull
+ public FileCopyPackagingElement createEmpty(@NotNull Project project) {
+ return new FileCopyPackagingElement();
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/FileCopyPackagingElement.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/FileCopyPackagingElement.java
new file mode 100644
index 0000000..6513c76
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/FileCopyPackagingElement.java
@@ -0,0 +1,141 @@
+/*
+ * 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.compiler.ant.Generator;
+import com.intellij.openapi.util.Comparing;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.vfs.VirtualFileManager;
+import com.intellij.packaging.artifacts.ArtifactType;
+import com.intellij.packaging.elements.*;
+import com.intellij.packaging.impl.ui.FileCopyPresentation;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import com.intellij.packaging.ui.PackagingElementPresentation;
+import com.intellij.util.PathUtil;
+import com.intellij.util.xmlb.annotations.Attribute;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public class FileCopyPackagingElement extends FileOrDirectoryCopyPackagingElement<FileCopyPackagingElement> implements RenameablePackagingElement {
+ @NonNls public static final String OUTPUT_FILE_NAME_ATTRIBUTE = "output-file-name";
+ private String myRenamedOutputFileName;
+
+ public FileCopyPackagingElement() {
+ super(PackagingElementFactoryImpl.FILE_COPY_ELEMENT_TYPE);
+ }
+
+ public FileCopyPackagingElement(String filePath) {
+ this();
+ myFilePath = filePath;
+ }
+
+ public FileCopyPackagingElement(String filePath, String outputFileName) {
+ this(filePath);
+ myRenamedOutputFileName = outputFileName;
+ }
+
+ public PackagingElementPresentation createPresentation(@NotNull ArtifactEditorContext context) {
+ return new FileCopyPresentation(myFilePath, getOutputFileName());
+ }
+
+ @Override
+ public List<? extends Generator> computeAntInstructions(@NotNull PackagingElementResolvingContext resolvingContext, @NotNull AntCopyInstructionCreator creator,
+ @NotNull ArtifactAntGenerationContext generationContext,
+ @NotNull ArtifactType artifactType) {
+ if (isDirectory()) {
+ return Collections.emptyList();
+ }
+ final String path = generationContext.getSubstitutedPath(myFilePath);
+ return Collections.singletonList((Generator)creator.createFileCopyInstruction(path, getOutputFileName()));
+ }
+
+ public String getOutputFileName() {
+ return myRenamedOutputFileName != null ? myRenamedOutputFileName : PathUtil.getFileName(myFilePath);
+ }
+
+ @Override
+ public void computeIncrementalCompilerInstructions(@NotNull IncrementalCompilerInstructionCreator creator,
+ @NotNull PackagingElementResolvingContext resolvingContext,
+ @NotNull ArtifactIncrementalCompilerContext compilerContext, @NotNull ArtifactType artifactType) {
+ final VirtualFile file = findFile();
+ if (file != null && file.isValid() && !file.isDirectory()) {
+ creator.addFileCopyInstruction(file, getOutputFileName());
+ }
+ }
+
+ @NonNls @Override
+ public String toString() {
+ return "file:" + myFilePath + (myRenamedOutputFileName != null ? ",rename to:" + myRenamedOutputFileName : "");
+ }
+
+ public boolean isDirectory() {
+ return new File(FileUtil.toSystemDependentName(myFilePath)).isDirectory();
+ }
+
+
+ @Override
+ public boolean isEqualTo(@NotNull PackagingElement<?> element) {
+ return element instanceof FileCopyPackagingElement && super.isEqualTo(element)
+ && Comparing.equal(myRenamedOutputFileName, ((FileCopyPackagingElement)element).getRenamedOutputFileName());
+ }
+
+ public FileCopyPackagingElement getState() {
+ return this;
+ }
+
+ public void loadState(FileCopyPackagingElement state) {
+ setFilePath(state.getFilePath());
+ setRenamedOutputFileName(state.getRenamedOutputFileName());
+ }
+
+ @Nullable
+ @Attribute(OUTPUT_FILE_NAME_ATTRIBUTE)
+ public String getRenamedOutputFileName() {
+ return myRenamedOutputFileName;
+ }
+
+ public void setRenamedOutputFileName(String renamedOutputFileName) {
+ myRenamedOutputFileName = renamedOutputFileName;
+ }
+
+ public String getName() {
+ return getOutputFileName();
+ }
+
+ public boolean canBeRenamed() {
+ return !isDirectory();
+ }
+
+ public void rename(@NotNull String newName) {
+ myRenamedOutputFileName = newName.equals(PathUtil.getFileName(myFilePath)) ? null : newName;
+ }
+
+ @Nullable
+ public VirtualFile getLibraryRoot() {
+ final String url = VfsUtil.getUrlForLibraryRoot(new File(FileUtil.toSystemDependentName(getFilePath())));
+ return VirtualFileManager.getInstance().findFileByUrl(url);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/FileOrDirectoryCopyPackagingElement.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/FileOrDirectoryCopyPackagingElement.java
new file mode 100644
index 0000000..eca74ff
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/FileOrDirectoryCopyPackagingElement.java
@@ -0,0 +1,63 @@
+/*
+ * 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.elements.PackagingElement;
+import com.intellij.packaging.elements.PackagingElementType;
+import com.intellij.util.xmlb.annotations.Attribute;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author nik
+ */
+public abstract class FileOrDirectoryCopyPackagingElement<T extends FileOrDirectoryCopyPackagingElement> extends PackagingElement<T> {
+ @NonNls public static final String PATH_ATTRIBUTE = "path";
+ protected String myFilePath;
+
+ public FileOrDirectoryCopyPackagingElement(PackagingElementType type) {
+ super(type);
+ }
+
+ protected FileOrDirectoryCopyPackagingElement(PackagingElementType type, String filePath) {
+ super(type);
+ myFilePath = filePath;
+ }
+
+ @Nullable
+ public VirtualFile findFile() {
+ return LocalFileSystem.getInstance().findFileByPath(myFilePath);
+ }
+
+ @Override
+ public boolean isEqualTo(@NotNull PackagingElement<?> element) {
+ return element instanceof FileOrDirectoryCopyPackagingElement &&
+ myFilePath != null &&
+ myFilePath.equals(((FileOrDirectoryCopyPackagingElement)element).getFilePath());
+ }
+
+ @Attribute(PATH_ATTRIBUTE)
+ public String getFilePath() {
+ return myFilePath;
+ }
+
+ public void setFilePath(String filePath) {
+ myFilePath = filePath;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/FilePathValidator.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/FilePathValidator.java
new file mode 100644
index 0000000..c31e006
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/FilePathValidator.java
@@ -0,0 +1,45 @@
+/*
+ * 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.openapi.ui.InputValidator;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.util.PathUtil;
+
+import java.util.List;
+
+/**
+ * @author nik
+ */
+class FilePathValidator implements InputValidator {
+ public boolean checkInput(String inputString) {
+ final List<String> fileNames = StringUtil.split(FileUtil.toSystemIndependentName(inputString), "/");
+ if (fileNames.isEmpty()) {
+ return false;
+ }
+ for (String fileName : fileNames) {
+ if (!PathUtil.isValidFileName(fileName)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean canClose(String inputString) {
+ return checkInput(inputString);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/LibraryElementType.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/LibraryElementType.java
new file mode 100644
index 0000000..591c50c
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/LibraryElementType.java
@@ -0,0 +1,82 @@
+/*
+ * 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectBundle;
+import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.elements.ComplexPackagingElementType;
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import com.intellij.util.PlatformIcons;
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+* @author nik
+*/
+public class LibraryElementType extends ComplexPackagingElementType<LibraryPackagingElement> {
+ public static final LibraryElementType LIBRARY_ELEMENT_TYPE = new LibraryElementType();
+
+ LibraryElementType() {
+ super("library", CompilerBundle.message("element.type.name.library.files"));
+ }
+
+ @Override
+ public Icon getCreateElementIcon() {
+ return PlatformIcons.LIBRARY_ICON;
+ }
+
+ @Override
+ public boolean canCreate(@NotNull ArtifactEditorContext context, @NotNull Artifact artifact) {
+ return !getAllLibraries(context).isEmpty();
+ }
+
+ @NotNull
+ public List<? extends LibraryPackagingElement> chooseAndCreate(@NotNull ArtifactEditorContext context, @NotNull Artifact artifact,
+ @NotNull CompositePackagingElement<?> parent) {
+ final List<Library> selected = context.chooseLibraries(ProjectBundle.message("dialog.title.packaging.choose.library"));
+ final List<LibraryPackagingElement> elements = new ArrayList<LibraryPackagingElement>();
+ for (Library library : selected) {
+ elements.add(new LibraryPackagingElement(library.getTable().getTableLevel(), library.getName(), null));
+ }
+ return elements;
+ }
+
+ private static List<Library> getAllLibraries(ArtifactEditorContext context) {
+ List<Library> libraries = new ArrayList<Library>();
+ ContainerUtil.addAll(libraries, LibraryTablesRegistrar.getInstance().getLibraryTable().getLibraries());
+ ContainerUtil.addAll(libraries, LibraryTablesRegistrar.getInstance().getLibraryTable(context.getProject()).getLibraries());
+ return libraries;
+ }
+
+ @NotNull
+ public LibraryPackagingElement createEmpty(@NotNull Project project) {
+ return new LibraryPackagingElement();
+ }
+
+ @Override
+ public String getShowContentActionText() {
+ return "Show Library Files";
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/LibraryPackagingElement.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/LibraryPackagingElement.java
new file mode 100644
index 0000000..d776410
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/LibraryPackagingElement.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.roots.LibraryOrderEntry;
+import com.intellij.openapi.roots.OrderEntry;
+import com.intellij.openapi.roots.OrderRootType;
+import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.roots.ui.configuration.ModulesProvider;
+import com.intellij.openapi.util.Comparing;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.ArtifactType;
+import com.intellij.packaging.elements.ComplexPackagingElement;
+import com.intellij.packaging.elements.PackagingElement;
+import com.intellij.packaging.elements.PackagingElementOutputKind;
+import com.intellij.packaging.elements.PackagingElementResolvingContext;
+import com.intellij.packaging.impl.ui.LibraryElementPresentation;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import com.intellij.packaging.ui.PackagingElementPresentation;
+import com.intellij.util.PathUtil;
+import com.intellij.util.xmlb.annotations.Attribute;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public class LibraryPackagingElement extends ComplexPackagingElement<LibraryPackagingElement> {
+ @NonNls public static final String LIBRARY_NAME_ATTRIBUTE = "name";
+ @NonNls public static final String MODULE_NAME_ATTRIBUTE = "module-name";
+ @NonNls public static final String LIBRARY_LEVEL_ATTRIBUTE = "level";
+ private String myLevel;
+ private String myLibraryName;
+ private String myModuleName;
+
+ public LibraryPackagingElement() {
+ super(LibraryElementType.LIBRARY_ELEMENT_TYPE);
+ }
+
+ public LibraryPackagingElement(String level, String libraryName, String moduleName) {
+ super(LibraryElementType.LIBRARY_ELEMENT_TYPE);
+ myLevel = level;
+ myLibraryName = libraryName;
+ myModuleName = moduleName;
+ }
+
+ public List<? extends PackagingElement<?>> getSubstitution(@NotNull PackagingElementResolvingContext context, @NotNull ArtifactType artifactType) {
+ final Library library = findLibrary(context);
+ if (library != null) {
+ final VirtualFile[] files = library.getFiles(OrderRootType.CLASSES);
+ final List<PackagingElement<?>> elements = new ArrayList<PackagingElement<?>>();
+ for (VirtualFile file : files) {
+ final String path = FileUtil.toSystemIndependentName(PathUtil.getLocalPath(file));
+ elements.add(file.isDirectory() && file.isInLocalFileSystem() ? new DirectoryCopyPackagingElement(path) : new FileCopyPackagingElement(path));
+ }
+ return elements;
+ }
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public PackagingElementOutputKind getFilesKind(PackagingElementResolvingContext context) {
+ final Library library = findLibrary(context);
+ return library != null ? getKindForLibrary(library) : PackagingElementOutputKind.OTHER;
+ }
+
+ public PackagingElementPresentation createPresentation(@NotNull ArtifactEditorContext context) {
+ return new LibraryElementPresentation(myLibraryName, myLevel, myModuleName, findLibrary(context), context);
+ }
+
+ @Override
+ public boolean isEqualTo(@NotNull PackagingElement<?> element) {
+ if (!(element instanceof LibraryPackagingElement)) {
+ return false;
+ }
+
+ LibraryPackagingElement packagingElement = (LibraryPackagingElement)element;
+ return myLevel != null && myLibraryName != null && myLevel.equals(packagingElement.getLevel())
+ && myLibraryName.equals(packagingElement.getLibraryName())
+ && Comparing.equal(myModuleName, packagingElement.getModuleName());
+ }
+
+ public LibraryPackagingElement getState() {
+ return this;
+ }
+
+ public void loadState(LibraryPackagingElement state) {
+ myLevel = state.getLevel();
+ myLibraryName = state.getLibraryName();
+ myModuleName = state.getModuleName();
+ }
+
+ @Attribute(LIBRARY_LEVEL_ATTRIBUTE)
+ public String getLevel() {
+ return myLevel;
+ }
+
+ public void setLevel(String level) {
+ myLevel = level;
+ }
+
+ @Attribute(LIBRARY_NAME_ATTRIBUTE)
+ public String getLibraryName() {
+ return myLibraryName;
+ }
+
+ public void setLibraryName(String libraryName) {
+ myLibraryName = libraryName;
+ }
+
+ @Attribute(MODULE_NAME_ATTRIBUTE)
+ public String getModuleName() {
+ return myModuleName;
+ }
+
+ public void setModuleName(String moduleName) {
+ myModuleName = moduleName;
+ }
+
+ @Override
+ public String toString() {
+ return "lib:" + myLibraryName + "(" + (myModuleName != null ? "module " + myModuleName: myLevel ) + ")";
+ }
+
+ @Nullable
+ public Library findLibrary(@NotNull PackagingElementResolvingContext context) {
+ if (myModuleName == null) {
+ return context.findLibrary(myLevel, myLibraryName);
+ }
+ final ModulesProvider modulesProvider = context.getModulesProvider();
+ final Module module = modulesProvider.getModule(myModuleName);
+ if (module != null) {
+ for (OrderEntry entry : modulesProvider.getRootModel(module).getOrderEntries()) {
+ if (entry instanceof LibraryOrderEntry) {
+ final LibraryOrderEntry libraryEntry = (LibraryOrderEntry)entry;
+ if (libraryEntry.isModuleLevel()) {
+ final String libraryName = libraryEntry.getLibraryName();
+ if (libraryName != null && libraryName.equals(myLibraryName)) {
+ return libraryEntry.getLibrary();
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ public static PackagingElementOutputKind getKindForLibrary(final Library library) {
+ boolean containsDirectories = false;
+ boolean containsJars = false;
+ for (VirtualFile file : library.getFiles(OrderRootType.CLASSES)) {
+ if (file.isInLocalFileSystem()) {
+ containsDirectories = true;
+ }
+ else {
+ containsJars = true;
+ }
+ }
+ return new PackagingElementOutputKind(containsDirectories, containsJars);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/ManifestFileUtil.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ManifestFileUtil.java
new file mode 100644
index 0000000..4aeda08
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ManifestFileUtil.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.CommonBundle;
+import com.intellij.ide.util.ClassFilter;
+import com.intellij.ide.util.TreeClassChooser;
+import com.intellij.ide.util.TreeClassChooserFactory;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.application.WriteAction;
+import com.intellij.openapi.compiler.make.ManifestBuilder;
+import com.intellij.openapi.deployment.DeploymentUtil;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.fileChooser.FileChooser;
+import com.intellij.openapi.fileChooser.FileChooserDescriptor;
+import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.OrderEnumerator;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.ui.TextFieldWithBrowseButton;
+import com.intellij.openapi.util.Computable;
+import com.intellij.openapi.util.Ref;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.ArtifactType;
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.packaging.elements.PackagingElement;
+import com.intellij.packaging.elements.PackagingElementFactory;
+import com.intellij.packaging.elements.PackagingElementResolvingContext;
+import com.intellij.packaging.impl.artifacts.ArtifactUtil;
+import com.intellij.packaging.impl.artifacts.PackagingElementPath;
+import com.intellij.packaging.impl.artifacts.PackagingElementProcessor;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import com.intellij.packaging.ui.ManifestFileConfiguration;
+import com.intellij.psi.JavaPsiFacade;
+import com.intellij.psi.PsiClass;
+import com.intellij.psi.search.GlobalSearchScope;
+import com.intellij.psi.util.PsiMethodUtil;
+import com.intellij.util.PathUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.jar.Attributes;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+
+/**
+ * @author nik
+ */
+public class ManifestFileUtil {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.ui.configuration.artifacts.ArtifactEditorContextImpl");
+ public static final String MANIFEST_PATH = JarFile.MANIFEST_NAME;
+ public static final String MANIFEST_FILE_NAME = PathUtil.getFileName(MANIFEST_PATH);
+ public static final String MANIFEST_DIR_NAME = PathUtil.getParentPath(MANIFEST_PATH);
+
+ private ManifestFileUtil() {
+ }
+
+ @Nullable
+ public static VirtualFile findManifestFile(@NotNull CompositePackagingElement<?> root, PackagingElementResolvingContext context, ArtifactType artifactType) {
+ return ArtifactUtil.findSourceFileByOutputPath(root, MANIFEST_PATH, context, artifactType);
+ }
+
+ @Nullable
+ public static VirtualFile suggestManifestFileDirectory(@NotNull CompositePackagingElement<?> root, PackagingElementResolvingContext context, ArtifactType artifactType) {
+ final VirtualFile metaInfDir = ArtifactUtil.findSourceFileByOutputPath(root, MANIFEST_DIR_NAME, context, artifactType);
+ if (metaInfDir != null) {
+ return metaInfDir;
+ }
+
+ final Ref<VirtualFile> sourceDir = Ref.create(null);
+ final Ref<VirtualFile> sourceFile = Ref.create(null);
+ ArtifactUtil.processElementsWithSubstitutions(root.getChildren(), context, artifactType, PackagingElementPath.EMPTY, new PackagingElementProcessor<PackagingElement<?>>() {
+ @Override
+ public boolean process(@NotNull PackagingElement<?> element, @NotNull PackagingElementPath path) {
+ if (element instanceof FileCopyPackagingElement) {
+ final VirtualFile file = ((FileCopyPackagingElement)element).findFile();
+ if (file != null) {
+ sourceFile.set(file);
+ }
+ }
+ else if (element instanceof DirectoryCopyPackagingElement) {
+ final VirtualFile file = ((DirectoryCopyPackagingElement)element).findFile();
+ if (file != null) {
+ sourceDir.set(file);
+ return false;
+ }
+ }
+ return true;
+ }
+ });
+
+ if (!sourceDir.isNull()) {
+ return sourceDir.get();
+ }
+
+
+ final Project project = context.getProject();
+ return suggestBaseDir(project, sourceFile.get());
+ }
+
+ @Nullable
+ public static VirtualFile suggestManifestFileDirectory(@NotNull Project project, @Nullable Module module) {
+ OrderEnumerator enumerator = module != null ? OrderEnumerator.orderEntries(module) : OrderEnumerator.orderEntries(project);
+ final VirtualFile[] files = enumerator.withoutDepModules().withoutLibraries().withoutSdk().productionOnly().sources().getRoots();
+ if (files.length > 0) {
+ return files[0];
+ }
+ return suggestBaseDir(project, null);
+ }
+
+
+ @Nullable
+ private static VirtualFile suggestBaseDir(@NotNull Project project, final @Nullable VirtualFile file) {
+ final VirtualFile[] contentRoots = ProjectRootManager.getInstance(project).getContentRoots();
+ if (file == null && contentRoots.length > 0) {
+ return contentRoots[0];
+ }
+
+ if (file != null) {
+ for (VirtualFile contentRoot : contentRoots) {
+ if (VfsUtil.isAncestor(contentRoot, file, false)) {
+ return contentRoot;
+ }
+ }
+ }
+
+ return project.getBaseDir();
+ }
+
+ public static Manifest readManifest(@NotNull VirtualFile manifestFile) {
+ try {
+ final InputStream inputStream = manifestFile.getInputStream();
+ final Manifest manifest;
+ try {
+ manifest = new Manifest(inputStream);
+ }
+ finally {
+ inputStream.close();
+ }
+ return manifest;
+ }
+ catch (IOException ignored) {
+ return new Manifest();
+ }
+ }
+
+ public static void updateManifest(@NotNull VirtualFile file, final @Nullable String mainClass, final @Nullable List<String> classpath, final boolean replaceValues) {
+ final Manifest manifest = readManifest(file);
+ final Attributes mainAttributes = manifest.getMainAttributes();
+
+ if (mainClass != null) {
+ mainAttributes.put(Attributes.Name.MAIN_CLASS, mainClass);
+ }
+ else if (replaceValues) {
+ mainAttributes.remove(Attributes.Name.MAIN_CLASS);
+ }
+
+ if (classpath != null && !classpath.isEmpty()) {
+ List<String> updatedClasspath;
+ if (replaceValues) {
+ updatedClasspath = classpath;
+ }
+ else {
+ updatedClasspath = new ArrayList<String>();
+ final String oldClasspath = (String)mainAttributes.get(Attributes.Name.CLASS_PATH);
+ if (!StringUtil.isEmpty(oldClasspath)) {
+ updatedClasspath.addAll(StringUtil.split(oldClasspath, " "));
+ }
+ for (String path : classpath) {
+ if (!updatedClasspath.contains(path)) {
+ updatedClasspath.add(path);
+ }
+ }
+ }
+ mainAttributes.put(Attributes.Name.CLASS_PATH, StringUtil.join(updatedClasspath, " "));
+ }
+ else if (replaceValues) {
+ mainAttributes.remove(Attributes.Name.CLASS_PATH);
+ }
+
+ ManifestBuilder.setVersionAttribute(mainAttributes);
+
+ try {
+ final OutputStream outputStream = file.getOutputStream(ManifestFileUtil.class);
+ try {
+ manifest.write(outputStream);
+ }
+ finally {
+ outputStream.close();
+ }
+ }
+ catch (IOException e) {
+ LOG.info(e);
+ }
+ }
+
+ @NotNull
+ public static ManifestFileConfiguration createManifestFileConfiguration(@NotNull VirtualFile manifestFile) {
+ final String path = manifestFile.getPath();
+ Manifest manifest = readManifest(manifestFile);
+ String mainClass = manifest.getMainAttributes().getValue(Attributes.Name.MAIN_CLASS);
+ final String classpathText = manifest.getMainAttributes().getValue(Attributes.Name.CLASS_PATH);
+ final List<String> classpath = new ArrayList<String>();
+ if (classpathText != null) {
+ classpath.addAll(StringUtil.split(classpathText, " "));
+ }
+ return new ManifestFileConfiguration(path, classpath, mainClass, manifestFile.isWritable());
+ }
+
+ public static List<String> getClasspathForElements(List<? extends PackagingElement<?>> elements, PackagingElementResolvingContext context, final ArtifactType artifactType) {
+ final List<String> classpath = new ArrayList<String>();
+ final PackagingElementProcessor<PackagingElement<?>> processor = new PackagingElementProcessor<PackagingElement<?>>() {
+ @Override
+ public boolean process(@NotNull PackagingElement<?> element, @NotNull PackagingElementPath path) {
+ if (element instanceof FileCopyPackagingElement) {
+ final String fileName = ((FileCopyPackagingElement)element).getOutputFileName();
+ classpath.add(DeploymentUtil.appendToPath(path.getPathString(), fileName));
+ }
+ else if (element instanceof DirectoryCopyPackagingElement) {
+ classpath.add(path.getPathString());
+ }
+ else if (element instanceof ArchivePackagingElement) {
+ final String archiveName = ((ArchivePackagingElement)element).getName();
+ classpath.add(DeploymentUtil.appendToPath(path.getPathString(), archiveName));
+ }
+ return true;
+ }
+ };
+ for (PackagingElement<?> element : elements) {
+ ArtifactUtil.processPackagingElements(element, null, processor, context, true, artifactType);
+ }
+ return classpath;
+ }
+
+ @Nullable
+ public static VirtualFile showDialogAndCreateManifest(final ArtifactEditorContext context, final CompositePackagingElement<?> element) {
+ FileChooserDescriptor descriptor = createDescriptorForManifestDirectory();
+ final VirtualFile directory = suggestManifestFileDirectory(element, context, context.getArtifactType());
+ final VirtualFile file = FileChooser.chooseFile(descriptor, context.getProject(), directory);
+ if (file == null) {
+ return null;
+ }
+
+ return createManifestFile(file, context.getProject());
+ }
+
+ @Nullable
+ public static VirtualFile createManifestFile(final @NotNull VirtualFile directory, final @NotNull Project project) {
+ ApplicationManager.getApplication().assertIsDispatchThread();
+ final Ref<IOException> exc = Ref.create(null);
+ final VirtualFile file = new WriteAction<VirtualFile>() {
+ protected void run(final Result<VirtualFile> result) {
+ VirtualFile dir = directory;
+ try {
+ if (!dir.getName().equals(MANIFEST_DIR_NAME)) {
+ dir = VfsUtil.createDirectoryIfMissing(dir, MANIFEST_DIR_NAME);
+ }
+ final VirtualFile file = dir.createChildData(this, MANIFEST_FILE_NAME);
+ final OutputStream output = file.getOutputStream(this);
+ try {
+ final Manifest manifest = new Manifest();
+ ManifestBuilder.setVersionAttribute(manifest.getMainAttributes());
+ manifest.write(output);
+ }
+ finally {
+ output.close();
+ }
+ result.setResult(file);
+ }
+ catch (IOException e) {
+ exc.set(e);
+ }
+ }
+ }.execute().getResultObject();
+
+ final IOException exception = exc.get();
+ if (exception != null) {
+ LOG.info(exception);
+ Messages.showErrorDialog(project, exception.getMessage(), CommonBundle.getErrorTitle());
+ return null;
+ }
+ return file;
+ }
+
+ public static FileChooserDescriptor createDescriptorForManifestDirectory() {
+ FileChooserDescriptor descriptor = FileChooserDescriptorFactory.createSingleFolderDescriptor();
+ descriptor.setTitle("Select Directory for META-INF/MANIFEST.MF file");
+ return descriptor;
+ }
+
+ public static void addManifestFileToLayout(final @NotNull String path, final @NotNull ArtifactEditorContext context,
+ final @NotNull CompositePackagingElement<?> element) {
+ context.editLayout(context.getArtifact(), new Runnable() {
+ public void run() {
+ final VirtualFile file = findManifestFile(element, context, context.getArtifactType());
+ if (file == null || !FileUtil.pathsEqual(file.getPath(), path)) {
+ PackagingElementFactory.getInstance().addFileCopy(element, MANIFEST_DIR_NAME, path, MANIFEST_FILE_NAME);
+ }
+ }
+ });
+ }
+
+ @Nullable
+ public static PsiClass selectMainClass(Project project, final @Nullable String initialClassName) {
+ final TreeClassChooserFactory chooserFactory = TreeClassChooserFactory.getInstance(project);
+ final GlobalSearchScope searchScope = GlobalSearchScope.allScope(project);
+ final PsiClass aClass = initialClassName != null ? JavaPsiFacade.getInstance(project).findClass(initialClassName, searchScope) : null;
+ final TreeClassChooser chooser =
+ chooserFactory.createWithInnerClassesScopeChooser("Select Main Class", searchScope, new MainClassFilter(), aClass);
+ chooser.showDialog();
+ return chooser.getSelected();
+ }
+
+ public static void setupMainClassField(final Project project, final TextFieldWithBrowseButton field) {
+ field.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ final PsiClass selected = selectMainClass(project, field.getText());
+ if (selected != null) {
+ field.setText(selected.getQualifiedName());
+ }
+ }
+ });
+ }
+
+ private static class MainClassFilter implements ClassFilter {
+ public boolean isAccepted(final PsiClass aClass) {
+ return ApplicationManager.getApplication().runReadAction(new Computable<Boolean>() {
+ @Override
+ public Boolean compute() {
+ return PsiMethodUtil.MAIN_CLASS.value(aClass) && PsiMethodUtil.hasMainMethod(aClass);
+ }
+ });
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/ModuleOutputElementTypeBase.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ModuleOutputElementTypeBase.java
new file mode 100644
index 0000000..6fed025
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ModuleOutputElementTypeBase.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2000-2011 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModulePointer;
+import com.intellij.openapi.module.ModulePointerManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.project.ProjectBundle;
+import com.intellij.openapi.roots.ui.configuration.ModulesProvider;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.packaging.elements.PackagingElement;
+import com.intellij.packaging.elements.PackagingElementType;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public abstract class ModuleOutputElementTypeBase<E extends ModuleOutputPackagingElementBase> extends PackagingElementType<E> {
+ public ModuleOutputElementTypeBase(String id, String presentableName) {
+ super(id, presentableName);
+ }
+
+ @Override
+ public boolean canCreate(@NotNull ArtifactEditorContext context, @NotNull Artifact artifact) {
+ return !getSuitableModules(context).isEmpty();
+ }
+
+ @NotNull
+ public List<? extends PackagingElement<?>> chooseAndCreate(@NotNull ArtifactEditorContext context, @NotNull Artifact artifact,
+ @NotNull CompositePackagingElement<?> parent) {
+ List<Module> suitableModules = getSuitableModules(context);
+ List<Module> selected = context.chooseModules(suitableModules, ProjectBundle.message("dialog.title.packaging.choose.module"));
+
+ final List<PackagingElement<?>> elements = new ArrayList<PackagingElement<?>>();
+ final ModulePointerManager pointerManager = ModulePointerManager.getInstance(context.getProject());
+ for (Module module : selected) {
+ elements.add(createElement(context.getProject(), pointerManager.create(module)));
+ }
+ return elements;
+ }
+
+ protected abstract ModuleOutputPackagingElementBase createElement(@NotNull Project project, @NotNull ModulePointer pointer);
+
+ private List<Module> getSuitableModules(ArtifactEditorContext context) {
+ ModulesProvider modulesProvider = context.getModulesProvider();
+ ArrayList<Module> modules = new ArrayList<Module>();
+ for (Module module : modulesProvider.getModules()) {
+ if (isSuitableModule(modulesProvider, module)) {
+ modules.add(module);
+ }
+ }
+ return modules;
+ }
+
+ public abstract boolean isSuitableModule(ModulesProvider modulesProvider, Module module);
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/ModuleOutputPackagingElement.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ModuleOutputPackagingElement.java
new file mode 100644
index 0000000..d73572d
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ModuleOutputPackagingElement.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.elements.PackagingElementResolvingContext;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collection;
+
+/**
+ * @author nik
+ */
+public interface ModuleOutputPackagingElement {
+ @Nullable
+ String getModuleName();
+
+ @Nullable
+ Module findModule(PackagingElementResolvingContext context);
+
+ @NotNull
+ Collection<VirtualFile> getSourceRoots(PackagingElementResolvingContext context);
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/ModuleOutputPackagingElementBase.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ModuleOutputPackagingElementBase.java
new file mode 100644
index 0000000..084cf6e
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ModuleOutputPackagingElementBase.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright 2000-2011 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.compiler.ant.BuildProperties;
+import com.intellij.compiler.ant.Generator;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModulePointer;
+import com.intellij.openapi.module.ModulePointerManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.CompilerModuleExtension;
+import com.intellij.openapi.roots.ui.configuration.DefaultModulesProvider;
+import com.intellij.openapi.roots.ui.configuration.ModulesProvider;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.ArtifactType;
+import com.intellij.packaging.elements.*;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.xmlb.annotations.Attribute;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public abstract class ModuleOutputPackagingElementBase extends PackagingElement<ModuleOutputPackagingElementBase.ModuleOutputPackagingElementState> implements ModuleOutputPackagingElement {
+ @NonNls public static final String MODULE_NAME_ATTRIBUTE = "name";
+ protected ModulePointer myModulePointer;
+ protected final Project myProject;
+
+ public ModuleOutputPackagingElementBase(PackagingElementType type, Project project, ModulePointer modulePointer) {
+ super(type);
+ myProject = project;
+ myModulePointer = modulePointer;
+ }
+
+ public ModuleOutputPackagingElementBase(PackagingElementType type, Project project) {
+ super(type);
+ myProject = project;
+ }
+
+ @Override
+ public List<? extends Generator> computeAntInstructions(@NotNull PackagingElementResolvingContext resolvingContext, @NotNull AntCopyInstructionCreator creator,
+ @NotNull ArtifactAntGenerationContext generationContext,
+ @NotNull ArtifactType artifactType) {
+ if (myModulePointer != null) {
+ final String moduleOutput = BuildProperties.propertyRef(getModuleOutputAntProperty(generationContext));
+ return Collections.singletonList(creator.createDirectoryContentCopyInstruction(moduleOutput));
+ }
+ return Collections.emptyList();
+ }
+
+ protected abstract String getModuleOutputAntProperty(ArtifactAntGenerationContext generationContext);
+
+ @Override
+ public void computeIncrementalCompilerInstructions(@NotNull IncrementalCompilerInstructionCreator creator,
+ @NotNull PackagingElementResolvingContext resolvingContext,
+ @NotNull ArtifactIncrementalCompilerContext compilerContext, @NotNull ArtifactType artifactType) {
+ final Module module = findModule(resolvingContext);
+ if (module != null) {
+ final CompilerModuleExtension extension = CompilerModuleExtension.getInstance(module);
+ if (extension != null) {
+ final VirtualFile output = getModuleOutputPath(extension);
+ if (output != null) {
+ creator.addDirectoryCopyInstructions(output, null);
+ }
+ }
+ }
+ }
+
+ @Nullable
+ protected abstract VirtualFile getModuleOutputPath(CompilerModuleExtension extension);
+
+ @NotNull
+ @Override
+ public PackagingElementOutputKind getFilesKind(PackagingElementResolvingContext context) {
+ return PackagingElementOutputKind.DIRECTORIES_WITH_CLASSES;
+ }
+
+ @Override
+ public boolean isEqualTo(@NotNull PackagingElement<?> element) {
+ return element.getClass() == getClass() && myModulePointer != null
+ && myModulePointer.equals(((ModuleOutputPackagingElementBase)element).myModulePointer);
+ }
+
+ public ModuleOutputPackagingElementState getState() {
+ final ModuleOutputPackagingElementState state = new ModuleOutputPackagingElementState();
+ if (myModulePointer != null) {
+ state.setModuleName(myModulePointer.getModuleName());
+ }
+ return state;
+ }
+
+ public void loadState(ModuleOutputPackagingElementState state) {
+ final String moduleName = state.getModuleName();
+ myModulePointer = moduleName != null ? ModulePointerManager.getInstance(myProject).create(moduleName) : null;
+ }
+
+ @Override
+ @Nullable
+ public String getModuleName() {
+ return myModulePointer != null ? myModulePointer.getModuleName() : null;
+ }
+
+ @Override
+ @Nullable
+ public Module findModule(PackagingElementResolvingContext context) {
+ if (myModulePointer != null) {
+ final Module module = myModulePointer.getModule();
+ final ModulesProvider modulesProvider = context.getModulesProvider();
+ if (module != null) {
+ if (modulesProvider instanceof DefaultModulesProvider//optimization
+ || ArrayUtil.contains(module, modulesProvider.getModules())) {
+ return module;
+ }
+ }
+ return modulesProvider.getModule(myModulePointer.getModuleName());
+ }
+ return null;
+ }
+
+ public static class ModuleOutputPackagingElementState {
+ private String myModuleName;
+
+ @Attribute(MODULE_NAME_ATTRIBUTE)
+ public String getModuleName() {
+ return myModuleName;
+ }
+
+ public void setModuleName(String moduleName) {
+ myModuleName = moduleName;
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/PackagingElementFactoryImpl.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/PackagingElementFactoryImpl.java
new file mode 100644
index 0000000..0d1737b
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/PackagingElementFactoryImpl.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright 2000-2011 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.extensions.Extensions;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModulePointer;
+import com.intellij.openapi.module.ModulePointerManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.OrderRootType;
+import com.intellij.openapi.roots.impl.libraries.LibraryImpl;
+import com.intellij.openapi.roots.impl.libraries.LibraryTableImplUtil;
+import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.roots.libraries.LibraryTable;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.JarFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.artifacts.ArtifactPointer;
+import com.intellij.packaging.artifacts.ArtifactPointerManager;
+import com.intellij.packaging.elements.*;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import com.intellij.util.ArrayUtil;
+import com.intellij.util.PathUtil;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public class PackagingElementFactoryImpl extends PackagingElementFactory {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.packaging.impl.elements.PackagingElementFactoryImpl");
+ public static final PackagingElementType<DirectoryPackagingElement> DIRECTORY_ELEMENT_TYPE = new DirectoryElementType();
+ public static final PackagingElementType<ArchivePackagingElement> ARCHIVE_ELEMENT_TYPE = new ArchiveElementType();
+ public static final PackagingElementType<FileCopyPackagingElement> FILE_COPY_ELEMENT_TYPE = new FileCopyElementType();
+ public static final PackagingElementType<DirectoryCopyPackagingElement> DIRECTORY_COPY_ELEMENT_TYPE = new DirectoryCopyElementType();
+ public static final PackagingElementType<ExtractedDirectoryPackagingElement> EXTRACTED_DIRECTORY_ELEMENT_TYPE = new ExtractedDirectoryElementType();
+ public static final PackagingElementType<ArtifactRootElement<?>> ARTIFACT_ROOT_ELEMENT_TYPE = new ArtifactRootElementType();
+ private static final PackagingElementType[] STANDARD_TYPES = {
+ DIRECTORY_ELEMENT_TYPE, ARCHIVE_ELEMENT_TYPE,
+ LibraryElementType.LIBRARY_ELEMENT_TYPE, ProductionModuleOutputElementType.ELEMENT_TYPE, TestModuleOutputElementType.ELEMENT_TYPE,
+ ArtifactElementType.ARTIFACT_ELEMENT_TYPE, FILE_COPY_ELEMENT_TYPE, DIRECTORY_COPY_ELEMENT_TYPE, EXTRACTED_DIRECTORY_ELEMENT_TYPE
+ };
+
+ @NotNull
+ @Override
+ public PackagingElementType<?>[] getNonCompositeElementTypes() {
+ final List<PackagingElementType> elementTypes = new ArrayList<PackagingElementType>();
+ for (PackagingElementType elementType : getAllElementTypes()) {
+ if (!(elementType instanceof CompositePackagingElementType)) {
+ elementTypes.add(elementType);
+ }
+ }
+ return elementTypes.toArray(new PackagingElementType[elementTypes.size()]);
+ }
+
+ @Override
+ @NotNull
+ public ComplexPackagingElementType<?>[] getComplexElementTypes() {
+ List<ComplexPackagingElementType<?>> types = new ArrayList<ComplexPackagingElementType<?>>();
+ for (PackagingElementType type : getAllElementTypes()) {
+ if (type instanceof ComplexPackagingElementType) {
+ types.add((ComplexPackagingElementType)type);
+ }
+ }
+ return types.toArray(new ComplexPackagingElementType[types.size()]);
+ }
+
+ @NotNull
+ @Override
+ public CompositePackagingElementType<?>[] getCompositeElementTypes() {
+ final List<CompositePackagingElementType> elementTypes = new ArrayList<CompositePackagingElementType>();
+ for (PackagingElementType elementType : getAllElementTypes()) {
+ if (elementType instanceof CompositePackagingElementType) {
+ elementTypes.add((CompositePackagingElementType)elementType);
+ }
+ }
+ return elementTypes.toArray(new CompositePackagingElementType[elementTypes.size()]);
+ }
+
+ @Override
+ public PackagingElementType<?> findElementType(String id) {
+ for (PackagingElementType elementType : getAllElementTypes()) {
+ if (elementType.getId().equals(id)) {
+ return elementType;
+ }
+ }
+ if (id.equals(ARTIFACT_ROOT_ELEMENT_TYPE.getId())) {
+ return ARTIFACT_ROOT_ELEMENT_TYPE;
+ }
+ return null;
+ }
+
+ @NotNull
+ @Override
+ public PackagingElementType[] getAllElementTypes() {
+ final PackagingElementType[] types = Extensions.getExtensions(PackagingElementType.EP_NAME);
+ return ArrayUtil.mergeArrays(STANDARD_TYPES, types);
+ }
+
+ @NotNull
+ @Override
+ public PackagingElement<?> createArtifactElement(@NotNull Artifact artifact, @NotNull Project project) {
+ return new ArtifactPackagingElement(project, ArtifactPointerManager.getInstance(project).createPointer(artifact));
+ }
+
+ @NotNull
+ public DirectoryPackagingElement createDirectory(@NotNull @NonNls String directoryName) {
+ return new DirectoryPackagingElement(directoryName);
+ }
+
+ @NotNull
+ @Override
+ public ArtifactRootElement<?> createArtifactRootElement() {
+ return new ArtifactRootElementImpl();
+ }
+
+ @Override
+ @NotNull
+ public CompositePackagingElement<?> getOrCreateDirectory(@NotNull CompositePackagingElement<?> parent, @NotNull String relativePath) {
+ return getOrCreateDirectoryOrArchive(parent, relativePath, true);
+ }
+
+ @NotNull
+ @Override
+ public CompositePackagingElement<?> getOrCreateArchive(@NotNull CompositePackagingElement<?> parent, @NotNull String relativePath) {
+ return getOrCreateDirectoryOrArchive(parent, relativePath, false);
+ }
+
+ @Override
+ public void addFileCopy(@NotNull CompositePackagingElement<?> root, @NotNull String outputDirectoryPath, @NotNull String sourceFilePath) {
+ addFileCopy(root, outputDirectoryPath, sourceFilePath, null);
+ }
+
+ @Override
+ public void addFileCopy(@NotNull CompositePackagingElement<?> root, @NotNull String outputDirectoryPath, @NotNull String sourceFilePath,
+ @Nullable String outputFileName) {
+ final String fileName = PathUtil.getFileName(sourceFilePath);
+ if (outputFileName != null && outputFileName.equals(fileName)) {
+ outputFileName = null;
+ }
+ getOrCreateDirectory(root, outputDirectoryPath).addOrFindChild(createFileCopy(sourceFilePath, outputFileName));
+ }
+
+ @NotNull
+ private CompositePackagingElement<?> getOrCreateDirectoryOrArchive(@NotNull CompositePackagingElement<?> root,
+ @NotNull @NonNls String path, final boolean directory) {
+ path = StringUtil.trimStart(StringUtil.trimEnd(path, "/"), "/");
+ if (path.length() == 0) {
+ return root;
+ }
+ int index = path.lastIndexOf('/');
+ String lastName = path.substring(index + 1);
+ String parentPath = index != -1 ? path.substring(0, index) : "";
+
+ final CompositePackagingElement<?> parent = getOrCreateDirectoryOrArchive(root, parentPath, true);
+ final CompositePackagingElement<?> last = directory ? createDirectory(lastName) : createArchive(lastName);
+ return parent.addOrFindChild(last);
+ }
+
+ @NotNull
+ public PackagingElement<?> createModuleOutput(@NotNull String moduleName, @NotNull Project project) {
+ final ModulePointer pointer = ModulePointerManager.getInstance(project).create(moduleName);
+ return new ProductionModuleOutputPackagingElement(project, pointer);
+ }
+
+ @NotNull
+ @Override
+ public PackagingElement<?> createModuleOutput(@NotNull Module module) {
+ final ModulePointer modulePointer = ModulePointerManager.getInstance(module.getProject()).create(module);
+ return new ProductionModuleOutputPackagingElement(module.getProject(), modulePointer);
+ }
+
+ @NotNull
+ @Override
+ public PackagingElement<?> createTestModuleOutput(@NotNull Module module) {
+ ModulePointer pointer = ModulePointerManager.getInstance(module.getProject()).create(module);
+ return new TestModuleOutputPackagingElement(module.getProject(), pointer);
+ }
+
+ @NotNull
+ @Override
+ public List<? extends PackagingElement<?>> createLibraryElements(@NotNull Library library) {
+ final LibraryTable table = library.getTable();
+ final String libraryName = library.getName();
+ if (table != null) {
+ return Collections.singletonList(createLibraryFiles(libraryName, table.getTableLevel(), null));
+ }
+ if (libraryName != null) {
+ final Module module = ((LibraryImpl)library).getModule();
+ if (module != null) {
+ return Collections.singletonList(createLibraryFiles(libraryName, LibraryTableImplUtil.MODULE_LEVEL, module.getName()));
+ }
+ }
+ final List<PackagingElement<?>> elements = new ArrayList<PackagingElement<?>>();
+ for (VirtualFile file : library.getFiles(OrderRootType.CLASSES)) {
+ final String path = FileUtil.toSystemIndependentName(PathUtil.getLocalPath(file));
+ elements.add(file.isDirectory() && file.isInLocalFileSystem() ? new DirectoryCopyPackagingElement(path) : new FileCopyPackagingElement(path));
+ }
+ return elements;
+ }
+
+ @NotNull
+ @Override
+ public PackagingElement<?> createArtifactElement(@NotNull ArtifactPointer artifactPointer, @NotNull Project project) {
+ return new ArtifactPackagingElement(project, artifactPointer);
+ }
+
+ @NotNull
+ @Override
+ public PackagingElement<?> createLibraryFiles(@NotNull String libraryName, @NotNull String level, String moduleName) {
+ return new LibraryPackagingElement(level, libraryName, moduleName);
+ }
+
+ @NotNull
+ public CompositePackagingElement<?> createArchive(@NotNull @NonNls String archiveFileName) {
+ return new ArchivePackagingElement(archiveFileName);
+ }
+
+ @Nullable
+ private static PackagingElement<?> findArchiveOrDirectoryByName(@NotNull CompositePackagingElement<?> parent, @NotNull String name) {
+ for (PackagingElement<?> element : parent.getChildren()) {
+ if (element instanceof ArchivePackagingElement && ((ArchivePackagingElement)element).getArchiveFileName().equals(name) ||
+ element instanceof DirectoryPackagingElement && ((DirectoryPackagingElement)element).getDirectoryName().equals(name)) {
+ return element;
+ }
+ }
+ return null;
+ }
+
+ @NotNull
+ public static String suggestFileName(@NotNull CompositePackagingElement<?> parent, @NonNls @NotNull String prefix, @NonNls @NotNull String suffix) {
+ String name = prefix + suffix;
+ int i = 2;
+ while (findArchiveOrDirectoryByName(parent, name) != null) {
+ name = prefix + i++ + suffix;
+ }
+ return name;
+ }
+
+ @NotNull
+ @Override
+ public PackagingElement<?> createDirectoryCopyWithParentDirectories(@NotNull String filePath, @NotNull String relativeOutputPath) {
+ return createParentDirectories(relativeOutputPath, new DirectoryCopyPackagingElement(filePath));
+ }
+
+ @Override
+ @NotNull
+ public PackagingElement<?> createExtractedDirectoryWithParentDirectories(@NotNull String jarPath, @NotNull String pathInJar,
+ @NotNull String relativeOutputPath) {
+ return createParentDirectories(relativeOutputPath, new ExtractedDirectoryPackagingElement(jarPath, pathInJar));
+ }
+
+ @NotNull
+ @Override
+ public PackagingElement<?> createExtractedDirectory(@NotNull VirtualFile jarEntry) {
+ LOG.assertTrue(jarEntry.getFileSystem() instanceof JarFileSystem, "Expected file from jar but file from " + jarEntry.getFileSystem() + " found");
+ final String fullPath = jarEntry.getPath();
+ final int jarEnd = fullPath.indexOf(JarFileSystem.JAR_SEPARATOR);
+ return new ExtractedDirectoryPackagingElement(fullPath.substring(0, jarEnd), fullPath.substring(jarEnd + 1));
+ }
+
+ @NotNull
+ @Override
+ public PackagingElement<?> createFileCopyWithParentDirectories(@NotNull String filePath, @NotNull String relativeOutputPath) {
+ return createFileCopyWithParentDirectories(filePath, relativeOutputPath, null);
+ }
+
+ @NotNull
+ @Override
+ public PackagingElement<?> createFileCopyWithParentDirectories(@NotNull String filePath,
+ @NotNull String relativeOutputPath,
+ @Nullable String outputFileName) {
+ return createParentDirectories(relativeOutputPath, createFileCopy(filePath, outputFileName));
+ }
+
+ @Override
+ public PackagingElement<?> createFileCopy(@NotNull String filePath, String outputFileName) {
+ return new FileCopyPackagingElement(filePath, outputFileName);
+ }
+
+ @NotNull
+ @Override
+ public PackagingElement<?> createParentDirectories(@NotNull String relativeOutputPath, @NotNull PackagingElement<?> element) {
+ return createParentDirectories(relativeOutputPath, Collections.singletonList(element)).get(0);
+ }
+
+ @NotNull
+ @Override
+ public List<? extends PackagingElement<?>> createParentDirectories(@NotNull String relativeOutputPath, @NotNull List<? extends PackagingElement<?>> elements) {
+ relativeOutputPath = StringUtil.trimStart(relativeOutputPath, "/");
+ if (relativeOutputPath.length() == 0) {
+ return elements;
+ }
+ int slash = relativeOutputPath.indexOf('/');
+ if (slash == -1) slash = relativeOutputPath.length();
+ String rootName = relativeOutputPath.substring(0, slash);
+ String pathTail = relativeOutputPath.substring(slash);
+ final DirectoryPackagingElement root = createDirectory(rootName);
+ final CompositePackagingElement<?> last = getOrCreateDirectory(root, pathTail);
+ last.addOrFindChildren(elements);
+ return Collections.singletonList(root);
+ }
+
+ public static CompositePackagingElement<?> createDirectoryOrArchiveWithParents(@NotNull String path, final boolean archive) {
+ path = FileUtil.toSystemIndependentName(path);
+ final String parentPath = PathUtil.getParentPath(path);
+ final String fileName = PathUtil.getFileName(path);
+ final PackagingElement<?> element = archive ? new ArchivePackagingElement(fileName) : new DirectoryPackagingElement(fileName);
+ return (CompositePackagingElement<?>)getInstance().createParentDirectories(parentPath, element);
+ }
+
+ private static class ArtifactRootElementType extends PackagingElementType<ArtifactRootElement<?>> {
+ protected ArtifactRootElementType() {
+ super("root", "");
+ }
+
+ @Override
+ public boolean canCreate(@NotNull ArtifactEditorContext context, @NotNull Artifact artifact) {
+ return false;
+ }
+
+ @NotNull
+ public List<? extends ArtifactRootElement<?>> chooseAndCreate(@NotNull ArtifactEditorContext context, @NotNull Artifact artifact,
+ @NotNull CompositePackagingElement<?> parent) {
+ throw new UnsupportedOperationException("'create' not implemented in " + getClass().getName());
+ }
+
+ @NotNull
+ public ArtifactRootElement<?> createEmpty(@NotNull Project project) {
+ return new ArtifactRootElementImpl();
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/ProductionModuleOutputElementType.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ProductionModuleOutputElementType.java
new file mode 100644
index 0000000..3ec8f0c
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ProductionModuleOutputElementType.java
@@ -0,0 +1,56 @@
+/*
+ * 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModulePointer;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ui.configuration.ModulesProvider;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+
+/**
+* @author nik
+*/
+public class ProductionModuleOutputElementType extends ModuleOutputElementTypeBase<ProductionModuleOutputPackagingElement> {
+ public static final ProductionModuleOutputElementType ELEMENT_TYPE = new ProductionModuleOutputElementType();
+
+ ProductionModuleOutputElementType() {
+ super("module-output", CompilerBundle.message("element.type.name.module.output"));
+ }
+
+ @NotNull
+ public ProductionModuleOutputPackagingElement createEmpty(@NotNull Project project) {
+ return new ProductionModuleOutputPackagingElement(project);
+ }
+
+ protected ModuleOutputPackagingElementBase createElement(@NotNull Project project, @NotNull ModulePointer pointer) {
+ return new ProductionModuleOutputPackagingElement(project, pointer);
+ }
+
+ @Override
+ public Icon getCreateElementIcon() {
+ return AllIcons.Nodes.Module;
+ }
+
+ @Override
+ public boolean isSuitableModule(ModulesProvider modulesProvider, Module module) {
+ return modulesProvider.getRootModel(module).getSourceRootUrls(false).length > 0;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/ProductionModuleOutputPackagingElement.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ProductionModuleOutputPackagingElement.java
new file mode 100644
index 0000000..6ee8e8c
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/ProductionModuleOutputPackagingElement.java
@@ -0,0 +1,75 @@
+/*
+ * 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModulePointer;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.CompilerModuleExtension;
+import com.intellij.openapi.roots.ModuleRootModel;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.elements.ArtifactAntGenerationContext;
+import com.intellij.packaging.elements.PackagingElementResolvingContext;
+import com.intellij.packaging.impl.ui.DelegatedPackagingElementPresentation;
+import com.intellij.packaging.impl.ui.ModuleElementPresentation;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import com.intellij.packaging.ui.PackagingElementPresentation;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * @author nik
+ */
+public class ProductionModuleOutputPackagingElement extends ModuleOutputPackagingElementBase {
+ public ProductionModuleOutputPackagingElement(@NotNull Project project) {
+ super(ProductionModuleOutputElementType.ELEMENT_TYPE, project);
+ }
+
+ public ProductionModuleOutputPackagingElement(@NotNull Project project, @NotNull ModulePointer modulePointer) {
+ super(ProductionModuleOutputElementType.ELEMENT_TYPE, project, modulePointer);
+ }
+
+ @NonNls @Override
+ public String toString() {
+ return "module:" + getModuleName();
+ }
+
+ protected String getModuleOutputAntProperty(ArtifactAntGenerationContext generationContext) {
+ return generationContext.getModuleOutputPath(myModulePointer.getModuleName());
+ }
+
+ protected VirtualFile getModuleOutputPath(CompilerModuleExtension extension) {
+ return extension.getCompilerOutputPath();
+ }
+
+ @NotNull
+ @Override
+ public Collection<VirtualFile> getSourceRoots(PackagingElementResolvingContext context) {
+ Module module = findModule(context);
+ if (module == null) return Collections.emptyList();
+
+ ModuleRootModel rootModel = context.getModulesProvider().getRootModel(module);
+ return Arrays.asList(rootModel.getSourceRoots(false));
+ }
+
+ public PackagingElementPresentation createPresentation(@NotNull ArtifactEditorContext context) {
+ return new DelegatedPackagingElementPresentation(new ModuleElementPresentation(myModulePointer, context, false));
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/TestModuleOutputElementType.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/TestModuleOutputElementType.java
new file mode 100644
index 0000000..c65cb16
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/TestModuleOutputElementType.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2000-2011 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModulePointer;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ContentEntry;
+import com.intellij.openapi.roots.SourceFolder;
+import com.intellij.openapi.roots.ui.configuration.ModulesProvider;
+import com.intellij.util.PlatformIcons;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+
+/**
+ * @author nik
+ */
+public class TestModuleOutputElementType extends ModuleOutputElementTypeBase<TestModuleOutputPackagingElement> {
+ public static final TestModuleOutputElementType ELEMENT_TYPE = new TestModuleOutputElementType();
+
+ public TestModuleOutputElementType() {
+ super("module-test-output", CompilerBundle.message("element.type.name.module.test.output"));
+ }
+
+ @NotNull
+ @Override
+ public TestModuleOutputPackagingElement createEmpty(@NotNull Project project) {
+ return new TestModuleOutputPackagingElement(project);
+ }
+
+ protected ModuleOutputPackagingElementBase createElement(@NotNull Project project, @NotNull ModulePointer pointer) {
+ return new TestModuleOutputPackagingElement(project, pointer);
+ }
+
+ @Override
+ public Icon getCreateElementIcon() {
+ return PlatformIcons.TEST_SOURCE_FOLDER;
+ }
+
+ public boolean isSuitableModule(ModulesProvider modulesProvider, Module module) {
+ for (ContentEntry entry : modulesProvider.getRootModel(module).getContentEntries()) {
+ for (SourceFolder folder : entry.getSourceFolders()) {
+ if (folder.isTestSource()) return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/elements/TestModuleOutputPackagingElement.java b/java/compiler/impl/src/com/intellij/packaging/impl/elements/TestModuleOutputPackagingElement.java
new file mode 100644
index 0000000..f3110a0
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/elements/TestModuleOutputPackagingElement.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2000-2011 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 com.intellij.packaging.impl.elements;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModulePointer;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.CompilerModuleExtension;
+import com.intellij.openapi.roots.ContentEntry;
+import com.intellij.openapi.roots.ModuleRootModel;
+import com.intellij.openapi.roots.SourceFolder;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.elements.ArtifactAntGenerationContext;
+import com.intellij.packaging.elements.PackagingElementResolvingContext;
+import com.intellij.packaging.impl.ui.DelegatedPackagingElementPresentation;
+import com.intellij.packaging.impl.ui.ModuleElementPresentation;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import com.intellij.packaging.ui.PackagingElementPresentation;
+import com.intellij.util.SmartList;
+import com.intellij.util.containers.ContainerUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public class TestModuleOutputPackagingElement extends ModuleOutputPackagingElementBase {
+ public TestModuleOutputPackagingElement(Project project) {
+ super(TestModuleOutputElementType.ELEMENT_TYPE, project);
+ }
+
+ public TestModuleOutputPackagingElement(Project project, ModulePointer modulePointer) {
+ super(TestModuleOutputElementType.ELEMENT_TYPE, project, modulePointer);
+ }
+
+ @Override
+ public String toString() {
+ return "module-tests:" + getModuleName();
+ }
+
+ protected String getModuleOutputAntProperty(ArtifactAntGenerationContext generationContext) {
+ return generationContext.getModuleTestOutputPath(myModulePointer.getModuleName());
+ }
+
+ protected VirtualFile getModuleOutputPath(CompilerModuleExtension extension) {
+ return extension.getCompilerOutputPathForTests();
+ }
+
+ @NotNull
+ @Override
+ public Collection<VirtualFile> getSourceRoots(PackagingElementResolvingContext context) {
+ Module module = findModule(context);
+ if (module == null) return Collections.emptyList();
+
+ List<VirtualFile> roots = new SmartList<VirtualFile>();
+ ModuleRootModel rootModel = context.getModulesProvider().getRootModel(module);
+ for (ContentEntry entry : rootModel.getContentEntries()) {
+ for (SourceFolder folder : entry.getSourceFolders()) {
+ if (folder.isTestSource()) {
+ ContainerUtil.addIfNotNull(folder.getFile(), roots);
+ }
+ }
+ }
+ return roots;
+ }
+
+ public PackagingElementPresentation createPresentation(@NotNull ArtifactEditorContext context) {
+ return new DelegatedPackagingElementPresentation(new ModuleElementPresentation(myModulePointer, context, true));
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/run/ArtifactChooser.java b/java/compiler/impl/src/com/intellij/packaging/impl/run/ArtifactChooser.java
new file mode 100644
index 0000000..c6e7aca
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/run/ArtifactChooser.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.packaging.impl.run;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.util.ElementsChooser;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.artifacts.ArtifactPointer;
+import com.intellij.ui.JBColor;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public class ArtifactChooser extends ElementsChooser<ArtifactPointer> {
+ private static final Comparator<ArtifactPointer> ARTIFACT_COMPARATOR = new Comparator<ArtifactPointer>() {
+ public int compare(ArtifactPointer o1, ArtifactPointer o2) {
+ return o1.getArtifactName().compareToIgnoreCase(o2.getArtifactName());
+ }
+ };
+ private static final ElementProperties INVALID_ARTIFACT_PROPERTIES = new ElementProperties() {
+ public Icon getIcon() {
+ return AllIcons.Nodes.Artifact;
+ }
+
+ public Color getColor() {
+ return JBColor.RED;
+ }
+ };
+
+ public ArtifactChooser(List<ArtifactPointer> pointers) {
+ super(pointers, false);
+ for (ArtifactPointer pointer : pointers) {
+ if (pointer.getArtifact() == null) {
+ setElementProperties(pointer, INVALID_ARTIFACT_PROPERTIES);
+ }
+ }
+ sort(ARTIFACT_COMPARATOR);
+ }
+
+ @Override
+ protected String getItemText(@NotNull ArtifactPointer value) {
+ return value.getArtifactName();
+ }
+
+ @Override
+ protected Icon getItemIcon(@NotNull ArtifactPointer value) {
+ final Artifact artifact = value.getArtifact();
+ return artifact != null ? artifact.getArtifactType().getIcon() : null;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/run/BuildArtifactsBeforeRunTask.java b/java/compiler/impl/src/com/intellij/packaging/impl/run/BuildArtifactsBeforeRunTask.java
new file mode 100644
index 0000000..690b679
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/run/BuildArtifactsBeforeRunTask.java
@@ -0,0 +1,117 @@
+/*
+ * 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 com.intellij.packaging.impl.run;
+
+import com.intellij.execution.BeforeRunTask;
+import com.intellij.openapi.project.Project;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.artifacts.ArtifactPointer;
+import com.intellij.packaging.artifacts.ArtifactPointerManager;
+import org.jdom.Element;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public class BuildArtifactsBeforeRunTask extends BeforeRunTask<BuildArtifactsBeforeRunTask> {
+ @NonNls public static final String NAME_ATTRIBUTE = "name";
+ @NonNls public static final String ARTIFACT_ELEMENT = "artifact";
+ private List<ArtifactPointer> myArtifactPointers = new ArrayList<ArtifactPointer>();
+ private final Project myProject;
+
+ public BuildArtifactsBeforeRunTask(Project project) {
+ super(BuildArtifactsBeforeRunTaskProvider.ID);
+ myProject = project;
+ }
+
+ @Override
+ public void readExternal(Element element) {
+ super.readExternal(element);
+ final List<Element> children = element.getChildren(ARTIFACT_ELEMENT);
+ final ArtifactPointerManager pointerManager = ArtifactPointerManager.getInstance(myProject);
+ for (Element child : children) {
+ myArtifactPointers.add(pointerManager.createPointer(child.getAttributeValue(NAME_ATTRIBUTE)));
+ }
+ }
+
+ @Override
+ public void writeExternal(Element element) {
+ super.writeExternal(element);
+ for (ArtifactPointer pointer : myArtifactPointers) {
+ element.addContent(new Element(ARTIFACT_ELEMENT).setAttribute(NAME_ATTRIBUTE, pointer.getArtifactName()));
+ }
+ }
+
+ @Override
+ public BeforeRunTask clone() {
+ final BuildArtifactsBeforeRunTask task = (BuildArtifactsBeforeRunTask)super.clone();
+ task.myArtifactPointers = new ArrayList<ArtifactPointer>(myArtifactPointers);
+ return task;
+ }
+
+ @Override
+ public int getItemsCount() {
+ return myArtifactPointers.size();
+ }
+
+ public List<ArtifactPointer> getArtifactPointers() {
+ return Collections.unmodifiableList(myArtifactPointers);
+ }
+
+ public void setArtifactPointers(List<ArtifactPointer> artifactPointers) {
+ myArtifactPointers = new ArrayList<ArtifactPointer>(artifactPointers);
+ }
+
+ public void addArtifact(Artifact artifact) {
+ final ArtifactPointer pointer = ArtifactPointerManager.getInstance(myProject).createPointer(artifact);
+ if (!myArtifactPointers.contains(pointer)) {
+ myArtifactPointers.add(pointer);
+ }
+ }
+
+ public void removeArtifact(@NotNull Artifact artifact) {
+ removeArtifact(ArtifactPointerManager.getInstance(myProject).createPointer(artifact));
+ }
+
+ public void removeArtifact(final @NotNull ArtifactPointer pointer) {
+ myArtifactPointers.remove(pointer);
+ }
+
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
+
+ BuildArtifactsBeforeRunTask that = (BuildArtifactsBeforeRunTask)o;
+
+ if (!myArtifactPointers.equals(that.myArtifactPointers)) return false;
+ if (!myProject.equals(that.myProject)) return false;
+
+ return true;
+ }
+
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + myArtifactPointers.hashCode();
+ result = 31 * result + myProject.hashCode();
+ return result;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/run/BuildArtifactsBeforeRunTaskProvider.java b/java/compiler/impl/src/com/intellij/packaging/impl/run/BuildArtifactsBeforeRunTaskProvider.java
new file mode 100644
index 0000000..a1fa1b4
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/run/BuildArtifactsBeforeRunTaskProvider.java
@@ -0,0 +1,260 @@
+/*
+ * 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 com.intellij.packaging.impl.run;
+
+import com.intellij.execution.BeforeRunTask;
+import com.intellij.execution.BeforeRunTaskProvider;
+import com.intellij.execution.RunManagerEx;
+import com.intellij.execution.configurations.RunConfiguration;
+import com.intellij.execution.impl.ConfigurationSettingsEditorWrapper;
+import com.intellij.execution.runners.ExecutionEnvironment;
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.DataManager;
+import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ModalityState;
+import com.intellij.openapi.application.ReadAction;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.compiler.*;
+import com.intellij.openapi.compiler.Compiler;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogBuilder;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.Ref;
+import com.intellij.packaging.artifacts.*;
+import com.intellij.packaging.impl.compiler.ArtifactAwareCompiler;
+import com.intellij.packaging.impl.compiler.ArtifactCompileScope;
+import com.intellij.packaging.impl.compiler.ArtifactsCompiler;
+import com.intellij.util.concurrency.Semaphore;
+import com.intellij.util.containers.ContainerUtil;
+import gnu.trove.THashSet;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author nik
+ */
+public class BuildArtifactsBeforeRunTaskProvider extends BeforeRunTaskProvider<BuildArtifactsBeforeRunTask> {
+ @NonNls public static final String BUILD_ARTIFACTS_ID = "BuildArtifacts";
+ public static final Key<BuildArtifactsBeforeRunTask> ID = Key.create(BUILD_ARTIFACTS_ID);
+ private final Project myProject;
+
+ public BuildArtifactsBeforeRunTaskProvider(Project project) {
+ myProject = project;
+ project.getMessageBus().connect().subscribe(ArtifactManager.TOPIC, new ArtifactAdapter() {
+ @Override
+ public void artifactRemoved(@NotNull Artifact artifact) {
+ final RunManagerEx runManager = RunManagerEx.getInstanceEx(myProject);
+ for (RunConfiguration configuration : runManager.getAllConfigurations()) {
+ final List<BuildArtifactsBeforeRunTask> tasks = runManager.getBeforeRunTasks(configuration, ID);
+ for (BuildArtifactsBeforeRunTask task : tasks) {
+ final String artifactName = artifact.getName();
+ final List<ArtifactPointer> pointersList = task.getArtifactPointers();
+ final ArtifactPointer[] pointers = pointersList.toArray(new ArtifactPointer[pointersList.size()]);
+ for (ArtifactPointer pointer : pointers) {
+ if (pointer.getArtifactName().equals(artifactName) && ArtifactManager.getInstance(myProject).findArtifact(artifactName) == null) {
+ task.removeArtifact(pointer);
+ }
+ }
+ }
+ }
+ }
+ });
+ }
+
+ public Key<BuildArtifactsBeforeRunTask> getId() {
+ return ID;
+ }
+
+ @Override
+ public Icon getIcon() {
+ return AllIcons.Nodes.Artifact;
+ }
+
+ @Override
+ public String getName() {
+ return CompilerBundle.message("build.artifacts.before.run.description.empty");
+ }
+
+ @Override
+ public Icon getTaskIcon(BuildArtifactsBeforeRunTask task) {
+ List<ArtifactPointer> pointers = task.getArtifactPointers();
+ if (pointers == null || pointers.isEmpty())
+ return getIcon();
+ Artifact artifact = pointers.get(0).getArtifact();
+ if (artifact == null)
+ return getIcon();
+ return artifact.getArtifactType().getIcon();
+ }
+
+ @Override
+ public String getDescription(BuildArtifactsBeforeRunTask task) {
+ final List<ArtifactPointer> pointers = task.getArtifactPointers();
+ if (pointers.isEmpty()) {
+ return CompilerBundle.message("build.artifacts.before.run.description.empty");
+ }
+ if (pointers.size() == 1) {
+ return CompilerBundle.message("build.artifacts.before.run.description.single", pointers.get(0).getArtifactName());
+ }
+ return CompilerBundle.message("build.artifacts.before.run.description.multiple", pointers.size());
+ }
+
+ public boolean isConfigurable() {
+ return true;
+ }
+
+ public BuildArtifactsBeforeRunTask createTask(RunConfiguration runConfiguration) {
+ if (myProject.isDefault()) return null;
+ return new BuildArtifactsBeforeRunTask(myProject);
+ }
+
+ public boolean configureTask(RunConfiguration runConfiguration, BuildArtifactsBeforeRunTask task) {
+ final Artifact[] artifacts = ArtifactManager.getInstance(myProject).getArtifacts();
+ Set<ArtifactPointer> pointers = new THashSet<ArtifactPointer>();
+ for (Artifact artifact : artifacts) {
+ pointers.add(ArtifactPointerManager.getInstance(myProject).createPointer(artifact));
+ }
+ pointers.addAll(task.getArtifactPointers());
+ ArtifactChooser chooser = new ArtifactChooser(new ArrayList<ArtifactPointer>(pointers));
+ chooser.markElements(task.getArtifactPointers());
+ chooser.setPreferredSize(new Dimension(400, 300));
+
+ DialogBuilder builder = new DialogBuilder(myProject);
+ builder.setTitle(CompilerBundle.message("build.artifacts.before.run.selector.title"));
+ builder.setDimensionServiceKey("#BuildArtifactsBeforeRunChooser");
+ builder.addOkAction();
+ builder.addCancelAction();
+ builder.setCenterPanel(chooser);
+ builder.setPreferedFocusComponent(chooser);
+ if (builder.show() == DialogWrapper.OK_EXIT_CODE) {
+ task.setArtifactPointers(chooser.getMarkedElements());
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean canExecuteTask(RunConfiguration configuration, BuildArtifactsBeforeRunTask task) {
+ for (ArtifactPointer pointer: task.getArtifactPointers()) {
+ if (pointer.getArtifact() != null)
+ return true;
+ }
+ return false;
+ }
+
+ public boolean executeTask(DataContext context,
+ RunConfiguration configuration,
+ ExecutionEnvironment env,
+ final BuildArtifactsBeforeRunTask task) {
+ final Ref<Boolean> result = Ref.create(false);
+ final Semaphore finished = new Semaphore();
+
+ final List<Artifact> artifacts = new ArrayList<Artifact>();
+ new ReadAction() {
+ protected void run(final Result result) {
+ for (ArtifactPointer pointer : task.getArtifactPointers()) {
+ ContainerUtil.addIfNotNull(pointer.getArtifact(), artifacts);
+ }
+ }
+ }.execute();
+
+ final CompileStatusNotification callback = new CompileStatusNotification() {
+ public void finished(boolean aborted, int errors, int warnings, CompileContext compileContext) {
+ result.set(!aborted && errors == 0);
+ finished.up();
+ }
+ };
+ final CompilerFilter compilerFilter = new CompilerFilter() {
+ public boolean acceptCompiler(Compiler compiler) {
+ return compiler instanceof ArtifactsCompiler
+ || compiler instanceof ArtifactAwareCompiler && ((ArtifactAwareCompiler)compiler).shouldRun(artifacts);
+ }
+ };
+
+ ApplicationManager.getApplication().invokeAndWait(new Runnable() {
+ public void run() {
+ final CompilerManager manager = CompilerManager.getInstance(myProject);
+ finished.down();
+ manager.make(ArtifactCompileScope.createArtifactsScope(myProject, artifacts), compilerFilter, callback);
+ }
+ }, ModalityState.NON_MODAL);
+
+ finished.waitFor();
+ return result.get();
+ }
+
+ public static void setBuildArtifactBeforeRunOption(@NotNull JComponent runConfigurationEditorComponent,
+ Project project,
+ @NotNull Artifact artifact,
+ final boolean enable) {
+ final DataContext dataContext = DataManager.getInstance().getDataContext(runConfigurationEditorComponent);
+ final ConfigurationSettingsEditorWrapper editor = ConfigurationSettingsEditorWrapper.CONFIGURATION_EDITOR_KEY.getData(dataContext);
+ if (editor != null) {
+ List<BeforeRunTask> tasks = editor.getStepsBeforeLaunch();
+ List<BuildArtifactsBeforeRunTask> myTasks = new ArrayList<BuildArtifactsBeforeRunTask>();
+ for (BeforeRunTask task : tasks) {
+ if (task instanceof BuildArtifactsBeforeRunTask) {
+ myTasks.add((BuildArtifactsBeforeRunTask)task);
+ }
+ }
+ if (enable && myTasks.isEmpty()) {
+ BuildArtifactsBeforeRunTask task = new BuildArtifactsBeforeRunTask(project);
+ task.addArtifact(artifact);
+ task.setEnabled(true);
+ editor.addBeforeLaunchStep(task);
+ }
+ else {
+ for (BuildArtifactsBeforeRunTask task : myTasks) {
+ if (enable) {
+ task.addArtifact(artifact);
+ task.setEnabled(true);
+ }
+ else {
+ task.removeArtifact(artifact);
+ if (task.getArtifactPointers().isEmpty()) {
+ task.setEnabled(false);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ public static void setBuildArtifactBeforeRun(@NotNull Project project, @NotNull RunConfiguration configuration, @NotNull Artifact artifact) {
+ RunManagerEx runManager = RunManagerEx.getInstanceEx(project);
+ final List<BuildArtifactsBeforeRunTask> buildArtifactsTasks = runManager.getBeforeRunTasks(configuration, ID);
+ if (buildArtifactsTasks.isEmpty()) { //Add new task if absent
+ BuildArtifactsBeforeRunTask task = new BuildArtifactsBeforeRunTask(project);
+ buildArtifactsTasks.add(task);
+ List<BeforeRunTask> tasks = runManager.getBeforeRunTasks(configuration);
+ tasks.add(task);
+ runManager.setBeforeRunTasks(configuration, tasks, false);
+ }
+
+ for (BuildArtifactsBeforeRunTask task : buildArtifactsTasks) {
+ task.setEnabled(true);
+ task.addArtifact(artifact);
+
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/ui/ArchiveElementPresentation.java b/java/compiler/impl/src/com/intellij/packaging/impl/ui/ArchiveElementPresentation.java
new file mode 100644
index 0000000..d48b43a
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/ui/ArchiveElementPresentation.java
@@ -0,0 +1,49 @@
+/*
+ * 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 com.intellij.packaging.impl.ui;
+
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.packaging.impl.elements.ArchivePackagingElement;
+import com.intellij.packaging.ui.PackagingElementPresentation;
+import com.intellij.packaging.ui.PackagingElementWeights;
+import com.intellij.ui.SimpleTextAttributes;
+import com.intellij.util.PlatformIcons;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author nik
+ */
+public class ArchiveElementPresentation extends PackagingElementPresentation {
+ private final ArchivePackagingElement myElement;
+
+ public ArchiveElementPresentation(ArchivePackagingElement element) {
+ myElement = element;
+ }
+
+ public String getPresentableName() {
+ return myElement.getArchiveFileName();
+ }
+
+ public void render(@NotNull PresentationData presentationData, SimpleTextAttributes mainAttributes, SimpleTextAttributes commentAttributes) {
+ presentationData.setIcon(PlatformIcons.JAR_ICON);
+ presentationData.addText(myElement.getArchiveFileName(), mainAttributes);
+ }
+
+ @Override
+ public int getWeight() {
+ return PackagingElementWeights.FILE_COPY;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/ui/ArtifactElementPresentation.java b/java/compiler/impl/src/com/intellij/packaging/impl/ui/ArtifactElementPresentation.java
new file mode 100644
index 0000000..27535c4
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/ui/ArtifactElementPresentation.java
@@ -0,0 +1,76 @@
+/*
+ * 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 com.intellij.packaging.impl.ui;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.artifacts.ArtifactPointer;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import com.intellij.packaging.ui.PackagingElementWeights;
+import com.intellij.packaging.ui.TreeNodePresentation;
+import com.intellij.ui.SimpleTextAttributes;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+
+/**
+ * @author nik
+ */
+public class ArtifactElementPresentation extends TreeNodePresentation {
+ private final ArtifactPointer myArtifactPointer;
+ private final ArtifactEditorContext myContext;
+
+ public ArtifactElementPresentation(ArtifactPointer artifactPointer, ArtifactEditorContext context) {
+ myArtifactPointer = artifactPointer;
+ myContext = context;
+ }
+
+ public String getPresentableName() {
+ return myArtifactPointer != null ? myArtifactPointer.getArtifactName(myContext.getArtifactModel()) : "<unknown>";
+ }
+
+ @Override
+ public boolean canNavigateToSource() {
+ return findArtifact() != null;
+ }
+
+ @Override
+ public void navigateToSource() {
+ final Artifact artifact = findArtifact();
+ if (artifact != null) {
+ myContext.selectArtifact(artifact);
+ }
+ }
+
+ public void render(@NotNull PresentationData presentationData, SimpleTextAttributes mainAttributes, SimpleTextAttributes commentAttributes) {
+ final Artifact artifact = findArtifact();
+ Icon icon = artifact != null ? artifact.getArtifactType().getIcon() : AllIcons.Nodes.Artifact;
+ presentationData.setIcon(icon);
+ presentationData.addText(getPresentableName(), artifact != null ? mainAttributes : SimpleTextAttributes.ERROR_ATTRIBUTES);
+ }
+
+ @Nullable
+ private Artifact findArtifact() {
+ return myArtifactPointer != null ? myArtifactPointer.findArtifact(myContext.getArtifactModel()) : null;
+ }
+
+ @Override
+ public int getWeight() {
+ return PackagingElementWeights.ARTIFACT;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/ui/ArtifactProblemsHolderBase.java b/java/compiler/impl/src/com/intellij/packaging/impl/ui/ArtifactProblemsHolderBase.java
new file mode 100644
index 0000000..c14e2a8
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/ui/ArtifactProblemsHolderBase.java
@@ -0,0 +1,40 @@
+/*
+ * 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 com.intellij.packaging.impl.ui;
+
+import com.intellij.packaging.elements.PackagingElementResolvingContext;
+import com.intellij.packaging.ui.ArtifactProblemsHolder;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author nik
+ */
+public abstract class ArtifactProblemsHolderBase implements ArtifactProblemsHolder {
+ private final PackagingElementResolvingContext myContext;
+
+ protected ArtifactProblemsHolderBase(PackagingElementResolvingContext context) {
+ myContext = context;
+ }
+
+ @NotNull
+ public PackagingElementResolvingContext getContext() {
+ return myContext;
+ }
+
+ public void registerError(@NotNull String message, @NotNull String problemTypeId) {
+ registerError(message, problemTypeId, null);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/ui/ChooseArtifactsDialog.java b/java/compiler/impl/src/com/intellij/packaging/impl/ui/ChooseArtifactsDialog.java
new file mode 100644
index 0000000..0dcb2f4
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/ui/ChooseArtifactsDialog.java
@@ -0,0 +1,41 @@
+/*
+ * 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 com.intellij.packaging.impl.ui;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.ide.util.ChooseElementsDialog;
+import com.intellij.packaging.artifacts.Artifact;
+
+import javax.swing.*;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public class ChooseArtifactsDialog extends ChooseElementsDialog<Artifact> {
+
+ public ChooseArtifactsDialog(Project project, List<? extends Artifact> items, String title, String description) {
+ super(project, items, title, description, true);
+ }
+
+ protected String getItemText(Artifact item) {
+ return item.getName();
+ }
+
+ protected Icon getItemIcon(Artifact item) {
+ return item.getArtifactType().getIcon();
+ }
+}
\ No newline at end of file
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/ui/DelegatedPackagingElementPresentation.java b/java/compiler/impl/src/com/intellij/packaging/impl/ui/DelegatedPackagingElementPresentation.java
new file mode 100644
index 0000000..8c5ac7d
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/ui/DelegatedPackagingElementPresentation.java
@@ -0,0 +1,63 @@
+/*
+ * 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 com.intellij.packaging.impl.ui;
+
+import com.intellij.packaging.ui.PackagingElementPresentation;
+import com.intellij.packaging.ui.TreeNodePresentation;
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.ui.SimpleTextAttributes;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author nik
+ */
+public class DelegatedPackagingElementPresentation extends PackagingElementPresentation {
+ private final TreeNodePresentation myDelegate;
+
+ public DelegatedPackagingElementPresentation(TreeNodePresentation delegate) {
+ myDelegate = delegate;
+ }
+
+ public String getPresentableName() {
+ return myDelegate.getPresentableName();
+ }
+
+ public String getSearchName() {
+ return myDelegate.getSearchName();
+ }
+
+ public void render(@NotNull PresentationData presentationData, SimpleTextAttributes mainAttributes, SimpleTextAttributes commentAttributes) {
+ myDelegate.render(presentationData, mainAttributes, commentAttributes);
+ }
+
+ @Nullable
+ public String getTooltipText() {
+ return myDelegate.getTooltipText();
+ }
+
+ public boolean canNavigateToSource() {
+ return myDelegate.canNavigateToSource();
+ }
+
+ public void navigateToSource() {
+ myDelegate.navigateToSource();
+ }
+
+ public int getWeight() {
+ return myDelegate.getWeight();
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/ui/DirectoryCopyPresentation.java b/java/compiler/impl/src/com/intellij/packaging/impl/ui/DirectoryCopyPresentation.java
new file mode 100644
index 0000000..52786c3
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/ui/DirectoryCopyPresentation.java
@@ -0,0 +1,75 @@
+/*
+ * 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 com.intellij.packaging.impl.ui;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.ui.PackagingElementPresentation;
+import com.intellij.packaging.ui.PackagingElementWeights;
+import com.intellij.ui.SimpleTextAttributes;
+import com.intellij.util.PathUtil;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author nik
+ */
+public class DirectoryCopyPresentation extends PackagingElementPresentation {
+ private final String mySourcePath;
+ private final String mySourceFileName;
+ private final VirtualFile myFile;
+
+ public DirectoryCopyPresentation(String filePath) {
+ mySourceFileName = PathUtil.getFileName(filePath);
+
+ String parentPath;
+ myFile = LocalFileSystem.getInstance().findFileByPath(filePath);
+ if (myFile != null) {
+ final VirtualFile parent = myFile.getParent();
+ parentPath = parent != null ? FileUtil.toSystemDependentName(parent.getPath()) : "";
+ }
+ else {
+ parentPath = FileUtil.toSystemDependentName(PathUtil.getParentPath(filePath));
+ }
+
+ mySourcePath = parentPath;
+ }
+
+ public String getPresentableName() {
+ return mySourceFileName;
+ }
+
+ public void render(@NotNull PresentationData presentationData, SimpleTextAttributes mainAttributes, SimpleTextAttributes commentAttributes) {
+ presentationData.setIcon(AllIcons.Nodes.CopyOfFolder);
+ if (myFile == null || !myFile.isDirectory()) {
+ mainAttributes = SimpleTextAttributes.ERROR_ATTRIBUTES;
+ final VirtualFile parentFile = LocalFileSystem.getInstance().findFileByPath(FileUtil.toSystemIndependentName(mySourcePath));
+ if (parentFile == null) {
+ commentAttributes = SimpleTextAttributes.ERROR_ATTRIBUTES;
+ }
+ }
+ presentationData.addText(CompilerBundle.message("node.text.0.directory.content", mySourceFileName), mainAttributes);
+ presentationData.addText(" (" + mySourcePath + ")", commentAttributes);
+ }
+
+ @Override
+ public int getWeight() {
+ return PackagingElementWeights.DIRECTORY_COPY;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/ui/DirectoryElementPresentation.java b/java/compiler/impl/src/com/intellij/packaging/impl/ui/DirectoryElementPresentation.java
new file mode 100644
index 0000000..cfdf2bb
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/ui/DirectoryElementPresentation.java
@@ -0,0 +1,49 @@
+/*
+ * 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 com.intellij.packaging.impl.ui;
+
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.packaging.impl.elements.DirectoryPackagingElement;
+import com.intellij.packaging.ui.PackagingElementPresentation;
+import com.intellij.packaging.ui.PackagingElementWeights;
+import com.intellij.ui.SimpleTextAttributes;
+import com.intellij.util.PlatformIcons;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author nik
+ */
+public class DirectoryElementPresentation extends PackagingElementPresentation {
+ private final DirectoryPackagingElement myElement;
+
+ public DirectoryElementPresentation(DirectoryPackagingElement element) {
+ myElement = element;
+ }
+
+ public String getPresentableName() {
+ return myElement.getDirectoryName();
+ }
+
+ public void render(@NotNull PresentationData presentationData, SimpleTextAttributes mainAttributes, SimpleTextAttributes commentAttributes) {
+ presentationData.setIcon(PlatformIcons.DIRECTORY_CLOSED_ICON);
+ presentationData.addText(myElement.getDirectoryName(), mainAttributes);
+ }
+
+ @Override
+ public int getWeight() {
+ return PackagingElementWeights.DIRECTORY;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/ui/ExtractedDirectoryPresentation.java b/java/compiler/impl/src/com/intellij/packaging/impl/ui/ExtractedDirectoryPresentation.java
new file mode 100644
index 0000000..6f9d0ac
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/ui/ExtractedDirectoryPresentation.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2000-2010 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 com.intellij.packaging.impl.ui;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.impl.elements.ExtractedDirectoryPackagingElement;
+import com.intellij.packaging.ui.PackagingElementPresentation;
+import com.intellij.packaging.ui.PackagingElementWeights;
+import com.intellij.ui.SimpleTextAttributes;
+import com.intellij.util.PathUtil;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author nik
+ */
+public class ExtractedDirectoryPresentation extends PackagingElementPresentation {
+ private final String myJarPath;
+ private final String myPathInJar;
+ private final VirtualFile myFile;
+
+ public ExtractedDirectoryPresentation(ExtractedDirectoryPackagingElement element) {
+ myFile = element.findFile();
+ myJarPath = element.getFilePath();
+ myPathInJar = element.getPathInJar();
+ }
+
+ public String getPresentableName() {
+ return PathUtil.getFileName(myJarPath) + myPathInJar;
+ }
+
+ public void render(@NotNull PresentationData presentationData, SimpleTextAttributes mainAttributes, SimpleTextAttributes commentAttributes) {
+ presentationData.setIcon(AllIcons.Nodes.ExtractedFolder);
+ final String parentPath = PathUtil.getParentPath(myJarPath);
+ if (myFile == null || !myFile.isDirectory()) {
+ mainAttributes = SimpleTextAttributes.ERROR_ATTRIBUTES;
+ final VirtualFile parentFile = LocalFileSystem.getInstance().findFileByPath(parentPath);
+ if (parentFile == null) {
+ commentAttributes = SimpleTextAttributes.ERROR_ATTRIBUTES;
+ }
+ }
+ presentationData.addText("Extracted '" + PathUtil.getFileName(myJarPath) + myPathInJar + "'", mainAttributes);
+ presentationData.addText(" (" + parentPath + ")", commentAttributes);
+ }
+
+ @Override
+ public int getWeight() {
+ return PackagingElementWeights.EXTRACTED_DIRECTORY;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/ui/FileCopyPresentation.java b/java/compiler/impl/src/com/intellij/packaging/impl/ui/FileCopyPresentation.java
new file mode 100644
index 0000000..bbd3304
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/ui/FileCopyPresentation.java
@@ -0,0 +1,83 @@
+/*
+ * 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 com.intellij.packaging.impl.ui;
+
+import com.intellij.icons.AllIcons;
+import com.intellij.ide.presentation.VirtualFilePresentation;
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.ui.PackagingElementPresentation;
+import com.intellij.packaging.ui.PackagingElementWeights;
+import com.intellij.ui.SimpleTextAttributes;
+import com.intellij.util.PathUtil;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * @author nik
+ */
+public class FileCopyPresentation extends PackagingElementPresentation {
+ private final String mySourcePath;
+ private final String myOutputFileName;
+ private final VirtualFile myFile;
+
+ public FileCopyPresentation(String filePath, String outputFileName) {
+ myOutputFileName = outputFileName;
+
+ String parentPath;
+ myFile = LocalFileSystem.getInstance().findFileByPath(filePath);
+ if (myFile != null) {
+ final VirtualFile parent = myFile.getParent();
+ parentPath = parent != null ? FileUtil.toSystemDependentName(parent.getPath()) : "";
+ }
+ else {
+ parentPath = FileUtil.toSystemDependentName(PathUtil.getParentPath(filePath));
+ }
+
+ String sourceFileName = PathUtil.getFileName(filePath);
+ if (!sourceFileName.equals(myOutputFileName)) {
+ mySourcePath = parentPath + "/" + sourceFileName;
+ }
+ else {
+ mySourcePath = parentPath;
+ }
+ }
+
+ public String getPresentableName() {
+ return myOutputFileName;
+ }
+
+ public void render(@NotNull PresentationData presentationData, SimpleTextAttributes mainAttributes, SimpleTextAttributes commentAttributes) {
+ if (myFile != null && !myFile.isDirectory()) {
+ presentationData.setIcon(VirtualFilePresentation.getIcon(myFile));
+ presentationData.addText(myOutputFileName, mainAttributes);
+ presentationData.addText(" (" + mySourcePath + ")", commentAttributes);
+ }
+ else {
+ presentationData.setIcon(AllIcons.FileTypes.Text);
+ presentationData.addText(myOutputFileName, SimpleTextAttributes.ERROR_ATTRIBUTES);
+ final VirtualFile parentFile = LocalFileSystem.getInstance().findFileByPath(FileUtil.toSystemIndependentName(mySourcePath));
+ presentationData.addText("(" + mySourcePath + ")",
+ parentFile != null ? commentAttributes : SimpleTextAttributes.ERROR_ATTRIBUTES);
+ }
+ }
+
+ @Override
+ public int getWeight() {
+ return PackagingElementWeights.FILE_COPY;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/ui/LibraryElementPresentation.java b/java/compiler/impl/src/com/intellij/packaging/impl/ui/LibraryElementPresentation.java
new file mode 100644
index 0000000..44905f8
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/ui/LibraryElementPresentation.java
@@ -0,0 +1,101 @@
+/*
+ * 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 com.intellij.packaging.impl.ui;
+
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.roots.impl.ModuleLibraryTable;
+import com.intellij.openapi.roots.impl.libraries.LibraryImpl;
+import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.roots.libraries.LibraryTable;
+import com.intellij.openapi.roots.libraries.LibraryTablePresentation;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import com.intellij.packaging.ui.PackagingElementPresentation;
+import com.intellij.packaging.ui.PackagingElementWeights;
+import com.intellij.ui.SimpleTextAttributes;
+import com.intellij.util.PlatformIcons;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author nik
+ */
+public class LibraryElementPresentation extends PackagingElementPresentation {
+ private final String myLevel;
+ private final String myModuleName;
+ private final Library myLibrary;
+ private final String myLibraryName;
+ private final ArtifactEditorContext myContext;
+
+ public LibraryElementPresentation(String libraryName, String level, @Nullable String moduleName, Library library, ArtifactEditorContext context) {
+ myLevel = level;
+ myModuleName = moduleName;
+ myLibrary = library;
+ myLibraryName = libraryName;
+ myContext = context;
+ }
+
+ public String getPresentableName() {
+ return myLibraryName;
+ }
+
+ @Override
+ public boolean canNavigateToSource() {
+ return myLibrary != null;
+ }
+
+ @Override
+ public void navigateToSource() {
+ myContext.selectLibrary(myLibrary);
+ }
+
+ public void render(@NotNull PresentationData presentationData, SimpleTextAttributes mainAttributes, SimpleTextAttributes commentAttributes) {
+ if (myLibrary != null) {
+ presentationData.setIcon(PlatformIcons.LIBRARY_ICON);
+ presentationData.addText(myLibraryName, mainAttributes);
+ presentationData.addText(getLibraryTableComment(myLibrary), commentAttributes);
+ }
+ else {
+ presentationData.addText(myLibraryName + " (" + (myModuleName != null ? "module '" + myModuleName + "'" : myLevel) + ")",
+ SimpleTextAttributes.ERROR_ATTRIBUTES);
+ }
+ }
+
+ @Override
+ public int getWeight() {
+ return PackagingElementWeights.LIBRARY;
+ }
+
+ public static String getLibraryTableDisplayName(final Library library) {
+ LibraryTable table = library.getTable();
+ LibraryTablePresentation presentation = table != null ? table.getPresentation() : ModuleLibraryTable.MODULE_LIBRARY_TABLE_PRESENTATION;
+ return presentation.getDisplayName(false);
+ }
+
+ public static String getLibraryTableComment(final Library library) {
+ LibraryTable libraryTable = library.getTable();
+ String displayName;
+ if (libraryTable != null) {
+ displayName = libraryTable.getPresentation().getDisplayName(false);
+ }
+ else {
+ Module module = ((LibraryImpl)library).getModule();
+ String tableName = getLibraryTableDisplayName(library);
+ displayName = module != null ? "'" + module.getName() + "' " + tableName : tableName;
+ }
+ return " (" + displayName + ")";
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/ui/ModuleElementPresentation.java b/java/compiler/impl/src/com/intellij/packaging/impl/ui/ModuleElementPresentation.java
new file mode 100644
index 0000000..af59bc0
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/ui/ModuleElementPresentation.java
@@ -0,0 +1,103 @@
+/*
+ * 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 com.intellij.packaging.impl.ui;
+
+import com.intellij.ide.projectView.PresentationData;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.module.ModifiableModuleModel;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModulePointer;
+import com.intellij.openapi.module.ModuleType;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import com.intellij.packaging.ui.PackagingElementWeights;
+import com.intellij.packaging.ui.TreeNodePresentation;
+import com.intellij.ui.SimpleTextAttributes;
+import com.intellij.util.PlatformIcons;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author nik
+ */
+public class ModuleElementPresentation extends TreeNodePresentation {
+ private final ModulePointer myModulePointer;
+ private final ArtifactEditorContext myContext;
+ private final boolean myTestOutput;
+
+ public ModuleElementPresentation(@Nullable ModulePointer modulePointer, @NotNull ArtifactEditorContext context, final boolean testOutput) {
+ myModulePointer = modulePointer;
+ myContext = context;
+ myTestOutput = testOutput;
+ }
+
+ public String getPresentableName() {
+ return myModulePointer != null ? myModulePointer.getModuleName() : "<unknown>";
+ }
+
+ @Override
+ public boolean canNavigateToSource() {
+ return findModule() != null;
+ }
+
+ @Nullable
+ private Module findModule() {
+ return myModulePointer != null ? myModulePointer.getModule() : null;
+ }
+
+ @Override
+ public void navigateToSource() {
+ final Module module = findModule();
+ if (module != null) {
+ myContext.selectModule(module);
+ }
+ }
+
+ public void render(@NotNull PresentationData presentationData, SimpleTextAttributes mainAttributes, SimpleTextAttributes commentAttributes) {
+ final Module module = findModule();
+ if (myTestOutput) {
+ presentationData.setIcon(PlatformIcons.TEST_SOURCE_FOLDER);
+ }
+ else if (module != null) {
+ presentationData.setIcon(ModuleType.get(module).getIcon());
+ }
+ String moduleName;
+ if (module != null) {
+ moduleName = module.getName();
+ final ModifiableModuleModel moduleModel = myContext.getModifiableModuleModel();
+ if (moduleModel != null) {
+ final String newName = moduleModel.getNewName(module);
+ if (newName != null) {
+ moduleName = newName;
+ }
+ }
+ }
+ else if (myModulePointer != null) {
+ moduleName = myModulePointer.getModuleName();
+ }
+ else {
+ moduleName = "<unknown>";
+ }
+
+ String text = myTestOutput ? CompilerBundle.message("node.text.0.test.compile.output", moduleName)
+ : CompilerBundle.message("node.text.0.compile.output", moduleName);
+ presentationData.addText(text, module != null ? mainAttributes : SimpleTextAttributes.ERROR_ATTRIBUTES);
+ }
+
+ @Override
+ public int getWeight() {
+ return PackagingElementWeights.MODULE;
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/ui/actions/PackageFileAction.java b/java/compiler/impl/src/com/intellij/packaging/impl/ui/actions/PackageFileAction.java
new file mode 100644
index 0000000..f72bd75
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/ui/actions/PackageFileAction.java
@@ -0,0 +1,104 @@
+/**
+ * @author cdr
+ */
+package com.intellij.packaging.impl.ui.actions;
+
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.actionSystem.PlatformDataKeys;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.compiler.CompilerManager;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ProjectFileIndex;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.util.Clock;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.openapi.wm.WindowManager;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.artifacts.ArtifactManager;
+import com.intellij.packaging.impl.artifacts.ArtifactBySourceFileFinder;
+import com.intellij.util.text.SyncDateFormat;
+import org.jetbrains.annotations.NotNull;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public class PackageFileAction extends AnAction {
+ private static final SyncDateFormat TIME_FORMAT = new SyncDateFormat(new SimpleDateFormat("h:mm:ss a"));
+
+ public PackageFileAction() {
+ super(CompilerBundle.message("action.name.package.file"), CompilerBundle.message("action.description.package.file"), null);
+ }
+
+ @Override
+ public void update(AnActionEvent e) {
+ boolean visible = false;
+ final Project project = e.getData(PlatformDataKeys.PROJECT);
+ if (project != null) {
+ final List<VirtualFile> files = getFilesToPackage(e, project);
+ if (!files.isEmpty()) {
+ visible = true;
+ e.getPresentation().setText(files.size() == 1 ? CompilerBundle.message("action.name.package.file") : CompilerBundle.message("action.name.package.files"));
+ }
+ }
+
+ e.getPresentation().setVisible(visible);
+ }
+
+ @NotNull
+ private static List<VirtualFile> getFilesToPackage(@NotNull AnActionEvent e, @NotNull Project project) {
+ final VirtualFile[] files = e.getData(PlatformDataKeys.VIRTUAL_FILE_ARRAY);
+ if (files == null) return Collections.emptyList();
+
+ List<VirtualFile> result = new ArrayList<VirtualFile>();
+ ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
+ final CompilerManager compilerManager = CompilerManager.getInstance(project);
+ for (VirtualFile file : files) {
+ if (file == null || file.isDirectory() ||
+ fileIndex.isInSourceContent(file) && compilerManager.isCompilableFileType(file.getFileType())) {
+ return Collections.emptyList();
+ }
+ final Collection<? extends Artifact> artifacts = ArtifactBySourceFileFinder.getInstance(project).findArtifacts(file);
+ for (Artifact artifact : artifacts) {
+ if (!StringUtil.isEmpty(artifact.getOutputPath())) {
+ result.add(file);
+ break;
+ }
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public void actionPerformed(AnActionEvent event) {
+ final Project project = event.getData(PlatformDataKeys.PROJECT);
+ if (project == null) return;
+
+ FileDocumentManager.getInstance().saveAllDocuments();
+ final List<VirtualFile> files = getFilesToPackage(event, project);
+ Artifact[] allArtifacts = ArtifactManager.getInstance(project).getArtifacts();
+ PackageFileWorker.startPackagingFiles(project, files, allArtifacts, new Runnable() {
+ public void run() {
+ setStatusText(project, files);
+ }
+ });
+ }
+
+ private static void setStatusText(Project project, List<VirtualFile> files) {
+ if (!files.isEmpty()) {
+ StringBuilder fileNames = new StringBuilder();
+ for (VirtualFile file : files) {
+ if (fileNames.length() != 0) fileNames.append(", ");
+ fileNames.append("'").append(file.getName()).append("'");
+ }
+ String time = TIME_FORMAT.format(Clock.getTime());
+ final String statusText = CompilerBundle.message("status.text.file.has.been.packaged", files.size(), fileNames, time);
+ WindowManager.getInstance().getStatusBar(project).setInfo(statusText);
+ }
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/ui/actions/PackageFileWorker.java b/java/compiler/impl/src/com/intellij/packaging/impl/ui/actions/PackageFileWorker.java
new file mode 100644
index 0000000..6be07f0
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/ui/actions/PackageFileWorker.java
@@ -0,0 +1,183 @@
+/*
+ * 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 com.intellij.packaging.impl.ui.actions;
+
+import com.intellij.notification.Notification;
+import com.intellij.notification.NotificationType;
+import com.intellij.notification.Notifications;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.application.ReadAction;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.compiler.CompilerBundle;
+import com.intellij.openapi.deployment.DeploymentUtil;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.progress.ProgressIndicator;
+import com.intellij.openapi.progress.ProgressManager;
+import com.intellij.openapi.progress.Task;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Trinity;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.elements.ArtifactRootElement;
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.packaging.impl.artifacts.ArtifactUtil;
+import com.intellij.packaging.impl.artifacts.PackagingElementPath;
+import com.intellij.packaging.impl.elements.ArchivePackagingElement;
+import com.intellij.util.PathUtil;
+import com.intellij.util.io.zip.JBZipEntry;
+import com.intellij.util.io.zip.JBZipFile;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public class PackageFileWorker {
+ private static final Logger LOG = Logger.getInstance("#com.intellij.packaging.impl.ui.actions.PackageFileWorker");
+ private final File myFile;
+ private final String myRelativeOutputPath;
+
+ private PackageFileWorker(File file, String relativeOutputPath) {
+ myFile = file;
+ myRelativeOutputPath = relativeOutputPath;
+ }
+
+ public static void startPackagingFiles(final Project project, final List<VirtualFile> files,
+ final Artifact[] artifacts, final Runnable onFinished) {
+ ProgressManager.getInstance().run(new Task.Backgroundable(project, "Packaging Files") {
+ @Override
+ public void run(@NotNull ProgressIndicator indicator) {
+ try {
+ for (final VirtualFile file : files) {
+ indicator.checkCanceled();
+ new ReadAction() {
+ protected void run(final Result result) {
+ try {
+ packageFile(file, project, artifacts);
+ }
+ catch (IOException e) {
+ String message = CompilerBundle.message("message.tect.package.file.io.error", e.toString());
+ Notifications.Bus.notify(new Notification("Package File", "Cannot package file", message, NotificationType.ERROR));
+ }
+ }
+ }.execute();
+ }
+ }
+ finally {
+ ApplicationManager.getApplication().invokeLater(onFinished);
+ }
+ }
+ });
+ }
+
+ public static void packageFile(@NotNull VirtualFile file, @NotNull Project project, final Artifact[] artifacts) throws IOException {
+ LOG.debug("Start packaging file: " + file.getPath());
+ final Collection<Trinity<Artifact, PackagingElementPath, String>> items = ArtifactUtil.findContainingArtifactsWithOutputPaths(file, project, artifacts);
+ File ioFile = VfsUtil.virtualToIoFile(file);
+ for (Trinity<Artifact, PackagingElementPath, String> item : items) {
+ final Artifact artifact = item.getFirst();
+ final String outputPath = artifact.getOutputPath();
+ if (!StringUtil.isEmpty(outputPath)) {
+ PackageFileWorker worker = new PackageFileWorker(ioFile, item.getThird());
+ LOG.debug(" package to " + outputPath);
+ worker.packageFile(outputPath, item.getSecond().getParents());
+ }
+ }
+ }
+
+ private void packageFile(String outputPath, List<CompositePackagingElement<?>> parents) throws IOException {
+ List<CompositePackagingElement<?>> parentsList = new ArrayList<CompositePackagingElement<?>>(parents);
+ Collections.reverse(parentsList);
+ if (!parentsList.isEmpty() && parentsList.get(0) instanceof ArtifactRootElement) {
+ parentsList = parentsList.subList(1, parentsList.size());
+ }
+ copyFile(outputPath, parentsList);
+ }
+
+ private void copyFile(String outputPath, List<CompositePackagingElement<?>> parents) throws IOException {
+ if (parents.isEmpty()) {
+ final String fullOutputPath = DeploymentUtil.appendToPath(outputPath, myRelativeOutputPath);
+ LOG.debug(" copying to " + fullOutputPath);
+ FileUtil.copy(myFile, new File(FileUtil.toSystemDependentName(fullOutputPath)));
+ return;
+ }
+
+ final CompositePackagingElement<?> element = parents.get(0);
+ final String nextOutputPath = outputPath + "/" + element.getName();
+ final List<CompositePackagingElement<?>> parentsTrail = parents.subList(1, parents.size());
+ if (element instanceof ArchivePackagingElement) {
+ packFile(nextOutputPath, "", parentsTrail);
+ }
+ else {
+ copyFile(nextOutputPath, parentsTrail);
+ }
+ }
+
+ private void packFile(String archivePath, String pathInArchive, List<CompositePackagingElement<?>> parents) throws IOException {
+ final File archiveFile = new File(FileUtil.toSystemDependentName(archivePath));
+ if (parents.isEmpty()) {
+ LOG.debug(" adding to archive " + archivePath);
+ JBZipFile file = getOrCreateZipFile(archiveFile);
+ try {
+ final String fullPathInArchive = DeploymentUtil.trimForwardSlashes(DeploymentUtil.appendToPath(pathInArchive, myRelativeOutputPath));
+ final JBZipEntry entry = file.getOrCreateEntry(fullPathInArchive);
+ entry.setData(FileUtil.loadFileBytes(myFile));
+ }
+ finally {
+ file.close();
+ }
+ return;
+ }
+
+ final CompositePackagingElement<?> element = parents.get(0);
+ final String nextPathInArchive = DeploymentUtil.trimForwardSlashes(DeploymentUtil.appendToPath(pathInArchive, element.getName()));
+ final List<CompositePackagingElement<?>> parentsTrail = parents.subList(1, parents.size());
+ if (element instanceof ArchivePackagingElement) {
+ JBZipFile zipFile = getOrCreateZipFile(archiveFile);
+ try {
+ final JBZipEntry entry = zipFile.getOrCreateEntry(nextPathInArchive);
+ LOG.debug(" extracting to temp file: " + nextPathInArchive + " from " + archivePath);
+ final File tempFile = FileUtil.createTempFile("packageFile" + FileUtil.sanitizeFileName(nextPathInArchive), FileUtil.getExtension(PathUtil.getFileName(nextPathInArchive)));
+ if (entry.getSize() != -1) {
+ FileUtil.writeToFile(tempFile, entry.getData());
+ }
+ packFile(FileUtil.toSystemIndependentName(tempFile.getAbsolutePath()), "", parentsTrail);
+ entry.setData(FileUtil.loadFileBytes(tempFile));
+ FileUtil.delete(tempFile);
+ }
+ finally {
+ zipFile.close();
+ }
+ }
+ else {
+ packFile(archivePath, nextPathInArchive, parentsTrail);
+ }
+ }
+
+ private static JBZipFile getOrCreateZipFile(File archiveFile) throws IOException {
+ FileUtil.createIfDoesntExist(archiveFile);
+ return new JBZipFile(archiveFile);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/ui/properties/ArchiveElementPropertiesPanel.java b/java/compiler/impl/src/com/intellij/packaging/impl/ui/properties/ArchiveElementPropertiesPanel.java
new file mode 100644
index 0000000..dd2902d
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/ui/properties/ArchiveElementPropertiesPanel.java
@@ -0,0 +1,28 @@
+/*
+ * 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 com.intellij.packaging.impl.ui.properties;
+
+import com.intellij.packaging.impl.elements.ArchivePackagingElement;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+
+/**
+ * @author nik
+ */
+public class ArchiveElementPropertiesPanel extends ElementWithManifestPropertiesPanel<ArchivePackagingElement> {
+ public ArchiveElementPropertiesPanel(ArchivePackagingElement element, final ArtifactEditorContext context) {
+ super(element, context);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/ui/properties/DirectoryElementPropertiesPanel.java b/java/compiler/impl/src/com/intellij/packaging/impl/ui/properties/DirectoryElementPropertiesPanel.java
new file mode 100644
index 0000000..804d236
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/ui/properties/DirectoryElementPropertiesPanel.java
@@ -0,0 +1,28 @@
+/*
+ * 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 com.intellij.packaging.impl.ui.properties;
+
+import com.intellij.packaging.impl.elements.DirectoryPackagingElement;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+
+/**
+ * @author nik
+ */
+public class DirectoryElementPropertiesPanel extends ElementWithManifestPropertiesPanel<DirectoryPackagingElement> {
+ public DirectoryElementPropertiesPanel(DirectoryPackagingElement element, ArtifactEditorContext context) {
+ super(element, context);
+ }
+}
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/ui/properties/ElementWithManifestPropertiesPanel.form b/java/compiler/impl/src/com/intellij/packaging/impl/ui/properties/ElementWithManifestPropertiesPanel.form
new file mode 100644
index 0000000..f1e8930
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/ui/properties/ElementWithManifestPropertiesPanel.form
@@ -0,0 +1,136 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.packaging.impl.ui.properties.ElementWithManifestPropertiesPanel">
+ <grid id="27dc6" binding="myMainPanel" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="5" left="5" bottom="5" right="5"/>
+ <constraints>
+ <xy x="20" y="20" width="500" height="400"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <vspacer id="e5716">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
+ </constraints>
+ </vspacer>
+ <grid id="98ebe" binding="myPropertiesPanel" layout-manager="CardLayout" hgap="0" vgap="0">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <grid id="e398c" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <card name="properties"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="9a3d6" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="label.text.main.class"/>
+ </properties>
+ </component>
+ <component id="d87aa" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myMainClassField">
+ <constraints>
+ <grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="40600" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text resource-bundle="messages/CompilerBundle" key="label.text.class.path"/>
+ </properties>
+ </component>
+ <component id="a5121" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myClasspathField">
+ <constraints>
+ <grid row="3" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties/>
+ </component>
+ <component id="e12c" class="javax.swing.JLabel" binding="myTitleLabel">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="element name"/>
+ </properties>
+ </component>
+ <component id="446b5" class="javax.swing.JLabel">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <labelFor value="aca67"/>
+ <text value="Manifest &File:"/>
+ </properties>
+ </component>
+ <component id="aca67" class="javax.swing.JTextField" binding="myManifestPathField">
+ <constraints>
+ <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
+ <preferred-size width="150" height="-1"/>
+ </grid>
+ </constraints>
+ <properties>
+ <editable value="false"/>
+ </properties>
+ </component>
+ </children>
+ </grid>
+ <grid id="4473f" layout-manager="GridLayoutManager" row-count="3" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
+ <margin top="0" left="0" bottom="0" right="0"/>
+ <constraints>
+ <card name="buttons"/>
+ </constraints>
+ <properties/>
+ <border type="none"/>
+ <children>
+ <component id="66e62" class="javax.swing.JButton" binding="myCreateManifestButton" default-binding="true">
+ <constraints>
+ <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="&Create Manifest..."/>
+ </properties>
+ </component>
+ <hspacer id="970f7">
+ <constraints>
+ <grid row="1" column="2" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ </hspacer>
+ <vspacer id="bf917">
+ <constraints>
+ <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
+ </constraints>
+ </vspacer>
+ <component id="c6af2" class="javax.swing.JButton" binding="myUseExistingManifestButton" default-binding="true">
+ <constraints>
+ <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="&Use Existing Manifest..."/>
+ </properties>
+ </component>
+ <component id="a0a7b" class="javax.swing.JLabel" binding="myManifestNotFoundLabel">
+ <constraints>
+ <grid row="0" column="0" row-span="1" col-span="3" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
+ </constraints>
+ <properties>
+ <text value="META-INF/MANIFEST.MF file not found"/>
+ </properties>
+ </component>
+ </children>
+ </grid>
+ </children>
+ </grid>
+ </children>
+ </grid>
+</form>
diff --git a/java/compiler/impl/src/com/intellij/packaging/impl/ui/properties/ElementWithManifestPropertiesPanel.java b/java/compiler/impl/src/com/intellij/packaging/impl/ui/properties/ElementWithManifestPropertiesPanel.java
new file mode 100644
index 0000000..ad58d51
--- /dev/null
+++ b/java/compiler/impl/src/com/intellij/packaging/impl/ui/properties/ElementWithManifestPropertiesPanel.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2000-2012 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 com.intellij.packaging.impl.ui.properties;
+
+import com.intellij.openapi.fileChooser.FileChooser;
+import com.intellij.openapi.fileChooser.FileChooserDescriptor;
+import com.intellij.openapi.ui.Messages;
+import com.intellij.openapi.ui.TextFieldWithBrowseButton;
+import com.intellij.openapi.util.Comparing;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.impl.elements.CompositeElementWithManifest;
+import com.intellij.packaging.impl.elements.ManifestFileUtil;
+import com.intellij.packaging.ui.ArtifactEditorContext;
+import com.intellij.packaging.ui.ManifestFileConfiguration;
+import com.intellij.packaging.ui.PackagingElementPropertiesPanel;
+import com.intellij.ui.DocumentAdapter;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import javax.swing.event.DocumentEvent;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public abstract class ElementWithManifestPropertiesPanel<E extends CompositeElementWithManifest<?>> extends PackagingElementPropertiesPanel {
+ private final E myElement;
+ private final ArtifactEditorContext myContext;
+ private JPanel myMainPanel;
+ private TextFieldWithBrowseButton myMainClassField;
+ private TextFieldWithBrowseButton myClasspathField;
+ private JLabel myTitleLabel;
+ private JButton myCreateManifestButton;
+ private JButton myUseExistingManifestButton;
+ private JPanel myPropertiesPanel;
+ private JTextField myManifestPathField;
+ private JLabel myManifestNotFoundLabel;
+ private ManifestFileConfiguration myManifestFileConfiguration;
+
+ public ElementWithManifestPropertiesPanel(E element, final ArtifactEditorContext context) {
+ myElement = element;
+ myContext = context;
+
+ ManifestFileUtil.setupMainClassField(context.getProject(), myMainClassField);
+
+ myClasspathField.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ Messages.showTextAreaDialog(myClasspathField.getTextField(), "Edit Classpath", "classpath-attribute-editor");
+ }
+ });
+ myClasspathField.getTextField().getDocument().addDocumentListener(new DocumentAdapter() {
+ @Override
+ protected void textChanged(DocumentEvent e) {
+ myContext.queueValidation();
+ }
+ });
+ myUseExistingManifestButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ chooseManifest();
+ }
+ });
+ myCreateManifestButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ createManifest();
+ }
+ });
+ }
+
+ private void createManifest() {
+ final VirtualFile file = ManifestFileUtil.showDialogAndCreateManifest(myContext, myElement);
+ if (file == null) {
+ return;
+ }
+
+ ManifestFileUtil.addManifestFileToLayout(file.getPath(), myContext, myElement);
+ updateManifest();
+ myContext.getThisArtifactEditor().updateLayoutTree();
+ }
+
+ private void chooseManifest() {
+ final FileChooserDescriptor descriptor = new FileChooserDescriptor(true, false, false, false, false, false) {
+ @Override
+ public boolean isFileVisible(VirtualFile file, boolean showHiddenFiles) {
+ return super.isFileVisible(file, showHiddenFiles) && (file.isDirectory() ||
+ file.getName().equalsIgnoreCase(ManifestFileUtil.MANIFEST_FILE_NAME));
+ }
+ };
+ descriptor.setTitle("Specify Path to MANIFEST.MF file");
+ final VirtualFile file = FileChooser.chooseFile(descriptor, myContext.getProject(), null);
+ if (file == null) return;
+
+ ManifestFileUtil.addManifestFileToLayout(file.getPath(), myContext, myElement);
+ updateManifest();
+ myContext.getThisArtifactEditor().updateLayoutTree();
+ }
+
+ private void updateManifest() {
+ myManifestFileConfiguration = myContext.getManifestFile(myElement, myContext.getArtifactType());
+ final String card;
+ if (myManifestFileConfiguration != null) {
+ card = "properties";
+ myManifestPathField.setText(FileUtil.toSystemDependentName(myManifestFileConfiguration.getManifestFilePath()));
+ myMainClassField.setText(StringUtil.notNullize(myManifestFileConfiguration.getMainClass()));
+ myMainClassField.setEnabled(myManifestFileConfiguration.isWritable());
+ myClasspathField.setText(StringUtil.join(myManifestFileConfiguration.getClasspath(), " "));
+ myClasspathField.setEnabled(myManifestFileConfiguration.isWritable());
+ }
+ else {
+ card = "buttons";
+ myManifestPathField.setText("");
+ }
+ ((CardLayout)myPropertiesPanel.getLayout()).show(myPropertiesPanel, card);
+ }
+
+ public void reset() {
+ myTitleLabel.setText("'" + myElement.getName() + "' manifest properties:");
+ myManifestNotFoundLabel.setText("META-INF/MANIFEST.MF file not found in '" + myElement.getName() + "'");
+ updateManifest();
+ }
+
+ public boolean isModified() {
+ return myManifestFileConfiguration != null && (!myManifestFileConfiguration.getClasspath().equals(getConfiguredClasspath())
+ || !Comparing.equal(myManifestFileConfiguration.getMainClass(), getConfiguredMainClass())
+ || !Comparing.equal(myManifestFileConfiguration.getManifestFilePath(), getConfiguredManifestPath()));
+ }
+
+ @Nullable
+ private String getConfiguredManifestPath() {
+ final String path = myManifestPathField.getText();
+ return path.length() != 0 ? FileUtil.toSystemIndependentName(path) : null;
+ }
+
+ @Override
+ public void apply() {
+ if (myManifestFileConfiguration != null) {
+ myManifestFileConfiguration.setMainClass(getConfiguredMainClass());
+ myManifestFileConfiguration.setClasspath(getConfiguredClasspath());
+ myManifestFileConfiguration.setManifestFilePath(getConfiguredManifestPath());
+ }
+ }
+
+ private List<String> getConfiguredClasspath() {
+ return StringUtil.split(myClasspathField.getText(), " ");
+ }
+
+ @NotNull
+ public JComponent createComponent() {
+ return myMainPanel;
+ }
+
+ @Nullable
+ private String getConfiguredMainClass() {
+ final String className = myMainClassField.getText();
+ return className.length() != 0 ? className : null;
+ }
+
+}
diff --git a/java/compiler/impl/testSrc/com/intellij/compiler/BaseCompilerTestCase.java b/java/compiler/impl/testSrc/com/intellij/compiler/BaseCompilerTestCase.java
new file mode 100644
index 0000000..ebd49e4
--- /dev/null
+++ b/java/compiler/impl/testSrc/com/intellij/compiler/BaseCompilerTestCase.java
@@ -0,0 +1,443 @@
+package com.intellij.compiler;
+
+import com.intellij.compiler.impl.CompileDriver;
+import com.intellij.compiler.impl.ExitStatus;
+import com.intellij.ide.highlighter.ModuleFileType;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.application.WriteAction;
+import com.intellij.openapi.application.ex.PathManagerEx;
+import com.intellij.openapi.compiler.*;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.module.ModuleManager;
+import com.intellij.openapi.projectRoots.Sdk;
+import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl;
+import com.intellij.openapi.roots.CompilerModuleExtension;
+import com.intellij.openapi.roots.CompilerProjectExtension;
+import com.intellij.openapi.roots.ModuleRootModificationUtil;
+import com.intellij.openapi.util.Ref;
+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.openapi.vfs.newvfs.NewVirtualFile;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.artifacts.ArtifactManager;
+import com.intellij.packaging.impl.compiler.ArtifactCompileScope;
+import com.intellij.testFramework.ModuleTestCase;
+import com.intellij.testFramework.PlatformTestCase;
+import com.intellij.testFramework.PsiTestUtil;
+import com.intellij.testFramework.VfsTestUtil;
+import com.intellij.util.ParameterizedRunnable;
+import com.intellij.util.concurrency.Semaphore;
+import com.intellij.util.io.TestFileSystemBuilder;
+import com.intellij.util.ui.UIUtil;
+import gnu.trove.THashSet;
+import junit.framework.Assert;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.jps.util.JpsPathUtil;
+
+import javax.swing.*;
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author nik
+ */
+public abstract class BaseCompilerTestCase extends ModuleTestCase {
+ protected boolean useExternalCompiler() {
+ return false;
+ }
+
+ @Override
+ protected void setUpModule() {
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ if (useExternalCompiler()) {
+ CompilerTestUtil.enableExternalCompiler(myProject);
+ }
+ else {
+ CompilerTestUtil.disableExternalCompiler(myProject);
+ }
+ }
+
+ @Override
+ protected Sdk getTestProjectJdk() {
+ if (useExternalCompiler()) {
+ return JavaAwareProjectJdkTableImpl.getInstanceEx().getInternalJdk();
+ }
+ return super.getTestProjectJdk();
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ for (Artifact artifact : getArtifactManager().getArtifacts()) {
+ final String outputPath = artifact.getOutputPath();
+ if (!StringUtil.isEmpty(outputPath)) {
+ FileUtil.delete(new File(FileUtil.toSystemDependentName(outputPath)));
+ }
+ }
+ if (useExternalCompiler()) {
+ CompilerTestUtil.disableExternalCompiler(myProject);
+ }
+
+ super.tearDown();
+ }
+
+ protected ArtifactManager getArtifactManager() {
+ return ArtifactManager.getInstance(myProject);
+ }
+
+ protected String getProjectBasePath() {
+ return getBaseDir().getPath();
+ }
+
+ protected VirtualFile getBaseDir() {
+ final VirtualFile baseDir = myProject.getBaseDir();
+ Assert.assertNotNull(baseDir);
+ return baseDir;
+ }
+
+ protected void copyToProject(String relativePath) {
+ File dir = PathManagerEx.findFileUnderProjectHome(relativePath, getClass());
+ final File target = new File(FileUtil.toSystemDependentName(getProjectBasePath()));
+ try {
+ FileUtil.copyDir(dir, target);
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ new WriteAction() {
+ protected void run(final Result result) {
+ VirtualFile virtualDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(target);
+ assertNotNull(target.getAbsolutePath() + " not found", virtualDir);
+ virtualDir.refresh(false, true);
+ }
+ }.execute();
+ }
+
+ protected Module addModule(final String moduleName, final @Nullable VirtualFile sourceRoot) {
+ return new WriteAction<Module>() {
+ @Override
+ protected void run(final Result<Module> result) {
+ final Module module = createModule(moduleName);
+ if (sourceRoot != null) {
+ PsiTestUtil.addSourceContentToRoots(module, sourceRoot);
+ }
+ ModuleRootModificationUtil.setModuleSdk(module, getTestProjectJdk());
+ result.setResult(module);
+ }
+ }.execute().getResultObject();
+ }
+
+ protected VirtualFile createFile(final String path) {
+ return createFile(path, "");
+ }
+
+ protected VirtualFile createFile(final String path, final String text) {
+ return VfsTestUtil.createFile(getBaseDir(), path, text);
+ }
+
+ protected CompilationLog make(final Artifact... artifacts) {
+ final CompileScope scope = ArtifactCompileScope.createArtifactsScope(myProject, Arrays.asList(artifacts));
+ return make(scope, CompilerFilter.ALL);
+ }
+
+ protected CompilationLog recompile(final Artifact... artifacts) {
+ final CompileScope scope = ArtifactCompileScope.createArtifactsScope(myProject, Arrays.asList(artifacts));
+ return compile(scope, CompilerFilter.ALL, true);
+ }
+
+ protected CompilationLog make(Module... modules) {
+ return make(getCompilerManager().createModulesCompileScope(modules, false), CompilerFilter.ALL);
+ }
+
+ protected CompilationLog recompile(Module... modules) {
+ return compile(getCompilerManager().createModulesCompileScope(modules, false), CompilerFilter.ALL, true);
+ }
+
+ protected CompilerManager getCompilerManager() {
+ return CompilerManager.getInstance(myProject);
+ }
+
+ protected void assertModulesUpToDate() {
+ boolean upToDate = getCompilerManager().isUpToDate(getCompilerManager().createProjectCompileScope(myProject));
+ assertTrue("Modules are not up to date", upToDate);
+ }
+
+ protected CompilationLog compile(boolean force, VirtualFile... files) {
+ return compile(getCompilerManager().createFilesCompileScope(files), CompilerFilter.ALL, force);
+ }
+
+ protected CompilationLog make(final CompileScope scope, final CompilerFilter filter) {
+ return compile(scope, filter, false);
+ }
+
+ protected CompilationLog compile(final CompileScope scope, final CompilerFilter filter, final boolean forceCompile) {
+ return compile(scope, filter, forceCompile, false);
+ }
+
+ protected CompilationLog compile(final CompileScope scope, final CompilerFilter filter, final boolean forceCompile,
+ final boolean errorsExpected) {
+ return compile(errorsExpected, new ParameterizedRunnable<CompileStatusNotification>() {
+ @Override
+ public void run(CompileStatusNotification callback) {
+ final CompilerManager compilerManager = getCompilerManager();
+ if (forceCompile) {
+ Assert.assertSame("Only 'ALL' filter is supported for forced compilation", CompilerFilter.ALL, filter);
+ compilerManager.compile(scope, callback);
+ }
+ else {
+ compilerManager.make(scope, filter, callback);
+ }
+ }
+ });
+ }
+
+ protected CompilationLog rebuild() {
+ return compile(false, new ParameterizedRunnable<CompileStatusNotification>() {
+ @Override
+ public void run(CompileStatusNotification compileStatusNotification) {
+ getCompilerManager().rebuild(compileStatusNotification);
+ }
+ });
+ }
+
+ protected CompilationLog compile(final boolean errorsExpected, final ParameterizedRunnable<CompileStatusNotification> action) {
+ CompilationLog log = compile(action);
+ if (errorsExpected && log.myErrors.length == 0) {
+ Assert.fail("compilation finished without errors");
+ }
+ else if (!errorsExpected && log.myErrors.length > 0) {
+ Assert.fail("compilation finished with errors: " + Arrays.toString(log.myErrors));
+ }
+ return log;
+ }
+
+ private CompilationLog compile(final ParameterizedRunnable<CompileStatusNotification> action) {
+ final Ref<CompilationLog> result = Ref.create(null);
+ final Semaphore semaphore = new Semaphore();
+ semaphore.down();
+ UIUtil.invokeAndWaitIfNeeded(new Runnable() {
+ @Override
+ public void run() {
+
+ CompilerManagerImpl.testSetup();
+ final CompileStatusNotification callback = new CompileStatusNotification() {
+ @Override
+ public void finished(boolean aborted, int errors, int warnings, CompileContext compileContext) {
+ try {
+ if (aborted) {
+ Assert.fail("compilation aborted");
+ }
+ ExitStatus status = CompileDriver.getExternalBuildExitStatus(compileContext);
+ result.set(new CompilationLog(status == ExitStatus.UP_TO_DATE,
+ CompilerManagerImpl.getPathsToRecompile(), CompilerManagerImpl.getPathsToDelete(),
+ compileContext.getMessages(CompilerMessageCategory.ERROR),
+ compileContext.getMessages(CompilerMessageCategory.WARNING)));
+ }
+ finally {
+ semaphore.up();
+ }
+ }
+ };
+ if (useExternalCompiler()) {
+ myProject.save();
+ CompilerTestUtil.saveSdkTable();
+ CompilerTestUtil.scanSourceRootsToRecompile(myProject);
+ }
+ action.run(callback);
+ }
+ });
+
+ final long start = System.currentTimeMillis();
+ while (!semaphore.waitFor(10)) {
+ if (System.currentTimeMillis() - start > 60 * 1000) {
+ throw new RuntimeException("timeout");
+ }
+ if (SwingUtilities.isEventDispatchThread()) {
+ UIUtil.dispatchAllInvocationEvents();
+ }
+ }
+ if (SwingUtilities.isEventDispatchThread()) {
+ UIUtil.dispatchAllInvocationEvents();
+ }
+
+ return result.get();
+ }
+
+ private Set<String> getRelativePaths(String[] paths) {
+ final Set<String> set = new THashSet<String>();
+ final String basePath = myProject.getBaseDir().getPath();
+ for (String path : paths) {
+ set.add(StringUtil.trimStart(StringUtil.trimStart(FileUtil.toSystemIndependentName(path), basePath), "/"));
+ }
+ return set;
+ }
+
+ protected void changeFile(VirtualFile file) {
+ changeFile(file, null);
+ }
+
+ protected void changeFile(VirtualFile file, final String newText) {
+ try {
+ if (newText != null) {
+ VfsUtil.saveText(file, newText);
+ }
+ ((NewVirtualFile)file).setTimeStamp(file.getTimeStamp() + 10);
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected void deleteFile(final VirtualFile file) {
+ new WriteAction() {
+ @Override
+ protected void run(final Result result) {
+ try {
+ file.delete(this);
+ }
+ catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ }
+ }.execute();
+ }
+
+ @Override
+ protected void setUpProject() throws Exception {
+ super.setUpProject();
+ final String baseUrl = myProject.getBaseDir().getUrl();
+ CompilerProjectExtension.getInstance(myProject).setCompilerOutputUrl(baseUrl + "/out");
+ }
+
+ @Override
+ protected File getIprFile() throws IOException {
+ File iprFile = super.getIprFile();
+ if (useExternalCompiler()) {
+ FileUtil.delete(iprFile);
+ }
+ return iprFile;
+ }
+
+ @Override
+ protected Module doCreateRealModule(String moduleName) {
+ if (useExternalCompiler()) {
+ //todo[nik] reuse code from PlatformTestCase
+ final VirtualFile baseDir = myProject.getBaseDir();
+ Assert.assertNotNull(baseDir);
+ final File moduleFile = new File(baseDir.getPath().replace('/', File.separatorChar), moduleName + ModuleFileType.DOT_DEFAULT_EXTENSION);
+ PlatformTestCase.myFilesToDelete.add(moduleFile);
+ return new WriteAction<Module>() {
+ @Override
+ protected void run(Result<Module> result) throws Throwable {
+ Module module = ModuleManager.getInstance(myProject)
+ .newModule(FileUtil.toSystemIndependentName(moduleFile.getAbsolutePath()), getModuleType().getId());
+ module.getModuleFile();
+ result.setResult(module);
+ }
+ }.execute().getResultObject();
+ }
+ return super.doCreateRealModule(moduleName);
+ }
+
+ @Override
+ protected boolean isRunInWriteAction() {
+ return false;
+ }
+
+ protected static void assertOutput(Module module, TestFileSystemBuilder item) {
+ File outputDir = getOutputDir(module);
+ Assert.assertTrue("Output directory " + outputDir.getAbsolutePath() + " doesn't exist", outputDir.exists());
+ item.build().assertDirectoryEqual(outputDir);
+ }
+
+ protected static void assertNoOutput(Module module) {
+ File dir = getOutputDir(module);
+ Assert.assertFalse("Output directory " + dir.getAbsolutePath() + " does exist", dir.exists());
+ }
+
+ protected static File getOutputDir(Module module) {
+ CompilerModuleExtension extension = CompilerModuleExtension.getInstance(module);
+ Assert.assertNotNull(extension);
+ String outputUrl = extension.getCompilerOutputUrl();
+ Assert.assertNotNull("Output directory for module '" + module.getName() + "' isn't specified", outputUrl);
+ return JpsPathUtil.urlToFile(outputUrl);
+ }
+
+ protected class CompilationLog {
+ private final Set<String> myRecompiledPaths;
+ private final Set<String> myDeletedPaths;
+ private final boolean myExternalBuildUpToDate;
+ private final CompilerMessage[] myErrors;
+ private final CompilerMessage[] myWarnings;
+
+ public CompilationLog(boolean externalBuildUpToDate, String[] recompiledPaths, String[] deletedPaths, CompilerMessage[] errors, CompilerMessage[] warnings) {
+ myExternalBuildUpToDate = externalBuildUpToDate;
+ myErrors = errors;
+ myWarnings = warnings;
+ myRecompiledPaths = getRelativePaths(recompiledPaths);
+ myDeletedPaths = getRelativePaths(deletedPaths);
+ }
+
+ public void assertUpToDate() {
+ if (useExternalCompiler()) {
+ assertTrue(myExternalBuildUpToDate);
+ }
+ else {
+ checkRecompiled();
+ checkDeleted();
+ }
+ }
+
+ public void assertRecompiled(String... expected) {
+ checkRecompiled(expected);
+ checkDeleted();
+ }
+
+ public void assertDeleted(String... expected) {
+ checkRecompiled();
+ checkDeleted(expected);
+ }
+
+ public void assertRecompiledAndDeleted(String[] recompiled, String... deleted) {
+ checkRecompiled(recompiled);
+ checkDeleted(deleted);
+ }
+
+ private void checkRecompiled(String... expected) {
+ assertSet("recompiled", myRecompiledPaths, expected);
+ }
+
+ private void checkDeleted(String... expected) {
+ assertSet("deleted", myDeletedPaths, expected);
+ }
+
+ public CompilerMessage[] getErrors() {
+ return myErrors;
+ }
+
+ public CompilerMessage[] getWarnings() {
+ return myWarnings;
+ }
+
+ private void assertSet(String name, Set<String> actual, String[] expected) {
+ if (useExternalCompiler()) return;
+ for (String path : expected) {
+ if (!actual.remove(path)) {
+ Assert.fail("'" + path + "' is not " + name + ". " + name + ": " + new HashSet<String>(actual));
+ }
+ }
+ if (!actual.isEmpty()) {
+ Assert.fail("'" + actual.iterator().next() + "' must not be " + name);
+ }
+ }
+ }
+}
diff --git a/java/compiler/impl/testSrc/com/intellij/compiler/CompilerTestUtil.java b/java/compiler/impl/testSrc/com/intellij/compiler/CompilerTestUtil.java
new file mode 100644
index 0000000..69cb63a
--- /dev/null
+++ b/java/compiler/impl/testSrc/com/intellij/compiler/CompilerTestUtil.java
@@ -0,0 +1,87 @@
+package com.intellij.compiler;
+
+import com.intellij.compiler.impl.TranslatingCompilerFilesMonitor;
+import com.intellij.compiler.impl.javaCompiler.javac.JavacConfiguration;
+import com.intellij.compiler.server.BuildManager;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.application.WriteAction;
+import com.intellij.openapi.application.ex.ApplicationManagerEx;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.projectRoots.ProjectJdkTable;
+import com.intellij.openapi.projectRoots.impl.JavaAwareProjectJdkTableImpl;
+import com.intellij.openapi.projectRoots.impl.ProjectJdkTableImpl;
+import com.intellij.openapi.roots.ProjectRootManager;
+import com.intellij.openapi.util.JDOMUtil;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.util.SystemProperties;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jetbrains.jps.model.java.compiler.JpsJavaCompilerOptions;
+import org.jetbrains.jps.model.serialization.JDomSerializationUtil;
+import org.jetbrains.jps.model.serialization.JpsGlobalLoader;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author nik
+ */
+public class CompilerTestUtil {
+ private CompilerTestUtil() {
+ }
+
+ public static void setupJavacForTests(Project project) {
+ CompilerConfigurationImpl compilerConfiguration = (CompilerConfigurationImpl)CompilerConfiguration.getInstance(project);
+ compilerConfiguration.projectOpened();
+ compilerConfiguration.setDefaultCompiler(compilerConfiguration.getJavacCompiler());
+
+ JpsJavaCompilerOptions javacSettings = JavacConfiguration.getOptions(project, JavacConfiguration.class);
+ javacSettings.setTestsUseExternalCompiler(true);
+ }
+
+ public static void scanSourceRootsToRecompile(Project project) {
+ // need this to emulate project opening
+ final List<VirtualFile> roots = Arrays.asList(ProjectRootManager.getInstance(project).getContentSourceRoots());
+ TranslatingCompilerFilesMonitor.getInstance().scanSourceContent(new TranslatingCompilerFilesMonitor.ProjectRef(project), roots, roots.size(), true);
+ }
+
+ public static void saveSdkTable() {
+ try {
+ ProjectJdkTableImpl table = (ProjectJdkTableImpl)ProjectJdkTable.getInstance();
+ File sdkFile = table.getExportFiles()[0];
+ FileUtil.createParentDirs(sdkFile);
+ Element root = new Element("application");
+ root.addContent(JDomSerializationUtil.createComponentElement(JpsGlobalLoader.SDK_TABLE_COMPONENT_NAME).addContent(table.getState().cloneContent()));
+ JDOMUtil.writeDocument(new Document(root), sdkFile, SystemProperties.getLineSeparator());
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void enableExternalCompiler(final Project project) {
+ new WriteAction() {
+ protected void run(final Result result) {
+ CompilerWorkspaceConfiguration.getInstance(project).USE_COMPILE_SERVER = true;
+ ApplicationManagerEx.getApplicationEx().doNotSave(false);
+ JavaAwareProjectJdkTableImpl table = JavaAwareProjectJdkTableImpl.getInstanceEx();
+ table.addJdk(table.getInternalJdk());
+ }
+ }.execute();
+ }
+
+ public static void disableExternalCompiler(final Project project) {
+ new WriteAction() {
+ protected void run(final Result result) {
+ CompilerWorkspaceConfiguration.getInstance(project).USE_COMPILE_SERVER = false;
+ ApplicationManagerEx.getApplicationEx().doNotSave(true);
+ JavaAwareProjectJdkTableImpl table = JavaAwareProjectJdkTableImpl.getInstanceEx();
+ table.removeJdk(table.getInternalJdk());
+ BuildManager.getInstance().stopWatchingProject(project);
+ }
+ }.execute();
+ }
+}
diff --git a/java/compiler/impl/testSrc/com/intellij/compiler/artifacts/ArtifactCompilerTestCase.java b/java/compiler/impl/testSrc/com/intellij/compiler/artifacts/ArtifactCompilerTestCase.java
new file mode 100644
index 0000000..ad03189
--- /dev/null
+++ b/java/compiler/impl/testSrc/com/intellij/compiler/artifacts/ArtifactCompilerTestCase.java
@@ -0,0 +1,116 @@
+package com.intellij.compiler.artifacts;
+
+import com.intellij.compiler.BaseCompilerTestCase;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.roots.DependencyScope;
+import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.util.io.FileUtil;
+import com.intellij.openapi.vfs.JarFileSystem;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VfsUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.artifacts.ArtifactType;
+import com.intellij.packaging.artifacts.ModifiableArtifactModel;
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.packaging.impl.artifacts.PlainArtifactType;
+import com.intellij.util.io.TestFileSystemBuilder;
+import com.intellij.util.io.TestFileSystemItem;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.io.IOException;
+
+import static com.intellij.compiler.artifacts.ArtifactsTestCase.commitModel;
+
+/**
+ * @author nik
+ */
+public abstract class ArtifactCompilerTestCase extends BaseCompilerTestCase {
+
+ protected void deleteArtifact(final Artifact artifact) {
+ final ModifiableArtifactModel model = getArtifactManager().createModifiableModel();
+ model.removeArtifact(artifact);
+ commitModel(model);
+ }
+
+ protected Artifact addArtifact(TestPackagingElementBuilder builder) {
+ return addArtifact("a", builder);
+ }
+
+ protected Artifact addArtifact(final String name, TestPackagingElementBuilder builder) {
+ return addArtifact(name, builder.build());
+ }
+
+ protected Artifact addArtifact(String name, final CompositePackagingElement<?> root) {
+ return addArtifact(name, PlainArtifactType.getInstance(), root);
+ }
+
+ protected Artifact addArtifact(final String name, final ArtifactType type, final CompositePackagingElement<?> root) {
+ return getArtifactManager().addArtifact(name, type, root);
+ }
+
+ protected Library addProjectLibrary(final @Nullable Module module, final String name, final VirtualFile... jars) {
+ return addProjectLibrary(module, name, DependencyScope.COMPILE, jars);
+ }
+
+ protected Library addProjectLibrary(final @Nullable Module module, final String name, final DependencyScope scope,
+ final VirtualFile... jars) {
+ return PackagingElementsTestCase.addProjectLibrary(myProject, module, name, scope, jars);
+ }
+
+ protected TestPackagingElementBuilder root() {
+ return TestPackagingElementBuilder.root(myProject);
+ }
+
+ protected TestPackagingElementBuilder archive(String name) {
+ return TestPackagingElementBuilder.archive(myProject, name);
+ }
+
+ protected CompilationLog compileProject() {
+ return make(getArtifactManager().getArtifacts());
+ }
+
+ protected void changeFileInJar(String jarPath, String pathInJar) throws Exception {
+ final VirtualFile jarFile = LocalFileSystem.getInstance().findFileByPath(jarPath);
+ assertNotNull(jarFile);
+ final VirtualFile jarRoot = JarFileSystem.getInstance().getJarRootForLocalFile(jarFile);
+ assertNotNull(jarRoot);
+ VirtualFile jarEntry = jarRoot.findFileByRelativePath(pathInJar);
+ assertNotNull(jarEntry);
+ assertNotNull(jarFile);
+ changeFile(jarFile);
+ jarFile.refresh(false, false);
+
+ jarEntry = jarRoot.findFileByRelativePath(pathInJar);
+ assertNotNull(jarEntry);
+ }
+
+ protected static TestFileSystemBuilder fs() {
+ return TestFileSystemItem.fs();
+ }
+
+ public static void assertNoOutput(Artifact artifact) {
+ final String outputPath = artifact.getOutputPath();
+ assertNotNull("output path not specified for " + artifact.getName(), outputPath);
+ assertFalse(new File(FileUtil.toSystemDependentName(outputPath)).exists());
+ }
+
+ public static void assertEmptyOutput(Artifact a1) throws IOException {
+ assertOutput(a1, ArtifactCompilerTestCase.fs());
+ }
+
+ public static void assertOutput(Artifact artifact, TestFileSystemBuilder item) {
+ final VirtualFile outputFile = getOutputDir(artifact);
+ outputFile.refresh(false, true);
+ item.build().assertDirectoryEqual(VfsUtil.virtualToIoFile(outputFile));
+ }
+
+ protected static VirtualFile getOutputDir(Artifact artifact) {
+ final String output = artifact.getOutputPath();
+ assertNotNull("output path not specified for " + artifact.getName(), output);
+ final VirtualFile outputFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(output);
+ assertNotNull("output file not found " + output, outputFile);
+ return outputFile;
+ }
+}
diff --git a/java/compiler/impl/testSrc/com/intellij/compiler/artifacts/ArtifactsTestCase.java b/java/compiler/impl/testSrc/com/intellij/compiler/artifacts/ArtifactsTestCase.java
new file mode 100644
index 0000000..9c60d0c
--- /dev/null
+++ b/java/compiler/impl/testSrc/com/intellij/compiler/artifacts/ArtifactsTestCase.java
@@ -0,0 +1,291 @@
+package com.intellij.compiler.artifacts;
+
+import com.intellij.facet.Facet;
+import com.intellij.facet.impl.DefaultFacetsProvider;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.application.WriteAction;
+import com.intellij.openapi.module.ModifiableModuleModel;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.ModifiableRootModel;
+import com.intellij.openapi.roots.ModuleRootManager;
+import com.intellij.openapi.roots.ModuleRootModificationUtil;
+import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.roots.ui.configuration.DefaultModulesProvider;
+import com.intellij.openapi.roots.ui.configuration.FacetsProvider;
+import com.intellij.openapi.roots.ui.configuration.ModulesProvider;
+import com.intellij.openapi.roots.ui.configuration.artifacts.*;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.*;
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.packaging.elements.ManifestFileProvider;
+import com.intellij.packaging.elements.PackagingElementResolvingContext;
+import com.intellij.packaging.impl.artifacts.PlainArtifactType;
+import com.intellij.packaging.impl.elements.ManifestFileUtil;
+import com.intellij.packaging.ui.ArtifactEditor;
+import com.intellij.packaging.ui.ManifestFileConfiguration;
+import com.intellij.testFramework.IdeaTestCase;
+import com.intellij.testFramework.PsiTestUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * @author nik
+ */
+public abstract class ArtifactsTestCase extends IdeaTestCase {
+ protected boolean mySetupModule;
+
+ protected ArtifactManager getArtifactManager() {
+ return ArtifactManager.getInstance(myProject);
+ }
+
+ @Override
+ protected void setUpModule() {
+ if (mySetupModule) {
+ super.setUpModule();
+ }
+ }
+
+ protected void deleteArtifact(final Artifact artifact) {
+ final ModifiableArtifactModel model = getArtifactManager().createModifiableModel();
+ model.removeArtifact(artifact);
+ commitModel(model);
+ }
+
+ protected static void commitModel(final ModifiableArtifactModel model) {
+ new WriteAction() {
+ @Override
+ protected void run(final Result result) {
+ model.commit();
+ }
+ }.execute();
+ }
+
+ protected Artifact rename(Artifact artifact, String newName) {
+ final ModifiableArtifactModel model = getArtifactManager().createModifiableModel();
+ model.getOrCreateModifiableArtifact(artifact).setName(newName);
+ commitModel(model);
+ return artifact;
+ }
+
+ protected Artifact addArtifact(String name) {
+ return addArtifact(name, null);
+ }
+
+ protected Artifact addArtifact(String name, final CompositePackagingElement<?> root) {
+ return addArtifact(name, PlainArtifactType.getInstance(), root);
+ }
+
+ protected Artifact addArtifact(final String name, final ArtifactType type, final CompositePackagingElement<?> root) {
+ return getArtifactManager().addArtifact(name, type, root);
+ }
+
+ protected PackagingElementResolvingContext getContext() {
+ return ArtifactManager.getInstance(myProject).getResolvingContext();
+ }
+
+ public static void renameFile(final VirtualFile file, final String newName) {
+ new WriteAction() {
+ @Override
+ protected void run(final Result result) {
+ try {
+ file.rename(IdeaTestCase.class, newName);
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ }.execute();
+ }
+
+ protected Module addModule(final String moduleName, final @Nullable VirtualFile sourceRoot) {
+ return new WriteAction<Module>() {
+ @Override
+ protected void run(final Result<Module> result) {
+ final Module module = createModule(moduleName);
+ if (sourceRoot != null) {
+ PsiTestUtil.addSourceContentToRoots(module, sourceRoot);
+ }
+ ModuleRootModificationUtil.setModuleSdk(module, getTestProjectJdk());
+ result.setResult(module);
+ }
+ }.execute().getResultObject();
+ }
+
+ public static class MockPackagingEditorContext extends ArtifactEditorContextImpl {
+ public MockPackagingEditorContext(ArtifactsStructureConfigurableContext parent, final ArtifactEditorEx editor) {
+ super(parent, editor);
+ }
+
+ @Override
+ public void selectArtifact(@NotNull Artifact artifact) {
+ }
+
+ @Override
+ public void selectFacet(@NotNull Facet<?> facet) {
+ }
+
+ @Override
+ public void selectModule(@NotNull Module module) {
+ }
+
+ @Override
+ public void selectLibrary(@NotNull Library library) {
+ }
+
+ @Override
+ public void queueValidation() {
+ }
+
+ @Override
+ public List<Artifact> chooseArtifacts(List<? extends Artifact> artifacts, String title) {
+ return new ArrayList<Artifact>(artifacts);
+ }
+
+ @Override
+ public List<Module> chooseModules(List<Module> modules, String title) {
+ return modules;
+ }
+
+ @Override
+ public List<Library> chooseLibraries(String title) {
+ return Collections.emptyList();
+ }
+ }
+
+ public class MockArtifactsStructureConfigurableContext implements ArtifactsStructureConfigurableContext {
+ private ModifiableArtifactModel myModifiableModel;
+ private final Map<Module, ModifiableRootModel> myModifiableRootModels = new HashMap<Module, ModifiableRootModel>();
+ private final Map<CompositePackagingElement<?>, ManifestFileConfiguration> myManifestFiles =
+ new HashMap<CompositePackagingElement<?>, ManifestFileConfiguration>();
+ private final ArtifactEditorManifestFileProvider myManifestFileProvider = new ArtifactEditorManifestFileProvider(this);
+
+ @Override
+ @NotNull
+ public ModifiableArtifactModel getOrCreateModifiableArtifactModel() {
+ if (myModifiableModel == null) {
+ myModifiableModel = ArtifactManager.getInstance(myProject).createModifiableModel();
+ }
+ return myModifiableModel;
+ }
+
+ @Override
+ public ModifiableModuleModel getModifiableModuleModel() {
+ return null;
+ }
+
+ @Override
+ @NotNull
+ public ModifiableRootModel getOrCreateModifiableRootModel(@NotNull Module module) {
+ ModifiableRootModel model = myModifiableRootModels.get(module);
+ if (model == null) {
+ model = ModuleRootManager.getInstance(module).getModifiableModel();
+ myModifiableRootModels.put(module, model);
+ }
+ return model;
+ }
+
+ @Override
+ public ArtifactEditorSettings getDefaultSettings() {
+ return new ArtifactEditorSettings();
+ }
+
+ @Override
+ @NotNull
+ public Project getProject() {
+ return myProject;
+ }
+
+ @Override
+ @NotNull
+ public ArtifactModel getArtifactModel() {
+ if (myModifiableModel != null) {
+ return myModifiableModel;
+ }
+ return ArtifactManager.getInstance(myProject);
+ }
+
+ public void commitModel() {
+ if (myModifiableModel != null) {
+ myModifiableModel.commit();
+ }
+ }
+
+ @Override
+ @NotNull
+ public ModulesProvider getModulesProvider() {
+ return new DefaultModulesProvider(myProject);
+ }
+
+ @Override
+ @NotNull
+ public FacetsProvider getFacetsProvider() {
+ return DefaultFacetsProvider.INSTANCE;
+ }
+
+ @Override
+ public Library findLibrary(@NotNull String level, @NotNull String libraryName) {
+ return ArtifactManager.getInstance(myProject).getResolvingContext().findLibrary(level, libraryName);
+ }
+
+ @NotNull
+ @Override
+ public ManifestFileProvider getManifestFileProvider() {
+ return myManifestFileProvider;
+ }
+
+ @Override
+ public ManifestFileConfiguration getManifestFile(CompositePackagingElement<?> element, ArtifactType artifactType) {
+ final VirtualFile manifestFile = ManifestFileUtil.findManifestFile(element, this, PlainArtifactType.getInstance());
+ if (manifestFile == null) {
+ return null;
+ }
+
+ ManifestFileConfiguration configuration = myManifestFiles.get(element);
+ if (configuration == null) {
+ configuration = ManifestFileUtil.createManifestFileConfiguration(manifestFile);
+ myManifestFiles.put(element, configuration);
+ }
+ return configuration;
+ }
+
+ @Override
+ public CompositePackagingElement<?> getRootElement(@NotNull Artifact artifact) {
+ return artifact.getRootElement();
+ }
+
+ @Override
+ public void editLayout(@NotNull Artifact artifact, Runnable action) {
+ final ModifiableArtifact modifiableArtifact = getOrCreateModifiableArtifactModel().getOrCreateModifiableArtifact(artifact);
+ modifiableArtifact.setRootElement(artifact.getRootElement());
+ action.run();
+ }
+
+ @Override
+ public ArtifactEditor getOrCreateEditor(Artifact artifact) {
+ throw new UnsupportedOperationException("'getOrCreateEditor' not implemented in " + getClass().getName());
+ }
+
+ @Override
+ @NotNull
+ public Artifact getOriginalArtifact(@NotNull Artifact artifact) {
+ if (myModifiableModel != null) {
+ return myModifiableModel.getOriginalArtifact(artifact);
+ }
+ return artifact;
+ }
+
+ @Override
+ public void queueValidation(Artifact artifact) {
+ }
+
+ @Override
+ @NotNull
+ public ArtifactProjectStructureElement getOrCreateArtifactElement(@NotNull Artifact artifact) {
+ throw new UnsupportedOperationException("'getOrCreateArtifactElement' not implemented in " + getClass().getName());
+ }
+ }
+}
diff --git a/java/compiler/impl/testSrc/com/intellij/compiler/artifacts/ArtifactsTestUtil.java b/java/compiler/impl/testSrc/com/intellij/compiler/artifacts/ArtifactsTestUtil.java
new file mode 100644
index 0000000..3eb811f
--- /dev/null
+++ b/java/compiler/impl/testSrc/com/intellij/compiler/artifacts/ArtifactsTestUtil.java
@@ -0,0 +1,133 @@
+package com.intellij.compiler.artifacts;
+
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.application.WriteAction;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.artifacts.ArtifactManager;
+import com.intellij.packaging.artifacts.ArtifactType;
+import com.intellij.packaging.artifacts.ModifiableArtifactModel;
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.packaging.elements.PackagingElement;
+import com.intellij.packaging.elements.PackagingElementFactory;
+import com.intellij.packaging.elements.PackagingElementResolvingContext;
+import com.intellij.packaging.impl.elements.ArchivePackagingElement;
+import com.intellij.packaging.impl.elements.DirectoryPackagingElement;
+import com.intellij.packaging.impl.elements.ManifestFileUtil;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.jar.Attributes;
+import java.util.jar.Manifest;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertNotNull;
+
+/**
+ * @author nik
+ */
+public class ArtifactsTestUtil {
+ public static String printToString(PackagingElement element, int level) {
+ StringBuilder builder = new StringBuilder(StringUtil.repeatSymbol(' ', level));
+ if (element instanceof ArchivePackagingElement) {
+ builder.append(((ArchivePackagingElement)element).getArchiveFileName());
+ }
+ else if (element instanceof DirectoryPackagingElement) {
+ builder.append(((DirectoryPackagingElement)element).getDirectoryName()).append("/");
+ }
+ else {
+ builder.append(element.toString());
+ }
+ builder.append("\n");
+ if (element instanceof CompositePackagingElement) {
+ for (PackagingElement<?> child : ((CompositePackagingElement<?>)element).getChildren()) {
+ builder.append(printToString(child, level + 1));
+ }
+ }
+ return builder.toString();
+ }
+
+ public static void assertLayout(PackagingElement element, String expected) {
+ assertEquals(adjustMultiLine(expected), printToString(element, 0));
+ }
+
+ private static String adjustMultiLine(String expected) {
+ final List<String> strings = StringUtil.split(StringUtil.trimStart(expected, "\n"), "\n");
+ int min = Integer.MAX_VALUE;
+ for (String s : strings) {
+ int k = 0;
+ while (k < s.length() && s.charAt(k) == ' ') {
+ k++;
+ }
+ min = Math.min(min, k);
+ }
+ List<String> lines = new ArrayList<String>();
+ for (String s : strings) {
+ lines.add(s.substring(min));
+ }
+ return StringUtil.join(lines, "\n") + "\n";
+ }
+
+ public static void assertLayout(Project project, String artifactName, String expected) {
+ assertLayout(findArtifact(project, artifactName).getRootElement(), expected);
+ }
+
+ public static void assertOutputPath(Project project, String artifactName, String expected) {
+ assertEquals(expected, findArtifact(project, artifactName).getOutputPath());
+ }
+
+ public static void assertOutputFileName(Project project, String artifactName, String expected) {
+ assertEquals(expected, findArtifact(project, artifactName).getRootElement().getName());
+ }
+
+ public static void setOutput(final Project project, final String artifactName, final String outputPath) {
+ new WriteAction() {
+ @Override
+ protected void run(final Result result) {
+ final ModifiableArtifactModel model = ArtifactManager.getInstance(project).createModifiableModel();
+ model.getOrCreateModifiableArtifact(findArtifact(project, artifactName)).setOutputPath(outputPath);
+ model.commit();
+ }
+ }.execute();
+ }
+
+ public static void addArtifactToLayout(final Project project, final Artifact parent, final Artifact toAdd) {
+ new WriteAction() {
+ @Override
+ protected void run(final Result result) {
+ final ModifiableArtifactModel model = ArtifactManager.getInstance(project).createModifiableModel();
+ final PackagingElement<?> artifactElement = PackagingElementFactory.getInstance().createArtifactElement(toAdd, project);
+ model.getOrCreateModifiableArtifact(parent).getRootElement().addOrFindChild(artifactElement);
+ model.commit();
+ }
+ }.execute();
+ }
+
+ public static Artifact findArtifact(Project project, String artifactName) {
+ final ArtifactManager manager = ArtifactManager.getInstance(project);
+ final Artifact artifact = manager.findArtifact(artifactName);
+ assertNotNull("'" + artifactName + "' artifact not found", artifact);
+ return artifact;
+ }
+
+ public static void assertManifest(Artifact artifact, PackagingElementResolvingContext context, @Nullable String mainClass, @Nullable String classpath) {
+ final CompositePackagingElement<?> rootElement = artifact.getRootElement();
+ final ArtifactType type = artifact.getArtifactType();
+ assertManifest(rootElement, context, type, mainClass, classpath);
+ }
+
+ public static void assertManifest(CompositePackagingElement<?> rootElement,
+ PackagingElementResolvingContext context,
+ ArtifactType type,
+ @Nullable String mainClass, @Nullable String classpath) {
+ final VirtualFile file = ManifestFileUtil.findManifestFile(rootElement, context, type);
+ assertNotNull(file);
+ final Manifest manifest = ManifestFileUtil.readManifest(file);
+ assertEquals(mainClass, manifest.getMainAttributes().getValue(Attributes.Name.MAIN_CLASS));
+ assertEquals(classpath, manifest.getMainAttributes().getValue(Attributes.Name.CLASS_PATH));
+ }
+
+}
diff --git a/java/compiler/impl/testSrc/com/intellij/compiler/artifacts/PackagingElementsTestCase.java b/java/compiler/impl/testSrc/com/intellij/compiler/artifacts/PackagingElementsTestCase.java
new file mode 100644
index 0000000..a6a667a
--- /dev/null
+++ b/java/compiler/impl/testSrc/com/intellij/compiler/artifacts/PackagingElementsTestCase.java
@@ -0,0 +1,130 @@
+package com.intellij.compiler.artifacts;
+
+import com.intellij.openapi.application.PathManager;
+import com.intellij.openapi.application.Result;
+import com.intellij.openapi.application.WriteAction;
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.DependencyScope;
+import com.intellij.openapi.roots.ModuleRootModificationUtil;
+import com.intellij.openapi.roots.OrderRootType;
+import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar;
+import com.intellij.openapi.vfs.JarFileSystem;
+import com.intellij.openapi.vfs.LocalFileSystem;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.elements.PackagingElement;
+import com.intellij.testFramework.VfsTestUtil;
+import com.intellij.util.PathUtil;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+
+/**
+ * @author nik
+ */
+public abstract class PackagingElementsTestCase extends ArtifactsTestCase {
+ protected Artifact addArtifact(TestPackagingElementBuilder builder) {
+ return addArtifact("a", builder);
+ }
+
+ protected Artifact addArtifact(final String name, TestPackagingElementBuilder builder) {
+ return addArtifact(name, builder.build());
+ }
+
+ protected static void assertLayout(Artifact artifact, String expected) {
+ assertLayout(artifact.getRootElement(), expected);
+ }
+
+ protected static void assertLayout(PackagingElement element, String expected) {
+ ArtifactsTestUtil.assertLayout(element, expected);
+ }
+
+ protected String getProjectBasePath() {
+ return getBaseDir().getPath();
+ }
+
+ protected VirtualFile getBaseDir() {
+ final VirtualFile baseDir = myProject.getBaseDir();
+ assertNotNull(baseDir);
+ return baseDir;
+ }
+
+ protected TestPackagingElementBuilder root() {
+ return TestPackagingElementBuilder.root(myProject);
+ }
+
+ protected TestPackagingElementBuilder archive(String name) {
+ return TestPackagingElementBuilder.archive(myProject, name);
+ }
+
+ protected VirtualFile createFile(final String path) {
+ return createFile(path, "");
+ }
+
+ protected VirtualFile createFile(final String path, final String text) {
+ return VfsTestUtil.createFile(getBaseDir(), path, text);
+ }
+
+ protected VirtualFile createDir(final String path) {
+ return VfsTestUtil.createDir(getBaseDir(), path);
+ }
+
+ protected static VirtualFile getJDomJar() {
+ return getJarFromLibDirectory("jdom.jar");
+ }
+
+ protected static String getLocalJarPath(VirtualFile jarEntry) {
+ return PathUtil.getLocalFile(jarEntry).getPath();
+ }
+
+ protected static String getJUnitJarPath() {
+ return getLocalJarPath(getJarFromLibDirectory("junit.jar"));
+ }
+
+ private static VirtualFile getJarFromLibDirectory(final String relativePath) {
+ final File file = PathManager.findFileInLibDirectory(relativePath);
+ final VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByIoFile(file);
+ assertNotNull(file.getAbsolutePath() + " not found", virtualFile);
+ final VirtualFile jarRoot = JarFileSystem.getInstance().getJarRootForLocalFile(virtualFile);
+ assertNotNull(jarRoot);
+ return jarRoot;
+ }
+
+ protected Library addProjectLibrary(final @Nullable Module module, final String name, final VirtualFile... jars) {
+ return addProjectLibrary(module, name, DependencyScope.COMPILE, jars);
+ }
+
+ protected Library addProjectLibrary(final @Nullable Module module, final String name, final DependencyScope scope,
+ final VirtualFile... jars) {
+ return addProjectLibrary(myProject, module, name, scope, jars);
+ }
+
+ static Library addProjectLibrary(final Project project, final @Nullable Module module, final String name, final DependencyScope scope,
+ final VirtualFile[] jars) {
+ return new WriteAction<Library>() {
+ @Override
+ protected void run(final Result<Library> result) {
+ final Library library = LibraryTablesRegistrar.getInstance().getLibraryTable(project).createLibrary(name);
+ final Library.ModifiableModel libraryModel = library.getModifiableModel();
+ for (VirtualFile jar : jars) {
+ libraryModel.addRoot(jar, OrderRootType.CLASSES);
+ }
+ libraryModel.commit();
+ if (module != null) {
+ ModuleRootModificationUtil.addDependency(module, library, scope, false);
+ }
+ result.setResult(library);
+ }
+ }.execute().getResultObject();
+ }
+
+ protected static void addModuleLibrary(final Module module, final VirtualFile jar) {
+ ModuleRootModificationUtil.addModuleLibrary(module, jar.getUrl());
+ }
+
+ protected static void addModuleDependency(final Module module, final Module dependency) {
+ ModuleRootModificationUtil.addDependency(module, dependency);
+ }
+}
diff --git a/java/compiler/impl/testSrc/com/intellij/compiler/artifacts/TestPackagingElementBuilder.java b/java/compiler/impl/testSrc/com/intellij/compiler/artifacts/TestPackagingElementBuilder.java
new file mode 100644
index 0000000..3c1f6376
--- /dev/null
+++ b/java/compiler/impl/testSrc/com/intellij/compiler/artifacts/TestPackagingElementBuilder.java
@@ -0,0 +1,101 @@
+package com.intellij.compiler.artifacts;
+
+import com.intellij.openapi.module.Module;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.roots.libraries.Library;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.packaging.artifacts.Artifact;
+import com.intellij.packaging.elements.CompositePackagingElement;
+import com.intellij.packaging.elements.PackagingElement;
+import com.intellij.packaging.elements.PackagingElementFactory;
+
+/**
+* @author nik
+*/
+public class TestPackagingElementBuilder {
+ private final CompositePackagingElement<?> myElement;
+ private final TestPackagingElementBuilder myParent;
+ private final Project myProject;
+
+ private TestPackagingElementBuilder(Project project, CompositePackagingElement<?> element, TestPackagingElementBuilder parent) {
+ myElement = element;
+ myParent = parent;
+ myProject = project;
+ }
+
+ public static TestPackagingElementBuilder root(Project project) {
+ return new TestPackagingElementBuilder(project, PackagingElementFactory.getInstance().createArtifactRootElement(), null);
+ }
+
+ public static TestPackagingElementBuilder archive(final Project project, String name) {
+ return new TestPackagingElementBuilder(project, PackagingElementFactory.getInstance().createArchive(name), null);
+ }
+
+ public CompositePackagingElement<?> build() {
+ TestPackagingElementBuilder builder = this;
+ while (builder.myParent != null) {
+ builder = builder.myParent;
+ }
+ return builder.myElement;
+ }
+
+ public TestPackagingElementBuilder file(VirtualFile file) {
+ return file(file.getPath());
+ }
+
+ public TestPackagingElementBuilder file(String path) {
+ myElement.addOrFindChild(getFactory().createFileCopyWithParentDirectories(path, "/"));
+ return this;
+ }
+
+ private static PackagingElementFactory getFactory() {
+ return PackagingElementFactory.getInstance();
+ }
+
+ public TestPackagingElementBuilder dirCopy(VirtualFile dir) {
+ return dirCopy(dir.getPath());
+ }
+
+ public TestPackagingElementBuilder dirCopy(String path) {
+ myElement.addOrFindChild(getFactory().createDirectoryCopyWithParentDirectories(path, "/"));
+ return this;
+ }
+
+ public TestPackagingElementBuilder extractedDir(String jarPath, String pathInJar) {
+ myElement.addOrFindChild(getFactory().createExtractedDirectoryWithParentDirectories(jarPath, pathInJar, "/"));
+ return this;
+ }
+
+ public TestPackagingElementBuilder module(Module module) {
+ myElement.addOrFindChild(getFactory().createModuleOutput(module));
+ return this;
+ }
+
+ public TestPackagingElementBuilder lib(Library library) {
+ myElement.addOrFindChildren(getFactory().createLibraryElements(library));
+ return this;
+ }
+
+ public TestPackagingElementBuilder artifact(Artifact artifact) {
+ myElement.addOrFindChild(getFactory().createArtifactElement(artifact, myProject));
+ return this;
+ }
+
+ public TestPackagingElementBuilder archive(String name) {
+ final CompositePackagingElement<?> archive = getFactory().createArchive(name);
+ return new TestPackagingElementBuilder(myProject, myElement.addOrFindChild(archive), this);
+ }
+
+ public TestPackagingElementBuilder dir(String name) {
+ return new TestPackagingElementBuilder(myProject, myElement.addOrFindChild(getFactory().createDirectory(name)), this);
+ }
+
+ public TestPackagingElementBuilder add(PackagingElement<?> element) {
+ myElement.addOrFindChild(element);
+ return this;
+ }
+
+ public TestPackagingElementBuilder end() {
+ return myParent;
+ }
+}