blob: 540bfe290645b3d1731c130c8d68a883439e48b8 [file] [log] [blame]
/*
* 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.openapi.roots.impl;
import com.intellij.openapi.CompositeDisposable;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.roots.impl.libraries.LibraryEx;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.roots.libraries.LibraryTable;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.pointers.VirtualFilePointerManager;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
/**
* @author dsl
*/
public class RootModelImpl extends RootModelBase implements ModifiableRootModel {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.RootModelImpl");
private final Set<ContentEntry> myContent = new TreeSet<ContentEntry>(ContentComparator.INSTANCE);
private final List<OrderEntry> myOrderEntries = new Order();
// cleared by myOrderEntries modification, see Order
@Nullable private OrderEntry[] myCachedOrderEntries;
@NotNull private final ModuleLibraryTable myModuleLibraryTable;
final ModuleRootManagerImpl myModuleRootManager;
private boolean myWritable;
private final VirtualFilePointerManager myFilePointerManager;
private boolean myDisposed = false;
private final Set<ModuleExtension> myExtensions = new TreeSet<ModuleExtension>();
private final RootConfigurationAccessor myConfigurationAccessor;
private final ProjectRootManagerImpl myProjectRootManager;
// have to register all child disposables using this fake object since all clients just call ModifiableModel.dispose()
private final CompositeDisposable myDisposable = new CompositeDisposable();
RootModelImpl(@NotNull ModuleRootManagerImpl moduleRootManager,
ProjectRootManagerImpl projectRootManager,
VirtualFilePointerManager filePointerManager) {
myModuleRootManager = moduleRootManager;
myProjectRootManager = projectRootManager;
myFilePointerManager = filePointerManager;
myWritable = false;
addSourceOrderEntries();
myModuleLibraryTable = new ModuleLibraryTable(this, myProjectRootManager);
for (ModuleExtension extension : Extensions.getExtensions(ModuleExtension.EP_NAME, moduleRootManager.getModule())) {
ModuleExtension model = extension.getModifiableModel(false);
registerOnDispose(model);
myExtensions.add(model);
}
myConfigurationAccessor = new RootConfigurationAccessor();
}
private void addSourceOrderEntries() {
myOrderEntries.add(new ModuleSourceOrderEntryImpl(this));
}
RootModelImpl(@NotNull Element element,
@NotNull ModuleRootManagerImpl moduleRootManager,
ProjectRootManagerImpl projectRootManager,
VirtualFilePointerManager filePointerManager, boolean writable) throws InvalidDataException {
myProjectRootManager = projectRootManager;
myFilePointerManager = filePointerManager;
myModuleRootManager = moduleRootManager;
myModuleLibraryTable = new ModuleLibraryTable(this, myProjectRootManager);
final List contentChildren = element.getChildren(ContentEntryImpl.ELEMENT_NAME);
for (Object aContentChildren : contentChildren) {
Element child = (Element)aContentChildren;
ContentEntryImpl contentEntry = new ContentEntryImpl(child, this);
myContent.add(contentEntry);
}
final List orderElements = element.getChildren(OrderEntryFactory.ORDER_ENTRY_ELEMENT_NAME);
boolean moduleSourceAdded = false;
for (Object orderElement : orderElements) {
Element child = (Element)orderElement;
final OrderEntry orderEntry = OrderEntryFactory.createOrderEntryByElement(child, this, myProjectRootManager);
if (orderEntry instanceof ModuleSourceOrderEntry) {
if (moduleSourceAdded) continue;
moduleSourceAdded = true;
}
myOrderEntries.add(orderEntry);
}
if (!moduleSourceAdded) {
myOrderEntries.add(new ModuleSourceOrderEntryImpl(this));
}
myWritable = writable;
RootModelImpl originalRootModel = moduleRootManager.getRootModel();
for (ModuleExtension extension : originalRootModel.myExtensions) {
ModuleExtension model = extension.getModifiableModel(false);
model.readExternal(element);
registerOnDispose(model);
myExtensions.add(model);
}
myConfigurationAccessor = new RootConfigurationAccessor();
}
@Override
public boolean isWritable() {
return myWritable;
}
public RootConfigurationAccessor getConfigurationAccessor() {
return myConfigurationAccessor;
}
//creates modifiable model
RootModelImpl(@NotNull RootModelImpl rootModel,
ModuleRootManagerImpl moduleRootManager,
final boolean writable,
final RootConfigurationAccessor rootConfigurationAccessor,
@NotNull VirtualFilePointerManager filePointerManager,
ProjectRootManagerImpl projectRootManager) {
myFilePointerManager = filePointerManager;
myModuleRootManager = moduleRootManager;
myProjectRootManager = projectRootManager;
myModuleLibraryTable = new ModuleLibraryTable(this, myProjectRootManager);
myWritable = writable;
myConfigurationAccessor = rootConfigurationAccessor;
final Set<ContentEntry> thatContent = rootModel.myContent;
for (ContentEntry contentEntry : thatContent) {
if (contentEntry instanceof ClonableContentEntry) {
ContentEntry cloned = ((ClonableContentEntry)contentEntry).cloneEntry(this);
myContent.add(cloned);
}
}
setOrderEntriesFrom(rootModel);
for (ModuleExtension extension : rootModel.myExtensions) {
ModuleExtension model = extension.getModifiableModel(writable);
registerOnDispose(model);
myExtensions.add(model);
}
}
private void setOrderEntriesFrom(@NotNull RootModelImpl rootModel) {
removeAllOrderEntries();
for (OrderEntry orderEntry : rootModel.myOrderEntries) {
if (orderEntry instanceof ClonableOrderEntry) {
myOrderEntries.add(((ClonableOrderEntry)orderEntry).cloneEntry(this, myProjectRootManager, myFilePointerManager));
}
}
}
private void removeAllOrderEntries() {
for (OrderEntry entry : myOrderEntries) {
Disposer.dispose((OrderEntryBaseImpl)entry);
}
myOrderEntries.clear();
}
@Override
@NotNull
public OrderEntry[] getOrderEntries() {
OrderEntry[] cachedOrderEntries = myCachedOrderEntries;
if (cachedOrderEntries == null) {
myCachedOrderEntries = cachedOrderEntries = myOrderEntries.toArray(new OrderEntry[myOrderEntries.size()]);
}
return cachedOrderEntries;
}
Iterator<OrderEntry> getOrderIterator() {
return Collections.unmodifiableList(myOrderEntries).iterator();
}
@Override
public void removeContentEntry(@NotNull ContentEntry entry) {
assertWritable();
LOG.assertTrue(myContent.contains(entry));
if (entry instanceof RootModelComponentBase) {
Disposer.dispose((RootModelComponentBase)entry);
RootModelImpl entryModel = ((RootModelComponentBase)entry).getRootModel();
LOG.assertTrue(entryModel == this, "Removing from " + this + " content entry obtained from " + entryModel);
}
myContent.remove(entry);
}
@Override
public void addOrderEntry(@NotNull OrderEntry entry) {
assertWritable();
LOG.assertTrue(!myOrderEntries.contains(entry));
myOrderEntries.add(entry);
}
@NotNull
@Override
public LibraryOrderEntry addLibraryEntry(@NotNull Library library) {
assertWritable();
final LibraryOrderEntry libraryOrderEntry = new LibraryOrderEntryImpl(library, this, myProjectRootManager);
assert libraryOrderEntry.isValid();
myOrderEntries.add(libraryOrderEntry);
return libraryOrderEntry;
}
@NotNull
@Override
public LibraryOrderEntry addInvalidLibrary(@NotNull String name, @NotNull String level) {
assertWritable();
final LibraryOrderEntry libraryOrderEntry = new LibraryOrderEntryImpl(name, level, this, myProjectRootManager);
myOrderEntries.add(libraryOrderEntry);
return libraryOrderEntry;
}
@NotNull
@Override
public ModuleOrderEntry addModuleOrderEntry(@NotNull Module module) {
assertWritable();
LOG.assertTrue(!module.equals(getModule()));
LOG.assertTrue(Comparing.equal(myModuleRootManager.getModule().getProject(), module.getProject()));
final ModuleOrderEntryImpl moduleOrderEntry = new ModuleOrderEntryImpl(module, this);
myOrderEntries.add(moduleOrderEntry);
return moduleOrderEntry;
}
@NotNull
@Override
public ModuleOrderEntry addInvalidModuleEntry(@NotNull String name) {
assertWritable();
LOG.assertTrue(!name.equals(getModule().getName()));
final ModuleOrderEntryImpl moduleOrderEntry = new ModuleOrderEntryImpl(name, this);
myOrderEntries.add(moduleOrderEntry);
return moduleOrderEntry;
}
@Nullable
@Override
public LibraryOrderEntry findLibraryOrderEntry(@NotNull Library library) {
for (OrderEntry orderEntry : getOrderEntries()) {
if (orderEntry instanceof LibraryOrderEntry && library.equals(((LibraryOrderEntry)orderEntry).getLibrary())) {
return (LibraryOrderEntry)orderEntry;
}
}
return null;
}
@Override
public void removeOrderEntry(@NotNull OrderEntry entry) {
assertWritable();
removeOrderEntryInternal(entry);
}
private void removeOrderEntryInternal(OrderEntry entry) {
LOG.assertTrue(myOrderEntries.contains(entry));
Disposer.dispose((OrderEntryBaseImpl)entry);
myOrderEntries.remove(entry);
}
@Override
public void rearrangeOrderEntries(@NotNull OrderEntry[] newEntries) {
assertWritable();
assertValidRearrangement(newEntries);
myOrderEntries.clear();
ContainerUtil.addAll(myOrderEntries, newEntries);
}
private void assertValidRearrangement(@NotNull OrderEntry[] newEntries) {
String error = checkValidRearrangement(newEntries);
LOG.assertTrue(error == null, error);
}
@Nullable
private String checkValidRearrangement(@NotNull OrderEntry[] newEntries) {
if (newEntries.length != myOrderEntries.size()) {
return "Size mismatch: old size=" + myOrderEntries.size() + "; new size=" + newEntries.length;
}
Set<OrderEntry> set = new HashSet<OrderEntry>();
for (OrderEntry newEntry : newEntries) {
if (!myOrderEntries.contains(newEntry)) {
return "Trying to add nonexisting order entry " + newEntry;
}
if (set.contains(newEntry)) {
return "Trying to add duplicate order entry " + newEntry;
}
set.add(newEntry);
}
return null;
}
@Override
public void clear() {
final Sdk jdk = getSdk();
removeAllContentEntries();
removeAllOrderEntries();
setSdk(jdk);
addSourceOrderEntries();
}
private void removeAllContentEntries() {
for (ContentEntry entry : myContent) {
if (entry instanceof RootModelComponentBase) {
Disposer.dispose((RootModelComponentBase)entry);
}
}
myContent.clear();
}
@Override
public void commit() {
myModuleRootManager.commitModel(this);
myWritable = false;
}
public void docommit() {
assert isWritable();
if (areOrderEntriesChanged()) {
getSourceModel().setOrderEntriesFrom(this);
}
for (ModuleExtension extension : myExtensions) {
if (extension.isChanged()) {
extension.commit();
}
}
if (areContentEntriesChanged()) {
getSourceModel().removeAllContentEntries();
for (ContentEntry contentEntry : myContent) {
ContentEntry cloned = ((ClonableContentEntry)contentEntry).cloneEntry(getSourceModel());
getSourceModel().myContent.add(cloned);
}
}
}
@Override
@NotNull
public LibraryTable getModuleLibraryTable() {
return myModuleLibraryTable;
}
@Override
public Project getProject() {
return myProjectRootManager.getProject();
}
@Override
@NotNull
public ContentEntry addContentEntry(@NotNull VirtualFile file) {
return addContentEntry(new ContentEntryImpl(file, this));
}
@Override
@NotNull
public ContentEntry addContentEntry(@NotNull String url) {
return addContentEntry(new ContentEntryImpl(url, this));
}
@Override
public boolean isDisposed() {
return myDisposed;
}
@NotNull
private ContentEntry addContentEntry(@NotNull ContentEntry e) {
if (myContent.contains(e)) {
for (ContentEntry contentEntry : getContentEntries()) {
if (ContentComparator.INSTANCE.compare(contentEntry, e) == 0) return contentEntry;
}
}
myContent.add(e);
return e;
}
public void writeExternal(@NotNull Element element) throws WriteExternalException {
for (ModuleExtension extension : myExtensions) {
extension.writeExternal(element);
}
for (ContentEntry contentEntry : getContent()) {
if (contentEntry instanceof ContentEntryImpl) {
final Element subElement = new Element(ContentEntryImpl.ELEMENT_NAME);
((ContentEntryImpl)contentEntry).writeExternal(subElement);
element.addContent(subElement);
}
}
for (OrderEntry orderEntry : getOrderEntries()) {
if (orderEntry instanceof WritableOrderEntry) {
((WritableOrderEntry)orderEntry).writeExternal(element);
}
}
}
@Override
public void setSdk(@Nullable Sdk jdk) {
assertWritable();
final JdkOrderEntry jdkLibraryEntry;
if (jdk != null) {
jdkLibraryEntry = new ModuleJdkOrderEntryImpl(jdk, this, myProjectRootManager);
}
else {
jdkLibraryEntry = null;
}
replaceEntryOfType(JdkOrderEntry.class, jdkLibraryEntry);
}
@Override
public void setInvalidSdk(@NotNull String jdkName, String jdkType) {
assertWritable();
replaceEntryOfType(JdkOrderEntry.class, new ModuleJdkOrderEntryImpl(jdkName, jdkType, this, myProjectRootManager));
}
@Override
public void inheritSdk() {
assertWritable();
replaceEntryOfType(JdkOrderEntry.class, new InheritedJdkOrderEntryImpl(this, myProjectRootManager));
}
@Override
public <T extends OrderEntry> void replaceEntryOfType(@NotNull Class<T> entryClass, @Nullable final T entry) {
assertWritable();
for (int i = 0; i < myOrderEntries.size(); i++) {
OrderEntry orderEntry = myOrderEntries.get(i);
if (entryClass.isInstance(orderEntry)) {
myOrderEntries.remove(i);
if (entry != null) {
myOrderEntries.add(i, entry);
}
return;
}
}
if (entry != null) {
myOrderEntries.add(0, entry);
}
}
@Override
public String getSdkName() {
for (OrderEntry orderEntry : getOrderEntries()) {
if (orderEntry instanceof JdkOrderEntry) {
return ((JdkOrderEntry)orderEntry).getJdkName();
}
}
return null;
}
public void assertWritable() {
LOG.assertTrue(myWritable);
}
public boolean isDependsOn(final Module module) {
for (OrderEntry entry : getOrderEntries()) {
if (entry instanceof ModuleOrderEntry) {
final Module module1 = ((ModuleOrderEntry)entry).getModule();
if (module1 == module) {
return true;
}
}
}
return false;
}
public boolean isOrderEntryDisposed() {
for (OrderEntry entry : myOrderEntries) {
if (entry instanceof RootModelComponentBase && ((RootModelComponentBase)entry).isDisposed()) return true;
}
return false;
}
@Override
protected Set<ContentEntry> getContent() {
return myContent;
}
private static class ContentComparator implements Comparator<ContentEntry> {
public static final ContentComparator INSTANCE = new ContentComparator();
@Override
public int compare(@NotNull final ContentEntry o1, @NotNull final ContentEntry o2) {
return o1.getUrl().compareTo(o2.getUrl());
}
}
@Override
@NotNull
public Module getModule() {
return myModuleRootManager.getModule();
}
@Override
public boolean isChanged() {
if (!myWritable) return false;
for (ModuleExtension moduleExtension : myExtensions) {
if (moduleExtension.isChanged()) return true;
}
return areOrderEntriesChanged() || areContentEntriesChanged();
}
private boolean areContentEntriesChanged() {
return ArrayUtil.lexicographicCompare(getContentEntries(), getSourceModel().getContentEntries()) != 0;
}
private boolean areOrderEntriesChanged() {
OrderEntry[] orderEntries = getOrderEntries();
OrderEntry[] sourceOrderEntries = getSourceModel().getOrderEntries();
if (orderEntries.length != sourceOrderEntries.length) return true;
for (int i = 0; i < orderEntries.length; i++) {
OrderEntry orderEntry = orderEntries[i];
OrderEntry sourceOrderEntry = sourceOrderEntries[i];
if (!orderEntriesEquals(orderEntry, sourceOrderEntry)) {
return true;
}
}
return false;
}
private static boolean orderEntriesEquals(@NotNull OrderEntry orderEntry1, @NotNull OrderEntry orderEntry2) {
if (!((OrderEntryBaseImpl)orderEntry1).sameType(orderEntry2)) return false;
if (orderEntry1 instanceof JdkOrderEntry) {
if (!(orderEntry2 instanceof JdkOrderEntry)) return false;
if (orderEntry1 instanceof InheritedJdkOrderEntry && orderEntry2 instanceof ModuleJdkOrderEntry) {
return false;
}
if (orderEntry2 instanceof InheritedJdkOrderEntry && orderEntry1 instanceof ModuleJdkOrderEntry) {
return false;
}
if (orderEntry1 instanceof ModuleJdkOrderEntry && orderEntry2 instanceof ModuleJdkOrderEntry) {
String name1 = ((ModuleJdkOrderEntry)orderEntry1).getJdkName();
String name2 = ((ModuleJdkOrderEntry)orderEntry2).getJdkName();
if (!Comparing.strEqual(name1, name2)) {
return false;
}
}
}
if (orderEntry1 instanceof ExportableOrderEntry) {
if (!(((ExportableOrderEntry)orderEntry1).isExported() == ((ExportableOrderEntry)orderEntry2).isExported())) {
return false;
}
if (!(((ExportableOrderEntry)orderEntry1).getScope() == ((ExportableOrderEntry)orderEntry2).getScope())) {
return false;
}
}
if (orderEntry1 instanceof ModuleOrderEntry) {
LOG.assertTrue(orderEntry2 instanceof ModuleOrderEntry);
ModuleOrderEntryImpl entry1 = (ModuleOrderEntryImpl)orderEntry1;
ModuleOrderEntryImpl entry2 = (ModuleOrderEntryImpl)orderEntry2;
return entry1.isProductionOnTestDependency() == entry2.isProductionOnTestDependency()
&& Comparing.equal(entry1.getModuleName(), entry2.getModuleName());
}
if (orderEntry1 instanceof LibraryOrderEntry) {
LOG.assertTrue(orderEntry2 instanceof LibraryOrderEntry);
LibraryOrderEntry libraryOrderEntry1 = (LibraryOrderEntry)orderEntry1;
LibraryOrderEntry libraryOrderEntry2 = (LibraryOrderEntry)orderEntry2;
boolean equal = Comparing.equal(libraryOrderEntry1.getLibraryName(), libraryOrderEntry2.getLibraryName())
&& Comparing.equal(libraryOrderEntry1.getLibraryLevel(), libraryOrderEntry2.getLibraryLevel());
if (!equal) return false;
Library library1 = libraryOrderEntry1.getLibrary();
Library library2 = libraryOrderEntry2.getLibrary();
if (library1 != null && library2 != null) {
if (!Arrays.equals(((LibraryEx)library1).getExcludedRootUrls(), ((LibraryEx)library2).getExcludedRootUrls())) {
return false;
}
}
}
final OrderRootType[] allTypes = OrderRootType.getAllTypes();
for (OrderRootType type : allTypes) {
final String[] orderedRootUrls1 = orderEntry1.getUrls(type);
final String[] orderedRootUrls2 = orderEntry2.getUrls(type);
if (!Arrays.equals(orderedRootUrls1, orderedRootUrls2)) {
return false;
}
}
return true;
}
void makeExternalChange(@NotNull Runnable runnable) {
if (myWritable || myDisposed) return;
myModuleRootManager.makeRootsChange(runnable);
}
@Override
public void dispose() {
assert !myDisposed;
Disposer.dispose(myDisposable);
myExtensions.clear();
myWritable = false;
myDisposed = true;
}
private class Order extends ArrayList<OrderEntry> {
@Override
public void clear() {
super.clear();
clearCachedEntries();
}
@NotNull
@Override
public OrderEntry set(int i, @NotNull OrderEntry orderEntry) {
super.set(i, orderEntry);
((OrderEntryBaseImpl)orderEntry).setIndex(i);
clearCachedEntries();
return orderEntry;
}
@Override
public boolean add(@NotNull OrderEntry orderEntry) {
super.add(orderEntry);
((OrderEntryBaseImpl)orderEntry).setIndex(size() - 1);
clearCachedEntries();
return true;
}
@Override
public void add(int i, OrderEntry orderEntry) {
super.add(i, orderEntry);
clearCachedEntries();
setIndicies(i);
}
@Override
public OrderEntry remove(int i) {
OrderEntry entry = super.remove(i);
setIndicies(i);
clearCachedEntries();
return entry;
}
@Override
public boolean remove(Object o) {
int index = indexOf(o);
if (index < 0) return false;
remove(index);
clearCachedEntries();
return true;
}
@Override
public boolean addAll(Collection<? extends OrderEntry> collection) {
int startSize = size();
boolean result = super.addAll(collection);
setIndicies(startSize);
clearCachedEntries();
return result;
}
@Override
public boolean addAll(int i, Collection<? extends OrderEntry> collection) {
boolean result = super.addAll(i, collection);
setIndicies(i);
clearCachedEntries();
return result;
}
@Override
public void removeRange(int i, int i1) {
super.removeRange(i, i1);
clearCachedEntries();
setIndicies(i);
}
@Override
public boolean removeAll(Collection<?> collection) {
boolean result = super.removeAll(collection);
setIndicies(0);
clearCachedEntries();
return result;
}
@Override
public boolean retainAll(Collection<?> collection) {
boolean result = super.retainAll(collection);
setIndicies(0);
clearCachedEntries();
return result;
}
private void clearCachedEntries() {
myCachedOrderEntries = null;
}
private void setIndicies(int startIndex) {
for (int j = startIndex; j < size(); j++) {
((OrderEntryBaseImpl)get(j)).setIndex(j);
}
}
}
private RootModelImpl getSourceModel() {
assertWritable();
return myModuleRootManager.getRootModel();
}
@Override
public String toString() {
return "RootModelImpl{" +
"module=" + getModule().getName() +
", writable=" + myWritable +
", disposed=" + myDisposed +
'}';
}
@Nullable
@Override
public <T> T getModuleExtension(@NotNull final Class<T> klass) {
for (ModuleExtension extension : myExtensions) {
if (klass.isAssignableFrom(extension.getClass())) {
//noinspection unchecked
return (T)extension;
}
}
return null;
}
void registerOnDispose(@NotNull Disposable disposable) {
myDisposable.add(disposable);
}
}