| /* |
| * 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.application.ApplicationManager; |
| import com.intellij.openapi.module.Module; |
| import com.intellij.openapi.util.Condition; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.openapi.vfs.VirtualFileWithId; |
| import com.intellij.psi.impl.PsiFileEx; |
| import com.intellij.psi.stubs.ObjectStubTree; |
| import com.intellij.psi.stubs.StubTreeLoader; |
| import com.intellij.psi.xml.XmlFile; |
| import com.intellij.semantic.SemElement; |
| import com.intellij.util.SmartList; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.intellij.util.containers.HashSet; |
| import com.intellij.util.xml.*; |
| import com.intellij.util.xml.events.DomEvent; |
| import com.intellij.util.xml.stubs.FileStub; |
| import com.intellij.xml.util.XmlUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.lang.ref.WeakReference; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * @author peter |
| */ |
| @SuppressWarnings({"HardCodedStringLiteral", "StringConcatenationInsideStringBufferAppend"}) |
| class FileDescriptionCachedValueProvider<T extends DomElement> implements SemElement{ |
| |
| private final XmlFile myXmlFile; |
| private volatile boolean myComputed; |
| private volatile DomFileElementImpl<T> myLastResult; |
| private final MyCondition myCondition = new MyCondition(); |
| |
| private final DomManagerImpl myDomManager; |
| private final DomService myDomService; |
| |
| public FileDescriptionCachedValueProvider(final DomManagerImpl domManager, final XmlFile xmlFile) { |
| myDomManager = domManager; |
| myXmlFile = xmlFile; |
| myDomService = DomService.getInstance(); |
| } |
| |
| @Nullable |
| public final DomFileElementImpl<T> getFileElement() { |
| if (myComputed) return myLastResult; |
| |
| DomFileElementImpl<T> result = _computeFileElement(false, myDomService.getXmlFileHeader(myXmlFile), null); |
| |
| synchronized (myCondition) { |
| if (myComputed) return myLastResult; |
| |
| myLastResult = result; |
| WeakReference<DomFileElementImpl> ref = result != null ? new WeakReference<DomFileElementImpl>(result) : null; |
| myXmlFile.putUserData(DomManagerImpl.CACHED_FILE_ELEMENT, ref); |
| myComputed = true; |
| return result; |
| } |
| } |
| |
| @Nullable |
| private DomFileElementImpl<T> _computeFileElement(final boolean fireEvents, |
| @NotNull final XmlFileHeader rootTagName, @Nullable StringBuilder sb) { |
| if (sb != null) { |
| sb.append(rootTagName).append("\n"); |
| } |
| |
| if (!myXmlFile.isValid()) { |
| return null; |
| } |
| if (sb != null) { |
| sb.append("File is valid\n"); |
| } |
| |
| if (!(myXmlFile.getFileType() instanceof DomSupportEnabled)) { |
| return null; |
| } |
| |
| if (sb != null) { |
| sb.append("File is of dom file type\n"); |
| } |
| |
| final DomFileDescription<T> description = findFileDescription(rootTagName, sb); |
| |
| final DomFileElementImpl oldValue = getLastValue(); |
| if (sb != null) { |
| sb.append("last " + oldValue + "\n"); |
| } |
| final List<DomEvent> events = fireEvents ? new SmartList<DomEvent>() : Collections.<DomEvent>emptyList(); |
| if (oldValue != null) { |
| if (fireEvents) { |
| events.add(new DomEvent(oldValue, false)); |
| } |
| } |
| |
| if (description == null) { |
| return null; |
| } |
| |
| final Class<T> rootElementClass = description.getRootElementClass(); |
| final XmlName xmlName = DomImplUtil.createXmlName(description.getRootTagName(), rootElementClass, null); |
| assert xmlName != null; |
| final EvaluatedXmlNameImpl rootTagName1 = EvaluatedXmlNameImpl.createEvaluatedXmlName(xmlName, xmlName.getNamespaceKey(), false); |
| |
| VirtualFile file = myXmlFile.getVirtualFile(); |
| FileStub stub = null; |
| if (description.hasStubs() && file instanceof VirtualFileWithId && !isFileParsed()) { |
| ApplicationManager.getApplication().assertReadAccessAllowed(); |
| if (!XmlUtil.isStubBuilding()) { |
| ObjectStubTree stubTree = StubTreeLoader.getInstance().readOrBuild(myXmlFile.getProject(), file, myXmlFile); |
| if (stubTree != null) { |
| stub = (FileStub)stubTree.getRoot(); |
| } |
| } |
| } |
| |
| DomFileElementImpl<T> result = new DomFileElementImpl<T>(myXmlFile, rootElementClass, rootTagName1, myDomManager, description, stub); |
| if (sb != null) { |
| sb.append("success " + result + "\n"); |
| } |
| |
| if (fireEvents) { |
| events.add(new DomEvent(result, true)); |
| } |
| return result; |
| } |
| |
| private boolean isFileParsed() { |
| return myXmlFile instanceof PsiFileEx && ((PsiFileEx)myXmlFile).isContentsLoaded(); |
| } |
| |
| @Nullable |
| private DomFileDescription<T> findFileDescription(final XmlFileHeader rootTagName, @Nullable StringBuilder sb) { |
| final DomFileDescription<T> mockDescription = myXmlFile.getUserData(DomManagerImpl.MOCK_DESCRIPTION); |
| if (mockDescription != null) return mockDescription; |
| |
| if (sb != null) { |
| sb.append("no mock\n"); |
| } |
| |
| final XmlFile originalFile = (XmlFile)myXmlFile.getOriginalFile(); |
| if (sb != null) { |
| sb.append("original: " + originalFile + "\n"); |
| } |
| if (!originalFile.equals(myXmlFile)) { |
| final FileDescriptionCachedValueProvider<T> provider = myDomManager.getOrCreateCachedValueProvider(originalFile); |
| final DomFileElementImpl<T> element = provider.getFileElement(); |
| if (sb != null) { |
| sb.append("originalDom " + element + "\n"); |
| } |
| return element == null ? null : element.getFileDescription(); |
| } |
| |
| //noinspection unchecked |
| final Set<DomFileDescription> namedDescriptions = myDomManager.getFileDescriptions(rootTagName.getRootTagLocalName()); |
| if (sb != null) { |
| sb.append("named " + new HashSet<DomFileDescription>(namedDescriptions) + "\n"); |
| } |
| DomFileDescription<T> description = ContainerUtil.find(namedDescriptions, myCondition); |
| if (description == null) { |
| final Set<DomFileDescription> unnamed = myDomManager.getAcceptingOtherRootTagNameDescriptions(); |
| description = ContainerUtil.find(unnamed, myCondition); |
| } |
| if (sb != null) { |
| sb.append("found " + description + "\n"); |
| } |
| return description; |
| } |
| |
| @Nullable |
| final DomFileElementImpl<T> getLastValue() { |
| return myLastResult; |
| } |
| |
| public String getFileElementWithLogging() { |
| final XmlFileHeader rootTagName = myDomService.getXmlFileHeader(myXmlFile); |
| final StringBuilder log = new StringBuilder(); |
| myLastResult = _computeFileElement(false, rootTagName, log); |
| return log.toString(); |
| } |
| |
| private class MyCondition implements Condition<DomFileDescription> { |
| public Module module; |
| |
| @Override |
| public boolean value(final DomFileDescription description) { |
| return description.isMyFile(myXmlFile, module); |
| } |
| } |
| |
| } |