summaryrefslogtreecommitdiff
path: root/platform/lang-impl/src/com/intellij/openapi/roots/impl/storage/ClasspathStorage.java
blob: 36e344ff0accae3849a1e78877cbebbaba9601fd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
/*
 * 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.roots.impl.storage;

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.PathMacroManager;
import com.intellij.openapi.components.StateStorage;
import com.intellij.openapi.components.StateStorageException;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.project.ProjectBundle;
import com.intellij.openapi.project.impl.ProjectMacrosUtil;
import com.intellij.openapi.roots.ModifiableRootModel;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ModuleRootModel;
import com.intellij.openapi.roots.impl.ModuleRootManagerImpl;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.WriteExternalException;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.*;
import com.intellij.openapi.vfs.tracker.VirtualFileTracker;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.messages.MessageBus;
import org.jdom.Element;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.serialization.JpsProjectLoader;

import java.io.File;
import java.io.IOException;
import java.util.*;

/**
 * Created by IntelliJ IDEA.
 * User: Vladislav.Kaznacheev
 * Date: Mar 9, 2007
 * Time: 1:42:06 PM
 */
public class ClasspathStorage implements StateStorage {
  private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.roots.impl.storage.ClasspathStorage");

  @NonNls public static final String SPECIAL_STORAGE = "special";

  public static final String DEFAULT_STORAGE_DESCR = ProjectBundle.message("project.roots.classpath.format.default.descr");

  @NonNls public static final String CLASSPATH_DIR_OPTION = JpsProjectLoader.CLASSPATH_DIR_ATTRIBUTE;

  @NonNls private static final String COMPONENT_TAG = "component";
  private Object mySession;
  private final ClasspathStorageProvider.ClasspathConverter myConverter;


  public ClasspathStorage(Module module) {
    myConverter = getProvider(ClassPathStorageUtil.getStorageType(module)).createConverter(module);
    final MessageBus messageBus = module.getMessageBus();
    final VirtualFileTracker virtualFileTracker =
      (VirtualFileTracker)module.getPicoContainer().getComponentInstanceOfType(VirtualFileTracker.class);
    if (virtualFileTracker != null && messageBus != null) {
      final ArrayList<VirtualFile> files = new ArrayList<VirtualFile>();
      try {
        myConverter.getFileSet().listFiles(files);
        for (VirtualFile file : files) {
          final Listener listener = messageBus.syncPublisher(STORAGE_TOPIC);
          virtualFileTracker.addTracker(file.getUrl(), new VirtualFileAdapter() {
            @Override
            public void contentsChanged(@NotNull final VirtualFileEvent event) {
              listener.storageFileChanged(event, ClasspathStorage.this);
            }
          }, true, module);
        }
      }
      catch (UnsupportedOperationException e) {
        //UnsupportedStorageProvider doesn't mean any files
      }
    }
  }

  private FileSet getFileSet() {
    return myConverter.getFileSet();
  }

  @Override
  @Nullable
  public <T> T getState(final Object component, @NotNull final String componentName, Class<T> stateClass, @Nullable T mergeInto)
    throws StateStorageException {
    assert component instanceof ModuleRootManager;
    assert componentName.equals("NewModuleRootManager");
    assert stateClass == ModuleRootManagerImpl.ModuleRootManagerState.class;

    try {
      final Module module = ((ModuleRootManagerImpl)component).getModule();
      final Element element = new Element(COMPONENT_TAG);
      final Set<String> macros;
      ModifiableRootModel model = null;
      try {
        model = ((ModuleRootManagerImpl)component).getModifiableModel();
        macros = myConverter.getClasspath(model, element);
      }
      finally {
        if (model != null) {
          model.dispose();
        }
      }

      final boolean macrosOk = ProjectMacrosUtil.checkNonIgnoredMacros(module.getProject(), macros);
      PathMacroManager.getInstance(module).expandPaths(element);
      ModuleRootManagerImpl.ModuleRootManagerState moduleRootManagerState = new ModuleRootManagerImpl.ModuleRootManagerState();
      moduleRootManagerState.readExternal(element);
      if (!macrosOk) {
        throw new StateStorageException(ProjectBundle.message("project.load.undefined.path.variables.error"));
      }
      //noinspection unchecked
      return (T)moduleRootManagerState;
    }
    catch (InvalidDataException e) {
      throw new StateStorageException(e.getMessage());
    }
    catch (IOException e) {
      throw new StateStorageException(e.getMessage());
    }
  }

