/*
* 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.openapi.components.impl.stores;
import com.intellij.openapi.components.PathMacroSubstitutor;
import com.intellij.openapi.components.TrackingPathMacroSubstitutor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.containers.SmartHashSet;
import com.intellij.util.containers.StringInterner;
import consulo.application.options.PathMacrosService;
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.util.*;
import static com.intellij.openapi.components.impl.stores.StateMap.getNewByteIfDiffers;
public class StorageData extends StorageDataBase {
private static final Logger LOG = Logger.getInstance(StorageData.class);
@NonNls public static final String COMPONENT = "component";
@NonNls public static final String NAME = "name";
private final StateMap myStates;
protected final String myRootElementName;
public StorageData(@NotNull String rootElementName) {
myStates = new StateMap();
myRootElementName = rootElementName;
}
StorageData(@NotNull StorageData storageData) {
myRootElementName = storageData.myRootElementName;
myStates = new StateMap(storageData.myStates);
}
@Override
@NotNull
public Set<String> getComponentNames() {
return myStates.keys();
}
public void load(@NotNull Element rootElement, @Nullable PathMacroSubstitutor pathMacroSubstitutor, boolean intern) {
if (pathMacroSubstitutor != null) {
pathMacroSubstitutor.expandPaths(rootElement);
}
StringInterner interner = intern ? new StringInterner() : null;
for (Iterator<Element> iterator = rootElement.getChildren(COMPONENT).iterator(); iterator.hasNext(); ) {
Element element = iterator.next();
String name = getComponentNameIfValid(element);
if (name == null || !(element.getAttributes().size() > 1 || !element.getChildren().isEmpty())) {
continue;
}
iterator.remove();
if (interner != null) {
JDOMUtil.internElement(element, interner);
}
myStates.put(name, element);
if (pathMacroSubstitutor instanceof TrackingPathMacroSubstitutor) {
((TrackingPathMacroSubstitutor)pathMacroSubstitutor).addUnknownMacros(name, PathMacrosService.getInstance().getMacroNames(element));
}
// remove only after "getMacroNames" - some PathMacroFilter requires element name attribute
element.removeAttribute(NAME);
}
}
@Nullable
static String getComponentNameIfValid(@NotNull Element element) {
String name = element.getAttributeValue(NAME);
if (StringUtil.isEmpty(name)) {
LOG.warn("No name attribute for component in " + JDOMUtil.writeElement(element));
return null;
}
return name;
}
@Nullable
protected Element save(@NotNull Map<String, Element> newLiveStates) {
if (myStates.isEmpty()) {
return null;
}
Element rootElement = new Element(myRootElementName);
String[] componentNames = ArrayUtil.toStringArray(myStates.keys());
Arrays.sort(componentNames);
for (String componentName : componentNames) {
assert componentName != null;
Element element = myStates.getElement(componentName, newLiveStates);
// name attribute should be first
List<Attribute> elementAttributes = element.getAttributes();
if (elementAttributes.isEmpty()) {
element.setAttribute(NAME, componentName);
}
else {
Attribute nameAttribute = element.getAttribute(NAME);
if (nameAttribute == null) {
nameAttribute = new Attribute(NAME, componentName);
elementAttributes.add(0, nameAttribute);
}
else {
nameAttribute.setValue(componentName);
if (elementAttributes.get(0) != nameAttribute) {
elementAttributes.remove(nameAttribute);
elementAttributes.add(0, nameAttribute);
}
}
}
rootElement.addContent(element);
}
return rootElement;
}
@Nullable
public Element getState(@NotNull String name) {
return myStates.getState(name);
}
@Nullable
public Element getStateAndArchive(@NotNull String name) {
return myStates.getStateAndArchive(name);
}
@Nullable
static StorageData setStateAndCloneIfNeed(@NotNull String componentName, @Nullable Element newState, @NotNull StorageData storageData, @NotNull Map<String, Element> newLiveStates) {
Object oldState = storageData.myStates.get(componentName);
if (newState == null || JDOMUtil.isEmpty(newState)) {
if (oldState == null) {
return null;
}
StorageData newStorageData = storageData.clone();
newStorageData.myStates.remove(componentName);
return newStorageData;
}
prepareElement(newState);
newLiveStates.put(componentName, newState);
byte[] newBytes = null;
if (oldState instanceof Element) {
if (JDOMUtil.areElementsEqual((Element)oldState, newState)) {
return null;
}
}
else if (oldState != null) {
newBytes = getNewByteIfDiffers(componentName, newState, (byte[])oldState);
if (newBytes == null) {
return null;
}
}
StorageData newStorageData = storageData.clone();
newStorageData.myStates.put(componentName, newBytes == null ? newState : newBytes);
return newStorageData;
}
@Nullable
final Object setState(@NotNull String componentName, @Nullable Element newState, @NotNull Map<String, Element> newLiveStates) {
if (newState == null || JDOMUtil.isEmpty(newState)) {
return myStates.remove(componentName);
}
prepareElement(newState);
newLiveStates.put(componentName, newState);
Object oldState = myStates.get(componentName);
byte[] newBytes = null;
if (oldState instanceof Element) {
if (JDOMUtil.areElementsEqual((Element)oldState, newState)) {
return null;
}
}
else if (oldState != null) {
newBytes = getNewByteIfDiffers(componentName, newState, (byte[])oldState);
if (newBytes == null) {
return null;
}
}
myStates.put(componentName, newBytes == null ? newState : newBytes);
return newState;
}
private static void prepareElement(@NotNull Element state) {
if (state.getParent() != null) {
LOG.warn("State element must not have parent " + JDOMUtil.writeElement(state));
state.detach();
}
state.setName(COMPONENT);
}
@Override
public StorageData clone() {
return new StorageData(this);
}
// newStorageData - myStates contains only live (unarchived) states
public Set<String> getChangedComponentNames(@NotNull StorageData newStorageData, @Nullable PathMacroSubstitutor substitutor) {
Set<String> bothStates = new SmartHashSet<String>(myStates.keys());
bothStates.retainAll(newStorageData.myStates.keys());
Set<String> diffs = new SmartHashSet<String>();
diffs.addAll(newStorageData.myStates.keys());
diffs.addAll(myStates.keys());
diffs.removeAll(bothStates);
for (String componentName : bothStates) {
myStates.compare(componentName, newStorageData.myStates, diffs);
}
return diffs;
}
@Override
public boolean hasState(@NotNull String componentName) {
return myStates.hasState(componentName);
}
}