blob: 33fac00bcd859f10c5fdaa684273bcf456203a20 [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.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.util.ArrayUtil;
import gnu.trove.THashMap;
import org.jdom.Attribute;
import org.jdom.Element;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.*;
public class StorageData {
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.components.impl.stores.StorageData");
@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(final String rootElementName) {
myComponentStates = new THashMap<String, Element>();
myRootElementName = rootElementName;
}
StorageData(StorageData storageData) {
myRootElementName = storageData.myRootElementName;
myComponentStates = new THashMap<String, Element>(storageData.myComponentStates);
}
public void load(@NotNull Element rootElement) throws IOException {
final Element[] elements = JDOMUtil.getElements(rootElement);
for (Element element : elements) {
if (element.getName().equals(COMPONENT)) {
final String name = element.getAttributeValue(NAME);
if (name == null) {
LOG.info("Broken content in file : " + this);
continue;
}
element.detach();
if (element.getAttributes().size() > 1 || !element.getChildren().isEmpty()) {
assert element.getAttributeValue(NAME) != null : "No name attribute for component: " + name + " in " + this;
Element existingElement = myComponentStates.get(name);
if (existingElement != null) {
element = mergeElements(name, element, existingElement);
}
myComponentStates.put(name, element);
}
}
}
}
private static Element mergeElements(final String name, final Element element1, final Element element2) {
ExtensionPoint<XmlConfigurationMerger> point = Extensions.getRootArea().getExtensionPoint("com.intellij.componentConfigurationMerger");
XmlConfigurationMerger[] mergers = point.getExtensions();
for (XmlConfigurationMerger merger : mergers) {
if (merger.getComponentName().equals(name)) {
return merger.merge(element1, element2);
}
}
return element1;
}
@NotNull
protected Element save() {
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 e = myComponentStates.get(name);
if (e != null) {
assert e.getAttributeValue(NAME) != null : "No name attribute for component: " + name + " in " + this;
e.removeAttribute(NAME);
}
return e;
}
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 HashSet<String>(myComponentStates.keySet());
bothStates.retainAll(storageData.myComponentStates.keySet());
Set<String> diffs = new HashSet<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(final String componentName) {
return myComponentStates.containsKey(componentName);
}
public void checkUnknownMacros(TrackingPathMacroSubstitutor pathMacroSubstitutor) {
if (pathMacroSubstitutor == null) return;
for (String componentName : myComponentStates.keySet()) {
final Set<String> unknownMacros = PathMacrosCollector.getMacroNames(myComponentStates.get(componentName));
if (!unknownMacros.isEmpty()) {
pathMacroSubstitutor.addUnknownMacros(componentName, unknownMacros);
}
}
}
}