[exception] 17394
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / components / impl / stores / XmlElementStorage.java
blobcf3e7cc0694057c9b6abd8114e418dd610407bde
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.openapi.components.impl.stores;
18 import com.intellij.application.options.PathMacrosCollector;
19 import com.intellij.openapi.Disposable;
20 import com.intellij.openapi.components.*;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.extensions.ExtensionPoint;
23 import com.intellij.openapi.extensions.Extensions;
24 import com.intellij.openapi.options.StreamProvider;
25 import com.intellij.openapi.util.Disposer;
26 import com.intellij.openapi.util.JDOMUtil;
27 import com.intellij.openapi.util.Pair;
28 import com.intellij.openapi.util.WriteExternalException;
29 import com.intellij.openapi.vfs.VirtualFile;
30 import com.intellij.util.containers.StringInterner;
31 import com.intellij.util.io.fs.IFile;
32 import org.jdom.Attribute;
33 import org.jdom.Document;
34 import org.jdom.Element;
35 import org.jetbrains.annotations.NonNls;
36 import org.jetbrains.annotations.NotNull;
37 import org.jetbrains.annotations.Nullable;
39 import java.io.IOException;
40 import java.util.*;
42 public abstract class XmlElementStorage implements StateStorage, Disposable {
43 @NonNls private static final Set<String> OBSOLETE_COMPONENT_NAMES = new HashSet<String>(Arrays.asList(
44 "Palette"
45 ));
46 private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.components.impl.stores.XmlElementStorage");
48 @NonNls private static final String COMPONENT = "component";
49 @NonNls private static final String ATTR_NAME = "name";
50 @NonNls private static final String NAME = ATTR_NAME;
52 protected TrackingPathMacroSubstitutor myPathMacroSubstitutor;
53 @NotNull private final String myRootElementName;
54 private Object mySession;
55 private StorageData myLoadedData;
56 protected static StringInterner ourInterner = new StringInterner();
57 protected final StreamProvider myStreamProvider;
58 protected final String myFileSpec;
59 private final ComponentRoamingManager myComponentRoamingManager;
60 protected final boolean myIsProjectSettings;
61 protected boolean myBlockSavingTheContent = false;
62 protected Integer myUpToDateHash;
63 protected Integer myProviderUpToDateHash;
64 private boolean mySavingDisabled = false;
66 private final Map<String, Element> myStorageComponentStates = new TreeMap<String, Element>();
68 private final ComponentVersionProvider myLocalVersionProvider;
69 private final ComponentVersionProvider myRemoteVersionProvider;
71 protected Map<String, Long> myProviderVersions = null;
73 protected ComponentVersionListener myListener = new ComponentVersionListener(){
74 public void componentStateChanged(String componentName) {
75 myLocalVersionProvider.changeVersion(componentName, System.currentTimeMillis());
80 protected XmlElementStorage(@Nullable final TrackingPathMacroSubstitutor pathMacroSubstitutor,
81 @NotNull Disposable parentDisposable,
82 @NotNull String rootElementName,
83 StreamProvider streamProvider,
84 String fileSpec,
85 ComponentRoamingManager componentRoamingManager, ComponentVersionProvider localComponentVersionsProvider) {
86 myPathMacroSubstitutor = pathMacroSubstitutor;
87 myRootElementName = rootElementName;
88 myStreamProvider = streamProvider;
89 myFileSpec = fileSpec;
90 myComponentRoamingManager = componentRoamingManager;
91 Disposer.register(parentDisposable, this);
92 myIsProjectSettings = "$PROJECT_FILE$".equals(myFileSpec) || myFileSpec.startsWith("$PROJECT_CONFIG_DIR$");
94 myLocalVersionProvider = localComponentVersionsProvider;
96 myRemoteVersionProvider = new ComponentVersionProvider(){
97 public long getVersion(String name) {
98 if (myProviderVersions == null) {
99 loadProviderVersions();
102 return myProviderVersions.containsKey(name) ? myProviderVersions.get(name).longValue() : 0;
106 public void changeVersion(String name, long version) {
107 if (myProviderVersions == null) {
108 loadProviderVersions();
111 myProviderVersions.put(name, version);
116 @Nullable
117 protected abstract Document loadDocument() throws StateStorageException;
119 @Nullable
120 public synchronized Element getState(final String componentName) throws StateStorageException {
121 final StorageData storageData = getStorageData(false);
122 final Element state = storageData.getState(componentName);
126 if (state != null) {
127 if (!myStorageComponentStates.containsKey(componentName)) {
128 myStorageComponentStates.put(componentName, state);
130 storageData.removeState(componentName);
133 return state;
136 public boolean hasState(final Object component, final String componentName, final Class<?> aClass, final boolean reloadData) throws StateStorageException {
137 final StorageData storageData = getStorageData(reloadData);
138 return storageData.hasState(componentName);
141 @Nullable
142 public <T> T getState(final Object component, final String componentName, Class<T> stateClass, @Nullable T mergeInto) throws StateStorageException {
143 final Element element = getState(componentName);
144 return DefaultStateSerializer.deserializeState(element, stateClass, mergeInto);
147 @NotNull
148 protected StorageData getStorageData(final boolean reloadData) throws StateStorageException {
149 if (myLoadedData != null && !reloadData) return myLoadedData;
151 myLoadedData = loadData(true, myListener);
153 return myLoadedData;
156 @NotNull
157 protected StorageData loadData(final boolean useProvidersData, ComponentVersionListener listener) throws StateStorageException {
158 Document document = loadDocument();
160 StorageData result = createStorageData();
162 if (document != null) {
163 loadState(result, document.getRootElement());
165 else {
166 LOG.info("Document was not loaded for " + myFileSpec);
169 if (!myIsProjectSettings && useProvidersData) {
170 for (RoamingType roamingType : RoamingType.values()) {
171 if (roamingType != RoamingType.DISABLED && roamingType != RoamingType.GLOBAL) {
172 try {
173 if (myStreamProvider.isEnabled()) {
174 final Document sharedDocument = StorageUtil.loadDocument(myStreamProvider.loadContent(myFileSpec, roamingType));
176 if (sharedDocument != null) {
177 filterComponentsDisabledForRoaming(sharedDocument.getRootElement(), roamingType);
178 filterOutOfDateComponents(sharedDocument.getRootElement());
180 loadState(result, sharedDocument.getRootElement());
184 catch (Exception e) {
185 LOG.warn(e);
193 return result;
196 protected void loadState(final StorageData result, final Element element) throws StateStorageException {
197 if (myPathMacroSubstitutor != null) {
198 myPathMacroSubstitutor.expandPaths(element);
201 JDOMUtil.internElement(element, ourInterner);
203 try {
204 result.load(element);
205 result.checkUnknownMacros(myPathMacroSubstitutor);
207 catch (IOException e) {
208 throw new StateStorageException(e);
212 @NotNull
213 protected StorageData createStorageData() {
214 return new StorageData(myRootElementName);
217 public void setDefaultState(final Element element) {
218 myLoadedData = createStorageData();
219 try {
220 loadState(myLoadedData, element);
222 catch (StateStorageException e) {
223 LOG.error(e);
227 @NotNull
228 public ExternalizationSession startExternalization() {
229 try {
230 final ExternalizationSession session = new MyExternalizationSession(getStorageData(false).clone(), myListener);
232 mySession = session;
233 return session;
235 catch (StateStorageException e) {
236 throw new RuntimeException(e);
240 @NotNull
241 public SaveSession startSave(final ExternalizationSession externalizationSession) {
242 assert mySession == externalizationSession;
244 final SaveSession saveSession = mySavingDisabled ? createNullSession() : createSaveSession((MyExternalizationSession)externalizationSession);
245 mySession = saveSession;
246 return saveSession;
249 private SaveSession createNullSession() {
250 return new SaveSession(){
251 public void save() throws StateStorageException {
255 public Set<String> analyzeExternalChanges(final Set<Pair<VirtualFile, StateStorage>> changedFiles) {
256 return Collections.emptySet();
259 public Collection<IFile> getStorageFilesToSave() throws StateStorageException {
260 return Collections.emptySet();
263 public List<IFile> getAllStorageFiles() {
264 return Collections.emptyList();
269 protected abstract MySaveSession createSaveSession(final MyExternalizationSession externalizationSession);
271 public void finishSave(final SaveSession saveSession) {
272 try {
273 LOG.assertTrue(mySession == saveSession, "mySession=" + mySession + " saveSession=" + saveSession);
274 } finally {
275 mySession = null;
279 public void disableSaving() {
280 mySavingDisabled = true;
283 protected class MyExternalizationSession implements ExternalizationSession {
284 private final StorageData myStorageData;
285 private final ComponentVersionListener myListener;
287 public MyExternalizationSession(final StorageData storageData, ComponentVersionListener listener) {
288 myStorageData = storageData;
289 myListener = listener;
292 public void setState(final Object component, final String componentName, final Object state, final Storage storageSpec) throws StateStorageException {
293 assert mySession == this;
295 try {
296 setState(componentName, DefaultStateSerializer.serializeState(state, storageSpec));
298 catch (WriteExternalException e) {
299 LOG.debug(e);
303 private synchronized void setState(final String componentName, final Element element) {
304 if (element.getAttributes().isEmpty() && element.getChildren().isEmpty()) return;
306 myStorageData.setState(componentName, element);
308 Element oldElement = myStorageComponentStates.get(componentName);
309 try {
310 if (oldElement != null && !JDOMUtil.areElementsEqual(oldElement, element)) {
311 myListener.componentStateChanged(componentName);
314 finally {
315 myStorageComponentStates.put(componentName, (Element)element.clone());
323 protected Document getDocument(StorageData data) {
324 final Element element = data.save();
326 if (myPathMacroSubstitutor != null) {
327 try {
328 myPathMacroSubstitutor.collapsePaths(element);
329 } finally {
330 myPathMacroSubstitutor.reset();
334 return new Document(element);
337 protected abstract class MySaveSession implements SaveSession {
338 StorageData myStorageData;
339 private Document myDocumentToSave;
341 public MySaveSession(MyExternalizationSession externalizationSession) {
342 myStorageData = externalizationSession.myStorageData;
346 public final boolean needsSave() throws StateStorageException {
347 assert mySession == this;
348 return _needsSave(calcHash());
351 private boolean _needsSave(final Integer hash) {
352 if (myBlockSavingTheContent) return false;
353 if (myUpToDateHash == null) {
354 if (hash != null) {
355 if (!physicalContentNeedsSave()) {
356 myUpToDateHash = hash;
357 return false;
359 else {
360 return true;
363 else {
364 return true;
367 else {
368 if (hash != null) {
369 if (hash.intValue() == myUpToDateHash.intValue()) {
370 return false;
372 if (!physicalContentNeedsSave()) {
373 myUpToDateHash = hash;
374 return false;
376 else {
377 return true;
381 else {
382 return physicalContentNeedsSave();
388 protected boolean physicalContentNeedsSave() {
389 return true;
392 protected abstract void doSave() throws StateStorageException;
394 public void clearHash() {
395 myUpToDateHash = null;
398 protected Integer calcHash() {
399 return null;
402 public final void save() throws StateStorageException {
403 assert mySession == this;
405 if (myBlockSavingTheContent) return;
407 Integer hash = calcHash();
409 try {
410 saveForProviders(hash);
412 finally {
413 saveLocally(hash);
417 private void saveLocally(final Integer hash) {
418 try {
419 if (!isHashUpToDate(hash)) {
420 if (_needsSave(hash)) {
421 doSave();
425 finally {
426 myUpToDateHash = hash;
430 private void saveForProviders(final Integer hash) {
431 if (myProviderUpToDateHash == null || !myProviderUpToDateHash.equals(hash)) {
432 try {
433 if (!myIsProjectSettings) {
434 for (RoamingType roamingType : RoamingType.values()) {
435 if (roamingType != RoamingType.DISABLED) {
436 try {
437 Document copy = (Document)getDocumentToSave().clone();
438 filterComponentsDisabledForRoaming(copy.getRootElement(), roamingType);
440 if (copy.getRootElement().getChildren().size() > 0) {
441 StorageUtil.sendContent(myStreamProvider, myFileSpec, copy, roamingType, true);
442 Document versionDoc = createVersionDocument(copy);
443 if (versionDoc.getRootElement().getChildren().size() > 0) {
444 StorageUtil.sendContent(myStreamProvider, myFileSpec + ".ver", versionDoc, roamingType, true);
448 catch (IOException e) {
449 LOG.warn(e);
456 finally {
457 myProviderUpToDateHash = hash;
463 private boolean isHashUpToDate(final Integer hash) {
464 return myUpToDateHash != null && myUpToDateHash.equals(hash);
467 public boolean isHashUpToDate() {
468 return isHashUpToDate(calcHash());
471 protected Document getDocumentToSave() {
472 if (myDocumentToSave != null) return myDocumentToSave;
474 final Element element = myStorageData.save();
475 myDocumentToSave = new Document(element);
477 if (myPathMacroSubstitutor != null) {
478 myPathMacroSubstitutor.collapsePaths(element);
481 return myDocumentToSave;
484 public StorageData getData() {
485 return myStorageData;
488 @Nullable
489 public Set<String> analyzeExternalChanges(final Set<Pair<VirtualFile,StateStorage>> changedFiles) {
490 try {
491 Document document = loadDocument();
493 StorageData storageData = createStorageData();
495 if (document != null) {
496 loadState(storageData, document.getRootElement());
497 return storageData.getDifference(myStorageData, myPathMacroSubstitutor);
499 else {
500 return Collections.emptySet();
505 catch (StateStorageException e) {
506 LOG.info(e);
509 return null;
513 private Document createVersionDocument(Document copy) {
514 return new Document(StateStorageManagerImpl.createComponentVersionsXml(loadVersions(copy)));
517 private Map<String, Long> loadVersions(Document copy) {
519 HashMap<String, Long> result = new HashMap<String, Long>();
521 List list = copy.getRootElement().getChildren(COMPONENT);
522 for (Object o : list) {
523 if (o instanceof Element) {
524 Element component = (Element)o;
525 String name = component.getAttributeValue(ATTR_NAME);
526 if (name != null) {
527 long version = myLocalVersionProvider.getVersion(name);
528 if (version != 0) {
529 result.put(name, version);
535 return result;
538 public void dispose() {
541 protected static class StorageData {
542 private final Map<String, Element> myComponentStates;
543 protected final String myRootElementName;
544 private Integer myHash;
546 public StorageData(final String rootElementName) {
547 myComponentStates = new TreeMap<String, Element>();
548 myRootElementName = rootElementName;
551 protected StorageData(StorageData storageData) {
552 myRootElementName = storageData.myRootElementName;
553 myComponentStates = new TreeMap<String, Element>(storageData.myComponentStates);
556 protected void load(@NotNull Element rootElement) throws IOException {
557 final Element[] elements = JDOMUtil.getElements(rootElement);
558 for (Element element : elements) {
559 if (element.getName().equals(COMPONENT)) {
560 final String name = element.getAttributeValue(NAME);
562 if (name == null) {
563 LOG.info("Broken content in file : " + this);
564 continue;
567 if (OBSOLETE_COMPONENT_NAMES.contains(name)) continue;
569 element.detach();
571 if (element.getAttributes().size() > 1 || !element.getChildren().isEmpty()) {
572 assert element.getAttributeValue(NAME) != null : "No name attribute for component: " + name + " in " + this;
574 Element existingElement = myComponentStates.get(name);
576 if (existingElement != null) {
577 element = mergeElements(name, element, existingElement);
580 myComponentStates.put(name, element);
586 private Element mergeElements(final String name, final Element element1, final Element element2) {
587 ExtensionPoint<XmlConfigurationMerger> point = Extensions.getRootArea().getExtensionPoint("com.intellij.componentConfigurationMerger");
588 XmlConfigurationMerger[] mergers = point.getExtensions();
589 for (XmlConfigurationMerger merger : mergers) {
590 if (merger.getComponentName().equals(name)) {
591 return merger.merge(element1, element2);
594 return element1;
597 @NotNull
598 protected Element save() {
599 Element rootElement = new Element(myRootElementName);
601 for (String componentName : myComponentStates.keySet()) {
602 assert componentName != null;
603 final Element element = myComponentStates.get(componentName);
605 if (element.getAttribute(NAME) == null) element.setAttribute(NAME, componentName);
607 rootElement.addContent((Element)element.clone());
610 return rootElement;
613 @Nullable
614 public Element getState(final String name) {
615 final Element e = myComponentStates.get(name);
617 if (e != null) {
618 assert e.getAttributeValue(NAME) != null : "No name attribute for component: " + name + " in " + this;
619 e.removeAttribute(NAME);
622 return e;
625 public void removeState(final String componentName) {
626 myComponentStates.remove(componentName);
627 clearHash();
630 private void setState(@NotNull final String componentName, final Element element) {
631 element.setName(COMPONENT);
633 //componentName should be first!
634 final List attributes = new ArrayList(element.getAttributes());
635 for (Object attribute : attributes) {
636 Attribute attr = (Attribute)attribute;
637 element.removeAttribute(attr);
640 element.setAttribute(NAME, componentName);
642 for (Object attribute : attributes) {
643 Attribute attr = (Attribute)attribute;
644 element.setAttribute(attr.getName(), attr.getValue());
647 myComponentStates.put(componentName, element);
648 clearHash();
651 public StorageData clone() {
652 return new StorageData(this);
655 public final int getHash() {
656 if (myHash == null) {
657 myHash = computeHash();
659 return myHash.intValue();
662 protected int computeHash() {
663 int result = 0;
665 for (String name : myComponentStates.keySet()) {
666 result = 31*result + name.hashCode();
667 result = 31*result + JDOMUtil.getTreeHash(myComponentStates.get(name));
670 return result;
673 protected void clearHash() {
674 myHash = null;
677 public Set<String> getDifference(final StorageData storageData, PathMacroSubstitutor substitutor) {
678 Set<String> bothStates = new HashSet<String>(myComponentStates.keySet());
679 bothStates.retainAll(storageData.myComponentStates.keySet());
681 Set<String> diffs = new HashSet<String>();
682 diffs.addAll(storageData.myComponentStates.keySet());
683 diffs.addAll(myComponentStates.keySet());
684 diffs.removeAll(bothStates);
686 for (String componentName : bothStates) {
687 final Element e1 = myComponentStates.get(componentName);
688 final Element e2 = storageData.myComponentStates.get(componentName);
690 // some configurations want to collapse path elements in writeExternal so make sure paths are expanded
691 if (substitutor != null) {
692 substitutor.expandPaths(e2);
695 if (!JDOMUtil.areElementsEqual(e1, e2)) {
696 diffs.add(componentName);
701 return diffs;
704 public boolean isEmpty() {
705 return myComponentStates.size() == 0;
708 public boolean hasState(final String componentName) {
709 return myComponentStates.containsKey(componentName);
712 public void checkUnknownMacros(TrackingPathMacroSubstitutor pathMacroSubstitutor) {
713 for (String componentName : myComponentStates.keySet()) {
714 final Set<String> unknownMacros = PathMacrosCollector.getMacroNames(myComponentStates.get(componentName));
715 if (!unknownMacros.isEmpty()) {
716 pathMacroSubstitutor.addUnknownMacros(componentName, unknownMacros);
722 public void resetData(){
723 myLoadedData = null;
726 public void reload(@NotNull final Set<String> changedComponents) throws StateStorageException {
727 final StorageData storageData = loadData(false, myListener);
729 final StorageData oldLoadedData = myLoadedData;
731 if (oldLoadedData != null) {
732 Set<String> componentsToRetain = new HashSet<String>(oldLoadedData.myComponentStates.keySet());
733 componentsToRetain.addAll(changedComponents);
735 // add empty configuration tags for removed components
736 for (String componentToRetain : componentsToRetain) {
737 if (!storageData.myComponentStates.containsKey(componentToRetain) && myStorageComponentStates.containsKey(componentToRetain)) {
738 Element emptyElement = new Element("component");
739 LOG.info("Create empty component element for " + componentsToRetain);
740 emptyElement.setAttribute(NAME, componentToRetain);
741 storageData.myComponentStates.put(componentToRetain, emptyElement);
745 storageData.myComponentStates.keySet().retainAll(componentsToRetain);
748 myLoadedData = storageData;
751 private void filterComponentsDisabledForRoaming(final Element element, final RoamingType roamingType) {
752 final List components = element.getChildren(COMPONENT);
754 List<Element> toDelete = new ArrayList<Element>();
756 for (Object componentObj : components) {
757 final Element componentElement = (Element)componentObj;
758 final String nameAttr = componentElement.getAttributeValue(NAME);
760 if (myComponentRoamingManager.getRoamingType(nameAttr) != roamingType) {
761 toDelete.add(componentElement);
765 for (Element toDeleteElement : toDelete) {
766 element.removeContent(toDeleteElement);
770 private void filterOutOfDateComponents(final Element element) {
771 final List components = element.getChildren(COMPONENT);
773 List<Element> toDelete = new ArrayList<Element>();
775 for (Object componentObj : components) {
776 final Element componentElement = (Element)componentObj;
777 final String nameAttr = componentElement.getAttributeValue(NAME);
779 if (myRemoteVersionProvider.getVersion(nameAttr) <= myLocalVersionProvider.getVersion(nameAttr)) {
780 toDelete.add(componentElement);
782 else {
783 myLocalVersionProvider.changeVersion(nameAttr, myRemoteVersionProvider.getVersion(nameAttr));
787 for (Element toDeleteElement : toDelete) {
788 element.removeContent(toDeleteElement);
793 private void loadProviderVersions() {
794 myProviderVersions = new TreeMap<String, Long>();
795 for (RoamingType type : RoamingType.values()) {
796 Document doc = null;
797 if (myStreamProvider.isEnabled()) {
798 try {
799 doc = StorageUtil.loadDocument(myStreamProvider.loadContent(myFileSpec + ".ver", type));
801 catch (IOException e) {
802 LOG.debug(e);
805 if (doc != null) {
806 StateStorageManagerImpl.loadComponentVersions(myProviderVersions, doc);