  @Override
  public boolean hasState(final Object component, @NotNull final String componentName, final Class<?> aClass, final boolean reloadData)
    throws StateStorageException {
    return true;
  }

  public void setState(@NotNull Object component, @NotNull String componentName, @NotNull Object state) throws StateStorageException {
    assert component instanceof ModuleRootManager;
    assert componentName.equals("NewModuleRootManager");
    assert state.getClass() == ModuleRootManagerImpl.ModuleRootManagerState.class;

    try {
      myConverter.setClasspath((ModuleRootManagerImpl)component);
    }
    catch (WriteExternalException e) {
      throw new StateStorageException(e.getMessage());
    }
    catch (IOException e) {
      throw new StateStorageException(e.getMessage());
    }
  }

  @Override
  @NotNull
  public ExternalizationSession startExternalization() {
    final ExternalizationSession session = new ExternalizationSession() {
      @Override
      public void setState(@NotNull final Object component, final String componentName, @NotNull final Object state, final Storage storageSpec)
        throws StateStorageException {
        assert mySession == this;
        ClasspathStorage.this.setState(component, componentName, state);
      }
    };

    mySession = session;
    return session;
  }

  @Override
  @NotNull
  public SaveSession startSave(@NotNull final ExternalizationSession externalizationSession) {
    assert mySession == externalizationSession;

    final SaveSession session = new MySaveSession();

    mySession = session;
    return session;
  }

  private static void convert2Io(List<File> list, ArrayList<VirtualFile> virtualFiles) {
    for (VirtualFile virtualFile : virtualFiles) {
      list.add(VfsUtilCore.virtualToIoFile(virtualFile));
    }
  }

  @Override
  public void finishSave(@NotNull final SaveSession saveSession) {
    try {
      LOG.assertTrue(mySession == saveSession);
    }
    finally {
      mySession = null;
    }
  }

  @Override
  public void reload(@NotNull final Set<String> changedComponents) throws StateStorageException {
  }

  public boolean needsSave() throws StateStorageException {
    return getFileSet().hasChanged();
  }

  public void save() throws StateStorageException {
    final Ref<IOException> ref = new Ref<IOException>();
    ApplicationManager.getApplication().runWriteAction(new Runnable() {
      @Override
      public void run() {
        try {
          getFileSet().commit();
        }
        catch (IOException e) {
          ref.set(e);
        }
      }
    });

    if (!ref.isNull()) {
      throw new StateStorageException(ref.get());
    }
  }

  @NotNull
  public static ClasspathStorageProvider getProvider(@NotNull String type) {
    for (ClasspathStorageProvider provider : getProviders()) {
      if (type.equals(provider.getID())) {
        return provider;
      }
    }
    return new UnsupportedStorageProvider(type);
  }

  @NotNull
  public static List<ClasspathStorageProvider> getProviders() {
    final List<ClasspathStorageProvider> list = new ArrayList<ClasspathStorageProvider>();
    list.add(new DefaultStorageProvider());
    ContainerUtil.addAll(list, Extensions.getExtensions(ClasspathStorageProvider.EXTENSION_POINT_NAME));
    return list;
  }

  @NotNull
  public static String getModuleDir(@NotNull Module module) {
    return new File(module.getModuleFilePath()).getParent();
  }

  public static String getStorageRootFromOptions(final Module module) {
    final String moduleRoot = getModuleDir(module);
    final String storageRef = module.getOptionValue(CLASSPATH_DIR_OPTION);
    if (storageRef == null) {
      return moduleRoot;
    }
    else if (FileUtil.isAbsolute(storageRef)) {
      return storageRef;
    }
    else {
      return FileUtil.toSystemIndependentName(new File(moduleRoot, storageRef).getPath());
    }
  }

  public static void setStorageType(@NotNull ModuleRootModel model, @NotNull String storageID) {
    final Module module = model.getModule();
    final String oldStorageType = ClassPathStorageUtil.getStorageType(module);
    if (oldStorageType.equals(storageID)) {
      return;
    }

    getProvider(oldStorageType).detach(module);

    if (storageID.equals(ClassPathStorageUtil.DEFAULT_STORAGE)) {
      module.clearOption(ClassPathStorageUtil.CLASSPATH_OPTION);
      module.clearOption(CLASSPATH_DIR_OPTION);
    }
    else {
      module.setOption(ClassPathStorageUtil.CLASSPATH_OPTION, storageID);
      module.setOption(CLASSPATH_DIR_OPTION, getProvider(storageID).getContentRoot(model));
    }
  }

