1 package com
.intellij
.openapi
.components
.impl
.stores
;
3 import com
.intellij
.openapi
.Disposable
;
4 import com
.intellij
.openapi
.application
.ex
.ApplicationManagerEx
;
5 import com
.intellij
.openapi
.components
.*;
6 import com
.intellij
.openapi
.diagnostic
.Logger
;
7 import com
.intellij
.openapi
.options
.StreamProvider
;
8 import com
.intellij
.openapi
.util
.Disposer
;
9 import com
.intellij
.openapi
.util
.JDOMUtil
;
10 import com
.intellij
.openapi
.util
.Pair
;
11 import com
.intellij
.openapi
.util
.text
.StringUtil
;
12 import com
.intellij
.openapi
.vfs
.VirtualFile
;
13 import com
.intellij
.util
.containers
.MultiMap
;
14 import com
.intellij
.util
.io
.fs
.IFile
;
15 import org
.jdom
.Document
;
16 import org
.jdom
.Element
;
17 import org
.jdom
.JDOMException
;
18 import org
.jetbrains
.annotations
.NotNull
;
19 import org
.jetbrains
.annotations
.Nullable
;
20 import org
.picocontainer
.MutablePicoContainer
;
21 import org
.picocontainer
.PicoContainer
;
24 import java
.io
.IOException
;
25 import java
.io
.InputStream
;
26 import java
.net
.ConnectException
;
28 import java
.util
.regex
.Matcher
;
29 import java
.util
.regex
.Pattern
;
31 public abstract class StateStorageManagerImpl
implements StateStorageManager
, Disposable
, StreamProvider
, ComponentVersionProvider
{
33 private static final Logger LOG
= Logger
.getInstance("#" + StateStorageManagerImpl
.class.getName());
35 private final Map
<String
, String
> myMacros
= new HashMap
<String
, String
>();
36 private final Map
<String
, StateStorage
> myStorages
= new HashMap
<String
, StateStorage
>();
37 private final Map
<String
, StateStorage
> myPathToStorage
= new HashMap
<String
, StateStorage
>();
38 private final TrackingPathMacroSubstitutor myPathMacroSubstitutor
;
39 private final String myRootTagName
;
40 private Object mySession
;
41 private final PicoContainer myPicoContainer
;
43 private Map
<String
, Long
> myComponentVersions
;
44 private final Object myComponentVersLock
= new Object();
46 private String myVersionsFilePath
= null;
48 private final MultiMap
<RoamingType
, StreamProvider
> myStreamProviders
= new MultiMap
<RoamingType
, StreamProvider
>();
50 public StateStorageManagerImpl(@Nullable final TrackingPathMacroSubstitutor pathMacroSubstitutor
,
51 final String rootTagName
,
52 @Nullable Disposable parentDisposable
,
53 PicoContainer picoContainer
) {
54 myPicoContainer
= picoContainer
;
55 myRootTagName
= rootTagName
;
56 myPathMacroSubstitutor
= pathMacroSubstitutor
;
57 if (parentDisposable
!= null) {
58 Disposer
.register(parentDisposable
, this);
62 public synchronized void addMacro(String macro
, String expansion
) {
63 myMacros
.put("$" + macro
+ "$", expansion
);
67 public StateStorage
getStateStorage(@NotNull final Storage storageSpec
) throws StateStorage
.StateStorageException
{
68 final String key
= getStorageSpecId(storageSpec
);
69 return getStateStorage(storageSpec
, key
);
73 private StateStorage
getStateStorage(final Storage storageSpec
, final String key
) throws StateStorage
.StateStorageException
{
74 if (myStorages
.get(key
) == null) {
75 final StateStorage stateStorage
= createStateStorage(storageSpec
);
76 putStorageToMap(key
, stateStorage
);
79 return myStorages
.get(key
);
83 public StateStorage
getFileStateStorage(final String fileName
) {
84 if (myStorages
.get(fileName
) == null) {
85 final StateStorage stateStorage
= createFileStateStorage(fileName
);
86 putStorageToMap(fileName
, stateStorage
);
89 return myStorages
.get(fileName
);
92 public Collection
<String
> getStorageFileNames() {
93 return Collections
.unmodifiableCollection(myStorages
.keySet());
96 private void putStorageToMap(final String key
, final StateStorage stateStorage
) {
97 if (stateStorage
!= null) {
98 if (stateStorage
instanceof FileBasedStorage
) {
99 //fixing problem with 2 macros for the same directory (APP_CONFIG and OPTIONS)
100 String filePath
= ((FileBasedStorage
)stateStorage
).getFilePath();
101 if (myPathToStorage
.containsKey(filePath
)) {
102 StateStorage existing
= myPathToStorage
.get(filePath
);
103 myStorages
.put(key
, existing
);
106 myPathToStorage
.put(filePath
, stateStorage
);
107 myStorages
.put(key
, stateStorage
);
111 myStorages
.put(key
, stateStorage
);
117 public long getVersion(String name
) {
118 Map
<String
, Long
> versions
= getComponentVersions();
119 return versions
.containsKey(name
) ? versions
.get(name
).longValue() : 0;
122 public void changeVersionsFilePath(String newPath
) {
123 myVersionsFilePath
= newPath
;
124 resetLocalVersions();
127 public void resetLocalVersions(){
128 synchronized (myComponentVersLock
) {
129 myComponentVersions
= null;
133 private Map
<String
, Long
> loadVersions() {
134 if (myVersionsFilePath
== null) {
135 myVersionsFilePath
= getVersionsFilePath();
138 TreeMap
<String
, Long
> result
= new TreeMap
<String
, Long
>();
139 String filePath
= getNotNullVersionsFilePath();
140 if (filePath
!= null) {
142 Document document
= JDOMUtil
.loadDocument(new File(filePath
));
143 loadComponentVersions(result
, document
);
145 catch (JDOMException e
) {
148 catch (IOException e
) {
155 private String
getNotNullVersionsFilePath() {
156 if (myVersionsFilePath
== null) {
157 myVersionsFilePath
= getVersionsFilePath();
160 return myVersionsFilePath
;
163 public static void loadComponentVersions(Map
<String
, Long
> result
, Document document
) {
164 List componentObjs
= document
.getRootElement().getChildren("component");
165 for (Object componentObj
: componentObjs
) {
166 if (componentObj
instanceof Element
) {
167 Element componentEl
= (Element
)componentObj
;
168 String name
= componentEl
.getAttributeValue("name");
169 String version
= componentEl
.getAttributeValue("version");
171 if (name
!= null && version
!= null) {
173 result
.put(name
, Long
.parseLong(version
));
175 catch (NumberFormatException e
) {
183 protected abstract String
getVersionsFilePath();
185 public void changeVersion(String name
, long version
) {
186 getComponentVersions().put(name
, version
);
190 private StateStorage
createStateStorage(final Storage storageSpec
) throws StateStorage
.StateStorageException
{
191 if (!storageSpec
.storageClass().equals(StorageAnnotationsDefaultValues
.NullStateStorage
.class)) {
192 final String key
= UUID
.randomUUID().toString();
193 ((MutablePicoContainer
)myPicoContainer
).registerComponentImplementation(key
, storageSpec
.storageClass());
195 return (StateStorage
)myPicoContainer
.getComponentInstance(key
);
197 else if (!storageSpec
.stateSplitter().equals(StorageAnnotationsDefaultValues
.NullStateSplitter
.class)) {
198 return createDirectoryStateStorage(storageSpec
.file(), storageSpec
.stateSplitter());
201 return createFileStateStorage(storageSpec
.file());
205 private static String
getStorageSpecId(final Storage storageSpec
) {
206 if (!storageSpec
.storageClass().equals(StorageAnnotationsDefaultValues
.NullStateStorage
.class)) {
207 return storageSpec
.storageClass().getName();
210 return storageSpec
.file();
214 public void clearStateStorage(@NotNull final String file
) {
215 myStorages
.remove(file
);
219 private StateStorage
createDirectoryStateStorage(final String file
, final Class
<?
extends StateSplitter
> splitterClass
)
220 throws StateStorage
.StateStorageException
{
221 final String expandedFile
= expandMacroses(file
);
222 if (expandedFile
== null) {
223 myStorages
.put(file
, null);
227 final StateSplitter splitter
;
230 splitter
= splitterClass
.newInstance();
232 catch (InstantiationException e
) {
233 throw new StateStorage
.StateStorageException(e
);
235 catch (IllegalAccessException e
) {
236 throw new StateStorage
.StateStorageException(e
);
239 return new DirectoryBasedStorage(myPathMacroSubstitutor
, expandedFile
, splitter
, this, myPicoContainer
);
243 StateStorage
createFileStateStorage(@NotNull final String fileSpec
) {
244 String expandedFile
= expandMacroses(fileSpec
);
245 if (expandedFile
== null) {
246 myStorages
.put(fileSpec
, null);
250 return createFileStateStorage(fileSpec
, expandedFile
, myRootTagName
, myPicoContainer
);
253 protected StateStorage
createFileStateStorage(final String fileSpec
, final String expandedFile
, final String rootTagName
,
254 final PicoContainer picoContainer
) {
255 return new FileBasedStorage(getMacroSubstitutor(fileSpec
), this, expandedFile
, fileSpec
, rootTagName
, this, picoContainer
,
256 ComponentRoamingManager
.getInstance(), this) {
258 protected StorageData
createStorageData() {
259 return StateStorageManagerImpl
.this.createStorageData(fileSpec
);
264 public void saveContent(final String fileSpec
, final InputStream content
, final long size
, final RoamingType roamingType
, boolean async
) {
266 for (StreamProvider streamProvider
: getStreamProviders(roamingType
)) {
268 if (streamProvider
.isEnabled()) {
269 streamProvider
.saveContent(fileSpec
, content
, size
, roamingType
, async
);
272 catch (ConnectException e
) {
273 LOG
.debug("Cannot send user profile to server: " + e
.getLocalizedMessage());
275 catch (Exception e
) {
283 public void deleteFile(final String fileSpec
, final RoamingType roamingType
) {
284 for (StreamProvider streamProvider
: getStreamProviders(roamingType
)) {
286 if (streamProvider
.isEnabled()) {
287 streamProvider
.deleteFile(fileSpec
, roamingType
);
290 catch (Exception e
) {
297 public StreamProvider
[] getStreamProviders(RoamingType type
) {
298 synchronized (myStreamProviders
) {
299 final Collection
<StreamProvider
> providers
= myStreamProviders
.get(type
);
300 return providers
.toArray(new StreamProvider
[providers
.size()]);
304 public Collection
<StreamProvider
> getStreamProviders() {
305 synchronized (myStreamProviders
) {
306 return Collections
.unmodifiableCollection(myStreamProviders
.values());
310 public InputStream
loadContent(final String fileSpec
, final RoamingType roamingType
) throws IOException
{
311 for (StreamProvider streamProvider
: getStreamProviders(roamingType
)) {
313 if (streamProvider
.isEnabled()) {
314 InputStream content
= streamProvider
.loadContent(fileSpec
, roamingType
);
316 if (content
!= null) return content
;
319 catch (ConnectException e
) {
320 LOG
.debug("Cannot send user profile o server: " + e
.getLocalizedMessage());
322 catch (Exception e
) {
331 public String
[] listSubFiles(final String fileSpec
) {
332 return new String
[0];
335 public boolean isEnabled() {
336 for (StreamProvider provider
: getStreamProviders()) {
337 if (provider
.isEnabled()) return true;
342 protected TrackingPathMacroSubstitutor
getMacroSubstitutor(@NotNull final String fileSpec
) {
343 return myPathMacroSubstitutor
;
347 protected abstract XmlElementStorage
.StorageData
createStorageData(String storageSpec
);
349 private static final Pattern MACRO_PATTERN
= Pattern
.compile("(\\$[^\\$]*\\$)");
352 public String
expandMacroses(final String file
) {
353 final Matcher matcher
= MACRO_PATTERN
.matcher(file
);
354 while (matcher
.find()) {
355 String m
= matcher
.group(1);
356 if (!myMacros
.containsKey(m
) || (!ApplicationManagerEx
.getApplication().isUnitTestMode() && myMacros
.get(m
) == null)) {
357 throw new IllegalArgumentException("Unknown macro: " + m
+ " in storage spec: " + file
);
362 String actualFile
= file
;
364 for (String macro
: myMacros
.keySet()) {
365 final String replacement
= myMacros
.get(macro
);
366 /*if (replacement == null) {
370 if (replacement
!= null) {
371 actualFile
= StringUtil
.replace(actualFile
, macro
, replacement
);
378 public ExternalizationSession
startExternalization() {
379 if (mySession
!= null) {
380 LOG
.error("Starting duplicate externalization session: " + mySession
);
382 ExternalizationSession session
= new MyExternalizationSession();
389 public SaveSession
startSave(final ExternalizationSession externalizationSession
) {
390 assert mySession
== externalizationSession
;
392 SaveSession session
= createSaveSession(externalizationSession
);
399 protected MySaveSession
createSaveSession(final ExternalizationSession externalizationSession
) {
400 return new MySaveSession((MyExternalizationSession
)externalizationSession
);
403 public void finishSave(final SaveSession saveSession
) {
405 assert mySession
== saveSession
: "mySession=" + mySession
+ " saveSession=" + saveSession
;
406 ((MySaveSession
)saveSession
).finishSave();
418 protected class MyExternalizationSession
implements ExternalizationSession
{
419 CompoundExternalizationSession myCompoundExternalizationSession
= new CompoundExternalizationSession();
421 public void setState(@NotNull final Storage
[] storageSpecs
, final Object component
, final String componentName
, final Object state
)
422 throws StateStorage
.StateStorageException
{
423 assert mySession
== this;
425 for (Storage storageSpec
: storageSpecs
) {
426 StateStorage stateStorage
= getStateStorage(storageSpec
);
427 if (stateStorage
== null) continue;
429 final StateStorage
.ExternalizationSession extSession
= myCompoundExternalizationSession
.getExternalizationSession(stateStorage
);
430 extSession
.setState(component
, componentName
, state
, storageSpec
);
434 public void setStateInOldStorage(Object component
, final String componentName
, Object state
) throws StateStorage
.StateStorageException
{
435 assert mySession
== this;
436 StateStorage stateStorage
= getOldStorage(component
, componentName
, StateStorageOperation
.WRITE
);
437 if (stateStorage
!= null) {
438 myCompoundExternalizationSession
.getExternalizationSession(stateStorage
).setState(component
, componentName
, state
, null);
444 public StateStorage
getOldStorage(Object component
, final String componentName
, final StateStorageOperation operation
) throws StateStorage
.StateStorageException
{
445 return getFileStateStorage(getOldStorageSpec(component
, componentName
, operation
));
448 protected abstract String
getOldStorageSpec(Object component
, final String componentName
, final StateStorageOperation operation
)
449 throws StateStorage
.StateStorageException
;
451 protected class MySaveSession
implements SaveSession
{
452 CompoundSaveSession myCompoundSaveSession
;
455 private final String myCreationPoint;
458 public String toString() {
459 return super.toString() + " " + myCreationPoint;
463 public MySaveSession(final MyExternalizationSession externalizationSession
) {
464 myCompoundSaveSession
= new CompoundSaveSession(externalizationSession
.myCompoundExternalizationSession
);
467 public List
<IFile
> getAllStorageFilesToSave() throws StateStorage
.StateStorageException
{
468 assert mySession
== this;
469 return myCompoundSaveSession
.getAllStorageFilesToSave();
472 public List
<IFile
> getAllStorageFiles() {
473 return myCompoundSaveSession
.getAllStorageFiles();
476 public void save() throws StateStorage
.StateStorageException
{
477 assert mySession
== this;
479 myCompoundSaveSession
.save();
482 public Set
<String
> getUsedMacros() {
483 assert mySession
== this;
484 return myCompoundSaveSession
.getUsedMacros();
487 public StateStorage
.SaveSession
getSaveSession(final String storage
) {
488 final StateStorage stateStorage
= myStorages
.get(storage
);
489 assert stateStorage
!= null;
490 return myCompoundSaveSession
.getSaveSession(stateStorage
);
493 public void finishSave() {
495 LOG
.assertTrue(mySession
== this);
497 myCompoundSaveSession
.finishSave();
501 //returns set of component which were changed, null if changes are much more than just component state.
503 public Set
<String
> analyzeExternalChanges(final Set
<Pair
<VirtualFile
, StateStorage
>> changedFiles
) {
504 Set
<String
> result
= new HashSet
<String
>();
506 nextSorage
: for (Pair
<VirtualFile
, StateStorage
> pair
: changedFiles
) {
507 final StateStorage stateStorage
= pair
.second
;
508 final StateStorage
.SaveSession saveSession
= myCompoundSaveSession
.getSaveSession(stateStorage
);
509 if (saveSession
== null) continue nextSorage
;
510 final Set
<String
> s
= saveSession
.analyzeExternalChanges(changedFiles
);
512 if (s
== null) return null;
520 public void dispose() {
523 public void registerStreamProvider(StreamProvider streamProvider
, final RoamingType type
) {
524 synchronized (myStreamProviders
) {
525 myStreamProviders
.putValue(type
, streamProvider
);
529 public void unregisterStreamProvider(StreamProvider streamProvider
, final RoamingType roamingType
) {
530 synchronized (myStreamProviders
) {
531 myStreamProviders
.removeValue(roamingType
, streamProvider
);
536 String filePath
= getNotNullVersionsFilePath();
537 if (filePath
!= null) {
538 new File(filePath
).getParentFile().mkdirs();
540 JDOMUtil
.writeDocument(new Document(createComponentVersionsXml(getComponentVersions())), filePath
, "\n");
542 catch (IOException e
) {
549 private Map
<String
, Long
> getComponentVersions() {
550 synchronized (myComponentVersLock
) {
551 if (myComponentVersions
== null) {
552 myComponentVersions
= loadVersions();
554 return myComponentVersions
;
558 public static Element
createComponentVersionsXml(Map
<String
, Long
> versions
) {
559 Element vers
= new Element("versions");
561 for (String name
: versions
.keySet()) {
562 long version
= versions
.get(name
);
564 Element element
= new Element("component");
565 vers
.addContent(element
);
566 element
.setAttribute("name", name
);
567 element
.setAttribute("version", String
.valueOf(version
));