diff --git a/jps/jps-builders/src/org/jetbrains/jps/indices/impl/ModuleExcludeIndexImpl.java b/jps/jps-builders/src/org/jetbrains/jps/indices/impl/ModuleExcludeIndexImpl.java index 80860bfe0c5a..be204a63d346 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/indices/impl/ModuleExcludeIndexImpl.java +++ b/jps/jps-builders/src/org/jetbrains/jps/indices/impl/ModuleExcludeIndexImpl.java @@ -15,13 +15,17 @@ */ package org.jetbrains.jps.indices.impl; +import com.intellij.openapi.fileTypes.impl.FileTypeAssocTable; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.io.FileUtilRt; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.containers.MultiMap; import gnu.trove.THashMap; import gnu.trove.THashSet; import org.jetbrains.jps.indices.ModuleExcludeIndex; +import org.jetbrains.jps.model.JpsExcludePattern; import org.jetbrains.jps.model.JpsModel; +import org.jetbrains.jps.model.fileTypes.FileNameMatcherFactory; import org.jetbrains.jps.model.java.JpsJavaExtensionService; import org.jetbrains.jps.model.java.JpsJavaModuleExtension; import org.jetbrains.jps.model.java.JpsJavaProjectExtension; @@ -41,10 +45,12 @@ public class ModuleExcludeIndexImpl implements ModuleExcludeIndex { private final Set myTopLevelContentRoots = new THashSet<>(FileUtil.FILE_HASHING_STRATEGY); private final Map> myModuleToExcludesMap = new THashMap<>(); private final Map> myModuleToContentMap = new THashMap<>(); + private final Map> myExcludeFromContentRootTables = new THashMap<>(FileUtil.FILE_HASHING_STRATEGY); public ModuleExcludeIndexImpl(JpsModel model) { final Collection allModules = model.getProject().getModules(); Map contentToModule = new THashMap<>(FileUtil.FILE_HASHING_STRATEGY); + MultiMap excludePatterns = MultiMap.createLinked(); for (final JpsModule module : allModules) { final ArrayList moduleExcludes = new ArrayList<>(); for (String url : module.getExcludeRootsList().getUrls()) { @@ -61,6 +67,9 @@ public class ModuleExcludeIndexImpl implements ModuleExcludeIndex { moduleExcludes.add(JpsPathUtil.urlToFile(testOutputUrl)); } } + for (JpsExcludePattern pattern : module.getExcludePatterns()) { + excludePatterns.putValue(pattern.getBaseDirUrl(), pattern.getPattern()); + } List contentUrls = module.getContentRootsList().getUrls(); final List moduleContent = new ArrayList<>(contentUrls.size()); for (String contentUrl : contentUrls) { @@ -78,6 +87,15 @@ public class ModuleExcludeIndexImpl implements ModuleExcludeIndex { myExcludedRoots.addAll(moduleExcludes); } + FileNameMatcherFactory factory = FileNameMatcherFactory.getInstance(); + for (Map.Entry> entry : excludePatterns.entrySet()) { + FileTypeAssocTable table = new FileTypeAssocTable<>(); + for (String pattern : entry.getValue()) { + table.addAssociation(factory.createMatcher(pattern), Boolean.TRUE); + } + myExcludeFromContentRootTables.put(JpsPathUtil.urlToFile(entry.getKey()), table); + } + JpsJavaProjectExtension projectExtension = JpsJavaExtensionService.getInstance().getProjectExtension(model.getProject()); if (projectExtension != null) { String url = projectExtension.getOutputUrl(); @@ -168,7 +186,7 @@ public class ModuleExcludeIndexImpl implements ModuleExcludeIndex { private enum FileLocation { IN_CONTENT, EXCLUDED, NOT_IN_PROJECT } - private static FileLocation determineFileLocation(File file, Collection roots, Collection excluded) { + private FileLocation determineFileLocation(File file, Collection roots, Collection excluded) { if (roots.isEmpty() && excluded.isEmpty()) { return FileLocation.NOT_IN_PROJECT; // optimization } @@ -177,6 +195,10 @@ public class ModuleExcludeIndexImpl implements ModuleExcludeIndex { if (excluded.contains(current)) { return FileLocation.EXCLUDED; } + FileTypeAssocTable table = myExcludeFromContentRootTables.get(current); + if (table != null && isExcludedByPattern(file, current, table)) { + return FileLocation.EXCLUDED; + } if (roots.contains(current)) { return FileLocation.IN_CONTENT; } @@ -185,6 +207,18 @@ public class ModuleExcludeIndexImpl implements ModuleExcludeIndex { return FileLocation.NOT_IN_PROJECT; } + private static boolean isExcludedByPattern(File file, File root, FileTypeAssocTable table) { + File current = file; + //noinspection FileEqualsUsage it's ok to compare files by 'equals' here be because these files are produced by the same 'getParentFile' calls + while (current != null && !current.equals(root)) { + if (table.findAssociatedFileType(current.getName()) != null) { + return true; + } + current = FileUtilRt.getParentFile(current); + } + return false; + } + @Override public Collection getModuleExcludes(JpsModule module) { return myModuleToExcludesMap.get(module); diff --git a/jps/jps-builders/testSrc/org/jetbrains/jps/indices/ModuleExcludeIndexTest.java b/jps/jps-builders/testSrc/org/jetbrains/jps/indices/ModuleExcludeIndexTest.java index abf828ad8195..be4919647e16 100644 --- a/jps/jps-builders/testSrc/org/jetbrains/jps/indices/ModuleExcludeIndexTest.java +++ b/jps/jps-builders/testSrc/org/jetbrains/jps/indices/ModuleExcludeIndexTest.java @@ -153,6 +153,23 @@ public class ModuleExcludeIndexTest extends JpsJavaModelTestCase { assertExcluded(src); } + public void testExcludeByPattern() { + File root1 = new File(myRoot, "root1"); + File root2 = new File(myRoot, "root2"); + JpsModule module = addModule(); + addContentRoot(module, root1); + addContentRoot(module, root2); + module.addExcludePattern(JpsPathUtil.pathToUrl(root1.getAbsolutePath()), "*.txt"); + module.addExcludePattern(JpsPathUtil.pathToUrl(root2.getAbsolutePath()), "out"); + assertExcluded(new File(root1, "a.txt")); + assertExcluded(new File(root1, "dir/a.txt")); + assertNotExcluded(new File(root1, "A.java")); + assertNotExcluded(new File(root2, "a.txt")); + assertExcluded(new File(root2, "out")); + assertExcluded(new File(root2, "out/A.java")); + assertExcluded(new File(root2, "dir/out/A.java")); + } + private static void addSourceRoot(JpsModule module, File src) { module.addSourceRoot(JpsPathUtil.pathToUrl(src.getAbsolutePath()), JavaSourceRootType.SOURCE); } diff --git a/jps/model-api/src/org/jetbrains/jps/model/JpsExcludePattern.java b/jps/model-api/src/org/jetbrains/jps/model/JpsExcludePattern.java new file mode 100644 index 000000000000..3e5ea1513475 --- /dev/null +++ b/jps/model-api/src/org/jetbrains/jps/model/JpsExcludePattern.java @@ -0,0 +1,33 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.jps.model; + +import org.jetbrains.annotations.NotNull; + +/** + * Specifies a pattern for names of files which should be excluded from a module. If name of a file under {@link #getBaseDirUrl() the base directory} + * matches {@link #getPattern() the pattern} it'll be excluded from the containing module, if name of a directory matches the pattern the directory + * and all of its contents will be excluded. '?' and '*' wildcards are supported. + * + * @author nik + */ +public interface JpsExcludePattern extends JpsElement { + @NotNull + String getBaseDirUrl(); + + @NotNull + String getPattern(); +} diff --git a/jps/model-api/src/org/jetbrains/jps/model/module/JpsModule.java b/jps/model-api/src/org/jetbrains/jps/model/module/JpsModule.java index 8a85c2f350b0..621e6151d914 100644 --- a/jps/model-api/src/org/jetbrains/jps/model/module/JpsModule.java +++ b/jps/model-api/src/org/jetbrains/jps/model/module/JpsModule.java @@ -56,6 +56,14 @@ public interface JpsModule extends JpsNamedElement, JpsReferenceableElement getExcludePatterns(); + JpsDependenciesList getDependenciesList(); @NotNull diff --git a/jps/model-impl/src/org/jetbrains/jps/model/impl/JpsExcludePatternImpl.java b/jps/model-impl/src/org/jetbrains/jps/model/impl/JpsExcludePatternImpl.java new file mode 100644 index 000000000000..999bea1bf20d --- /dev/null +++ b/jps/model-impl/src/org/jetbrains/jps/model/impl/JpsExcludePatternImpl.java @@ -0,0 +1,55 @@ +/* + * Copyright 2000-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.jps.model.impl; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.model.JpsExcludePattern; +import org.jetbrains.jps.model.ex.JpsElementBase; + +/** + * @author nik + */ +public class JpsExcludePatternImpl extends JpsElementBase implements JpsExcludePattern { + private final String myBaseDirUrl; + private final String myPattern; + + public JpsExcludePatternImpl(@NotNull String baseDirUrl, @NotNull String pattern) { + myBaseDirUrl = baseDirUrl; + myPattern = pattern; + } + + @NotNull + @Override + public String getBaseDirUrl() { + return myBaseDirUrl; + } + + @NotNull + @Override + public String getPattern() { + return myPattern; + } + + @NotNull + @Override + public JpsExcludePatternImpl createCopy() { + return new JpsExcludePatternImpl(myBaseDirUrl, myPattern); + } + + @Override + public void applyChanges(@NotNull JpsExcludePatternImpl modified) { + } +} diff --git a/jps/model-impl/src/org/jetbrains/jps/model/module/impl/JpsModuleImpl.java b/jps/model-impl/src/org/jetbrains/jps/model/module/impl/JpsModuleImpl.java index e7c7c14d5394..34768dd4a3ad 100644 --- a/jps/model-impl/src/org/jetbrains/jps/model/module/impl/JpsModuleImpl.java +++ b/jps/model-impl/src/org/jetbrains/jps/model/module/impl/JpsModuleImpl.java @@ -19,7 +19,9 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.model.*; import org.jetbrains.jps.model.ex.JpsElementChildRoleBase; +import org.jetbrains.jps.model.ex.JpsElementCollectionRole; import org.jetbrains.jps.model.ex.JpsNamedCompositeElementBase; +import org.jetbrains.jps.model.impl.JpsExcludePatternImpl; import org.jetbrains.jps.model.impl.JpsUrlListRole; import org.jetbrains.jps.model.library.JpsLibrary; import org.jetbrains.jps.model.library.JpsLibraryCollection; @@ -32,6 +34,7 @@ import org.jetbrains.jps.model.library.sdk.JpsSdkType; import org.jetbrains.jps.model.library.sdk.JpsSdkReference; import org.jetbrains.jps.model.module.*; +import java.util.Collections; import java.util.List; /** @@ -41,6 +44,7 @@ public class JpsModuleImpl

extends JpsNamedCompositeElemen private static final JpsUrlListRole CONTENT_ROOTS_ROLE = new JpsUrlListRole("content roots"); private static final JpsUrlListRole EXCLUDED_ROOTS_ROLE = new JpsUrlListRole("excluded roots"); private static final JpsElementChildRole DEPENDENCIES_LIST_CHILD_ROLE = JpsElementChildRoleBase.create("dependencies"); + private static final JpsElementCollectionRole EXCLUDE_PATTERNS_ROLE = JpsElementCollectionRole.create(JpsElementChildRoleBase.create("exclude patterns")); private final JpsModuleType

myModuleType; private final JpsLibraryCollection myLibraryCollection; @@ -140,6 +144,29 @@ public class JpsModuleImpl

extends JpsNamedCompositeElemen } } + @Override + public void addExcludePattern(@NotNull String baseDirUrl, @NotNull String pattern) { + myContainer.getOrSetChild(EXCLUDE_PATTERNS_ROLE).addChild(new JpsExcludePatternImpl(baseDirUrl, pattern)); + } + + @Override + public void removeExcludePattern(@NotNull String baseDirUrl, @NotNull String pattern) { + JpsElementCollection child = myContainer.getChild(EXCLUDE_PATTERNS_ROLE); + if (child != null) { + for (JpsExcludePattern excludePattern : child.getElements()) { + if (excludePattern.getBaseDirUrl().equals(baseDirUrl) && excludePattern.getPattern().equals(pattern)) { + child.removeChild(excludePattern); + } + } + } + } + + @Override + public List getExcludePatterns() { + JpsElementCollection child = myContainer.getChild(EXCLUDE_PATTERNS_ROLE); + return child != null ? child.getElements() : Collections.emptyList(); + } + @NotNull @Override public JpsDependenciesList getDependenciesList() { diff --git a/jps/model-impl/testSrc/org/jetbrains/jps/model/JpsModuleTest.java b/jps/model-impl/testSrc/org/jetbrains/jps/model/JpsModuleTest.java index 438090741fda..48ba23803d4f 100644 --- a/jps/model-impl/testSrc/org/jetbrains/jps/model/JpsModuleTest.java +++ b/jps/model-impl/testSrc/org/jetbrains/jps/model/JpsModuleTest.java @@ -52,6 +52,14 @@ public class JpsModuleTest extends JpsModelTestCase { assertSameElements(ContainerUtil.newArrayList(modules), module); } + public void testExcludedPatterns() { + JpsModule module = myProject.addModule("m", JpsJavaModuleType.INSTANCE); + module.addExcludePattern("file://url", "*.class"); + JpsExcludePattern pattern = assertOneElement(module.getExcludePatterns()); + assertEquals("file://url", pattern.getBaseDirUrl()); + assertEquals("*.class", pattern.getPattern()); + } + public void testModifiableModel() { final JpsModule module = myProject.addModule("m", JpsJavaModuleType.INSTANCE); final JpsModuleSourceRoot root0 = module.addSourceRoot("url1", JavaSourceRootType.SOURCE); diff --git a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/module/JpsModuleRootModelSerializer.java b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/module/JpsModuleRootModelSerializer.java index 0ee14783bc43..e9f22c480dbe 100644 --- a/jps/model-serialization/src/org/jetbrains/jps/model/serialization/module/JpsModuleRootModelSerializer.java +++ b/jps/model-serialization/src/org/jetbrains/jps/model/serialization/module/JpsModuleRootModelSerializer.java @@ -21,10 +21,7 @@ import com.intellij.util.text.UniqueNameGenerator; import org.jdom.Element; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jetbrains.jps.model.JpsCompositeElement; -import org.jetbrains.jps.model.JpsElement; -import org.jetbrains.jps.model.JpsElementFactory; -import org.jetbrains.jps.model.JpsElementReference; +import org.jetbrains.jps.model.*; import org.jetbrains.jps.model.java.JpsJavaSdkType; import org.jetbrains.jps.model.java.JpsJavaSdkTypeWrapper; import org.jetbrains.jps.model.library.JpsLibrary; @@ -54,6 +51,8 @@ public class JpsModuleRootModelSerializer { public static final String PACKAGE_PREFIX_ATTRIBUTE = "packagePrefix"; public static final String IS_TEST_SOURCE_ATTRIBUTE = "isTestSource"; public static final String EXCLUDE_FOLDER_TAG = "excludeFolder"; + public static final String EXCLUDE_PATTERN_TAG = "excludePattern"; + public static final String EXCLUDE_PATTERN_ATTRIBUTE = "pattern"; public static final String ORDER_ENTRY_TAG = "orderEntry"; public static final String TYPE_ATTRIBUTE = "type"; public static final String SOURCE_FOLDER_TYPE = "sourceFolder"; @@ -85,6 +84,9 @@ public class JpsModuleRootModelSerializer { for (Element excludeElement : getChildren(contentElement, EXCLUDE_FOLDER_TAG)) { module.getExcludeRootsList().addUrl(excludeElement.getAttributeValue(URL_ATTRIBUTE)); } + for (Element excludePatternElement : getChildren(contentElement, EXCLUDE_PATTERN_TAG)) { + module.addExcludePattern(url, excludePatternElement.getAttributeValue(EXCLUDE_PATTERN_ATTRIBUTE)); + } } final JpsDependenciesList dependenciesList = module.getDependenciesList(); @@ -202,6 +204,11 @@ public class JpsModuleRootModelSerializer { contentElement.addContent(element); } } + for (JpsExcludePattern pattern : module.getExcludePatterns()) { + if (pattern.getBaseDirUrl().equals(url)) { + contentElement.addContent(new Element(EXCLUDE_PATTERN_TAG).setAttribute(EXCLUDE_PATTERN_ATTRIBUTE, pattern.getPattern())); + } + } } for (JpsDependencyElement dependency : module.getDependenciesList().getDependencies()) { diff --git a/jps/model-serialization/testData/excludePatterns/excludePatterns.iml b/jps/model-serialization/testData/excludePatterns/excludePatterns.iml new file mode 100644 index 000000000000..ddd52dfb7e29 --- /dev/null +++ b/jps/model-serialization/testData/excludePatterns/excludePatterns.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/jps/model-serialization/testData/excludePatterns/excludePatterns.ipr b/jps/model-serialization/testData/excludePatterns/excludePatterns.ipr new file mode 100644 index 000000000000..5cc11b0935ba --- /dev/null +++ b/jps/model-serialization/testData/excludePatterns/excludePatterns.ipr @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/jps/model-serialization/testSrc/org/jetbrains/jps/model/serialization/JpsProjectSerializationTest.java b/jps/model-serialization/testSrc/org/jetbrains/jps/model/serialization/JpsProjectSerializationTest.java index 6a4bac3eee48..6b6414f891e6 100644 --- a/jps/model-serialization/testSrc/org/jetbrains/jps/model/serialization/JpsProjectSerializationTest.java +++ b/jps/model-serialization/testSrc/org/jetbrains/jps/model/serialization/JpsProjectSerializationTest.java @@ -21,6 +21,7 @@ import org.jdom.Element; import org.jetbrains.jps.model.JpsDummyElement; import org.jetbrains.jps.model.JpsEncodingConfigurationService; import org.jetbrains.jps.model.JpsEncodingProjectConfiguration; +import org.jetbrains.jps.model.JpsExcludePattern; import org.jetbrains.jps.model.artifact.JpsArtifactService; import org.jetbrains.jps.model.java.*; import org.jetbrains.jps.model.library.JpsLibrary; @@ -117,6 +118,16 @@ public class JpsProjectSerializationTest extends JpsSerializationTestCase { assertSame(productionModule, testModuleProperties.getProductionModule()); } + public void testExcludePatterns() { + String projectPath = "/jps/model-serialization/testData/excludePatterns"; + loadProject(projectPath + "/excludePatterns.ipr"); + JpsModule module = assertOneElement(myProject.getModules()); + JpsExcludePattern pattern = assertOneElement(module.getExcludePatterns()); + assertEquals("*.class", pattern.getPattern()); + assertEquals(assertOneElement(module.getContentRootsList().getUrls()), pattern.getBaseDirUrl()); + doTestSaveModule(module, projectPath + "/excludePatterns.iml"); + } + public void testProjectSdkWithoutType() { loadProject("/jps/model-serialization/testData/projectSdkWithoutType/projectSdkWithoutType.ipr"); JpsSdkReference reference = myProject.getSdkReferencesTable().getSdkReference(JpsJavaSdkType.INSTANCE);