  public static void moduleRenamed(Module module, String newName) {
    getProvider(ClassPathStorageUtil.getStorageType(module)).moduleRenamed(module, newName);
  }

  public static void modulePathChanged(Module module, String path) {
    getProvider(ClassPathStorageUtil.getStorageType(module)).modulePathChanged(module, path);
  }

  private static class DefaultStorageProvider implements ClasspathStorageProvider {
    @Override
    @NonNls
    public String getID() {
      return ClassPathStorageUtil.DEFAULT_STORAGE;
    }

    @Override
    @Nls
    public String getDescription() {
      return DEFAULT_STORAGE_DESCR;
    }

    @Override
    public void assertCompatible(final ModuleRootModel model) throws ConfigurationException {
    }

    @Override
    public void detach(Module module) {
    }

    @Override
    public void moduleRenamed(Module module, String newName) {
      //do nothing
    }

    @Override
    public ClasspathConverter createConverter(Module module) {
      throw new UnsupportedOperationException(getDescription());
    }

    @Override
    public String getContentRoot(ModuleRootModel model) {
      return null;
    }

    @Override
    public void modulePathChanged(Module module, String path) {
    }
  }

  public static class UnsupportedStorageProvider implements ClasspathStorageProvider {
    private final String myType;

    public UnsupportedStorageProvider(final String type) {
      myType = type;
    }

    @Override
    @NonNls
    public String getID() {
      return myType;
    }

    @Override
    @Nls
    public String getDescription() {
      return "Unsupported classpath format " + myType;
    }

    @Override
    public void assertCompatible(final ModuleRootModel model) throws ConfigurationException {
      throw new UnsupportedOperationException(getDescription());
    }

    @Override
    public void detach(final Module module) {
      throw new UnsupportedOperationException(getDescription());
    }

    @Override
    public void moduleRenamed(Module module, String newName) {
      throw new UnsupportedOperationException(getDescription());
    }

    @Override
    public ClasspathConverter createConverter(final Module module) {
      return new ClasspathConverter() {
        @Override
        public FileSet getFileSet() {
          throw new StateStorageException(getDescription());
        }

        @Override
        public Set<String> getClasspath(final ModifiableRootModel model, final Element element) throws InvalidDataException {
          throw new InvalidDataException(getDescription());
        }

        @Override
        public void setClasspath(ModuleRootModel model) throws WriteExternalException {
          throw new WriteExternalException(getDescription());
        }
      };
    }

    @Override
    public String getContentRoot(ModuleRootModel model) {
      return null;
    }

    @Override
    public void modulePathChanged(Module module, String path) {
      throw new UnsupportedOperationException(getDescription());
    }
  }

  private class MySaveSession implements SaveSession, SafeWriteRequestor {
    public boolean needsSave() throws StateStorageException {
      assert mySession == this;
      return ClasspathStorage.this.needsSave();
    }

    @Override
    public void save() throws StateStorageException {
      assert mySession == this;
      ClasspathStorage.this.save();
    }

    @Override
    @Nullable
    public Set<String> analyzeExternalChanges(@NotNull final Set<Pair<VirtualFile, StateStorage>> changedFiles) {
      return null;
    }

    @NotNull
    @Override
    public Collection<File> getStorageFilesToSave() throws StateStorageException {
      if (needsSave()) {
        final List<File> list = new ArrayList<File>();
        final ArrayList<VirtualFile> virtualFiles = new ArrayList<VirtualFile>();
        getFileSet().listModifiedFiles(virtualFiles);
        convert2Io(list, virtualFiles);
        return list;
      }
      else {
        return Collections.emptyList();
      }
    }

    @NotNull
    @Override
    public List<File> getAllStorageFiles() {
      List<File> list = new ArrayList<File>();
      ArrayList<VirtualFile> virtualFiles = new ArrayList<VirtualFile>();
      getFileSet().listFiles(virtualFiles);
      convert2Io(list, virtualFiles);
      return list;
    }
  }
}