projectModel-impl extracted, with cyclic/incorrect dependencies for now
[idea/community.git] / platform / projectModel-impl / src / com / intellij / openapi / roots / impl / ContentEntryImpl.java
1 /*
2  * Copyright 2000-2012 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.openapi.roots.impl;
18
19 import com.intellij.openapi.Disposable;
20 import com.intellij.openapi.diagnostic.Logger;
21 import com.intellij.openapi.extensions.Extensions;
22 import com.intellij.openapi.roots.*;
23 import com.intellij.openapi.util.Disposer;
24 import com.intellij.openapi.util.InvalidDataException;
25 import com.intellij.openapi.util.WriteExternalException;
26 import com.intellij.openapi.util.io.FileUtil;
27 import com.intellij.openapi.vfs.VfsUtilCore;
28 import com.intellij.openapi.vfs.VirtualFile;
29 import com.intellij.openapi.vfs.pointers.VirtualFilePointer;
30 import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager;
31 import com.intellij.util.ArrayUtil;
32 import org.jdom.Element;
33 import org.jetbrains.annotations.NonNls;
34 import org.jetbrains.annotations.NotNull;
35 import org.jetbrains.annotations.Nullable;
36
37 import java.util.*;
38
39 /**
40  *  @author dsl
41  */
42 public class ContentEntryImpl extends RootModelComponentBase implements ContentEntry, ClonableContentEntry, Comparable<ContentEntryImpl> {
43   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.SimpleContentEntryImpl");
44   @NotNull private final VirtualFilePointer myRoot;
45   @NonNls public static final String ELEMENT_NAME = "content";
46   private final LinkedHashSet<SourceFolder> mySourceFolders = new LinkedHashSet<SourceFolder>();
47   private final TreeSet<ExcludeFolder> myExcludeFolders = new TreeSet<ExcludeFolder>(ContentFolderComparator.INSTANCE);
48   private final TreeSet<ExcludedOutputFolder> myExcludedOutputFolders = new TreeSet<ExcludedOutputFolder>(ContentFolderComparator.INSTANCE);
49   @NonNls public static final String URL_ATTRIBUTE = "url";
50
51   ContentEntryImpl(@NotNull VirtualFile file, @NotNull RootModelImpl m) {
52     this(file.getUrl(), m);
53   }
54
55   ContentEntryImpl(@NotNull String url, @NotNull RootModelImpl m) {
56     super(m);
57     myRoot = VirtualFilePointerManager.getInstance().create(url, this, null);
58   }
59
60   ContentEntryImpl(@NotNull Element e, @NotNull RootModelImpl m) throws InvalidDataException {
61     this(getUrlFrom(e), m);
62     initSourceFolders(e);
63     initExcludeFolders(e);
64   }
65
66   private static String getUrlFrom(@NotNull Element e) throws InvalidDataException {
67     LOG.assertTrue(ELEMENT_NAME.equals(e.getName()));
68
69     String url = e.getAttributeValue(URL_ATTRIBUTE);
70     if (url == null) throw new InvalidDataException();
71     return url;
72   }
73
74   private void initSourceFolders(@NotNull Element e) throws InvalidDataException {
75     mySourceFolders.clear();
76     for (Object child : e.getChildren(SourceFolderImpl.ELEMENT_NAME)) {
77       mySourceFolders.add(new SourceFolderImpl((Element)child, this));
78     }
79   }
80
81   private void initExcludeFolders(@NotNull Element e) throws InvalidDataException {
82     myExcludeFolders.clear();
83     for (Object child : e.getChildren(ExcludeFolderImpl.ELEMENT_NAME)) {
84       myExcludeFolders.add(new ExcludeFolderImpl((Element)child, this));
85     }
86   }
87
88   @Override
89   public VirtualFile getFile() {
90     //assert !isDisposed();
91     final VirtualFile file = myRoot.getFile();
92     return file == null || !file.isDirectory() ? null : file;
93   }
94
95   @Override
96   @NotNull
97   public String getUrl() {
98     return myRoot.getUrl();
99   }
100
101   @Override
102   public SourceFolder[] getSourceFolders() {
103     return mySourceFolders.toArray(new SourceFolder[mySourceFolders.size()]);
104   }
105
106   @Override
107   @NotNull
108   public VirtualFile[] getSourceFolderFiles() {
109     assert !isDisposed();
110     final SourceFolder[] sourceFolders = getSourceFolders();
111     ArrayList<VirtualFile> result = new ArrayList<VirtualFile>(sourceFolders.length);
112     for (SourceFolder sourceFolder : sourceFolders) {
113       final VirtualFile file = sourceFolder.getFile();
114       if (file != null) {
115         result.add(file);
116       }
117     }
118     return VfsUtilCore.toVirtualFileArray(result);
119   }
120
121   @Override
122   public ExcludeFolder[] getExcludeFolders() {
123     //assert !isDisposed();
124     final ArrayList<ExcludeFolder> result = new ArrayList<ExcludeFolder>(myExcludeFolders);
125     for (DirectoryIndexExcludePolicy excludePolicy : Extensions.getExtensions(DirectoryIndexExcludePolicy.EP_NAME, getRootModel().getProject())) {
126       final VirtualFilePointer[] files = excludePolicy.getExcludeRootsForModule(getRootModel());
127       for (VirtualFilePointer file : files) {
128         addExcludeForOutputPath(file, result);
129       }
130     }
131     if (getRootModel().isExcludeExplodedDirectory()) {
132       addExcludeForOutputPath(getRootModel().myExplodedDirectoryPointer, result);
133     }
134     return result.toArray(new ExcludeFolder[result.size()]);
135   }
136
137   private void addExcludeForOutputPath(@Nullable final VirtualFilePointer outputPath, @NotNull ArrayList<ExcludeFolder> result) {
138     if (outputPath == null) return;
139     final VirtualFile outputPathFile = outputPath.getFile();
140     final VirtualFile file = myRoot.getFile();
141     if (outputPathFile != null && file != null /* TODO: ??? && VfsUtil.isAncestor(file, outputPathFile, false) */) {
142       result.add(new ExcludedOutputFolderImpl(this, outputPath));
143     }
144   }
145
146   @Override
147   @NotNull
148   public VirtualFile[] getExcludeFolderFiles() {
149     assert !isDisposed();
150     final ExcludeFolder[] excludeFolders = getExcludeFolders();
151     ArrayList<VirtualFile> result = new ArrayList<VirtualFile>();
152     for (ExcludeFolder excludeFolder : excludeFolders) {
153       final VirtualFile file = excludeFolder.getFile();
154       if (file != null) {
155         result.add(file);
156       }
157     }
158     return VfsUtilCore.toVirtualFileArray(result);
159   }
160
161   @Override
162   public SourceFolder addSourceFolder(@NotNull VirtualFile file, boolean isTestSource) {
163     assertCanAddFolder(file);
164     return addSourceFolder(new SourceFolderImpl(file, isTestSource, this));
165   }
166
167   @Override
168   public SourceFolder addSourceFolder(@NotNull VirtualFile file, boolean isTestSource, @NotNull String packagePrefix) {
169     assertCanAddFolder(file);
170     return addSourceFolder(new SourceFolderImpl(file, isTestSource, packagePrefix, this));
171   }
172
173   @Override
174   public SourceFolder addSourceFolder(@NotNull String url, boolean isTestSource) {
175     assertFolderUnderMe(url);
176     return addSourceFolder(new SourceFolderImpl(url, isTestSource, this));
177   }
178
179   private SourceFolder addSourceFolder(SourceFolderImpl f) {
180     mySourceFolders.add(f);
181     return f;
182   }
183
184   @Override
185   public void removeSourceFolder(@NotNull SourceFolder sourceFolder) {
186     assert !isDisposed();
187     assertCanRemoveFrom(sourceFolder, mySourceFolders);
188     mySourceFolders.remove(sourceFolder);
189   }
190
191   @Override
192   public void clearSourceFolders() {
193     assert !isDisposed();
194     getRootModel().assertWritable();
195     mySourceFolders.clear();
196   }
197
198   @Override
199   public ExcludeFolder addExcludeFolder(@NotNull VirtualFile file) {
200     assert !isDisposed();
201     assertCanAddFolder(file);
202     return addExcludeFolder(new ExcludeFolderImpl(file, this));
203   }
204
205   @Override
206   public ExcludeFolder addExcludeFolder(@NotNull String url) {
207     assert !isDisposed();
208     assertCanAddFolder(url);
209     return addExcludeFolder(new ExcludeFolderImpl(url, this));
210   }
211
212   private void assertCanAddFolder(@NotNull VirtualFile file) {
213     assertCanAddFolder(file.getUrl());
214   }
215
216   private void assertCanAddFolder(@NotNull String url) {
217     getRootModel().assertWritable();
218     assertFolderUnderMe(url);
219   }
220
221   @Override
222   public void removeExcludeFolder(@NotNull ExcludeFolder excludeFolder) {
223     assert !isDisposed();
224     assertCanRemoveFrom(excludeFolder, myExcludeFolders);
225     myExcludeFolders.remove(excludeFolder);
226   }
227
228   @Override
229   public void clearExcludeFolders() {
230     assert !isDisposed();
231     getRootModel().assertWritable();
232     myExcludeFolders.clear();
233   }
234
235   private ExcludeFolder addExcludeFolder(ExcludeFolder f) {
236     myExcludeFolders.add(f);
237     return f;
238   }
239
240   private <T extends ContentFolder> void assertCanRemoveFrom(T f, @NotNull Set<T> ff) {
241     getRootModel().assertWritable();
242     LOG.assertTrue(ff.contains(f));
243   }
244
245   private void assertFolderUnderMe(@NotNull String url) {
246     final String path = VfsUtilCore.urlToPath(url);
247     final String rootPath = VfsUtilCore.urlToPath(getUrl());
248     if (!FileUtil.isAncestor(rootPath, path, false)) {
249       LOG.error("The file '" + path + "' is not under content entry root '" + rootPath + "'");
250     }
251   }
252
253   @Override
254   public boolean isSynthetic() {
255     return false;
256   }
257
258   @Override
259   @NotNull
260   public ContentEntry cloneEntry(@NotNull RootModelImpl rootModel) {
261     assert !isDisposed();
262     ContentEntryImpl cloned = new ContentEntryImpl(myRoot.getUrl(), rootModel);
263     for (final SourceFolder sourceFolder : mySourceFolders) {
264       if (sourceFolder instanceof ClonableContentFolder) {
265         ContentFolder folder = ((ClonableContentFolder)sourceFolder).cloneFolder(cloned);
266         cloned.mySourceFolders.add((SourceFolder)folder);
267       }
268     }
269
270     for (final ExcludeFolder excludeFolder : myExcludeFolders) {
271       if (excludeFolder instanceof ClonableContentFolder) {
272         ContentFolder folder = ((ClonableContentFolder)excludeFolder).cloneFolder(cloned);
273         cloned.myExcludeFolders.add((ExcludeFolder)folder);
274       }
275     }
276
277     for (ExcludedOutputFolder excludedOutputFolder : myExcludedOutputFolders) {
278       if (excludedOutputFolder instanceof ClonableContentFolder) {
279         ContentFolder folder = ((ClonableContentFolder)excludedOutputFolder).cloneFolder(cloned);
280         cloned.myExcludedOutputFolders.add((ExcludedOutputFolder)folder);
281       }
282     }
283
284     return cloned;
285   }
286
287   @Override
288   public void dispose() {
289     super.dispose();
290     for (final Object mySourceFolder : mySourceFolders) {
291       ContentFolder contentFolder = (ContentFolder)mySourceFolder;
292       Disposer.dispose((Disposable)contentFolder);
293     }
294     for (final ExcludeFolder excludeFolder : myExcludeFolders) {
295       Disposer.dispose((Disposable)excludeFolder);
296     }
297   }
298
299   public void writeExternal(@NotNull Element element) throws WriteExternalException {
300     assert !isDisposed();
301     LOG.assertTrue(ELEMENT_NAME.equals(element.getName()));
302     element.setAttribute(URL_ATTRIBUTE, myRoot.getUrl());
303     for (final SourceFolder sourceFolder : mySourceFolders) {
304       if (sourceFolder instanceof SourceFolderImpl) {
305         final Element subElement = new Element(SourceFolderImpl.ELEMENT_NAME);
306         ((SourceFolderImpl)sourceFolder).writeExternal(subElement);
307         element.addContent(subElement);
308       }
309     }
310
311     for (final ExcludeFolder excludeFolder : myExcludeFolders) {
312       if (excludeFolder instanceof ExcludeFolderImpl) {
313         final Element subElement = new Element(ExcludeFolderImpl.ELEMENT_NAME);
314         ((ExcludeFolderImpl)excludeFolder).writeExternal(subElement);
315         element.addContent(subElement);
316       }
317     }
318   }
319
320   //private static final class ContentFolderComparator implements Comparator<ContentFolder> {
321   //  public static final ContentFolderComparator INSTANCE = new ContentFolderComparator();
322   //
323   //  public int compare(ContentFolder o1, ContentFolder o2) {
324   //    if (o1 instanceof ContentFolderBaseImpl && o2 instanceof ContentFolderBaseImpl) {
325   //      ((ContentFolderBaseImpl)o1).compareTo((ContentFolderBaseImpl)o2);
326   //    }
327   //    int i = o1.getUrl().compareTo(o2.getUrl());
328   //    if (i != 0) return i;
329   //    return System.identityHashCode(o1) - System.identityHashCode(o2);
330   //  }
331   //}
332   private static final class ContentFolderComparator implements Comparator<ContentFolder> {
333     public static final ContentFolderComparator INSTANCE = new ContentFolderComparator();
334
335     @Override
336     public int compare(@NotNull ContentFolder o1, @NotNull ContentFolder o2) {
337       return o1.getUrl().compareTo(o2.getUrl());
338     }
339   }
340
341   @Override
342   public int compareTo(@NotNull ContentEntryImpl other) {
343     int i = getUrl().compareTo(other.getUrl());
344     if (i != 0) return i;
345     i = ArrayUtil.lexicographicCompare(getSourceFolders(), other.getSourceFolders());
346     if (i != 0) return i;
347     i = ArrayUtil.lexicographicCompare(getExcludeFolders(), other.getExcludeFolders());
348     if (i != 0) return i;
349
350     ExcludedOutputFolder[] excludedOutputFolders = myExcludedOutputFolders.toArray(new ExcludedOutputFolder[myExcludedOutputFolders.size()]);
351     ExcludedOutputFolder[] otherExcludedOutputFolders = other.myExcludedOutputFolders.toArray(new ExcludedOutputFolder[other.myExcludedOutputFolders.size()]);
352     return ArrayUtil.lexicographicCompare(excludedOutputFolders, otherExcludedOutputFolders);
353   }
354 }