blob: 699440e22b5472849befa7296aaccedc659a76b8 [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.components.impl.stores;
import com.intellij.application.options.PathMacrosCollector;
import com.intellij.ide.plugins.IdeaPluginDescriptorImpl;
import com.intellij.openapi.components.PathMacroSubstitutor;
import com.intellij.openapi.components.TrackingPathMacroSubstitutor;
import com.intellij.openapi.components.XmlConfigurationMerger;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.ExtensionPoint;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ArrayUtil;
import gnu.trove.THashMap;
import gnu.trove.THashSet;
import org.jdom.Attribute;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
public class StorageData {
private static final Logger LOG = Logger.getInstance(StorageData.class);
@NonNls public static final String COMPONENT = "component";
@NonNls public static final String NAME = "name";
final Map<String, Element> myComponentStates;
protected final String myRootElementName;
private int myHash = -1;
public StorageData(@NotNull String rootElementName) {
myComponentStates = new THashMap<String, Element>();
myRootElementName = rootElementName;
}
StorageData(@NotNull StorageData storageData) {
myRootElementName = storageData.myRootElementName;
myComponentStates = new THashMap<String, Element>(storageData.myComponentStates);
}
public void load(@NotNull Element rootElement, @Nullable PathMacroSubstitutor pathMacroSubstitutor, boolean intern) {
if (pathMacroSubstitutor != null) {
pathMacroSubstitutor.expandPaths(rootElement);
}
for (Iterator<Element> iterator = rootElement.getChildren(COMPONENT).iterator(); iterator.hasNext(); ) {
Element element = iterator.next();
String name = element.getAttributeValue(NAME);
if (name == null) {
LOG.info("Broken content in file : " + this);
continue;
}
if (element.getAttributes().size() > 1 || !element.getChildren().isEmpty()) {
assert element.getAttributeValue(NAME) != null : "No name attribute for component: " + name + " in " + this;
iterator.remove();
if (intern) {
IdeaPluginDescriptorImpl.internJDOMElement(element);
}
Element serverElement = myComponentStates.get(name);
if (serverElement != null) {
element = mergeElements(name, element, serverElement);
}
myComponentStates.put(name, element);
}
}
if (pathMacroSubstitutor instanceof TrackingPathMacroSubstitutor) {
for (String componentName : myComponentStates.keySet()) {
((TrackingPathMacroSubstitutor)pathMacroSubstitutor).addUnknownMacros(componentName, PathMacrosCollector.getMacroNames(myComponentStates.get(componentName)));
}
}
}
@NotNull
private static Element mergeElements(@NotNull String name, @NotNull Element localElement, @NotNull Element serverElement) {
ExtensionPoint<XmlConfigurationMerger> point = Extensions.getRootArea().getExtensionPoint("com.intellij.componentConfigurationMerger");
for (XmlConfigurationMerger merger : point.getExtensions()) {
if (merger.getComponentName().equals(name)) {
return merger.merge(serverElement, localElement);
}
}
return serverElement;
}
@Nullable
protected Element save() {
if (myComponentStates.isEmpty()) {
return null;
}
Element rootElement = new Element(myRootElementName);
String[] componentNames = ArrayUtil.toStringArray(myComponentStates.keySet());
Arrays.sort(componentNames);
for (String componentName : componentNames) {
assert componentName != null;
final Element element = myComponentStates.get(componentName);
if (element.getAttribute(NAME) == null) element.setAttribute(NAME, componentName);
rootElement.addContent(element.clone());
}
return rootElement;
}
@Nullable
public Element getState(final String name) {
final Element element = myComponentStates.get(name);
if (element != null) {
assert element.getAttributeValue(NAME) != null : "No name attribute for component: " + name + " in " + this;
element.removeAttribute(NAME);
}
return element;
}
void removeState(final String componentName) {
myComponentStates.remove(componentName);
clearHash();
}
void setState(@NotNull final String componentName, final Element element) {
element.setName(COMPONENT);
//componentName should be first!
final List<Attribute> attributes = new ArrayList<Attribute>(element.getAttributes());
for (Attribute attribute : attributes) {
element.removeAttribute(attribute);
}
element.setAttribute(NAME, componentName);
for (Attribute attribute : attributes) {
element.setAttribute(attribute.getName(), attribute.getValue());
}
myComponentStates.put(componentName, element);
clearHash();
}
@Override
public StorageData clone() {
return new StorageData(this);
}
public final int getHash() {
if (myHash == -1) {
myHash = computeHash();
if (myHash == -1) {
myHash = 0;
}
}
return myHash;
}
protected int computeHash() {
int result = 0;
for (String name : myComponentStates.keySet()) {
result = 31 * result + name.hashCode();
result = 31 * result + JDOMUtil.getTreeHash(myComponentStates.get(name));
}
return result;
}
protected void clearHash() {
myHash = -1;
}
public Set<String> getDifference(final StorageData storageData, PathMacroSubstitutor substitutor) {
Set<String> bothStates = new THashSet<String>(myComponentStates.keySet());
bothStates.retainAll(storageData.myComponentStates.keySet());
Set<String> diffs = new THashSet<String>();
diffs.addAll(storageData.myComponentStates.keySet());
diffs.addAll(myComponentStates.keySet());
diffs.removeAll(bothStates);
for (String componentName : bothStates) {
final Element e1 = myComponentStates.get(componentName);
final Element e2 = storageData.myComponentStates.get(componentName);
// some configurations want to collapse path elements in writeExternal so make sure paths are expanded
if (substitutor != null) {
substitutor.expandPaths(e2);
}
if (!JDOMUtil.areElementsEqual(e1, e2)) {
diffs.add(componentName);
}
}
return diffs;
}
public boolean isEmpty() {
return myComponentStates.isEmpty();
}
public boolean hasState(@NotNull String componentName) {
return myComponentStates.containsKey(componentName);
}
@NotNull
public static Element load(@NotNull VirtualFile file) throws IOException, JDOMException {
InputStream stream = file.getInputStream();
try {
return JDOMUtil.loadDocument(stream).getRootElement();
}
finally {
stream.close();
}
}
}