/*
* 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.StateSplitterEx;
import com.intellij.openapi.components.TrackingPathMacroSubstitutor;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.JDOMUtil;
import com.intellij.openapi.util.text.StringUtilRt;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.StringInterner;
import consulo.application.options.PathMacrosService;
import gnu.trove.THashMap;
import gnu.trove.TObjectObjectProcedure;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.intellij.openapi.components.impl.stores.StateMap.getNewByteIfDiffers;
public class DirectoryStorageData extends StorageDataBase {
private static final Logger LOG = Logger.getInstance(DirectoryStorageData.class);
public static final String DEFAULT_EXT = ".xml";
private final Map<String, StateMap> myStates;
public DirectoryStorageData() {
this(new THashMap<>());
}
private DirectoryStorageData(@NotNull Map<String, StateMap> states) {
myStates = states;
}
@Override
@NotNull
public Set<String> getComponentNames() {
return myStates.keySet();
}
boolean isEmpty() {
return myStates.isEmpty();
}
public static boolean isStorageFile(@NotNull VirtualFile file) {
// ignore system files like .DS_Store on Mac
return StringUtilRt.endsWithIgnoreCase(file.getNameSequence(), DEFAULT_EXT);
}
public void loadFrom(@Nullable VirtualFile dir, @Nullable TrackingPathMacroSubstitutor pathMacroSubstitutor) {
if (dir == null || !dir.exists()) {
return;
}
StringInterner interner = new StringInterner();
for (VirtualFile file : dir.getChildren()) {
if (!isStorageFile(file)) {
continue;
}
try {
Element element = JDOMUtil.loadDocument(file.contentsToByteArray()).getRootElement();
String name = StorageData.getComponentNameIfValid(element);
if (name == null) {
continue;
}
if (!element.getName().equals(StorageData.COMPONENT)) {
LOG.error("Incorrect root tag name (" + element.getName() + ") in " + file.getPresentableUrl());
continue;
}
List<Element> elementChildren = element.getChildren();
if (elementChildren.isEmpty()) {
continue;
}
Element state = (Element)elementChildren.get(0).detach();
JDOMUtil.internElement(state, interner);
if (pathMacroSubstitutor != null) {
pathMacroSubstitutor.expandPaths(state);
pathMacroSubstitutor.addUnknownMacros(name, PathMacrosService.getInstance().getMacroNames(state));
}
setState(name, file.getName(), state);
}
catch (IOException | JDOMException e) {
LOG.info("Unable to load state", e);
}
}
}
@Nullable
public static DirectoryStorageData setStateAndCloneIfNeed(@NotNull String componentName,
@Nullable String fileName,
@Nullable Element newState,
@NotNull DirectoryStorageData storageData) {
StateMap fileToState = storageData.myStates.get(componentName);
Object oldState = fileToState == null || fileName == null ? null : fileToState.get(fileName);
if (fileName == null || newState == null || JDOMUtil.isEmpty(newState)) {
if (fileName == null) {
if (fileToState == null) {
return null;
}
}
else if (oldState == null) {
return null;
}
DirectoryStorageData newStorageData = storageData.clone();
if (fileName == null) {
newStorageData.myStates.remove(componentName);
}
else {
StateMap clonedFileToState = newStorageData.myStates.get(componentName);
if (clonedFileToState.size() == 1) {
newStorageData.myStates.remove(componentName);
}
else {
clonedFileToState.remove(fileName);
if (clonedFileToState.isEmpty()) {
newStorageData.myStates.remove(componentName);
}
}
}
return newStorageData;
}
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;
}
}
DirectoryStorageData newStorageData = storageData.clone();
newStorageData.put(componentName, fileName, newBytes == null ? newState : newBytes);
return newStorageData;
}
@Nullable
public Object setState(@NotNull String componentName, @Nullable String fileName, @Nullable Element newState) {
StateMap fileToState = myStates.get(componentName);
if (fileName == null || newState == null || JDOMUtil.isEmpty(newState)) {
if (fileToState == null) {
return null;
}
else if (fileName == null) {
return myStates.remove(componentName);
}
else {
Object oldState = fileToState.remove(fileName);
if (fileToState.isEmpty()) {
myStates.remove(componentName);
}
return oldState;
}
}
if (fileToState == null) {
fileToState = new StateMap();
myStates.put(componentName, fileToState);
fileToState.put(fileName, newState);
}
else {
Object oldState = fileToState.get(fileName);
byte[] newBytes = null;
if (oldState instanceof Element) {
if (JDOMUtil.areElementsEqual((Element)oldState, newState)) {
return null;
}
}
else if (oldState != null) {
newBytes = getNewByteIfDiffers(fileName, newState, (byte[])oldState);
if (newBytes == null) {
return null;
}
}
fileToState.put(fileName, newBytes == null ? newState : newBytes);
}
return newState;
}
private void put(@NotNull String componentName, @NotNull String fileName, @NotNull Object state) {
StateMap fileToState = myStates.get(componentName);
if (fileToState == null) {
fileToState = new StateMap();
myStates.put(componentName, fileToState);
}
fileToState.put(fileName, state);
}
void processComponent(@NotNull String componentName, @NotNull TObjectObjectProcedure<String, Object> consumer) {
StateMap map = myStates.get(componentName);
if (map != null) {
map.forEachEntry(consumer);
}
}
@Override
protected DirectoryStorageData clone() {
return new DirectoryStorageData(new THashMap<>(myStates));
}
public void clear() {
myStates.clear();
}
@Override
public boolean hasState(@NotNull String componentName) {
StateMap fileToState = myStates.get(componentName);
return fileToState != null && fileToState.hasStates();
}
@Nullable
public Element getCompositeStateAndArchive(@NotNull String componentName, @NotNull StateSplitterEx splitter) {
StateMap fileToState = myStates.get(componentName);
Element state = new Element(StorageData.COMPONENT);
if (fileToState == null || fileToState.isEmpty()) {
return state;
}
for (String fileName : fileToState.keys()) {
Element subState = fileToState.getStateAndArchive(fileName);
if (subState == null) {
return null;
}
splitter.mergeStateInto(state, subState);
}
return state;
}
@NotNull
public Element stateToElement(@NotNull String key, @Nullable Object state) {
return StateMap.stateToElement(key, state, Collections.<String, Element>emptyMap());
}
@NotNull
public Set<String> getFileNames(@NotNull String componentName) {
StateMap fileToState = myStates.get(componentName);
return fileToState == null || fileToState.isEmpty() ? Collections.<String>emptySet() : fileToState.keys();
}
}