blob: 591795941d298026d520118cda5036881f1b2383 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.util.xml.impl;
import com.intellij.ide.highlighter.DomSupportEnabled;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectFileIndex;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Factory;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vfs.*;
import com.intellij.openapi.vfs.newvfs.NewVirtualFile;
import com.intellij.pom.PomManager;
import com.intellij.pom.PomModel;
import com.intellij.pom.PomModelAspect;
import com.intellij.pom.event.PomModelEvent;
import com.intellij.pom.event.PomModelListener;
import com.intellij.pom.xml.XmlAspect;
import com.intellij.pom.xml.XmlChangeSet;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiManagerEx;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlElement;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.reference.SoftReference;
import com.intellij.semantic.SemKey;
import com.intellij.semantic.SemService;
import com.intellij.util.EventDispatcher;
import com.intellij.util.SmartList;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.xml.*;
import com.intellij.util.xml.events.DomEvent;
import com.intellij.util.xml.reflect.AbstractDomChildrenDescription;
import com.intellij.util.xml.reflect.DomGenericInfo;
import net.sf.cglib.proxy.AdvancedProxy;
import net.sf.cglib.proxy.InvocationHandler;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.ref.WeakReference;
import java.lang.reflect.Type;
import java.util.*;
/**
* @author peter
*/
public final class DomManagerImpl extends DomManager {
private static final Key<Object> MOCK = Key.create("MockElement");
static final Key<WeakReference<DomFileElementImpl>> CACHED_FILE_ELEMENT = Key.create("CACHED_FILE_ELEMENT");
static final Key<DomFileDescription> MOCK_DESCRIPTION = Key.create("MockDescription");
static final SemKey<FileDescriptionCachedValueProvider> FILE_DESCRIPTION_KEY = SemKey.createKey("FILE_DESCRIPTION_KEY");
static final SemKey<DomInvocationHandler> DOM_HANDLER_KEY = SemKey.createKey("DOM_HANDLER_KEY");
static final SemKey<IndexedElementInvocationHandler> DOM_INDEXED_HANDLER_KEY = DOM_HANDLER_KEY.subKey("DOM_INDEXED_HANDLER_KEY");
static final SemKey<CollectionElementInvocationHandler> DOM_COLLECTION_HANDLER_KEY = DOM_HANDLER_KEY.subKey("DOM_COLLECTION_HANDLER_KEY");
static final SemKey<CollectionElementInvocationHandler> DOM_CUSTOM_HANDLER_KEY = DOM_HANDLER_KEY.subKey("DOM_CUSTOM_HANDLER_KEY");
static final SemKey<AttributeChildInvocationHandler> DOM_ATTRIBUTE_HANDLER_KEY = DOM_HANDLER_KEY.subKey("DOM_ATTRIBUTE_HANDLER_KEY");
private final EventDispatcher<DomEventListener> myListeners = EventDispatcher.create(DomEventListener.class);
private final GenericValueReferenceProvider myGenericValueReferenceProvider = new GenericValueReferenceProvider();
private final Project myProject;
private final SemService mySemService;
private final DomApplicationComponent myApplicationComponent;
private boolean myChanging;
public DomManagerImpl(Project project) {
super(project);
myProject = project;
mySemService = SemService.getSemService(project);
myApplicationComponent = DomApplicationComponent.getInstance();
final PomModel pomModel = PomManager.getModel(project);
pomModel.addModelListener(new PomModelListener() {
@Override
public void modelChanged(PomModelEvent event) {
if (myChanging) return;
final XmlChangeSet changeSet = (XmlChangeSet)event.getChangeSet(pomModel.getModelAspect(XmlAspect.class));
if (changeSet != null) {
for (XmlFile file : changeSet.getChangedFiles()) {
DomFileElementImpl<DomElement> element = getCachedFileElement(file);
if (element != null) {
fireEvent(new DomEvent(element, false));
}
}
}
}
@Override
public boolean isAspectChangeInteresting(PomModelAspect aspect) {
return aspect instanceof XmlAspect;
}
}, project);
VirtualFileManager.getInstance().addVirtualFileListener(new VirtualFileAdapter() {
private final List<DomEvent> myDeletionEvents = new SmartList<DomEvent>();
@Override
public void contentsChanged(@NotNull VirtualFileEvent event) {
if (!event.isFromSave()) {
fireEvents(calcDomChangeEvents(event.getFile()));
}
}
@Override
public void fileMoved(@NotNull VirtualFileMoveEvent event) {
fireEvents(calcDomChangeEvents(event.getFile()));
}
@Override
public void beforeFileDeletion(@NotNull final VirtualFileEvent event) {
myDeletionEvents.addAll(calcDomChangeEvents(event.getFile()));
}
@Override
public void fileDeleted(@NotNull VirtualFileEvent event) {
if (!myDeletionEvents.isEmpty()) {
fireEvents(myDeletionEvents);
myDeletionEvents.clear();
}
}
@Override
public void propertyChanged(@NotNull VirtualFilePropertyEvent event) {
final VirtualFile file = event.getFile();
if (!file.isDirectory() && VirtualFile.PROP_NAME.equals(event.getPropertyName())) {
fireEvents(calcDomChangeEvents(file));
}
}
}, myProject);
}
public long getPsiModificationCount() {
return PsiManager.getInstance(getProject()).getModificationTracker().getModificationCount();
}
public <T extends DomInvocationHandler> void cacheHandler(SemKey<T> key, XmlElement element, T handler) {
mySemService.setCachedSemElement(key, element, handler);
}
private PsiFile getCachedPsiFile(VirtualFile file) {
return ((PsiManagerEx)PsiManager.getInstance(myProject)).getFileManager().getCachedPsiFile(file);
}
private List<DomEvent> calcDomChangeEvents(final VirtualFile file) {
if (!(file instanceof NewVirtualFile)) return Collections.emptyList();
final List<DomEvent> events = ContainerUtil.newArrayList();
VfsUtilCore.visitChildrenRecursively(file, new VirtualFileVisitor() {
@Override
public boolean visitFile(@NotNull VirtualFile file) {
if (!ProjectFileIndex.SERVICE.getInstance(myProject).isInContent(file)) return false;
if (!file.isDirectory() && StdFileTypes.XML == file.getFileType()) {
final PsiFile psiFile = getCachedPsiFile(file);
if (psiFile != null && StdFileTypes.XML.equals(psiFile.getFileType()) && psiFile instanceof XmlFile) {
final DomFileElementImpl domElement = getCachedFileElement((XmlFile)psiFile);
if (domElement != null) {
events.add(new DomEvent(domElement, false));
}
}
}
return true;
}
@Nullable
@Override
public Iterable<VirtualFile> getChildrenIterable(@NotNull VirtualFile file) {
return ((NewVirtualFile)file).getCachedChildren();
}
});
return events;
}
@SuppressWarnings({"MethodOverridesStaticMethodOfSuperclass"})
public static DomManagerImpl getDomManager(Project project) {
return (DomManagerImpl)DomManager.getDomManager(project);
}
@Override
public void addDomEventListener(DomEventListener listener, Disposable parentDisposable) {
myListeners.addListener(listener, parentDisposable);
}
@Override
public final ConverterManager getConverterManager() {
return ServiceManager.getService(ConverterManager.class);
}
@Override
public final void addPsiReferenceFactoryForClass(Class clazz, PsiReferenceFactory psiReferenceFactory) {
myGenericValueReferenceProvider.addReferenceProviderForClass(clazz, psiReferenceFactory);
}
@Override
public final ModelMerger createModelMerger() {
return new ModelMergerImpl();
}
final void fireEvent(DomEvent event) {
if (mySemService.isInsideAtomicChange()) return;
incModificationCount();
myListeners.getMulticaster().eventOccured(event);
}
private void fireEvents(Collection<DomEvent> events) {
for (DomEvent event : events) {
fireEvent(event);
}
}
@Override
public final DomGenericInfo getGenericInfo(final Type type) {
return myApplicationComponent.getStaticGenericInfo(type);
}
@Nullable
public static DomInvocationHandler getDomInvocationHandler(DomElement proxy) {
if (proxy instanceof DomFileElement) {
return null;
}
if (proxy instanceof DomInvocationHandler) {
return (DomInvocationHandler)proxy;
}
final InvocationHandler handler = AdvancedProxy.getInvocationHandler(proxy);
if (handler instanceof StableInvocationHandler) {
//noinspection unchecked
final DomElement element = ((StableInvocationHandler<DomElement>)handler).getWrappedElement();
return element == null ? null : getDomInvocationHandler(element);
}
if (handler instanceof DomInvocationHandler) {
return (DomInvocationHandler)handler;
}
return null;
}
@NotNull
public static DomInvocationHandler getNotNullHandler(DomElement proxy) {
DomInvocationHandler handler = getDomInvocationHandler(proxy);
if (handler == null) {
throw new AssertionError("null handler for " + proxy);
}
return handler;
}
public static StableInvocationHandler getStableInvocationHandler(Object proxy) {
return (StableInvocationHandler)AdvancedProxy.getInvocationHandler(proxy);
}
public DomApplicationComponent getApplicationComponent() {
return myApplicationComponent;
}
@Override
public final Project getProject() {
return myProject;
}
@Override
@NotNull
public final <T extends DomElement> DomFileElementImpl<T> getFileElement(final XmlFile file, final Class<T> aClass, String rootTagName) {
//noinspection unchecked
if (file.getUserData(MOCK_DESCRIPTION) == null) {
file.putUserData(MOCK_DESCRIPTION, new MockDomFileDescription<T>(aClass, rootTagName, file));
mySemService.clearCache();
}
final DomFileElementImpl<T> fileElement = getFileElement(file);
assert fileElement != null;
return fileElement;
}
@SuppressWarnings({"unchecked"})
@NotNull
final <T extends DomElement> FileDescriptionCachedValueProvider<T> getOrCreateCachedValueProvider(final XmlFile xmlFile) {
//noinspection ConstantConditions
return mySemService.getSemElement(FILE_DESCRIPTION_KEY, xmlFile);
}
public final Set<DomFileDescription> getFileDescriptions(String rootTagName) {
return myApplicationComponent.getFileDescriptions(rootTagName);
}
public final Set<DomFileDescription> getAcceptingOtherRootTagNameDescriptions() {
return myApplicationComponent.getAcceptingOtherRootTagNameDescriptions();
}
@NotNull
@NonNls
public final String getComponentName() {
return getClass().getName();
}
final void runChange(Runnable change) {
final boolean b = setChanging(true);
try {
change.run();
}
finally {
setChanging(b);
}
}
final boolean setChanging(final boolean changing) {
boolean oldChanging = myChanging;
if (changing) {
assert !oldChanging;
}
myChanging = changing;
return oldChanging;
}
@Override
@Nullable
public final <T extends DomElement> DomFileElementImpl<T> getFileElement(XmlFile file) {
if (file == null) return null;
if (!(file.getFileType() instanceof DomSupportEnabled)) return null;
final VirtualFile virtualFile = file.getVirtualFile();
if (virtualFile != null && virtualFile.isDirectory()) return null;
return this.<T>getOrCreateCachedValueProvider(file).getFileElement();
}
@Nullable
static <T extends DomElement> DomFileElementImpl<T> getCachedFileElement(@NotNull XmlFile file) {
//noinspection unchecked
return SoftReference.dereference(file.getUserData(CACHED_FILE_ELEMENT));
}
@Override
@Nullable
public final <T extends DomElement> DomFileElementImpl<T> getFileElement(XmlFile file, Class<T> domClass) {
final DomFileDescription description = getDomFileDescription(file);
if (description != null && myApplicationComponent.assignabilityCache.isAssignable(domClass, description.getRootElementClass())) {
return getFileElement(file);
}
return null;
}
@Override
@Nullable
public final DomElement getDomElement(final XmlTag element) {
if (myChanging) return null;
final DomInvocationHandler handler = getDomHandler(element);
return handler != null ? handler.getProxy() : null;
}
@Override
@Nullable
public GenericAttributeValue getDomElement(final XmlAttribute attribute) {
if (myChanging) return null;
final AttributeChildInvocationHandler handler = mySemService.getSemElement(DOM_ATTRIBUTE_HANDLER_KEY, attribute);
return handler == null ? null : (GenericAttributeValue)handler.getProxy();
}
@Nullable
public DomInvocationHandler getDomHandler(final XmlElement tag) {
if (tag == null) return null;
List<DomInvocationHandler> cached = mySemService.getCachedSemElements(DOM_HANDLER_KEY, tag);
if (cached != null && !cached.isEmpty()) {
return cached.get(0);
}
return mySemService.getSemElement(DOM_HANDLER_KEY, tag);
}
@Override
@Nullable
public AbstractDomChildrenDescription findChildrenDescription(@NotNull final XmlTag tag, @NotNull final DomElement parent) {
return findChildrenDescription(tag, getDomInvocationHandler(parent));
}
static AbstractDomChildrenDescription findChildrenDescription(final XmlTag tag, final DomInvocationHandler parent) {
final DomGenericInfoEx info = parent.getGenericInfo();
return info.findChildrenDescription(parent, tag.getLocalName(), tag.getNamespace(), false, tag.getName());
}
public final boolean isDomFile(@Nullable PsiFile file) {
return file instanceof XmlFile && getFileElement((XmlFile)file) != null;
}
@Nullable
public final DomFileDescription<?> getDomFileDescription(PsiElement element) {
if (element instanceof XmlElement) {
final PsiFile psiFile = element.getContainingFile();
if (psiFile instanceof XmlFile) {
return getDomFileDescription((XmlFile)psiFile);
}
}
return null;
}
@Override
public final <T extends DomElement> T createMockElement(final Class<T> aClass, final Module module, final boolean physical) {
final XmlFile file = (XmlFile)PsiFileFactory.getInstance(myProject).createFileFromText("a.xml", StdFileTypes.XML, "", (long)0, physical);
file.putUserData(MOCK_ELEMENT_MODULE, module);
file.putUserData(MOCK, new Object());
return getFileElement(file, aClass, "I_sincerely_hope_that_nobody_will_have_such_a_root_tag_name").getRootElement();
}
@Override
public final boolean isMockElement(DomElement element) {
return DomUtil.getFile(element).getUserData(MOCK) != null;
}
@Override
public final <T extends DomElement> T createStableValue(final Factory<T> provider) {
return createStableValue(provider, new Condition<T>() {
@Override
public boolean value(T t) {
return t.isValid();
}
});
}
@Override
public final <T> T createStableValue(final Factory<T> provider, final Condition<T> validator) {
final T initial = provider.create();
assert initial != null;
final StableInvocationHandler handler = new StableInvocationHandler<T>(initial, provider, validator);
final Set<Class> intf = new HashSet<Class>();
ContainerUtil.addAll(intf, initial.getClass().getInterfaces());
intf.add(StableElement.class);
//noinspection unchecked
return (T)AdvancedProxy.createProxy(initial.getClass().getSuperclass(), intf.toArray(new Class[intf.size()]),
handler);
}
public final <T extends DomElement> void registerFileDescription(final DomFileDescription<T> description, Disposable parentDisposable) {
registerFileDescription(description);
Disposer.register(parentDisposable, new Disposable() {
@Override
public void dispose() {
getFileDescriptions(description.getRootTagName()).remove(description);
getAcceptingOtherRootTagNameDescriptions().remove(description);
}
});
}
@Override
public final void registerFileDescription(final DomFileDescription description) {
mySemService.clearCache();
myApplicationComponent.registerFileDescription(description);
}
@Override
@NotNull
public final DomElement getResolvingScope(GenericDomValue element) {
final DomFileDescription<?> description = DomUtil.getFileElement(element).getFileDescription();
return description.getResolveScope(element);
}
@Override
@Nullable
public final DomElement getIdentityScope(DomElement element) {
final DomFileDescription description = DomUtil.getFileElement(element).getFileDescription();
return description.getIdentityScope(element);
}
@Override
public TypeChooserManager getTypeChooserManager() {
return myApplicationComponent.getTypeChooserManager();
}
public void performAtomicChange(@NotNull Runnable change) {
mySemService.performAtomicChange(change);
if (!mySemService.isInsideAtomicChange()) {
incModificationCount();
}
}
public SemService getSemService() {
return mySemService;
}
}