mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
<root>
|
||||
<item name='org.hamcrest.SelfDescribing void describeTo(org.hamcrest.Description) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name='org.hamcrest.TypeSafeDiagnosingMatcher boolean matchesSafely(T, org.hamcrest.Description) 0'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
<item name='org.hamcrest.TypeSafeDiagnosingMatcher boolean matchesSafely(T, org.hamcrest.Description) 1'>
|
||||
<annotation name='org.jetbrains.annotations.NotNull'/>
|
||||
</item>
|
||||
</root>
|
||||
@@ -17,6 +17,7 @@ import org.jetbrains.io.ChannelExceptionHandler
|
||||
import org.jetbrains.io.Decoder
|
||||
import org.jetbrains.io.MessageDecoder
|
||||
import org.jetbrains.io.NettyUtil
|
||||
import org.jetbrains.testFramework.FixtureRule
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
package org.jetbrains.ide
|
||||
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import com.intellij.openapi.vfs.CharsetToolkit
|
||||
import io.netty.handler.codec.http.HttpResponseStatus
|
||||
import org.hamcrest.CoreMatchers.equalTo
|
||||
import org.jetbrains.ide.TestManager.TestDescriptor
|
||||
import org.jetbrains.testFramework.FixtureRule
|
||||
import org.junit.Assert.assertThat
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.RuleChain
|
||||
|
||||
import org.hamcrest.CoreMatchers.equalTo
|
||||
import org.jetbrains.ide.TestManager.TestDescriptor
|
||||
import org.junit.Assert.assertThat
|
||||
import java.net.URL
|
||||
import java.net.HttpURLConnection
|
||||
import com.google.gson.stream.JsonWriter
|
||||
import java.io.OutputStreamWriter
|
||||
import com.intellij.openapi.vfs.CharsetToolkit
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.OutputStreamWriter
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
|
||||
public class RestApiTest {
|
||||
private val fixtureManager = FixtureRule()
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.jetbrains.ide
|
||||
|
||||
import com.intellij.openapi.application.invokeAndWaitIfNeed
|
||||
import com.intellij.openapi.application.writeAction
|
||||
import com.intellij.openapi.roots.ModuleRootManager
|
||||
import com.intellij.openapi.roots.ModuleRootModificationUtil
|
||||
import com.intellij.openapi.util.io.FileUtilRt
|
||||
@@ -63,10 +65,10 @@ class TestManager(val projectFixture: IdeaProjectTestFixture) : TestWatcher() {
|
||||
}
|
||||
|
||||
invokeAndWaitIfNeed {
|
||||
val normalizedFilePath = FileUtilRt.toSystemIndependentName(filePath)
|
||||
val normalizedFilePath = FileUtilRt.toSystemIndependentName(filePath!!)
|
||||
if (annotation!!.relativeToProject) {
|
||||
val root = projectFixture.getProject().getBaseDir()
|
||||
runWriteAction {
|
||||
writeAction {
|
||||
fileToDelete = root.findOrCreateChildData(this@TestManager, normalizedFilePath)
|
||||
}
|
||||
}
|
||||
@@ -75,7 +77,7 @@ class TestManager(val projectFixture: IdeaProjectTestFixture) : TestWatcher() {
|
||||
ModuleRootModificationUtil.updateModel(projectFixture.getModule()) { model ->
|
||||
val contentEntry = model.getContentEntries()[0]
|
||||
val contentRoot = contentEntry.getFile()!!
|
||||
runWriteAction {
|
||||
writeAction {
|
||||
contentRoot.findChild(EXCLUDED_DIR_NAME)?.delete(this@TestManager)
|
||||
fileToDelete = contentRoot.createChildDirectory(this@TestManager, EXCLUDED_DIR_NAME)
|
||||
fileToDelete!!.createChildData(this@TestManager, normalizedFilePath)
|
||||
@@ -87,7 +89,7 @@ class TestManager(val projectFixture: IdeaProjectTestFixture) : TestWatcher() {
|
||||
}
|
||||
else {
|
||||
val root = ModuleRootManager.getInstance(projectFixture.getModule()).getSourceRoots()[0]
|
||||
runWriteAction {
|
||||
writeAction {
|
||||
fileToDelete = root.findOrCreateChildData(this@TestManager, normalizedFilePath)
|
||||
}
|
||||
}
|
||||
@@ -101,11 +103,11 @@ class TestManager(val projectFixture: IdeaProjectTestFixture) : TestWatcher() {
|
||||
}
|
||||
|
||||
if (fileToDelete != null) {
|
||||
invokeAndWaitIfNeed { runWriteAction { fileToDelete?.delete(this@TestManager) } }
|
||||
invokeAndWaitIfNeed { writeAction { fileToDelete?.delete(this@TestManager) } }
|
||||
fileToDelete = null
|
||||
}
|
||||
|
||||
if (ioFileToDelete != null && !FileUtilRt.delete(ioFileToDelete)) {
|
||||
if (ioFileToDelete != null && !FileUtilRt.delete(ioFileToDelete!!)) {
|
||||
ioFileToDelete!!.deleteOnExit()
|
||||
}
|
||||
|
||||
|
||||
@@ -46,8 +46,6 @@ public abstract class SchemesManager<T extends Scheme, E extends ExternalizableS
|
||||
@Nullable
|
||||
public abstract T findSchemeByName(@NotNull String schemeName);
|
||||
|
||||
public abstract void save();
|
||||
|
||||
public abstract void setCurrentSchemeName(@Nullable String schemeName);
|
||||
|
||||
@Nullable
|
||||
@@ -60,6 +58,9 @@ public abstract class SchemesManager<T extends Scheme, E extends ExternalizableS
|
||||
|
||||
public abstract File getRootDirectory();
|
||||
|
||||
/**
|
||||
* Must be called before {@link #loadSchemes}
|
||||
*/
|
||||
public void loadBundledScheme(@NotNull String resourceName, @NotNull Object requestor, @NotNull ThrowableConvertor<Element, T, Throwable> convertor) {
|
||||
}
|
||||
|
||||
@@ -69,4 +70,11 @@ public abstract class SchemesManager<T extends Scheme, E extends ExternalizableS
|
||||
|
||||
public void setSchemes(@NotNull List<T> schemes, @Nullable Condition<T> removeCondition) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Bundled / read-only (or overriding) scheme cannot be renamed or deleted.
|
||||
*/
|
||||
public boolean isMetadataEditable(@NotNull E scheme) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,5 +11,6 @@
|
||||
<orderEntry type="library" name="picocontainer" level="project" />
|
||||
<orderEntry type="module" module-name="boot" />
|
||||
<orderEntry type="library" name="Guava" level="project" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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.application
|
||||
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
public inline fun writeAction(runnable: () -> Unit) {
|
||||
val token = WriteAction.start()
|
||||
try {
|
||||
runnable()
|
||||
}
|
||||
finally {
|
||||
token.finish()
|
||||
}
|
||||
}
|
||||
|
||||
public fun invokeAndWaitIfNeed(runnable: () -> Unit) {
|
||||
val app = ApplicationManager.getApplication()
|
||||
if (app == null) {
|
||||
if (SwingUtilities.isEventDispatchThread()) runnable() else SwingUtilities.invokeAndWait(runnable)
|
||||
}
|
||||
else {
|
||||
if (app.isDispatchThread()) runnable() else app.invokeAndWait(runnable, ModalityState.any())
|
||||
}
|
||||
}
|
||||
@@ -48,10 +48,6 @@ public class EmptySchemesManager extends SchemesManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentSchemeName(String schemeName) {
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import java.util.Collection;
|
||||
* @author traff
|
||||
*/
|
||||
public abstract class AbstractCollectionComboBoxModel<T> extends CollectionListModel<T> implements ComboBoxModel {
|
||||
private T mySelection;
|
||||
protected T mySelection;
|
||||
|
||||
public AbstractCollectionComboBoxModel(@Nullable T selection) {
|
||||
mySelection = selection;
|
||||
|
||||
@@ -42,5 +42,6 @@
|
||||
<orderEntry type="module" module-name="built-in-server" scope="RUNTIME" />
|
||||
<orderEntry type="library" name="imgscalr" level="project" />
|
||||
<orderEntry type="module" module-name="built-in-server-api" />
|
||||
<orderEntry type="library" name="KotlinJavaRuntime" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
+10
-10
@@ -43,11 +43,11 @@ public class QuickListsManager implements ExportableApplicationComponent {
|
||||
private static final String LIST_TAG = "list";
|
||||
|
||||
private final ActionManager myActionManager;
|
||||
private final SchemesManager<QuickList, QuickList> mySchemesManager;
|
||||
private final SchemesManager<QuickList, QuickList> mySchemeManager;
|
||||
|
||||
public QuickListsManager(@NotNull ActionManager actionManager, @NotNull SchemesManagerFactory schemesManagerFactory) {
|
||||
myActionManager = actionManager;
|
||||
mySchemesManager = schemesManagerFactory.createSchemesManager(FILE_SPEC,
|
||||
mySchemeManager = schemesManagerFactory.createSchemesManager(FILE_SPEC,
|
||||
new BaseSchemeProcessor<QuickList>() {
|
||||
@NotNull
|
||||
@Override
|
||||
@@ -73,7 +73,7 @@ public class QuickListsManager implements ExportableApplicationComponent {
|
||||
@Override
|
||||
@NotNull
|
||||
public File[] getExportFiles() {
|
||||
return new File[]{mySchemesManager.getRootDirectory()};
|
||||
return new File[]{mySchemeManager.getRootDirectory()};
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -99,7 +99,7 @@ public class QuickListsManager implements ExportableApplicationComponent {
|
||||
public void initComponent() {
|
||||
for (BundledQuickListsProvider provider : BundledQuickListsProvider.EP_NAME.getExtensions()) {
|
||||
for (final String path : provider.getBundledListsRelativePaths()) {
|
||||
mySchemesManager.loadBundledScheme(path, provider, new ThrowableConvertor<Element, QuickList, Throwable>() {
|
||||
mySchemeManager.loadBundledScheme(path, provider, new ThrowableConvertor<Element, QuickList, Throwable>() {
|
||||
@Override
|
||||
public QuickList convert(Element element) throws Throwable {
|
||||
return createItem(element);
|
||||
@@ -107,7 +107,7 @@ public class QuickListsManager implements ExportableApplicationComponent {
|
||||
});
|
||||
}
|
||||
}
|
||||
mySchemesManager.loadSchemes();
|
||||
mySchemeManager.loadSchemes();
|
||||
registerActions();
|
||||
}
|
||||
|
||||
@@ -116,20 +116,20 @@ public class QuickListsManager implements ExportableApplicationComponent {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<QuickList> getQuickLists() {
|
||||
return mySchemesManager.getAllSchemes();
|
||||
public SchemesManager<QuickList, QuickList> getSchemeManager() {
|
||||
return mySchemeManager;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public QuickList[] getAllQuickLists() {
|
||||
Collection<QuickList> lists = mySchemesManager.getAllSchemes();
|
||||
Collection<QuickList> lists = mySchemeManager.getAllSchemes();
|
||||
return lists.toArray(new QuickList[lists.size()]);
|
||||
}
|
||||
|
||||
private void registerActions() {
|
||||
// to prevent exception if 2 or more targets have the same name
|
||||
Set<String> registeredIds = new THashSet<String>();
|
||||
for (QuickList list : mySchemesManager.getAllSchemes()) {
|
||||
for (QuickList list : mySchemeManager.getAllSchemes()) {
|
||||
String actionId = list.getActionId();
|
||||
if (registeredIds.add(actionId)) {
|
||||
myActionManager.registerAction(actionId, new InvokeQuickListAction(list));
|
||||
@@ -145,7 +145,7 @@ public class QuickListsManager implements ExportableApplicationComponent {
|
||||
|
||||
public void setQuickLists(@NotNull List<QuickList> quickLists) {
|
||||
unregisterActions();
|
||||
mySchemesManager.setSchemes(quickLists);
|
||||
mySchemeManager.setSchemes(quickLists);
|
||||
registerActions();
|
||||
}
|
||||
|
||||
|
||||
-89
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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.RoamingType;
|
||||
import com.intellij.openapi.util.Condition;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
public abstract class StreamProvider {
|
||||
public static final StreamProvider[] EMPTY_ARRAY = new StreamProvider[0];
|
||||
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* fileSpec Only main fileSpec, not version
|
||||
*/
|
||||
public boolean isApplicable(@NotNull String fileSpec, @NotNull RoamingType roamingType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fileSpec
|
||||
* @param content bytes of content, size of array is not actual size of data, you must use {@code size}
|
||||
* @param size actual size of data
|
||||
*/
|
||||
public abstract void saveContent(@NotNull String fileSpec, @NotNull byte[] content, int size, @NotNull RoamingType roamingType) throws IOException;
|
||||
|
||||
@Nullable
|
||||
public abstract InputStream loadContent(@NotNull String fileSpec, @NotNull RoamingType roamingType) throws IOException;
|
||||
|
||||
@NotNull
|
||||
public Collection<String> listSubFiles(@NotNull String fileSpec, @NotNull RoamingType roamingType) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* You must close passed input stream.
|
||||
*/
|
||||
public void processChildren(@NotNull String path, @NotNull RoamingType roamingType, @NotNull Condition<String> filter, @NotNull ChildrenProcessor processor) {
|
||||
for (String name : listSubFiles(path, roamingType)) {
|
||||
if (!filter.value(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
InputStream input;
|
||||
try {
|
||||
input = loadContent(path + '/' + name, roamingType);
|
||||
}
|
||||
catch (IOException e) {
|
||||
StorageUtil.LOG.error(e);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (input != null && !processor.process(name, input)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public abstract static class ChildrenProcessor {
|
||||
public abstract boolean process(@NotNull String name, @NotNull InputStream input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete file or directory
|
||||
*/
|
||||
public abstract void delete(@NotNull String fileSpec, @NotNull RoamingType roamingType);
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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.RoamingType
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
public interface StreamProvider {
|
||||
public open fun isEnabled(): Boolean = true
|
||||
|
||||
/**
|
||||
* fileSpec Only main fileSpec, not version
|
||||
*/
|
||||
public open fun isApplicable(fileSpec: String, roamingType: RoamingType): Boolean = true
|
||||
|
||||
/**
|
||||
* @param fileSpec
|
||||
* *
|
||||
* @param content bytes of content, size of array is not actual size of data, you must use `size`
|
||||
* *
|
||||
* @param size actual size of data
|
||||
*/
|
||||
public fun saveContent(fileSpec: String, content: ByteArray, size: Int, roamingType: RoamingType)
|
||||
|
||||
public fun loadContent(fileSpec: String, roamingType: RoamingType): InputStream?
|
||||
|
||||
public open fun listSubFiles(fileSpec: String, roamingType: RoamingType): Collection<String> = emptyList()
|
||||
|
||||
/**
|
||||
* You must close passed input stream.
|
||||
*/
|
||||
public open fun processChildren(path: String, roamingType: RoamingType, filter: (name: String) -> Boolean, processor: (name: String, input: InputStream, readOnly: Boolean) -> Boolean) {
|
||||
for (name in listSubFiles(path, roamingType)) {
|
||||
if (!filter(name)) {
|
||||
continue
|
||||
}
|
||||
|
||||
val input: InputStream?
|
||||
try {
|
||||
input = loadContent("$path/$name", roamingType)
|
||||
}
|
||||
catch (e: IOException) {
|
||||
StorageUtil.LOG.error(e)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
if (input != null && !processor(name, input, false)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete file or directory
|
||||
*/
|
||||
public fun delete(fileSpec: String, roamingType: RoamingType)
|
||||
}
|
||||
@@ -30,7 +30,9 @@ import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.HashMap;
|
||||
import com.intellij.util.containers.OrderedSet;
|
||||
import gnu.trove.THashMap;
|
||||
import gnu.trove.TObjectObjectProcedure;
|
||||
import org.jdom.Element;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -39,7 +41,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
import javax.swing.*;
|
||||
import java.awt.event.InputEvent;
|
||||
import java.awt.event.KeyEvent;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
@@ -77,7 +78,7 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap {
|
||||
private KeymapImpl myParent;
|
||||
private boolean myCanModify = true;
|
||||
|
||||
private final Map<String, LinkedHashSet<Shortcut>> myActionId2ListOfShortcuts = new THashMap<String, LinkedHashSet<Shortcut>>();
|
||||
private final THashMap<String, OrderedSet<Shortcut>> myActionId2ListOfShortcuts = new THashMap<String, OrderedSet<Shortcut>>();
|
||||
|
||||
/**
|
||||
* Don't use this field directly! Use it only through <code>getKeystroke2ListOfIds</code>.
|
||||
@@ -134,21 +135,26 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap {
|
||||
|
||||
@NotNull
|
||||
public KeymapImpl copy() {
|
||||
KeymapImpl newKeymap = new KeymapImpl();
|
||||
return copyTo(newKeymap);
|
||||
return copyTo(new KeymapImpl());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public KeymapImpl copyTo(@NotNull KeymapImpl otherKeymap) {
|
||||
public KeymapImpl copyTo(@NotNull final KeymapImpl otherKeymap) {
|
||||
otherKeymap.myParent = myParent;
|
||||
otherKeymap.myName = myName;
|
||||
otherKeymap.myCanModify = canModify();
|
||||
|
||||
otherKeymap.cleanShortcutsCache();
|
||||
|
||||
for (Map.Entry<String, LinkedHashSet<Shortcut>> entry : myActionId2ListOfShortcuts.entrySet()) {
|
||||
otherKeymap.myActionId2ListOfShortcuts.put(entry.getKey(), new LinkedHashSet<Shortcut>(entry.getValue()));
|
||||
}
|
||||
otherKeymap.myActionId2ListOfShortcuts.clear();
|
||||
otherKeymap.myActionId2ListOfShortcuts.ensureCapacity(myActionId2ListOfShortcuts.size());
|
||||
myActionId2ListOfShortcuts.forEachEntry(new TObjectObjectProcedure<String, OrderedSet<Shortcut>>() {
|
||||
@Override
|
||||
public boolean execute(String actionId, OrderedSet<Shortcut> shortcuts) {
|
||||
otherKeymap.myActionId2ListOfShortcuts.put(actionId, new OrderedSet<Shortcut>(shortcuts));
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return otherKeymap;
|
||||
}
|
||||
|
||||
@@ -195,9 +201,9 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap {
|
||||
}
|
||||
|
||||
private void addShortcutSilently(String actionId, Shortcut shortcut, final boolean checkParentShortcut) {
|
||||
LinkedHashSet<Shortcut> list = myActionId2ListOfShortcuts.get(actionId);
|
||||
OrderedSet<Shortcut> list = myActionId2ListOfShortcuts.get(actionId);
|
||||
if (list == null) {
|
||||
list = new LinkedHashSet<Shortcut>();
|
||||
list = new OrderedSet<Shortcut>();
|
||||
myActionId2ListOfShortcuts.put(actionId, list);
|
||||
Shortcut[] boundShortcuts = getBoundShortcuts(actionId);
|
||||
if (boundShortcuts != null) {
|
||||
@@ -230,7 +236,7 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap {
|
||||
|
||||
@Override
|
||||
public void removeShortcut(String actionId, Shortcut toDelete) {
|
||||
LinkedHashSet<Shortcut> list = myActionId2ListOfShortcuts.get(actionId);
|
||||
OrderedSet<Shortcut> list = myActionId2ListOfShortcuts.get(actionId);
|
||||
if (list != null) {
|
||||
Iterator<Shortcut> it = list.iterator();
|
||||
while (it.hasNext()) {
|
||||
@@ -253,7 +259,7 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap {
|
||||
|
||||
if (inherited != null) {
|
||||
boolean affected = false;
|
||||
LinkedHashSet<Shortcut> newShortcuts = new LinkedHashSet<Shortcut>(inherited.length);
|
||||
OrderedSet<Shortcut> newShortcuts = new OrderedSet<Shortcut>(inherited.length);
|
||||
for (Shortcut eachInherited : inherited) {
|
||||
if (toDelete.equals(eachInherited)) {
|
||||
// skip this one
|
||||
@@ -306,7 +312,7 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap {
|
||||
}
|
||||
|
||||
private <T extends Shortcut>void addAction2ShortcutsMap(final String actionId, final Map<T, List<String>> strokesMap, final Class<T> shortcutClass) {
|
||||
LinkedHashSet<Shortcut> listOfShortcuts = _getShortcuts(actionId);
|
||||
OrderedSet<Shortcut> listOfShortcuts = _getShortcuts(actionId);
|
||||
for (Shortcut shortcut : listOfShortcuts) {
|
||||
if (!shortcutClass.isAssignableFrom(shortcut.getClass())) {
|
||||
continue;
|
||||
@@ -328,7 +334,7 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap {
|
||||
}
|
||||
|
||||
private void addKeystrokesMap(final String actionId, final Map<KeyStroke, List<String>> strokesMap) {
|
||||
LinkedHashSet<Shortcut> listOfShortcuts = _getShortcuts(actionId);
|
||||
OrderedSet<Shortcut> listOfShortcuts = _getShortcuts(actionId);
|
||||
for (Shortcut shortcut : listOfShortcuts) {
|
||||
if (!(shortcut instanceof KeyboardShortcut)) {
|
||||
continue;
|
||||
@@ -347,14 +353,14 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap {
|
||||
}
|
||||
}
|
||||
|
||||
private LinkedHashSet<Shortcut> _getShortcuts(final String actionId) {
|
||||
private OrderedSet<Shortcut> _getShortcuts(final String actionId) {
|
||||
KeymapManagerEx keymapManager = getKeymapManager();
|
||||
LinkedHashSet<Shortcut> listOfShortcuts = myActionId2ListOfShortcuts.get(actionId);
|
||||
OrderedSet<Shortcut> listOfShortcuts = myActionId2ListOfShortcuts.get(actionId);
|
||||
if (listOfShortcuts != null) {
|
||||
return listOfShortcuts;
|
||||
}
|
||||
else {
|
||||
listOfShortcuts = new LinkedHashSet<Shortcut>();
|
||||
listOfShortcuts = new OrderedSet<Shortcut>();
|
||||
}
|
||||
|
||||
final String actionBinding = keymapManager.getActionBinding(actionId);
|
||||
@@ -519,7 +525,7 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap {
|
||||
@NotNull
|
||||
@Override
|
||||
public Shortcut[] getShortcuts(String actionId) {
|
||||
LinkedHashSet<Shortcut> shortcuts = myActionId2ListOfShortcuts.get(actionId);
|
||||
OrderedSet<Shortcut> shortcuts = myActionId2ListOfShortcuts.get(actionId);
|
||||
|
||||
if (shortcuts == null) {
|
||||
Shortcut[] boundShortcuts = getBoundShortcuts(actionId);
|
||||
@@ -539,7 +545,7 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap {
|
||||
|
||||
@Nullable
|
||||
private Shortcut[] getOwnShortcuts(String actionId) {
|
||||
LinkedHashSet<Shortcut> own = myActionId2ListOfShortcuts.get(actionId);
|
||||
OrderedSet<Shortcut> own = myActionId2ListOfShortcuts.get(actionId);
|
||||
if (own == null) return null;
|
||||
return own.isEmpty() ? ourEmptyShortcutsArray : own.toArray(new Shortcut[own.size()]);
|
||||
}
|
||||
@@ -685,7 +691,7 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap {
|
||||
}
|
||||
// Add read shortcuts
|
||||
for (String id : id2shortcuts.keySet()) {
|
||||
myActionId2ListOfShortcuts.put(id, new LinkedHashSet<Shortcut>(2)); // It's a trick! After that parent's shortcuts are not added to the keymap
|
||||
myActionId2ListOfShortcuts.put(id, new OrderedSet<Shortcut>(2)); // It's a trick! After that parent's shortcuts are not added to the keymap
|
||||
ArrayList<Shortcut> shortcuts = id2shortcuts.get(id);
|
||||
for (Shortcut shortcut : shortcuts) {
|
||||
addShortcutSilently(id, shortcut, false);
|
||||
@@ -711,7 +717,7 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap {
|
||||
for (String actionId : ownActionIds) {
|
||||
Element actionElement = new Element(ACTION);
|
||||
actionElement.setAttribute(ID_ATTRIBUTE, actionId);
|
||||
// Save keyboad shortcuts
|
||||
// Save keyboard shortcuts
|
||||
Shortcut[] shortcuts = getShortcuts(actionId);
|
||||
for (Shortcut shortcut : shortcuts) {
|
||||
if (shortcut instanceof KeyboardShortcut) {
|
||||
@@ -805,23 +811,23 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap {
|
||||
// modifiers
|
||||
|
||||
int modifiers = shortcut.getModifiers();
|
||||
if ((MouseEvent.SHIFT_DOWN_MASK & modifiers) > 0) {
|
||||
if ((InputEvent.SHIFT_DOWN_MASK & modifiers) > 0) {
|
||||
buffer.append(SHIFT);
|
||||
buffer.append(' ');
|
||||
}
|
||||
if ((MouseEvent.CTRL_DOWN_MASK & modifiers) > 0) {
|
||||
if ((InputEvent.CTRL_DOWN_MASK & modifiers) > 0) {
|
||||
buffer.append(CONTROL);
|
||||
buffer.append(' ');
|
||||
}
|
||||
if ((MouseEvent.META_DOWN_MASK & modifiers) > 0) {
|
||||
if ((InputEvent.META_DOWN_MASK & modifiers) > 0) {
|
||||
buffer.append(META);
|
||||
buffer.append(' ');
|
||||
}
|
||||
if ((MouseEvent.ALT_DOWN_MASK & modifiers) > 0) {
|
||||
if ((InputEvent.ALT_DOWN_MASK & modifiers) > 0) {
|
||||
buffer.append(ALT);
|
||||
buffer.append(' ');
|
||||
}
|
||||
if ((MouseEvent.ALT_GRAPH_DOWN_MASK & modifiers) > 0) {
|
||||
if ((InputEvent.ALT_GRAPH_DOWN_MASK & modifiers) > 0) {
|
||||
buffer.append(ALT_GRAPH);
|
||||
buffer.append(' ');
|
||||
}
|
||||
@@ -849,7 +855,7 @@ public class KeymapImpl extends ExternalizableSchemeAdapter implements Keymap {
|
||||
cleanShortcutsCache();
|
||||
}
|
||||
|
||||
public boolean hasOwnActionId(String actionId) {
|
||||
public boolean hasOwnActionId(@NotNull String actionId) {
|
||||
return myActionId2ListOfShortcuts.containsKey(actionId);
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,6 @@ public class ActionsTree {
|
||||
|
||||
if (rowBounds.intersects(clip)) {
|
||||
Object node = getPathForRow(row).getLastPathComponent();
|
||||
|
||||
if (node instanceof DefaultMutableTreeNode) {
|
||||
Object data = ((DefaultMutableTreeNode)node).getUserObject();
|
||||
Rectangle fullRowRect = new Rectangle(visibleRect.x, rowBounds.y, visibleRect.width, rowBounds.height);
|
||||
@@ -106,30 +105,20 @@ public class ActionsTree {
|
||||
@Override
|
||||
public void mouseMoved(MouseEvent e) {
|
||||
String description = getDescription(e);
|
||||
if (description != null) {
|
||||
ActionMenu.showDescriptionInStatusBar(true, myTree, description);
|
||||
}
|
||||
else {
|
||||
ActionMenu.showDescriptionInStatusBar(false, myTree, null);
|
||||
}
|
||||
ActionMenu.showDescriptionInStatusBar(description != null, myTree, description);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getDescription(@NotNull MouseEvent e) {
|
||||
TreePath path = myTree.getPathForLocation(e.getX(), e.getY());
|
||||
if (path == null) return null;
|
||||
DefaultMutableTreeNode node = path == null ? null : (DefaultMutableTreeNode)path.getLastPathComponent();
|
||||
Object userObject = node == null ? null : node.getUserObject();
|
||||
if (!(userObject instanceof String)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
|
||||
if (node == null) return null;
|
||||
|
||||
Object userObject = node.getUserObject();
|
||||
if (!(userObject instanceof String)) return null;
|
||||
|
||||
String actionId = (String)userObject;
|
||||
AnAction action = ActionManager.getInstance().getActionOrStub(actionId);
|
||||
if (action == null) return null;
|
||||
|
||||
return action.getTemplatePresentation().getDescription();
|
||||
AnAction action = ActionManager.getInstance().getActionOrStub((String)userObject);
|
||||
return action == null ? null : action.getTemplatePresentation().getDescription();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -140,6 +129,11 @@ public class ActionsTree {
|
||||
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
|
||||
}
|
||||
|
||||
// silently replace current map
|
||||
void setKeymap(@NotNull Keymap keymap) {
|
||||
myKeymap = keymap;
|
||||
}
|
||||
|
||||
public JComponent getComponent() {
|
||||
return myComponent;
|
||||
}
|
||||
@@ -171,7 +165,7 @@ public class ActionsTree {
|
||||
return (QuickList)userObject;
|
||||
}
|
||||
|
||||
public void reset(Keymap keymap, final QuickList[] allQuickLists) {
|
||||
public void reset(@NotNull Keymap keymap, @NotNull QuickList[] allQuickLists) {
|
||||
reset(keymap, allQuickLists, myFilter, null);
|
||||
}
|
||||
|
||||
@@ -188,7 +182,7 @@ public class ActionsTree {
|
||||
reset(myKeymap, currentQuickListIds, filter, null);
|
||||
}
|
||||
|
||||
private void reset(final Keymap keymap, final QuickList[] allQuickLists, String filter, @Nullable KeyboardShortcut shortcut) {
|
||||
private void reset(@NotNull Keymap keymap, @NotNull QuickList[] allQuickLists, String filter, @Nullable KeyboardShortcut shortcut) {
|
||||
myKeymap = keymap;
|
||||
|
||||
final PathsKeeper pathsKeeper = new PathsKeeper();
|
||||
@@ -198,11 +192,11 @@ public class ActionsTree {
|
||||
|
||||
ActionManager actionManager = ActionManager.getInstance();
|
||||
Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(myComponent));
|
||||
Group mainGroup = ActionsTreeUtil.createMainGroup(project, myKeymap, allQuickLists, filter, true,
|
||||
ActionsTreeUtil.isActionFiltered(actionManager, myKeymap, shortcut, filter, true));
|
||||
Group mainGroup = ActionsTreeUtil.createMainGroup(project, keymap, allQuickLists, filter, true,
|
||||
ActionsTreeUtil.isActionFiltered(actionManager, keymap, shortcut, filter, true));
|
||||
if ((filter != null && filter.length() > 0 || shortcut != null) && mainGroup.initIds().isEmpty()){
|
||||
mainGroup = ActionsTreeUtil.createMainGroup(project, myKeymap, allQuickLists, filter, false,
|
||||
ActionsTreeUtil.isActionFiltered(actionManager, myKeymap, shortcut, filter, false));
|
||||
mainGroup = ActionsTreeUtil.createMainGroup(project, keymap, allQuickLists, filter, false,
|
||||
ActionsTreeUtil.isActionFiltered(actionManager, keymap, shortcut, filter, false));
|
||||
}
|
||||
myRoot = ActionsTreeUtil.createNode(mainGroup);
|
||||
myMainGroup = mainGroup;
|
||||
@@ -251,14 +245,7 @@ public class ActionsTree {
|
||||
if (userObject instanceof QuickList) {
|
||||
userObject = ((QuickList)userObject).getActionId();
|
||||
}
|
||||
|
||||
if (userObject instanceof String) {
|
||||
Shortcut[] shortcuts = myKeymap.getShortcuts((String)userObject);
|
||||
return KeymapUtil.getShortcutsText(shortcuts);
|
||||
}
|
||||
else {
|
||||
return "";
|
||||
}
|
||||
return userObject instanceof String ? KeymapUtil.getShortcutsText(myKeymap.getShortcuts((String)userObject)) : "";
|
||||
}
|
||||
else {
|
||||
return "???";
|
||||
|
||||
+5
-6
@@ -34,6 +34,7 @@ import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
@@ -214,7 +215,8 @@ public class KeyboardShortcutDialog extends DialogWrapper {
|
||||
myConflictInfoArea.setForeground(JBColor.RED);
|
||||
if (loaded) {
|
||||
myConflictInfoArea.setText(KeyMapBundle.message("assigned.to.info.message", buffer.toString()));
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
myConflictInfoArea.setText("Assigned to " + buffer.toString() + " which is now not loaded but may be loaded later");
|
||||
}
|
||||
}
|
||||
@@ -224,13 +226,10 @@ public class KeyboardShortcutDialog extends DialogWrapper {
|
||||
mySecondStrokePanel.setEnabled(myEnableSecondKeystroke.isSelected());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public KeyboardShortcut getKeyboardShortcut() {
|
||||
KeyStroke firstStroke = myFirstStrokePanel.getKeyStroke();
|
||||
if (firstStroke == null) {
|
||||
return null;
|
||||
}
|
||||
KeyStroke secondStroke = myEnableSecondKeystroke.isSelected() ? mySecondStrokePanel.getKeyStroke() : null;
|
||||
return new KeyboardShortcut(firstStroke, secondStroke);
|
||||
return firstStroke == null ? null : new KeyboardShortcut(firstStroke, myEnableSecondKeystroke.isSelected() ? mySecondStrokePanel.getKeyStroke() : null);
|
||||
}
|
||||
|
||||
static String getTextByKeyStroke(KeyStroke keyStroke) {
|
||||
|
||||
@@ -119,8 +119,6 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
}
|
||||
});
|
||||
|
||||
private Keymap mySelectedKeymap;
|
||||
|
||||
private JButton myCopyButton;
|
||||
private JButton myDeleteButton;
|
||||
private JButton myResetToDefault;
|
||||
@@ -146,7 +144,7 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
@Override
|
||||
public void propertyChange(@NotNull final PropertyChangeEvent evt) {
|
||||
if (evt.getPropertyName().equals("ancestor") && evt.getNewValue() != null && evt.getOldValue() == null && myQuickListsModified) {
|
||||
processCurrentKeymapChanged(myQuickLists);
|
||||
currentKeymapChanged();
|
||||
myQuickListsModified = false;
|
||||
}
|
||||
}
|
||||
@@ -185,9 +183,7 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
myEditor.getComboBox().addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(@NotNull ActionEvent e) {
|
||||
if (myEditor.getModel().getSelectedItem() != mySelectedKeymap) {
|
||||
processCurrentKeymapChanged(myQuickLists);
|
||||
}
|
||||
currentKeymapChanged();
|
||||
}
|
||||
});
|
||||
panel.add(createKeymapNamePanel(), new GridBagConstraints(3, 0, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(0, 10, 0, 0), 0, 0));
|
||||
@@ -207,10 +203,13 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
@Override
|
||||
public void processCurrentKeymapChanged(@NotNull QuickList[] ids) {
|
||||
myQuickLists = ids;
|
||||
currentKeymapChanged();
|
||||
}
|
||||
|
||||
private void currentKeymapChanged() {
|
||||
myResetToDefault.setEnabled(false);
|
||||
|
||||
Keymap selectedKeymap = myEditor.getModel().getSelected();
|
||||
mySelectedKeymap = selectedKeymap;
|
||||
|
||||
boolean editable = selectedKeymap != null && selectedKeymap.canModify();
|
||||
myDeleteButton.setEnabled(editable);
|
||||
@@ -228,16 +227,12 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
}
|
||||
else {
|
||||
myBaseKeymapLabel.setText(KeyMapBundle.message("based.on.keymap.label", parent.getPresentableName()));
|
||||
if (selectedKeymap.canModify() && ((KeymapImpl)mySelectedKeymap).getOwnActionIds().length > 0) {
|
||||
if (selectedKeymap.canModify() && ((KeymapImpl)selectedKeymap).getOwnActionIds().length > 0) {
|
||||
myResetToDefault.setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
resetActionsTree(selectedKeymap);
|
||||
}
|
||||
|
||||
private void resetActionsTree(@NotNull Keymap keymap) {
|
||||
myActionsTree.reset(myEditor.getMutable(keymap), myQuickLists);
|
||||
myActionsTree.reset(selectedKeymap, myQuickLists);
|
||||
}
|
||||
|
||||
private JPanel createKeymapButtonsPanel() {
|
||||
@@ -450,7 +445,7 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
|
||||
private JPanel createFilteringPanel() {
|
||||
//noinspection ConstantConditions
|
||||
resetActionsTree(myEditor.getModel().getSelected());
|
||||
myActionsTree.reset(myEditor.getModel().getSelected(), myQuickLists);
|
||||
|
||||
final JLabel firstLabel = new JLabel(KeyMapBundle.message("filter.first.stroke.input"));
|
||||
final JCheckBox enable2Shortcut = new JCheckBox(KeyMapBundle.message("filter.second.stroke.input"));
|
||||
@@ -482,12 +477,11 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
});
|
||||
|
||||
IJSwingUtilities.adjustComponentsOnMac(firstLabel, firstShortcut);
|
||||
//noinspection deprecation
|
||||
JPanel filterComponent = FormBuilder.createFormBuilder()
|
||||
.addLabeledComponent(firstLabel, firstShortcut, true)
|
||||
.addComponent(enable2Shortcut)
|
||||
.setVerticalGap(0)
|
||||
.setIndent(5)
|
||||
.setHorizontalGap(5)
|
||||
.addComponent(secondShortcut)
|
||||
.getPanel();
|
||||
|
||||
@@ -525,31 +519,21 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
myActionsTree.filter(option, myQuickLists);
|
||||
}
|
||||
|
||||
private void addKeyboardShortcut(Shortcut shortcut) {
|
||||
String actionId = myActionsTree.getSelectedActionId();
|
||||
if (actionId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!createKeymapCopyIfNeeded()) return;
|
||||
private void addKeyboardShortcut(@NotNull String actionId, @Nullable Shortcut shortcut) {
|
||||
Keymap keymap = createKeymapCopyIfNeeded();
|
||||
|
||||
KeyboardShortcutDialog dialog = new KeyboardShortcutDialog(this, actionId, myQuickLists);
|
||||
|
||||
|
||||
KeyboardShortcut selectedKeyboardShortcut = shortcut instanceof KeyboardShortcut ? (KeyboardShortcut)shortcut : null;
|
||||
|
||||
dialog.setData(mySelectedKeymap, selectedKeyboardShortcut);
|
||||
dialog.setData(keymap, shortcut instanceof KeyboardShortcut ? (KeyboardShortcut)shortcut : null);
|
||||
if (!dialog.showAndGet()) {
|
||||
return;
|
||||
}
|
||||
|
||||
KeyboardShortcut keyboardShortcut = dialog.getKeyboardShortcut();
|
||||
|
||||
if (keyboardShortcut == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, ArrayList<KeyboardShortcut>> conflicts = mySelectedKeymap.getConflicts(actionId, keyboardShortcut);
|
||||
Map<String, ArrayList<KeyboardShortcut>> conflicts = keymap.getConflicts(actionId, keyboardShortcut);
|
||||
if (!conflicts.isEmpty()) {
|
||||
int result = Messages.showYesNoCancelDialog(
|
||||
this,
|
||||
@@ -563,7 +547,7 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
if (result == Messages.YES) {
|
||||
for (String id : conflicts.keySet()) {
|
||||
for (KeyboardShortcut s : conflicts.get(id)) {
|
||||
mySelectedKeymap.removeShortcut(id, s);
|
||||
keymap.removeShortcut(id, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -573,20 +557,20 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
}
|
||||
|
||||
// if shortcut is already registered to this action, just select it in the list
|
||||
Shortcut[] shortcuts = mySelectedKeymap.getShortcuts(actionId);
|
||||
Shortcut[] shortcuts = keymap.getShortcuts(actionId);
|
||||
for (Shortcut s : shortcuts) {
|
||||
if (s.equals(keyboardShortcut)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mySelectedKeymap.addShortcut(actionId, keyboardShortcut);
|
||||
keymap.addShortcut(actionId, keyboardShortcut);
|
||||
if (StringUtil.startsWithChar(actionId, '$')) {
|
||||
mySelectedKeymap.addShortcut(KeyMapBundle.message("editor.shortcut", actionId.substring(1)), keyboardShortcut);
|
||||
keymap.addShortcut(KeyMapBundle.message("editor.shortcut", actionId.substring(1)), keyboardShortcut);
|
||||
}
|
||||
|
||||
repaintLists();
|
||||
processCurrentKeymapChanged(myQuickLists);
|
||||
currentKeymapChanged();
|
||||
}
|
||||
|
||||
private void addMouseShortcut(Shortcut shortcut, ShortcutRestrictions restrictions) {
|
||||
@@ -595,14 +579,14 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
return;
|
||||
}
|
||||
|
||||
if (!createKeymapCopyIfNeeded()) return;
|
||||
Keymap keymap = createKeymapCopyIfNeeded();
|
||||
|
||||
MouseShortcut mouseShortcut = shortcut instanceof MouseShortcut ? (MouseShortcut)shortcut : null;
|
||||
|
||||
MouseShortcutDialog dialog = new MouseShortcutDialog(
|
||||
this,
|
||||
mouseShortcut,
|
||||
mySelectedKeymap,
|
||||
keymap,
|
||||
actionId,
|
||||
myActionsTree.getMainGroup(),
|
||||
restrictions
|
||||
@@ -617,7 +601,7 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
return;
|
||||
}
|
||||
|
||||
String[] actionIds = mySelectedKeymap.getActionIds(mouseShortcut);
|
||||
String[] actionIds = keymap.getActionIds(mouseShortcut);
|
||||
if (actionIds.length > 1 || (actionIds.length == 1 && !actionId.equals(actionIds[0]))) {
|
||||
int result = Messages.showYesNoCancelDialog(
|
||||
this,
|
||||
@@ -630,7 +614,7 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
|
||||
if (result == Messages.YES) {
|
||||
for (String id : actionIds) {
|
||||
mySelectedKeymap.removeShortcut(id, mouseShortcut);
|
||||
keymap.removeShortcut(id, mouseShortcut);
|
||||
}
|
||||
}
|
||||
else if (result != Messages.NO) {
|
||||
@@ -640,50 +624,51 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
|
||||
// if shortcut is already registered to this action, just select it in the list
|
||||
|
||||
Shortcut[] shortcuts = mySelectedKeymap.getShortcuts(actionId);
|
||||
Shortcut[] shortcuts = keymap.getShortcuts(actionId);
|
||||
for (Shortcut shortcut1 : shortcuts) {
|
||||
if (shortcut1.equals(mouseShortcut)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mySelectedKeymap.addShortcut(actionId, mouseShortcut);
|
||||
keymap.addShortcut(actionId, mouseShortcut);
|
||||
if (StringUtil.startsWithChar(actionId, '$')) {
|
||||
mySelectedKeymap.addShortcut(KeyMapBundle.message("editor.shortcut", actionId.substring(1)), mouseShortcut);
|
||||
keymap.addShortcut(KeyMapBundle.message("editor.shortcut", actionId.substring(1)), mouseShortcut);
|
||||
}
|
||||
|
||||
repaintLists();
|
||||
processCurrentKeymapChanged(myQuickLists);
|
||||
currentKeymapChanged();
|
||||
}
|
||||
|
||||
private void repaintLists() {
|
||||
myActionsTree.getComponent().repaint();
|
||||
}
|
||||
|
||||
private boolean createKeymapCopyIfNeeded() {
|
||||
if (mySelectedKeymap.canModify()) return true;
|
||||
|
||||
final Keymap selectedKeymap = myEditor.getModel().getSelected();
|
||||
if (selectedKeymap == null) {
|
||||
return false;
|
||||
@NotNull
|
||||
private Keymap createKeymapCopyIfNeeded() {
|
||||
Keymap keymap = myEditor.getModel().getSelected();
|
||||
assert keymap != null;
|
||||
if (keymap.canModify()) {
|
||||
Keymap mutable = myEditor.getMutable(keymap);
|
||||
myActionsTree.setKeymap(mutable);
|
||||
return mutable;
|
||||
}
|
||||
|
||||
KeymapImpl newKeymap = ((KeymapImpl)selectedKeymap).deriveKeymap();
|
||||
|
||||
String newKeymapName = KeyMapBundle.message("new.keymap.name", selectedKeymap.getPresentableName());
|
||||
String newKeymapName = KeyMapBundle.message("new.keymap.name", keymap.getPresentableName());
|
||||
if (!tryNewKeymapName(newKeymapName)) {
|
||||
for (int i = 0; ; i++) {
|
||||
newKeymapName = KeyMapBundle.message("new.indexed.keymap.name", selectedKeymap.getPresentableName(), i);
|
||||
newKeymapName = KeyMapBundle.message("new.indexed.keymap.name", keymap.getPresentableName(), i);
|
||||
if (tryNewKeymapName(newKeymapName)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
KeymapImpl newKeymap = ((KeymapImpl)keymap).deriveKeymap();
|
||||
newKeymap.setName(newKeymapName);
|
||||
newKeymap.setCanModify(true);
|
||||
|
||||
final int indexOf = myEditor.getModel().getElementIndex(selectedKeymap);
|
||||
int indexOf = myEditor.getModel().getElementIndex(keymap);
|
||||
if (indexOf >= 0) {
|
||||
myEditor.getModel().add(indexOf + 1, newKeymap);
|
||||
}
|
||||
@@ -692,28 +677,8 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
}
|
||||
|
||||
myEditor.getModel().setSelectedItem(newKeymap);
|
||||
processCurrentKeymapChanged(myQuickLists);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void removeShortcut(Shortcut shortcut) {
|
||||
String actionId = myActionsTree.getSelectedActionId();
|
||||
if (actionId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!createKeymapCopyIfNeeded()) return;
|
||||
|
||||
if (shortcut == null) return;
|
||||
|
||||
mySelectedKeymap.removeShortcut(actionId, shortcut);
|
||||
if (StringUtil.startsWithChar(actionId, '$')) {
|
||||
mySelectedKeymap.removeShortcut(KeyMapBundle.message("editor.shortcut", actionId.substring(1)), shortcut);
|
||||
}
|
||||
|
||||
repaintLists();
|
||||
processCurrentKeymapChanged(myQuickLists);
|
||||
currentKeymapChanged();
|
||||
return newKeymap;
|
||||
}
|
||||
|
||||
private void copyKeymap() {
|
||||
@@ -738,7 +703,7 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
myEditor.getModel().add(newKeymap);
|
||||
myEditor.getModel().setSelectedItem(newKeymap);
|
||||
myEditor.getComboBox().getEditor().selectAll();
|
||||
processCurrentKeymapChanged(myQuickLists);
|
||||
currentKeymapChanged();
|
||||
}
|
||||
|
||||
private boolean tryNewKeymapName(String name) {
|
||||
@@ -759,7 +724,7 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
}
|
||||
|
||||
myEditor.getModel().remove(keymap);
|
||||
processCurrentKeymapChanged(myQuickLists);
|
||||
currentKeymapChanged();
|
||||
}
|
||||
|
||||
private void resetKeymap() {
|
||||
@@ -768,7 +733,7 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
return;
|
||||
}
|
||||
((KeymapImpl)keymap).clearOwnActionsIds();
|
||||
processCurrentKeymapChanged(myQuickLists);
|
||||
currentKeymapChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -784,11 +749,11 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
myNonEnglishKeyboardSupportOption.setSelected(KeyboardSettingsExternalizable.getInstance().isNonEnglishKeyboardSupportEnabled());
|
||||
}
|
||||
|
||||
mySelectedKeymap = null;
|
||||
Keymap selectedKeymap = null;
|
||||
List<Keymap> list = getManagerKeymaps();
|
||||
for (Keymap keymap : list) {
|
||||
if (mySelectedKeymap == null && keymap == KeymapManagerEx.getInstanceEx().getActiveKeymap()) {
|
||||
mySelectedKeymap = keymap;
|
||||
if (selectedKeymap == null && keymap == KeymapManagerEx.getInstanceEx().getActiveKeymap()) {
|
||||
selectedKeymap = keymap;
|
||||
}
|
||||
}
|
||||
myEditor.reset(list);
|
||||
@@ -797,23 +762,23 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
KeymapImpl keymap = new KeymapImpl();
|
||||
keymap.setName(KeyMapBundle.message("keymap.no.name"));
|
||||
myEditor.getModel().add(keymap);
|
||||
mySelectedKeymap = keymap;
|
||||
selectedKeymap = keymap;
|
||||
}
|
||||
|
||||
myEditor.getModel().setSelectedItem(mySelectedKeymap);
|
||||
myEditor.getModel().setSelectedItem(selectedKeymap);
|
||||
|
||||
processCurrentKeymapChanged(myQuickLists);
|
||||
currentKeymapChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply() throws ConfigurationException {
|
||||
myEditor.ensureNonEmptyNames("Quick list should have non empty name");
|
||||
myEditor.ensureNonEmptyNames(KeyMapBundle.message("configuration.all.keymaps.should.have.non.empty.names.error.message"));
|
||||
|
||||
ensureUniqueKeymapNames();
|
||||
KeymapManagerImpl keymapManager = (KeymapManagerImpl)KeymapManager.getInstance();
|
||||
// we must specify the same filter, which was used to get original items
|
||||
keymapManager.setKeymaps(myEditor.apply(), KEYMAP_FILTER);
|
||||
keymapManager.setActiveKeymap(mySelectedKeymap);
|
||||
keymapManager.setActiveKeymap(myEditor.getModel().getSelected());
|
||||
ActionToolbarImpl.updateAllToolbarsImmediately();
|
||||
}
|
||||
|
||||
@@ -828,7 +793,7 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
|
||||
@Override
|
||||
public boolean isModified() {
|
||||
return !Comparing.equal(mySelectedKeymap, KeymapManager.getInstance().getActiveKeymap()) || myEditor.isModified();
|
||||
return !Comparing.equal(myEditor.getModel().getSelected(), KeymapManager.getInstance().getActiveKeymap()) || myEditor.isModified();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -873,34 +838,59 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
|
||||
@Nullable
|
||||
public Shortcut[] getCurrentShortcuts(@NotNull String actionId) {
|
||||
return mySelectedKeymap == null ? null : mySelectedKeymap.getShortcuts(actionId);
|
||||
Keymap keymap = myEditor.getModel().getSelected();
|
||||
return keymap == null ? null : keymap.getShortcuts(actionId);
|
||||
}
|
||||
|
||||
private void editSelection(InputEvent e) {
|
||||
final String actionId = myActionsTree.getSelectedActionId();
|
||||
if (actionId == null) return;
|
||||
String actionId = myActionsTree.getSelectedActionId();
|
||||
if (actionId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
DefaultActionGroup group = createEditActionGroup(actionId);
|
||||
if (e instanceof MouseEvent && ((MouseEvent)e).isPopupTrigger()) {
|
||||
ActionManager.getInstance()
|
||||
.createActionPopupMenu(ActionPlaces.UNKNOWN, group)
|
||||
.getComponent()
|
||||
.show(e.getComponent(), ((MouseEvent)e).getX(), ((MouseEvent)e).getY());
|
||||
}
|
||||
else {
|
||||
DataContext dataContext = DataManager.getInstance().getDataContext(this);
|
||||
ListPopup popup = JBPopupFactory.getInstance().createActionGroupPopup("Edit Shortcuts",
|
||||
group,
|
||||
dataContext,
|
||||
JBPopupFactory.ActionSelectionAid.SPEEDSEARCH,
|
||||
true);
|
||||
|
||||
if (e instanceof MouseEvent) {
|
||||
popup.show(new RelativePoint((MouseEvent)e));
|
||||
}
|
||||
else {
|
||||
popup.showInBestPositionFor(dataContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private DefaultActionGroup createEditActionGroup(@NotNull final String actionId) {
|
||||
DefaultActionGroup group = new DefaultActionGroup();
|
||||
|
||||
final Shortcut[] shortcuts = getCurrentShortcuts(actionId);
|
||||
final Set<String> abbreviations = AbbreviationManager.getInstance().getAbbreviations(actionId);
|
||||
|
||||
final ShortcutRestrictions restrictions = ActionShortcutRestrictions.getInstance().getForActionId(actionId);
|
||||
|
||||
if (restrictions.allowKeyboardShortcut) {
|
||||
group.add(new DumbAwareAction("Add Keyboard Shortcut") {
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
Shortcut firstKeyboard = null;
|
||||
assert shortcuts != null;
|
||||
for (Shortcut shortcut : shortcuts) {
|
||||
Shortcut firstShortcut = null;
|
||||
Keymap keymap = myEditor.getModel().getSelected();
|
||||
assert keymap != null;
|
||||
for (Shortcut shortcut : keymap.getShortcuts(actionId)) {
|
||||
if (shortcut instanceof KeyboardShortcut) {
|
||||
firstKeyboard = shortcut;
|
||||
firstShortcut = shortcut;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
addKeyboardShortcut(firstKeyboard);
|
||||
addKeyboardShortcut(actionId, firstShortcut);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -910,8 +900,9 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
Shortcut firstMouse = null;
|
||||
assert shortcuts != null;
|
||||
for (Shortcut shortcut : shortcuts) {
|
||||
Keymap keymap = myEditor.getModel().getSelected();
|
||||
assert keymap != null;
|
||||
for (Shortcut shortcut : keymap.getShortcuts(actionId)) {
|
||||
if (shortcut instanceof MouseShortcut) {
|
||||
firstMouse = shortcut;
|
||||
break;
|
||||
@@ -926,10 +917,9 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
group.add(new DumbAwareAction("Add Abbreviation") {
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
final String abbr = Messages.showInputDialog("Enter new abbreviation:", "Abbreviation", null);
|
||||
String abbr = Messages.showInputDialog("Enter new abbreviation:", "Abbreviation", null);
|
||||
if (abbr != null) {
|
||||
String actionId = myActionsTree.getSelectedActionId();
|
||||
AbbreviationManager.getInstance().register(abbr, actionId);
|
||||
AbbreviationManager.getInstance().register(abbr, myActionsTree.getSelectedActionId());
|
||||
repaintLists();
|
||||
}
|
||||
}
|
||||
@@ -943,29 +933,32 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
|
||||
group.addSeparator();
|
||||
|
||||
assert shortcuts != null;
|
||||
for (final Shortcut shortcut : shortcuts) {
|
||||
Keymap keymap = myEditor.getModel().getSelected();
|
||||
assert keymap != null;
|
||||
for (final Shortcut shortcut : keymap.getShortcuts(actionId)) {
|
||||
group.add(new DumbAwareAction("Remove " + KeymapUtil.getShortcutText(shortcut)) {
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
removeShortcut(shortcut);
|
||||
Keymap keymap = createKeymapCopyIfNeeded();
|
||||
keymap.removeShortcut(actionId, shortcut);
|
||||
if (StringUtil.startsWithChar(actionId, '$')) {
|
||||
keymap.removeShortcut(KeyMapBundle.message("editor.shortcut", actionId.substring(1)), shortcut);
|
||||
}
|
||||
|
||||
repaintLists();
|
||||
currentKeymapChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (Registry.is("actionSystem.enableAbbreviations")) {
|
||||
for (final String abbreviation : abbreviations) {
|
||||
for (final String abbreviation : AbbreviationManager.getInstance().getAbbreviations(actionId)) {
|
||||
group.addAction(new DumbAwareAction("Remove Abbreviation '" + abbreviation + "'") {
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
AbbreviationManager.getInstance().remove(abbreviation, actionId);
|
||||
repaintLists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull AnActionEvent e) {
|
||||
super.update(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -973,37 +966,17 @@ public class KeymapPanel extends JPanel implements SearchableConfigurable, Confi
|
||||
group.add(new DumbAwareAction("Reset Shortcuts") {
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
((KeymapImpl)mySelectedKeymap).clearOwnActionsId(actionId);
|
||||
processCurrentKeymapChanged(myQuickLists);
|
||||
((KeymapImpl)createKeymapCopyIfNeeded()).clearOwnActionsId(actionId);
|
||||
currentKeymapChanged();
|
||||
repaintLists();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(@NotNull AnActionEvent e) {
|
||||
e.getPresentation().setVisible(mySelectedKeymap.canModify() && ((KeymapImpl)mySelectedKeymap).hasOwnActionId(actionId));
|
||||
super.update(e);
|
||||
e.getPresentation().setVisible(((KeymapImpl)myEditor.getModel().getSelected()).hasOwnActionId(actionId));
|
||||
}
|
||||
});
|
||||
|
||||
if (e instanceof MouseEvent && ((MouseEvent)e).isPopupTrigger()) {
|
||||
final ActionPopupMenu popupMenu = ActionManager.getInstance().createActionPopupMenu(ActionPlaces.UNKNOWN, group);
|
||||
popupMenu.getComponent().show(e.getComponent(), ((MouseEvent)e).getX(), ((MouseEvent)e).getY());
|
||||
}
|
||||
else {
|
||||
final DataContext dataContext = DataManager.getInstance().getDataContext(this);
|
||||
final ListPopup popup = JBPopupFactory.getInstance()
|
||||
.createActionGroupPopup("Edit Shortcuts",
|
||||
group,
|
||||
dataContext,
|
||||
JBPopupFactory.ActionSelectionAid.SPEEDSEARCH,
|
||||
true);
|
||||
|
||||
if (e instanceof MouseEvent) {
|
||||
popup.show(new RelativePoint((MouseEvent)e));
|
||||
}
|
||||
else {
|
||||
popup.showInBestPositionFor(dataContext);
|
||||
}
|
||||
}
|
||||
return group;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="1d65e" class="javax.swing.JTextField" binding="myDisplayName">
|
||||
<component id="1d65e" class="javax.swing.JTextField" binding="myName">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.intellij.openapi.actionSystem.ActionManager;
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.ex.QuickList;
|
||||
import com.intellij.openapi.actionSystem.ex.QuickListsManager;
|
||||
import com.intellij.openapi.keymap.KeyMapBundle;
|
||||
import com.intellij.openapi.keymap.KeymapManager;
|
||||
import com.intellij.ui.*;
|
||||
@@ -36,7 +37,7 @@ class QuickListPanel {
|
||||
private final CollectionListModel<Object> actionsModel;
|
||||
private JPanel myPanel;
|
||||
private final JBList myActionsList;
|
||||
JTextField myDisplayName;
|
||||
JTextField myName;
|
||||
private JTextField myDescription;
|
||||
private JPanel myListPanel;
|
||||
QuickList item;
|
||||
@@ -97,10 +98,10 @@ class QuickListPanel {
|
||||
return;
|
||||
}
|
||||
|
||||
item.setName(myDisplayName.getText().trim());
|
||||
item.setName(myName.getText().trim());
|
||||
item.setDescription(myDescription.getText().trim());
|
||||
|
||||
ListModel model = getActionsList().getModel();
|
||||
ListModel model = myActionsList.getModel();
|
||||
int size = model.getSize();
|
||||
String[] ids;
|
||||
if (size == 0) {
|
||||
@@ -124,7 +125,8 @@ class QuickListPanel {
|
||||
return;
|
||||
}
|
||||
|
||||
myDisplayName.setText(this.item.getName());
|
||||
myName.setText(item.getName());
|
||||
myName.setEnabled(QuickListsManager.getInstance().getSchemeManager().isMetadataEditable(item));
|
||||
myDescription.setText(item.getDescription());
|
||||
|
||||
actionsModel.removeAll();
|
||||
@@ -140,18 +142,6 @@ class QuickListPanel {
|
||||
}
|
||||
}
|
||||
|
||||
public JList getActionsList() {
|
||||
return myActionsList;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return myDescription.getText();
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return myDisplayName.getText();
|
||||
}
|
||||
|
||||
private void includeActionId(@NotNull String id) {
|
||||
if (QuickList.SEPARATOR_ID.equals(id) || actionsModel.getElementIndex(id) == -1) {
|
||||
actionsModel.add(id);
|
||||
|
||||
@@ -30,7 +30,7 @@ public class QuickListsPanel extends ConfigurableBase<QuickListsUi, List<QuickLi
|
||||
@NotNull
|
||||
@Override
|
||||
protected List<QuickList> getSettings() {
|
||||
return QuickListsManager.getInstance().getQuickLists();
|
||||
return QuickListsManager.getInstance().getSchemeManager().getAllSchemes();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -62,7 +62,13 @@ class QuickListsUi implements ConfigurableUi<List<QuickList>> {
|
||||
public String getName(@NotNull QuickList item) {
|
||||
return item.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRemovable(@NotNull QuickList item) {
|
||||
return QuickListsManager.getInstance().getSchemeManager().isMetadataEditable(item);
|
||||
}
|
||||
};
|
||||
|
||||
private final ListModelEditor<QuickList> editor = new ListModelEditor<QuickList>(itemEditor);
|
||||
|
||||
private JComponent component;
|
||||
@@ -74,6 +80,8 @@ class QuickListsUi implements ConfigurableUi<List<QuickList>> {
|
||||
|
||||
final CardLayout cardLayout = new CardLayout();
|
||||
|
||||
// doesn't make any sense (and in any case scheme manager cannot preserve order)
|
||||
editor.disableUpDownActions();
|
||||
editor.getList().addListSelectionListener(new ListSelectionListener() {
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent e) {
|
||||
@@ -90,12 +98,12 @@ class QuickListsUi implements ConfigurableUi<List<QuickList>> {
|
||||
});
|
||||
|
||||
itemPanel = new QuickListPanel(editor.getModel());
|
||||
itemPanel.myDisplayName.getDocument().addDocumentListener(new DocumentAdapter() {
|
||||
itemPanel.myName.getDocument().addDocumentListener(new DocumentAdapter() {
|
||||
@Override
|
||||
protected void textChanged(DocumentEvent e) {
|
||||
QuickList item = itemPanel.item;
|
||||
if (item != null) {
|
||||
String name = itemPanel.myDisplayName.getText();
|
||||
String name = itemPanel.myName.getText();
|
||||
boolean changed = !item.getName().equals(name);
|
||||
item.setName(name);
|
||||
if (changed) {
|
||||
|
||||
@@ -0,0 +1,831 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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.options
|
||||
|
||||
import com.intellij.openapi.application.AccessToken
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.WriteAction
|
||||
import com.intellij.openapi.application.ex.DecodeDefaultsUtil
|
||||
import com.intellij.openapi.application.writeAction
|
||||
import com.intellij.openapi.components.RoamingType
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.openapi.components.impl.stores.DirectoryBasedStorage
|
||||
import com.intellij.openapi.components.impl.stores.DirectoryStorageData
|
||||
import com.intellij.openapi.components.impl.stores.StorageUtil
|
||||
import com.intellij.openapi.components.impl.stores.StreamProvider
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.extensions.AbstractExtensionPointBean
|
||||
import com.intellij.openapi.util.Comparing
|
||||
import com.intellij.openapi.util.Condition
|
||||
import com.intellij.openapi.util.JDOMUtil
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.util.text.StringUtilRt
|
||||
import com.intellij.openapi.vfs.*
|
||||
import com.intellij.openapi.vfs.tracker.VirtualFileTracker
|
||||
import com.intellij.util.PathUtil
|
||||
import com.intellij.util.PathUtilRt
|
||||
import com.intellij.util.SmartList
|
||||
import com.intellij.util.ThrowableConvertor
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import com.intellij.util.io.URLUtil
|
||||
import com.intellij.util.text.UniqueNameGenerator
|
||||
import gnu.trove.THashMap
|
||||
import gnu.trove.THashSet
|
||||
import gnu.trove.TObjectObjectProcedure
|
||||
import gnu.trove.TObjectProcedure
|
||||
import org.jdom.Document
|
||||
import org.jdom.Element
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.util.ArrayList
|
||||
import java.util.Collections
|
||||
|
||||
val LOG = Logger.getInstance(javaClass<SchemesManagerFactoryImpl>())
|
||||
|
||||
public class SchemeManagerImpl<T : Scheme, E : ExternalizableScheme>(private val fileSpec: String,
|
||||
private val processor: SchemeProcessor<E>,
|
||||
private val roamingType: RoamingType,
|
||||
private val provider: StreamProvider?,
|
||||
private val ioDirectory: File) : SchemesManager<T, E>(), SafeWriteRequestor {
|
||||
private val schemes = ArrayList<T>()
|
||||
private val readOnlyExternalizableSchemes = THashMap<String, E>()
|
||||
|
||||
private var currentScheme: T? = null
|
||||
private var currentSchemeName: String? = null
|
||||
|
||||
private var directory: VirtualFile? = null
|
||||
|
||||
private val schemeExtension: String
|
||||
private val updateExtension: Boolean
|
||||
|
||||
private val filesToDelete = THashSet<String>()
|
||||
|
||||
// scheme could be changed - so, hashcode will be changed - we must use identity hashing strategy
|
||||
private val schemeToInfo = THashMap<E, ExternalInfo>(ContainerUtil.identityStrategy())
|
||||
|
||||
init {
|
||||
if (processor is SchemeExtensionProvider) {
|
||||
schemeExtension = processor.getSchemeExtension()
|
||||
updateExtension = processor.isUpgradeNeeded()
|
||||
}
|
||||
else {
|
||||
schemeExtension = DirectoryStorageData.DEFAULT_EXT
|
||||
updateExtension = false
|
||||
}
|
||||
|
||||
val application = ApplicationManager.getApplication()
|
||||
val virtualFileTracker = ServiceManager.getService(javaClass<VirtualFileTracker>())
|
||||
if (virtualFileTracker != null) {
|
||||
val baseDirPath = ioDirectory.getAbsolutePath().replace(File.separatorChar, '/')
|
||||
virtualFileTracker.addTracker(LocalFileSystem.PROTOCOL_PREFIX + baseDirPath, object : VirtualFileAdapter() {
|
||||
override fun contentsChanged(event: VirtualFileEvent) {
|
||||
if (event.getRequestor() != null || !isMy(event)) {
|
||||
return
|
||||
}
|
||||
|
||||
val scheme = findExternalizableSchemeByFileName(event.getFile().getName())
|
||||
var oldCurrentScheme: T? = null
|
||||
if (scheme != null) {
|
||||
oldCurrentScheme = getCurrentScheme()
|
||||
@suppress("UNCHECKED_CAST")
|
||||
removeScheme(scheme as T)
|
||||
processor.onSchemeDeleted(scheme)
|
||||
}
|
||||
|
||||
val readScheme = readSchemeFromFile(event.getFile(), false)
|
||||
if (readScheme != null) {
|
||||
processor.initScheme(readScheme)
|
||||
processor.onSchemeAdded(readScheme)
|
||||
|
||||
var newCurrentScheme = getCurrentScheme()
|
||||
if (oldCurrentScheme != null && newCurrentScheme == null) {
|
||||
setCurrentSchemeName(readScheme.getName())
|
||||
newCurrentScheme = getCurrentScheme()
|
||||
}
|
||||
|
||||
if (oldCurrentScheme !== newCurrentScheme) {
|
||||
processor.onCurrentSchemeChanged(oldCurrentScheme)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun fileCreated(event: VirtualFileEvent) {
|
||||
if (event.getRequestor() == null) {
|
||||
if (event.getFile().isDirectory()) {
|
||||
val dir = getDirectory()
|
||||
if (event.getFile() == dir) {
|
||||
for (file in dir!!.getChildren()) {
|
||||
if (isMy(file)) {
|
||||
schemeCreatedExternally(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (isMy(event)) {
|
||||
schemeCreatedExternally(event.getFile())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun schemeCreatedExternally(file: VirtualFile) {
|
||||
val readScheme = readSchemeFromFile(file, false)
|
||||
if (readScheme != null) {
|
||||
processor.initScheme(readScheme)
|
||||
processor.onSchemeAdded(readScheme)
|
||||
}
|
||||
}
|
||||
|
||||
override fun fileDeleted(event: VirtualFileEvent) {
|
||||
if (event.getRequestor() == null) {
|
||||
if (event.getFile().isDirectory()) {
|
||||
val dir = directory
|
||||
if (event.getFile() == dir) {
|
||||
directory = null
|
||||
for (file in dir!!.getChildren()) {
|
||||
if (isMy(file)) {
|
||||
schemeDeletedExternally(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (isMy(event)) {
|
||||
schemeDeletedExternally(event.getFile())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun schemeDeletedExternally(file: VirtualFile) {
|
||||
val scheme = findExternalizableSchemeByFileName(file.getName())
|
||||
var oldCurrentScheme: T? = null
|
||||
if (scheme != null) {
|
||||
oldCurrentScheme = getCurrentScheme()
|
||||
@suppress("UNCHECKED_CAST")
|
||||
removeScheme(scheme as T)
|
||||
processor.onSchemeDeleted(scheme)
|
||||
}
|
||||
|
||||
var newCurrentScheme = getCurrentScheme()
|
||||
if (oldCurrentScheme != null && newCurrentScheme == null) {
|
||||
if (!schemes.isEmpty()) {
|
||||
setCurrentSchemeName(schemes.get(0).getName())
|
||||
newCurrentScheme = getCurrentScheme()
|
||||
}
|
||||
}
|
||||
|
||||
if (oldCurrentScheme !== newCurrentScheme) {
|
||||
processor.onCurrentSchemeChanged(oldCurrentScheme)
|
||||
}
|
||||
}
|
||||
}, false, application)
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadBundledScheme(resourceName: String, requestor: Any, convertor: ThrowableConvertor<Element, T, Throwable>) {
|
||||
try {
|
||||
val url = if (requestor is AbstractExtensionPointBean)
|
||||
requestor.getLoaderForClass().getResource(resourceName)
|
||||
else
|
||||
DecodeDefaultsUtil.getDefaults(requestor, resourceName)
|
||||
if (url == null) {
|
||||
LOG.error("Cannot read scheme from $resourceName")
|
||||
return
|
||||
}
|
||||
|
||||
val element = JDOMUtil.load(URLUtil.openStream(url))
|
||||
val scheme = convertor.convert(element)
|
||||
if (scheme is ExternalizableScheme) {
|
||||
val fileName = PathUtilRt.getFileName(url.getPath())
|
||||
val extension = getFileExtension(fileName, true)
|
||||
val info = ExternalInfo(fileName.substring(0, fileName.length() - extension.length()), extension)
|
||||
info.hash = JDOMUtil.getTreeHash(element, true)
|
||||
info.schemeName = scheme.getName()
|
||||
@suppress("UNCHECKED_CAST")
|
||||
val oldInfo = schemeToInfo.put(scheme as E, info)
|
||||
LOG.assertTrue(oldInfo == null)
|
||||
val oldScheme = readOnlyExternalizableSchemes.put(scheme.getName(), scheme)
|
||||
if (oldScheme != null) {
|
||||
LOG.warn("Duplicated scheme ${scheme.getName()} - old: $oldScheme, new $scheme")
|
||||
}
|
||||
}
|
||||
|
||||
schemes.add(scheme)
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
LOG.error("Cannot read scheme from $resourceName", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFileExtension(fileName: CharSequence, allowAny: Boolean): String {
|
||||
return if (StringUtilRt.endsWithIgnoreCase(fileName, schemeExtension)) {
|
||||
schemeExtension
|
||||
}
|
||||
else if (StringUtilRt.endsWithIgnoreCase(fileName, DirectoryStorageData.DEFAULT_EXT)) {
|
||||
DirectoryStorageData.DEFAULT_EXT
|
||||
}
|
||||
else if (allowAny) {
|
||||
PathUtil.getFileExtension(fileName.toString())!!
|
||||
}
|
||||
else {
|
||||
throw IllegalStateException("Scheme file extension $fileName is unknown, must be filtered out")
|
||||
}
|
||||
}
|
||||
|
||||
private fun isMy(event: VirtualFileEvent) = isMy(event.getFile())
|
||||
|
||||
private fun isMy(file: VirtualFile) = StringUtilRt.endsWithIgnoreCase(file.getNameSequence(), schemeExtension)
|
||||
|
||||
override fun loadSchemes(): Collection<E> {
|
||||
val newSchemesOffset = schemes.size()
|
||||
if (provider != null && provider.isEnabled()) {
|
||||
provider.processChildren(fileSpec, roamingType, { canRead(it) }) { name, input, readOnly ->
|
||||
val scheme = loadScheme(name, input, true)
|
||||
if (readOnly && scheme != null) {
|
||||
readOnlyExternalizableSchemes.put(scheme.name, scheme)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
else {
|
||||
val dir = getDirectory()
|
||||
val files = dir?.getChildren()
|
||||
if (files != null) {
|
||||
for (file in files) {
|
||||
readSchemeFromFile(file, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val list = SmartList<E>()
|
||||
for (i in newSchemesOffset..schemes.size() - 1) {
|
||||
@suppress("UNCHECKED_CAST")
|
||||
val scheme = schemes[i] as E
|
||||
processor.initScheme(scheme)
|
||||
list.add(scheme)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
public fun reload() {
|
||||
// we must not remove non-persistent (e.g. predefined) schemes, because we cannot load it (obviously)
|
||||
for (i in schemes.indices.reversed()) {
|
||||
val scheme = schemes.get(i)
|
||||
@suppress("UNCHECKED_CAST")
|
||||
if (scheme is ExternalizableScheme && getState(scheme as E) != BaseSchemeProcessor.State.NON_PERSISTENT) {
|
||||
schemes.remove(i)
|
||||
if (scheme === currentScheme) {
|
||||
currentScheme = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
retainExternalInfo(schemes)
|
||||
|
||||
loadSchemes()
|
||||
}
|
||||
|
||||
private fun findExternalizableSchemeByFileName(fileName: String): E? {
|
||||
for (scheme in schemes) {
|
||||
@suppress("UNCHECKED_CAST")
|
||||
if (scheme is ExternalizableScheme && fileName == "${getFileName(scheme)}$schemeExtension") {
|
||||
return scheme as E
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun isOverwriteOnLoad(existingScheme: E): Boolean {
|
||||
if (readOnlyExternalizableSchemes.get(existingScheme.getName()) === existingScheme) {
|
||||
// so, bundled scheme is shadowed
|
||||
return true
|
||||
}
|
||||
|
||||
val info = schemeToInfo.get(existingScheme)
|
||||
// scheme from file with old extension, so, we must ignore it
|
||||
return info != null && schemeExtension != info.fileExtension
|
||||
}
|
||||
|
||||
private fun loadScheme(fileName: CharSequence, input: InputStream, duringLoad: Boolean): E? {
|
||||
try {
|
||||
val element = JDOMUtil.load(input)
|
||||
@suppress("DEPRECATED_SYMBOL_WITH_MESSAGE", "UNCHECKED_CAST")
|
||||
val scheme = (if (processor is BaseSchemeProcessor<*>) {
|
||||
processor.readScheme(element, duringLoad) as E?
|
||||
}
|
||||
else {
|
||||
processor.readScheme(Document(element.detach() as Element))
|
||||
}) ?: return null
|
||||
|
||||
val extension = getFileExtension(fileName, false)
|
||||
val fileNameWithoutExtension = fileName.subSequence(0, fileName.length() - extension.length()).toString()
|
||||
if (duringLoad) {
|
||||
if (filesToDelete.isNotEmpty() && filesToDelete.contains(fileName.toString())) {
|
||||
LOG.warn("Scheme file $fileName is not loaded because marked to delete")
|
||||
return null
|
||||
}
|
||||
|
||||
val existingScheme = findSchemeByName(scheme.getName())
|
||||
if (existingScheme != null) {
|
||||
@suppress("UNCHECKED_CAST")
|
||||
if (existingScheme is ExternalizableScheme && isOverwriteOnLoad(existingScheme as E)) {
|
||||
removeScheme(existingScheme)
|
||||
}
|
||||
else {
|
||||
// We don't load scheme with duplicated name - if we generate unique name for it, it will be saved then with new name.
|
||||
// It is not what all can expect. Such situation in most cases indicates error on previous level, so, we just warn about it.
|
||||
LOG.warn("Scheme file $fileName is not loaded because defines duplicated name ${scheme.getName()}")
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var info: ExternalInfo? = schemeToInfo.get(scheme)
|
||||
if (info == null) {
|
||||
info = ExternalInfo(fileNameWithoutExtension, extension)
|
||||
schemeToInfo.put(scheme, info)
|
||||
}
|
||||
else {
|
||||
info.setFileNameWithoutExtension(fileNameWithoutExtension, extension)
|
||||
}
|
||||
info.hash = JDOMUtil.getTreeHash(element, true)
|
||||
info.schemeName = scheme.getName()
|
||||
|
||||
@suppress("UNCHECKED_CAST")
|
||||
if (duringLoad) {
|
||||
schemes.add(scheme as T)
|
||||
}
|
||||
else {
|
||||
addScheme(scheme as T)
|
||||
}
|
||||
return scheme
|
||||
}
|
||||
catch (e: Exception) {
|
||||
LOG.error("Cannot read scheme $fileName", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFileName(scheme: ExternalizableScheme) = schemeToInfo.get(scheme)?.fileNameWithoutExtension
|
||||
|
||||
private fun canRead(name: CharSequence): Boolean {
|
||||
return updateExtension && StringUtilRt.endsWithIgnoreCase(name, DirectoryStorageData.DEFAULT_EXT) || StringUtilRt.endsWithIgnoreCase(name, schemeExtension)
|
||||
}
|
||||
|
||||
private fun readSchemeFromFile(file: VirtualFile, duringLoad: Boolean): E? {
|
||||
val fileName = file.getNameSequence()
|
||||
if (file.isDirectory() || !canRead(fileName)) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
return loadScheme(fileName, file.getInputStream(), duringLoad)
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
LOG.error("Cannot read scheme $fileName", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun save(errors: MutableList<Throwable>) {
|
||||
var hasSchemes = false
|
||||
val nameGenerator = UniqueNameGenerator()
|
||||
val schemesToSave = SmartList<E>()
|
||||
for (scheme in schemes) {
|
||||
@suppress("UNCHECKED_CAST")
|
||||
if (scheme is ExternalizableScheme) {
|
||||
val state = getState(scheme as E)
|
||||
if (state === BaseSchemeProcessor.State.NON_PERSISTENT) {
|
||||
continue
|
||||
}
|
||||
|
||||
hasSchemes = true
|
||||
|
||||
if (state !== BaseSchemeProcessor.State.UNCHANGED) {
|
||||
schemesToSave.add(scheme)
|
||||
}
|
||||
|
||||
val fileName = getFileName(scheme)
|
||||
if (fileName != null && !isRenamed(scheme)) {
|
||||
nameGenerator.addExistingName(fileName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (scheme in schemesToSave) {
|
||||
errors.catch {
|
||||
saveScheme(scheme, nameGenerator)
|
||||
}
|
||||
}
|
||||
|
||||
val dir = getDirectory()
|
||||
deleteFiles(dir, errors)
|
||||
|
||||
if (!hasSchemes && dir != null) {
|
||||
removeDirectoryIfEmpty(dir, errors)
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeDirectoryIfEmpty(dir: VirtualFile, errors: MutableList<Throwable>) {
|
||||
for (file in dir.getChildren()) {
|
||||
if (!file.`is`(VFileProperty.HIDDEN)) {
|
||||
LOG.info("Directory " + dir.getNameSequence() + " is not deleted: at least one file " + file.getNameSequence() + " exists")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
LOG.info("Remove schemes directory " + dir.getNameSequence())
|
||||
directory = null
|
||||
|
||||
val token = WriteAction.start()
|
||||
try {
|
||||
dir.delete(this)
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
errors.add(e)
|
||||
}
|
||||
finally {
|
||||
token.finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getState(scheme: E): BaseSchemeProcessor.State {
|
||||
return if (processor is BaseSchemeProcessor<*>) {
|
||||
(processor as BaseSchemeProcessor<E>).getState(scheme)
|
||||
}
|
||||
else {
|
||||
@suppress("DEPRECATED_SYMBOL_WITH_MESSAGE")
|
||||
if (processor.shouldBeSaved(scheme)) BaseSchemeProcessor.State.POSSIBLY_CHANGED else BaseSchemeProcessor.State.NON_PERSISTENT
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveScheme(scheme: E, nameGenerator: UniqueNameGenerator) {
|
||||
var externalInfo: ExternalInfo? = schemeToInfo.get(scheme)
|
||||
val currentFileNameWithoutExtension = if (externalInfo == null) null else externalInfo.fileNameWithoutExtension
|
||||
val parent = processor.writeScheme(scheme)
|
||||
val element = if (parent == null || parent is Element) parent as Element? else (parent as Document).detachRootElement()
|
||||
if (JDOMUtil.isEmpty(element)) {
|
||||
externalInfo?.scheduleDelete()
|
||||
return
|
||||
}
|
||||
|
||||
var fileNameWithoutExtension = currentFileNameWithoutExtension
|
||||
if (fileNameWithoutExtension == null || isRenamed(scheme)) {
|
||||
fileNameWithoutExtension = nameGenerator.generateUniqueName(FileUtil.sanitizeName(scheme.getName()))
|
||||
}
|
||||
|
||||
val newHash = JDOMUtil.getTreeHash(element!!, true)
|
||||
if (externalInfo != null && currentFileNameWithoutExtension === fileNameWithoutExtension && newHash == externalInfo.hash) {
|
||||
return
|
||||
}
|
||||
|
||||
// save only if scheme differs from bundled
|
||||
val bundledScheme = readOnlyExternalizableSchemes.get(scheme.getName())
|
||||
if (bundledScheme != null && schemeToInfo.get(bundledScheme)!!.hash == newHash) {
|
||||
externalInfo?.scheduleDelete()
|
||||
return
|
||||
}
|
||||
|
||||
val fileName = fileNameWithoutExtension!! + schemeExtension
|
||||
// file will be overwritten, so, we don't need to delete it
|
||||
filesToDelete.remove(fileName)
|
||||
|
||||
// stream provider always use LF separator
|
||||
val byteOut = StorageUtil.writeToBytes(element, "\n")
|
||||
|
||||
var providerPath: String?
|
||||
if (provider != null && provider.isEnabled()) {
|
||||
providerPath = fileSpec + '/' + fileName
|
||||
if (!provider.isApplicable(providerPath, roamingType)) {
|
||||
providerPath = null
|
||||
}
|
||||
}
|
||||
else {
|
||||
providerPath = null
|
||||
}
|
||||
|
||||
// if another new scheme uses old name of this scheme, so, we must not delete it (as part of rename operation)
|
||||
val renamed = externalInfo != null && fileNameWithoutExtension !== currentFileNameWithoutExtension && nameGenerator.value(currentFileNameWithoutExtension)
|
||||
if (providerPath == null) {
|
||||
var file: VirtualFile? = null
|
||||
var dir = getDirectory()
|
||||
if (dir == null || !dir.isValid()) {
|
||||
dir = DirectoryBasedStorage.createDir(ioDirectory, this)
|
||||
directory = dir!!
|
||||
}
|
||||
|
||||
if (renamed) {
|
||||
file = dir.findChild(externalInfo!!.fileName)
|
||||
if (file != null) {
|
||||
writeAction {
|
||||
file!!.rename(this, fileName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (file == null) {
|
||||
file = DirectoryBasedStorage.getFile(fileName, dir, this)
|
||||
}
|
||||
|
||||
writeAction {
|
||||
file!!.getOutputStream(this).use {
|
||||
byteOut.writeTo(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (renamed) {
|
||||
externalInfo!!.scheduleDelete()
|
||||
}
|
||||
provider!!.saveContent(providerPath, byteOut.getInternalBuffer(), byteOut.size(), roamingType)
|
||||
}
|
||||
|
||||
if (externalInfo == null) {
|
||||
externalInfo = ExternalInfo(fileNameWithoutExtension, schemeExtension)
|
||||
schemeToInfo.put(scheme, externalInfo)
|
||||
}
|
||||
else {
|
||||
externalInfo.setFileNameWithoutExtension(fileNameWithoutExtension, schemeExtension)
|
||||
}
|
||||
externalInfo.hash = newHash
|
||||
externalInfo.schemeName = scheme.getName()
|
||||
}
|
||||
|
||||
|
||||
private fun ExternalInfo.scheduleDelete() {
|
||||
filesToDelete.add(fileName)
|
||||
}
|
||||
|
||||
private fun isRenamed(scheme: ExternalizableScheme): Boolean {
|
||||
val info = schemeToInfo.get(scheme)
|
||||
return info != null && scheme.getName() != info.schemeName
|
||||
}
|
||||
|
||||
private fun deleteFiles(dir: VirtualFile?, errors: MutableList<Throwable>) {
|
||||
if (filesToDelete.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (provider != null && provider.isEnabled()) {
|
||||
for (name in filesToDelete) {
|
||||
errors.catch {
|
||||
StorageUtil.delete(provider, fileSpec + '/' + name, roamingType)
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (dir != null) {
|
||||
var token: AccessToken? = null
|
||||
try {
|
||||
for (file in dir.getChildren()) {
|
||||
if (filesToDelete.contains(file.getName())) {
|
||||
if (token == null) {
|
||||
token = WriteAction.start()
|
||||
}
|
||||
|
||||
errors.catch {
|
||||
file.delete(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (token != null) {
|
||||
token.finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filesToDelete.clear()
|
||||
}
|
||||
|
||||
private fun getDirectory(): VirtualFile? {
|
||||
var result = directory
|
||||
if (result == null) {
|
||||
result = LocalFileSystem.getInstance().findFileByIoFile(ioDirectory)
|
||||
directory = result
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override fun getRootDirectory() = ioDirectory
|
||||
|
||||
override fun setSchemes(newSchemes: List<T>, removeCondition: Condition<T>?) {
|
||||
if (removeCondition == null) {
|
||||
schemes.clear()
|
||||
}
|
||||
else {
|
||||
for (i in schemes.indices.reversed()) {
|
||||
if (removeCondition.value(schemes.get(i))) {
|
||||
schemes.remove(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
retainExternalInfo(newSchemes)
|
||||
|
||||
schemes.addAll(newSchemes)
|
||||
|
||||
if (currentSchemeName != null) {
|
||||
currentScheme = findSchemeByName(currentSchemeName!!)
|
||||
if (currentScheme != null) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
currentScheme = schemes.firstOrNull()
|
||||
currentSchemeName = currentScheme?.getName()
|
||||
}
|
||||
|
||||
private fun retainExternalInfo(newSchemes: List<T>) {
|
||||
if (schemeToInfo.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
schemeToInfo.retainEntries(object : TObjectObjectProcedure<E, ExternalInfo> {
|
||||
override fun execute(scheme: E, info: ExternalInfo): Boolean {
|
||||
if (readOnlyExternalizableSchemes.get(scheme.getName()) == scheme) {
|
||||
return true
|
||||
}
|
||||
|
||||
for (t in newSchemes) {
|
||||
// by identity
|
||||
if (t === scheme) {
|
||||
if (filesToDelete.isNotEmpty()) {
|
||||
filesToDelete.remove("${info.fileName}")
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
info.scheduleDelete()
|
||||
return false
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun addNewScheme(scheme: T, replaceExisting: Boolean) {
|
||||
var toReplace = -1
|
||||
for (i in schemes.indices) {
|
||||
val existing = schemes.get(i)
|
||||
if (existing.getName() == scheme.getName()) {
|
||||
if (!Comparing.equal<Class<out Scheme>>(existing.javaClass, scheme.javaClass)) {
|
||||
LOG.warn("'${scheme.getName()}' ${existing.javaClass.getSimpleName()} replaced with ${scheme.javaClass.getSimpleName()}")
|
||||
}
|
||||
|
||||
toReplace = i
|
||||
if (replaceExisting && existing is ExternalizableScheme) {
|
||||
val oldInfo = schemeToInfo.remove(existing)
|
||||
if (oldInfo != null && scheme is ExternalizableScheme && !schemeToInfo.containsKey(scheme)) {
|
||||
@suppress("UNCHECKED_CAST")
|
||||
schemeToInfo.put(scheme as E, oldInfo)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if (toReplace == -1) {
|
||||
schemes.add(scheme)
|
||||
}
|
||||
else if (replaceExisting || scheme !is ExternalizableScheme) {
|
||||
schemes.set(toReplace, scheme)
|
||||
}
|
||||
else {
|
||||
scheme.renameScheme(UniqueNameGenerator.generateUniqueName(scheme.getName(), collectExistingNames(schemes)))
|
||||
schemes.add(scheme)
|
||||
}
|
||||
|
||||
if (scheme is ExternalizableScheme && filesToDelete.isNotEmpty()) {
|
||||
val info = schemeToInfo.get(scheme)
|
||||
if (info != null) {
|
||||
filesToDelete.remove("${info.fileName}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun collectExistingNames(schemes: Collection<T>): Collection<String> {
|
||||
val result = THashSet<String>(schemes.size())
|
||||
for (scheme in schemes) {
|
||||
result.add(scheme.getName())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
override fun clearAllSchemes() {
|
||||
schemeToInfo.forEachValue(object : TObjectProcedure<ExternalInfo> {
|
||||
override fun execute(info: ExternalInfo): Boolean {
|
||||
info.scheduleDelete()
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
currentScheme = null
|
||||
schemes.clear()
|
||||
schemeToInfo.clear()
|
||||
}
|
||||
|
||||
override fun getAllSchemes() = Collections.unmodifiableList(schemes)
|
||||
|
||||
override fun findSchemeByName(schemeName: String): T? {
|
||||
for (scheme in schemes) {
|
||||
if (scheme.getName() == schemeName) {
|
||||
return scheme
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun setCurrentSchemeName(schemeName: String?) {
|
||||
currentSchemeName = schemeName
|
||||
currentScheme = null
|
||||
}
|
||||
|
||||
override fun getCurrentScheme(): T? {
|
||||
var scheme = currentScheme
|
||||
if (scheme == null && currentSchemeName != null) {
|
||||
scheme = findSchemeByName(currentSchemeName!!)
|
||||
if (scheme != null) {
|
||||
currentScheme = scheme
|
||||
}
|
||||
}
|
||||
return scheme
|
||||
}
|
||||
|
||||
override fun removeScheme(scheme: T) {
|
||||
for (i in schemes.size() - 1 downTo 0) {
|
||||
val s = schemes.get(i)
|
||||
if (scheme.getName() == s.getName()) {
|
||||
if (currentScheme == s) {
|
||||
currentScheme = null
|
||||
}
|
||||
|
||||
if (s is ExternalizableScheme) {
|
||||
schemeToInfo.remove(s)?.scheduleDelete()
|
||||
}
|
||||
schemes.remove(i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAllSchemeNames(): Collection<String> {
|
||||
if (schemes.isEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val names = ArrayList<String>(schemes.size())
|
||||
for (scheme in schemes) {
|
||||
names.add(scheme.getName())
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
override fun isMetadataEditable(scheme: E) = !readOnlyExternalizableSchemes.containsKey(scheme.name)
|
||||
|
||||
private class ExternalInfo(var fileNameWithoutExtension: String, var fileExtension: String?) {
|
||||
// we keep it to detect rename
|
||||
var schemeName: String? = null
|
||||
var hash: Int = 0
|
||||
|
||||
val fileName: String
|
||||
get() = "$fileNameWithoutExtension$fileExtension"
|
||||
|
||||
fun setFileNameWithoutExtension(nameWithoutExtension: String, extension: String) {
|
||||
fileNameWithoutExtension = nameWithoutExtension
|
||||
fileExtension = extension
|
||||
}
|
||||
|
||||
override fun toString() = fileName
|
||||
}
|
||||
|
||||
override fun toString() = fileSpec
|
||||
}
|
||||
|
||||
private fun ExternalizableScheme.renameScheme(newName: String) {
|
||||
if (newName != getName()) {
|
||||
setName(newName)
|
||||
LOG.assertTrue(newName == getName())
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun MutableList<Throwable>.catch(runnable: () -> Unit) {
|
||||
try {
|
||||
runnable()
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
add(e)
|
||||
}
|
||||
}
|
||||
|
||||
inline val Scheme.name: String
|
||||
get() = getName()
|
||||
+7
-10
@@ -34,7 +34,7 @@ import java.util.List;
|
||||
public final class SchemesManagerFactoryImpl extends SchemesManagerFactory implements SettingsSavingComponent {
|
||||
private static final Logger LOG = Logger.getInstance(SchemesManagerFactoryImpl.class);
|
||||
|
||||
private final List<SchemesManagerImpl> myRegisteredManagers = ContainerUtil.createLockFreeCopyOnWriteList();
|
||||
private final List<SchemeManagerImpl> myRegisteredManagers = ContainerUtil.createLockFreeCopyOnWriteList();
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
@@ -44,13 +44,13 @@ public final class SchemesManagerFactoryImpl extends SchemesManagerFactory imple
|
||||
StateStorageManager storageManager = ((ApplicationImpl)ApplicationManager.getApplication()).getStateStore().getStateStorageManager();
|
||||
String baseDirPath = storageManager.expandMacros(fileSpec);
|
||||
StreamProvider provider = storageManager.getStreamProvider();
|
||||
SchemesManagerImpl<T, E> manager = new SchemesManagerImpl<T, E>(fileSpec, processor, roamingType, provider, new File(baseDirPath));
|
||||
SchemeManagerImpl<T, E> manager = new SchemeManagerImpl<T, E>(fileSpec, processor, roamingType, provider, new File(baseDirPath));
|
||||
myRegisteredManagers.add(manager);
|
||||
return manager;
|
||||
}
|
||||
|
||||
public void process(@NotNull Consumer<SchemesManagerImpl> processor) {
|
||||
for (SchemesManagerImpl manager : myRegisteredManagers) {
|
||||
public void process(@NotNull Consumer<SchemeManagerImpl> processor) {
|
||||
for (SchemeManagerImpl manager : myRegisteredManagers) {
|
||||
try {
|
||||
processor.consume(manager);
|
||||
}
|
||||
@@ -62,15 +62,12 @@ public final class SchemesManagerFactoryImpl extends SchemesManagerFactory imple
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
List<Throwable> errors = null;
|
||||
for (SchemesManager registeredManager : myRegisteredManagers) {
|
||||
List<Throwable> errors = new SmartList<Throwable>();
|
||||
for (SchemeManagerImpl registeredManager : myRegisteredManagers) {
|
||||
try {
|
||||
registeredManager.save();
|
||||
registeredManager.save(errors);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
if (errors == null) {
|
||||
errors = new SmartList<Throwable>();
|
||||
}
|
||||
errors.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,949 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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.options;
|
||||
|
||||
import com.intellij.openapi.application.AccessToken;
|
||||
import com.intellij.openapi.application.Application;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.WriteAction;
|
||||
import com.intellij.openapi.application.ex.DecodeDefaultsUtil;
|
||||
import com.intellij.openapi.components.RoamingType;
|
||||
import com.intellij.openapi.components.ServiceManager;
|
||||
import com.intellij.openapi.components.impl.stores.DirectoryBasedStorage;
|
||||
import com.intellij.openapi.components.impl.stores.DirectoryStorageData;
|
||||
import com.intellij.openapi.components.impl.stores.StorageUtil;
|
||||
import com.intellij.openapi.components.impl.stores.StreamProvider;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.extensions.AbstractExtensionPointBean;
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.openapi.util.JDOMUtil;
|
||||
import com.intellij.openapi.util.WriteExternalException;
|
||||
import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.text.StringUtilRt;
|
||||
import com.intellij.openapi.vfs.*;
|
||||
import com.intellij.openapi.vfs.tracker.VirtualFileTracker;
|
||||
import com.intellij.util.PathUtil;
|
||||
import com.intellij.util.PathUtilRt;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.util.ThrowableConvertor;
|
||||
import com.intellij.util.io.URLUtil;
|
||||
import com.intellij.util.lang.CompoundRuntimeException;
|
||||
import com.intellij.util.text.UniqueNameGenerator;
|
||||
import gnu.trove.*;
|
||||
import org.jdom.Document;
|
||||
import org.jdom.Element;
|
||||
import org.jdom.Parent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
|
||||
public final class SchemesManagerImpl<T extends Scheme, E extends ExternalizableScheme> extends SchemesManager<T, E> implements SafeWriteRequestor {
|
||||
private static final Logger LOG = Logger.getInstance(SchemesManagerFactoryImpl.class);
|
||||
|
||||
private final ArrayList<T> mySchemes = new ArrayList<T>();
|
||||
private volatile T myCurrentScheme;
|
||||
@Nullable
|
||||
private String myCurrentSchemeName;
|
||||
|
||||
private final String myFileSpec;
|
||||
private final SchemeProcessor<E> myProcessor;
|
||||
private final RoamingType myRoamingType;
|
||||
|
||||
private final StreamProvider provider;
|
||||
private final File myIoDir;
|
||||
private VirtualFile myDir;
|
||||
|
||||
private final String mySchemeExtension;
|
||||
private final boolean myUpdateExtension;
|
||||
|
||||
private final Set<String> filesToDelete = new THashSet<String>();
|
||||
|
||||
// scheme could be changed - so, hashcode will be changed - we must use identity hashing strategy
|
||||
@SuppressWarnings("unchecked")
|
||||
private final THashMap<ExternalizableScheme, ExternalInfo> schemeToInfo = new THashMap<ExternalizableScheme, ExternalInfo>(TObjectHashingStrategy.IDENTITY);
|
||||
|
||||
public SchemesManagerImpl(@NotNull String fileSpec,
|
||||
@NotNull SchemeProcessor<E> processor,
|
||||
@NotNull RoamingType roamingType,
|
||||
@Nullable StreamProvider provider,
|
||||
@NotNull File baseDir) {
|
||||
myFileSpec = fileSpec;
|
||||
myProcessor = processor;
|
||||
myRoamingType = roamingType;
|
||||
this.provider = provider;
|
||||
myIoDir = baseDir;
|
||||
if (processor instanceof SchemeExtensionProvider) {
|
||||
mySchemeExtension = ((SchemeExtensionProvider)processor).getSchemeExtension();
|
||||
myUpdateExtension = ((SchemeExtensionProvider)processor).isUpgradeNeeded();
|
||||
}
|
||||
else {
|
||||
mySchemeExtension = DirectoryStorageData.DEFAULT_EXT;
|
||||
myUpdateExtension = false;
|
||||
}
|
||||
|
||||
Application application = ApplicationManager.getApplication();
|
||||
VirtualFileTracker virtualFileTracker = application == null ? null : ServiceManager.getService(VirtualFileTracker.class);
|
||||
if (virtualFileTracker != null) {
|
||||
final String baseDirPath = myIoDir.getAbsolutePath().replace(File.separatorChar, '/');
|
||||
virtualFileTracker.addTracker(LocalFileSystem.PROTOCOL_PREFIX + baseDirPath, new VirtualFileAdapter() {
|
||||
@Override
|
||||
public void contentsChanged(@NotNull VirtualFileEvent event) {
|
||||
if (event.getRequestor() != null || !isMy(event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
E scheme = findExternalizableSchemeByFileName(event.getFile().getName());
|
||||
T oldCurrentScheme = null;
|
||||
if (scheme != null) {
|
||||
oldCurrentScheme = getCurrentScheme();
|
||||
//noinspection unchecked
|
||||
removeScheme((T)scheme);
|
||||
myProcessor.onSchemeDeleted(scheme);
|
||||
}
|
||||
|
||||
E readScheme = readSchemeFromFile(event.getFile(), null);
|
||||
if (readScheme != null) {
|
||||
myProcessor.initScheme(readScheme);
|
||||
myProcessor.onSchemeAdded(readScheme);
|
||||
|
||||
T newCurrentScheme = getCurrentScheme();
|
||||
if (oldCurrentScheme != null && newCurrentScheme == null) {
|
||||
setCurrentSchemeName(readScheme.getName());
|
||||
newCurrentScheme = getCurrentScheme();
|
||||
}
|
||||
|
||||
if (oldCurrentScheme != newCurrentScheme) {
|
||||
myProcessor.onCurrentSchemeChanged(oldCurrentScheme);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fileCreated(@NotNull VirtualFileEvent event) {
|
||||
if (event.getRequestor() == null) {
|
||||
if (event.getFile().isDirectory()) {
|
||||
VirtualFile dir = getDirectory();
|
||||
if (event.getFile().equals(dir)) {
|
||||
for (VirtualFile file : dir.getChildren()) {
|
||||
if (isMy(file)) {
|
||||
schemeCreatedExternally(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (isMy(event)) {
|
||||
schemeCreatedExternally(event.getFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void schemeCreatedExternally(@NotNull VirtualFile file) {
|
||||
E readScheme = readSchemeFromFile(file, null);
|
||||
if (readScheme != null) {
|
||||
myProcessor.initScheme(readScheme);
|
||||
myProcessor.onSchemeAdded(readScheme);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fileDeleted(@NotNull VirtualFileEvent event) {
|
||||
if (event.getRequestor() == null) {
|
||||
if (event.getFile().isDirectory()) {
|
||||
VirtualFile dir = myDir;
|
||||
if (event.getFile().equals(dir)) {
|
||||
myDir = null;
|
||||
for (VirtualFile file : dir.getChildren()) {
|
||||
if (isMy(file)) {
|
||||
schemeDeletedExternally(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (isMy(event)) {
|
||||
schemeDeletedExternally(event.getFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void schemeDeletedExternally(@NotNull VirtualFile file) {
|
||||
E scheme = findExternalizableSchemeByFileName(file.getName());
|
||||
T oldCurrentScheme = null;
|
||||
if (scheme != null) {
|
||||
oldCurrentScheme = getCurrentScheme();
|
||||
//noinspection unchecked
|
||||
removeScheme((T)scheme);
|
||||
myProcessor.onSchemeDeleted(scheme);
|
||||
}
|
||||
|
||||
T newCurrentScheme = getCurrentScheme();
|
||||
if (oldCurrentScheme != null && newCurrentScheme == null) {
|
||||
if (!mySchemes.isEmpty()) {
|
||||
setCurrentSchemeName(mySchemes.get(0).getName());
|
||||
newCurrentScheme = getCurrentScheme();
|
||||
}
|
||||
}
|
||||
|
||||
if (oldCurrentScheme != newCurrentScheme) {
|
||||
myProcessor.onCurrentSchemeChanged(oldCurrentScheme);
|
||||
}
|
||||
}
|
||||
}, false, application);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadBundledScheme(@NotNull String resourceName, @NotNull Object requestor, @NotNull ThrowableConvertor<Element, T, Throwable> convertor) {
|
||||
try {
|
||||
URL url = requestor instanceof AbstractExtensionPointBean
|
||||
? (((AbstractExtensionPointBean)requestor).getLoaderForClass().getResource(resourceName))
|
||||
: DecodeDefaultsUtil.getDefaults(requestor, resourceName);
|
||||
if (url == null) {
|
||||
// Error shouldn't occur during this operation thus we report error instead of info
|
||||
LOG.error("Cannot read scheme from " + resourceName);
|
||||
return;
|
||||
}
|
||||
Element element = JDOMUtil.load(URLUtil.openStream(url));
|
||||
T scheme = convertor.convert(element);
|
||||
|
||||
if (scheme instanceof ExternalizableScheme) {
|
||||
String fileName = PathUtilRt.getFileName(url.getPath());
|
||||
String extension = getFileExtension(fileName, true);
|
||||
ExternalInfo info = new ExternalInfo(fileName.substring(0, fileName.length() - extension.length()), extension);
|
||||
info.hash = JDOMUtil.getTreeHash(element, true);
|
||||
info.schemeName = scheme.getName();
|
||||
ExternalInfo oldInfo = schemeToInfo.put((ExternalizableScheme)scheme, info);
|
||||
LOG.assertTrue(oldInfo == null);
|
||||
}
|
||||
|
||||
addNewScheme(scheme, false);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
LOG.error("Cannot read scheme from " + resourceName, e);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private String getFileExtension(@NotNull CharSequence fileName, boolean allowAny) {
|
||||
String extension;
|
||||
if (StringUtilRt.endsWithIgnoreCase(fileName, mySchemeExtension)) {
|
||||
extension = mySchemeExtension;
|
||||
}
|
||||
else if (StringUtilRt.endsWithIgnoreCase(fileName, DirectoryStorageData.DEFAULT_EXT)) {
|
||||
extension = DirectoryStorageData.DEFAULT_EXT;
|
||||
}
|
||||
else if (allowAny) {
|
||||
extension = PathUtil.getFileExtension(fileName.toString());
|
||||
LOG.assertTrue(extension != null);
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("Scheme file extension " + fileName + " is unknown, must be filtered out");
|
||||
}
|
||||
return extension;
|
||||
}
|
||||
|
||||
private boolean isMy(@NotNull VirtualFileEvent event) {
|
||||
return isMy(event.getFile());
|
||||
}
|
||||
|
||||
private boolean isMy(@NotNull VirtualFile file) {
|
||||
return StringUtilRt.endsWithIgnoreCase(file.getNameSequence(), mySchemeExtension);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Collection<E> loadSchemes() {
|
||||
final Map<String, E> result = new LinkedHashMap<String, E>();
|
||||
if (provider != null && provider.isEnabled()) {
|
||||
provider.processChildren(myFileSpec, myRoamingType, new Condition<String>() {
|
||||
@Override
|
||||
public boolean value(@NotNull String name) {
|
||||
return canRead(name);
|
||||
}
|
||||
}, new StreamProvider.ChildrenProcessor() {
|
||||
@Override
|
||||
public boolean process(@NotNull String name, @NotNull InputStream input) {
|
||||
loadScheme(name, input, result);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
VirtualFile dir = getDirectory();
|
||||
VirtualFile[] files = dir == null ? null : dir.getChildren();
|
||||
if (files != null) {
|
||||
for (VirtualFile file : files) {
|
||||
readSchemeFromFile(file, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Collection<E> list = result.values();
|
||||
for (E scheme : list) {
|
||||
myProcessor.initScheme(scheme);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
public void reload() {
|
||||
// we must not remove non-persistent (e.g. predefined) schemes, because we cannot load it (obviously)
|
||||
for (int i = mySchemes.size() - 1; i >= 0; i--) {
|
||||
T scheme = mySchemes.get(i);
|
||||
//noinspection unchecked
|
||||
if (scheme instanceof ExternalizableScheme && getState(((E)scheme)) != BaseSchemeProcessor.State.NON_PERSISTENT) {
|
||||
mySchemes.remove(i);
|
||||
if (scheme == myCurrentScheme) {
|
||||
myCurrentScheme = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
retainExternalInfo(mySchemes);
|
||||
|
||||
loadSchemes();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private E findExternalizableSchemeByFileName(@NotNull String fileName) {
|
||||
for (T scheme : mySchemes) {
|
||||
if (scheme instanceof ExternalizableScheme) {
|
||||
if (fileName.equals((getFileName((ExternalizableScheme)scheme)) + mySchemeExtension)) {
|
||||
//noinspection CastConflictsWithInstanceof,unchecked
|
||||
return (E)scheme;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isOverwriteOnLoad(@NotNull T existingScheme) {
|
||||
if (existingScheme instanceof ExternalizableScheme) {
|
||||
ExternalInfo info = schemeToInfo.get(existingScheme);
|
||||
if (info != null && !mySchemeExtension.equals(info.fileExtension)) {
|
||||
// scheme from file with old extension, so, we must ignore it
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private E loadScheme(@NotNull CharSequence fileName, @NotNull InputStream input, @Nullable Map<String, E> result) {
|
||||
boolean duringLoad = result != null;
|
||||
try {
|
||||
Element element = JDOMUtil.load(input);
|
||||
E scheme;
|
||||
if (myProcessor instanceof BaseSchemeProcessor) {
|
||||
scheme = ((BaseSchemeProcessor<E>)myProcessor).readScheme(element, duringLoad);
|
||||
}
|
||||
else {
|
||||
//noinspection deprecation
|
||||
scheme = myProcessor.readScheme(new Document((Element)element.detach()));
|
||||
}
|
||||
|
||||
if (scheme == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String extension = getFileExtension(fileName, false);
|
||||
String fileNameWithoutExtension = fileName.subSequence(0, fileName.length() - extension.length()).toString();
|
||||
if (duringLoad) {
|
||||
if (filesToDelete.contains(fileName.toString())) {
|
||||
LOG.warn("Scheme file " + fileName + " is not loaded because marked to delete");
|
||||
return null;
|
||||
}
|
||||
|
||||
T existingScheme = findSchemeByName(scheme.getName());
|
||||
if (existingScheme != null) {
|
||||
if (isOverwriteOnLoad(existingScheme)) {
|
||||
removeScheme(existingScheme);
|
||||
}
|
||||
else {
|
||||
// We don't load scheme with duplicated name - if we generate unique name for it, it will be saved then with new name.
|
||||
// It is not what all can expect. Such situation in most cases indicates error on previous level, so, we just warn about it.
|
||||
LOG.warn("Scheme file " + fileName + " is not loaded because defines duplicated name " + scheme.getName());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ExternalInfo info = schemeToInfo.get(scheme);
|
||||
if (info == null) {
|
||||
info = new ExternalInfo(fileNameWithoutExtension, extension);
|
||||
schemeToInfo.put(scheme, info);
|
||||
}
|
||||
else {
|
||||
info.setFileNameWithoutExtension(fileNameWithoutExtension, extension);
|
||||
}
|
||||
info.hash = JDOMUtil.getTreeHash(element, true);
|
||||
info.schemeName = scheme.getName();
|
||||
|
||||
//noinspection unchecked
|
||||
T s = (T)scheme;
|
||||
if (duringLoad) {
|
||||
mySchemes.add(s);
|
||||
}
|
||||
else {
|
||||
addScheme(s);
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
result.put(scheme.getName(), scheme);
|
||||
}
|
||||
return scheme;
|
||||
}
|
||||
catch (Exception e) {
|
||||
LOG.error("Cannot read scheme " + fileName, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getFileName(@NotNull ExternalizableScheme scheme) {
|
||||
ExternalInfo info = schemeToInfo.get(scheme);
|
||||
return info == null ? null : info.fileNameWithoutExtension;
|
||||
}
|
||||
|
||||
private boolean canRead(@NotNull CharSequence name) {
|
||||
return myUpdateExtension && StringUtilRt.endsWithIgnoreCase(name, DirectoryStorageData.DEFAULT_EXT) || StringUtilRt.endsWithIgnoreCase(name, mySchemeExtension);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private E readSchemeFromFile(@NotNull VirtualFile file, @Nullable Map<String, E> result) {
|
||||
CharSequence fileName = file.getNameSequence();
|
||||
if (file.isDirectory() || !canRead(fileName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return loadScheme(fileName, file.getInputStream(), result);
|
||||
}
|
||||
catch (IOException e) {
|
||||
LOG.error("Cannot read scheme " + fileName, e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save() {
|
||||
boolean hasSchemes = false;
|
||||
UniqueNameGenerator nameGenerator = new UniqueNameGenerator();
|
||||
List<E> schemesToSave = new SmartList<E>();
|
||||
for (T scheme : mySchemes) {
|
||||
if (scheme instanceof ExternalizableScheme) {
|
||||
//noinspection CastConflictsWithInstanceof,unchecked
|
||||
E eScheme = (E)scheme;
|
||||
BaseSchemeProcessor.State state = getState(eScheme);
|
||||
if (state == BaseSchemeProcessor.State.NON_PERSISTENT) {
|
||||
continue;
|
||||
}
|
||||
|
||||
hasSchemes = true;
|
||||
|
||||
if (state != BaseSchemeProcessor.State.UNCHANGED) {
|
||||
schemesToSave.add(eScheme);
|
||||
}
|
||||
|
||||
String fileName = getFileName(eScheme);
|
||||
if (fileName != null && !isRenamed(eScheme)) {
|
||||
nameGenerator.addExistingName(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Throwable> errors = null;
|
||||
|
||||
for (E scheme : schemesToSave) {
|
||||
try {
|
||||
saveScheme(scheme, nameGenerator);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
if (errors == null) {
|
||||
errors = new SmartList<Throwable>();
|
||||
}
|
||||
errors.add(e);
|
||||
}
|
||||
}
|
||||
|
||||
VirtualFile dir = getDirectory();
|
||||
errors = deleteFiles(dir, errors);
|
||||
|
||||
if (!hasSchemes && dir != null) {
|
||||
errors = removeDirectoryIfEmpty(dir, errors);
|
||||
}
|
||||
|
||||
CompoundRuntimeException.doThrow(errors);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private List<Throwable> removeDirectoryIfEmpty(@NotNull VirtualFile dir, @Nullable List<Throwable> errors) {
|
||||
for (VirtualFile file : dir.getChildren()) {
|
||||
if (!file.is(VFileProperty.HIDDEN)) {
|
||||
LOG.info("Directory " + dir.getNameSequence() + " is not deleted: at least one file " + file.getNameSequence() + " exists");
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
|
||||
LOG.info("Remove schemes directory " + dir.getNameSequence());
|
||||
myDir = null;
|
||||
|
||||
AccessToken token = WriteAction.start();
|
||||
try {
|
||||
dir.delete(this);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
if (errors == null) {
|
||||
errors = new SmartList<Throwable>();
|
||||
}
|
||||
errors.add(e);
|
||||
}
|
||||
finally {
|
||||
token.finish();
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private BaseSchemeProcessor.State getState(@NotNull E scheme) {
|
||||
if (myProcessor instanceof BaseSchemeProcessor) {
|
||||
return ((BaseSchemeProcessor<E>)myProcessor).getState(scheme);
|
||||
}
|
||||
else {
|
||||
//noinspection deprecation
|
||||
return myProcessor.shouldBeSaved(scheme) ? BaseSchemeProcessor.State.POSSIBLY_CHANGED : BaseSchemeProcessor.State.NON_PERSISTENT;
|
||||
}
|
||||
}
|
||||
|
||||
private void saveScheme(@NotNull E scheme, @NotNull UniqueNameGenerator nameGenerator) throws WriteExternalException, IOException {
|
||||
@Nullable
|
||||
ExternalInfo externalInfo = schemeToInfo.get(scheme);
|
||||
String currentFileNameWithoutExtension = externalInfo == null ? null : externalInfo.fileNameWithoutExtension;
|
||||
Parent parent = myProcessor.writeScheme(scheme);
|
||||
Element element = parent == null || parent instanceof Element ? (Element)parent : ((Document)parent).detachRootElement();
|
||||
if (JDOMUtil.isEmpty(element)) {
|
||||
if (externalInfo != null) {
|
||||
scheduleDelete(externalInfo);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
String fileNameWithoutExtension = currentFileNameWithoutExtension;
|
||||
if (fileNameWithoutExtension == null || isRenamed(scheme)) {
|
||||
fileNameWithoutExtension = nameGenerator.generateUniqueName(FileUtil.sanitizeName(scheme.getName()));
|
||||
}
|
||||
|
||||
int newHash = JDOMUtil.getTreeHash(element, true);
|
||||
if (externalInfo != null && currentFileNameWithoutExtension == fileNameWithoutExtension && newHash == externalInfo.hash) {
|
||||
return;
|
||||
}
|
||||
|
||||
String fileName = fileNameWithoutExtension + mySchemeExtension;
|
||||
// file will be overwritten, so, we don't need to delete it
|
||||
filesToDelete.remove(fileName);
|
||||
|
||||
// stream provider always use LF separator
|
||||
BufferExposingByteArrayOutputStream byteOut = StorageUtil.writeToBytes(element, "\n");
|
||||
|
||||
String providerPath;
|
||||
if (provider != null && provider.isEnabled()) {
|
||||
providerPath = myFileSpec + '/' + fileName;
|
||||
if (!provider.isApplicable(providerPath, myRoamingType)) {
|
||||
providerPath = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
providerPath = null;
|
||||
}
|
||||
|
||||
// if another new scheme uses old name of this scheme, so, we must not delete it (as part of rename operation)
|
||||
boolean renamed = externalInfo != null && fileNameWithoutExtension != currentFileNameWithoutExtension && nameGenerator.value(currentFileNameWithoutExtension);
|
||||
if (providerPath == null) {
|
||||
VirtualFile file = null;
|
||||
VirtualFile dir = getDirectory();
|
||||
if (dir == null || !dir.isValid()) {
|
||||
dir = DirectoryBasedStorage.createDir(myIoDir, this);
|
||||
myDir = dir;
|
||||
}
|
||||
|
||||
if (renamed) {
|
||||
file = dir.findChild(externalInfo.fileNameWithoutExtension + externalInfo.fileExtension);
|
||||
if (file != null) {
|
||||
AccessToken token = WriteAction.start();
|
||||
try {
|
||||
file.rename(this, fileName);
|
||||
}
|
||||
finally {
|
||||
token.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (file == null) {
|
||||
file = DirectoryBasedStorage.getFile(fileName, dir, this);
|
||||
}
|
||||
|
||||
AccessToken token = WriteAction.start();
|
||||
try {
|
||||
OutputStream out = file.getOutputStream(this);
|
||||
try {
|
||||
byteOut.writeTo(out);
|
||||
}
|
||||
finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
finally {
|
||||
token.finish();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (renamed) {
|
||||
scheduleDelete(externalInfo);
|
||||
}
|
||||
provider.saveContent(providerPath, byteOut.getInternalBuffer(), byteOut.size(), myRoamingType);
|
||||
}
|
||||
|
||||
if (externalInfo == null) {
|
||||
externalInfo = new ExternalInfo(fileNameWithoutExtension, mySchemeExtension);
|
||||
schemeToInfo.put(scheme, externalInfo);
|
||||
}
|
||||
else {
|
||||
externalInfo.setFileNameWithoutExtension(fileNameWithoutExtension, mySchemeExtension);
|
||||
}
|
||||
externalInfo.hash = newHash;
|
||||
externalInfo.schemeName = scheme.getName();
|
||||
}
|
||||
|
||||
|
||||
private void scheduleDelete(@NotNull ExternalInfo externalInfo) {
|
||||
filesToDelete.add(externalInfo.fileNameWithoutExtension + externalInfo.fileExtension);
|
||||
}
|
||||
|
||||
private boolean isRenamed(@NotNull ExternalizableScheme scheme) {
|
||||
ExternalInfo info = schemeToInfo.get(scheme);
|
||||
return info != null && !scheme.getName().equals(info.schemeName);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private List<Throwable> deleteFiles(@Nullable VirtualFile dir, List<Throwable> errors) {
|
||||
if (filesToDelete.isEmpty()) {
|
||||
return errors;
|
||||
}
|
||||
|
||||
if (provider != null && provider.isEnabled()) {
|
||||
for (String name : filesToDelete) {
|
||||
try {
|
||||
StorageUtil.delete(provider, myFileSpec + '/' + name, myRoamingType);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
if (errors == null) {
|
||||
errors = new SmartList<Throwable>();
|
||||
}
|
||||
errors.add(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (dir != null) {
|
||||
AccessToken token = null;
|
||||
try {
|
||||
for (VirtualFile file : dir.getChildren()) {
|
||||
if (filesToDelete.contains(file.getName())) {
|
||||
if (token == null) {
|
||||
token = WriteAction.start();
|
||||
}
|
||||
|
||||
try {
|
||||
file.delete(this);
|
||||
}
|
||||
catch (IOException e) {
|
||||
if (errors == null) {
|
||||
errors = new SmartList<Throwable>();
|
||||
}
|
||||
errors.add(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally {
|
||||
if (token != null) {
|
||||
token.finish();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
filesToDelete.clear();
|
||||
return errors;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private VirtualFile getDirectory() {
|
||||
VirtualFile result = myDir;
|
||||
if (result == null) {
|
||||
result = LocalFileSystem.getInstance().findFileByIoFile(myIoDir);
|
||||
myDir = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getRootDirectory() {
|
||||
return myIoDir;
|
||||
}
|
||||
|
||||
private void schemeAdded(@NotNull T scheme) {
|
||||
if (!(scheme instanceof ExternalizableScheme)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ExternalInfo info = schemeToInfo.get(scheme);
|
||||
if (info != null) {
|
||||
filesToDelete.remove(info.fileNameWithoutExtension + info.fileExtension);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSchemes(@NotNull final List<T> newSchemes, @Nullable Condition<T> removeCondition) {
|
||||
if (removeCondition == null) {
|
||||
mySchemes.clear();
|
||||
}
|
||||
else {
|
||||
for (int i = mySchemes.size() - 1; i >= 0; i--) {
|
||||
T scheme = mySchemes.get(i);
|
||||
if (removeCondition.value(scheme)) {
|
||||
mySchemes.remove(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
retainExternalInfo(newSchemes);
|
||||
|
||||
mySchemes.ensureCapacity(newSchemes.size());
|
||||
for (T scheme : newSchemes) {
|
||||
mySchemes.add(scheme);
|
||||
schemeAdded(scheme);
|
||||
}
|
||||
|
||||
if (myCurrentSchemeName != null) {
|
||||
myCurrentScheme = findSchemeByName(myCurrentSchemeName);
|
||||
if (myCurrentScheme != null) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
myCurrentScheme = mySchemes.isEmpty() ? null : mySchemes.get(0);
|
||||
myCurrentSchemeName = myCurrentScheme == null ? null : myCurrentScheme.getName();
|
||||
}
|
||||
|
||||
private void retainExternalInfo(@NotNull final List<T> schemes) {
|
||||
if (schemeToInfo.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
schemeToInfo.retainEntries(new TObjectObjectProcedure<ExternalizableScheme, ExternalInfo>() {
|
||||
@Override
|
||||
public boolean execute(ExternalizableScheme scheme, ExternalInfo info) {
|
||||
for (T t : schemes) {
|
||||
if (t == scheme) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
scheduleDelete(info);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addNewScheme(@NotNull T scheme, boolean replaceExisting) {
|
||||
int toReplace = -1;
|
||||
for (int i = 0; i < mySchemes.size(); i++) {
|
||||
T existing = mySchemes.get(i);
|
||||
if (existing.getName().equals(scheme.getName())) {
|
||||
if (!Comparing.equal(existing.getClass(), scheme.getClass())) {
|
||||
LOG.warn("'" + scheme.getName() + "' " + existing.getClass().getSimpleName() + " replaced with " + scheme.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
toReplace = i;
|
||||
if (replaceExisting && existing instanceof ExternalizableScheme) {
|
||||
ExternalInfo oldInfo = schemeToInfo.remove((ExternalizableScheme)existing);
|
||||
if (oldInfo != null && scheme instanceof ExternalizableScheme) {
|
||||
ExternalInfo newInfo = schemeToInfo.get((ExternalizableScheme)scheme);
|
||||
if (newInfo == null) {
|
||||
schemeToInfo.put((ExternalizableScheme)scheme, oldInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (toReplace == -1) {
|
||||
mySchemes.add(scheme);
|
||||
}
|
||||
else if (replaceExisting || !(scheme instanceof ExternalizableScheme)) {
|
||||
mySchemes.set(toReplace, scheme);
|
||||
}
|
||||
else {
|
||||
//noinspection unchecked
|
||||
renameScheme((ExternalizableScheme)scheme, UniqueNameGenerator.generateUniqueName(scheme.getName(), collectExistingNames(mySchemes)));
|
||||
mySchemes.add(scheme);
|
||||
}
|
||||
|
||||
schemeAdded(scheme);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Collection<String> collectExistingNames(@NotNull Collection<T> schemes) {
|
||||
Set<String> result = new THashSet<String>(schemes.size());
|
||||
for (T scheme : schemes) {
|
||||
result.add(scheme.getName());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearAllSchemes() {
|
||||
schemeToInfo.forEachValue(new TObjectProcedure<ExternalInfo>() {
|
||||
@Override
|
||||
public boolean execute(ExternalInfo info) {
|
||||
scheduleDelete(info);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
myCurrentScheme = null;
|
||||
mySchemes.clear();
|
||||
schemeToInfo.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public List<T> getAllSchemes() {
|
||||
return Collections.unmodifiableList(mySchemes);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public T findSchemeByName(@NotNull String schemeName) {
|
||||
for (T scheme : mySchemes) {
|
||||
if (scheme.getName().equals(schemeName)) {
|
||||
return scheme;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentSchemeName(@Nullable String schemeName) {
|
||||
myCurrentSchemeName = schemeName;
|
||||
myCurrentScheme = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public T getCurrentScheme() {
|
||||
T scheme = myCurrentScheme;
|
||||
if (scheme == null && myCurrentSchemeName != null) {
|
||||
scheme = findSchemeByName(myCurrentSchemeName);
|
||||
if (scheme != null) {
|
||||
myCurrentScheme = scheme;
|
||||
}
|
||||
}
|
||||
return scheme;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeScheme(@NotNull T scheme) {
|
||||
for (int i = 0, n = mySchemes.size(); i < n; i++) {
|
||||
T s = mySchemes.get(i);
|
||||
if (scheme.getName().equals(s.getName())) {
|
||||
if (myCurrentScheme == s) {
|
||||
myCurrentScheme = null;
|
||||
}
|
||||
|
||||
if (s instanceof ExternalizableScheme) {
|
||||
ExternalInfo info = schemeToInfo.remove((ExternalizableScheme)s);
|
||||
if (info != null) {
|
||||
scheduleDelete(info);
|
||||
}
|
||||
}
|
||||
mySchemes.remove(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Collection<String> getAllSchemeNames() {
|
||||
if (mySchemes.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<String> names = new ArrayList<String>(mySchemes.size());
|
||||
for (T scheme : mySchemes) {
|
||||
names.add(scheme.getName());
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
private static void renameScheme(@NotNull ExternalizableScheme scheme, @NotNull String newName) {
|
||||
if (!newName.equals(scheme.getName())) {
|
||||
scheme.setName(newName);
|
||||
LOG.assertTrue(newName.equals(scheme.getName()));
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ExternalInfo {
|
||||
public ExternalInfo(@NotNull String fileNameWithoutExtension, @NotNull String fileExtension) {
|
||||
this.fileNameWithoutExtension = fileNameWithoutExtension;
|
||||
this.fileExtension = fileExtension;
|
||||
}
|
||||
|
||||
// we keep it to detect rename
|
||||
private String schemeName;
|
||||
|
||||
|
||||
@NotNull
|
||||
private String fileNameWithoutExtension;
|
||||
|
||||
private int hash;
|
||||
|
||||
private String fileExtension;
|
||||
|
||||
public void setFileNameWithoutExtension(@NotNull String nameWithoutExtension, @NotNull String extension) {
|
||||
fileNameWithoutExtension = nameWithoutExtension;
|
||||
fileExtension = extension;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return fileNameWithoutExtension;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return myFileSpec;
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import gnu.trove.TObjectObjectProcedure;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class CollectionModelEditor<T, E extends CollectionItemEditor<T>> implements ElementProducer<T> {
|
||||
@@ -129,10 +130,7 @@ public abstract class CollectionModelEditor<T, E extends CollectionItemEditor<T>
|
||||
mutable = itemEditor.clone(item, false);
|
||||
modifiedToOriginal.put(mutable, item);
|
||||
originalToModified.put(item, mutable);
|
||||
|
||||
List<T> items = getItems();
|
||||
// silently replace item
|
||||
items.set(index == -1 ? ContainerUtil.indexOfIdentity(items, item) : index, mutable);
|
||||
silentlyReplaceItem(item, mutable, index);
|
||||
}
|
||||
return mutable;
|
||||
}
|
||||
@@ -146,4 +144,26 @@ public abstract class CollectionModelEditor<T, E extends CollectionItemEditor<T>
|
||||
modifiedToOriginal.forEachEntry(procedure);
|
||||
}
|
||||
}
|
||||
|
||||
protected void silentlyReplaceItem(@NotNull T oldItem, @NotNull T newItem, int index) {
|
||||
// silently replace item
|
||||
List<T> items = getItems();
|
||||
items.set(index == -1 ? ContainerUtil.indexOfIdentity(items, oldItem) : index, newItem);
|
||||
}
|
||||
|
||||
protected final boolean areSelectedItemsRemovable(@NotNull ListSelectionModel selectionMode) {
|
||||
int minSelectionIndex = selectionMode.getMinSelectionIndex();
|
||||
int maxSelectionIndex = selectionMode.getMaxSelectionIndex();
|
||||
if (minSelectionIndex < 0 || maxSelectionIndex < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
List<T> items = getItems();
|
||||
for (int i = minSelectionIndex; i <= maxSelectionIndex; i++) {
|
||||
if (itemEditor.isRemovable(items.get(i))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.intellij.util.ui;
|
||||
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.ui.*;
|
||||
import com.intellij.ui.components.JBList;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
@@ -29,19 +30,19 @@ public class ListModelEditor<T> extends ListModelEditorBase<T> {
|
||||
|
||||
private JBList list = new JBList(model);
|
||||
|
||||
public ListModelEditor(@NotNull final ListItemEditor<T> itemEditor) {
|
||||
public ListModelEditor(@NotNull ListItemEditor<T> itemEditor) {
|
||||
super(itemEditor);
|
||||
|
||||
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
list.setCellRenderer(new MyListCellRenderer());
|
||||
|
||||
toolbarDecorator = ToolbarDecorator.createDecorator(list)
|
||||
toolbarDecorator = ToolbarDecorator.createDecorator(list, model)
|
||||
.setAddAction(new AnActionButtonRunnable() {
|
||||
@Override
|
||||
public void run(AnActionButton button) {
|
||||
if (!model.isEmpty()) {
|
||||
T lastItem = model.getElementAt(model.getSize() - 1);
|
||||
if (itemEditor.isEmpty(lastItem)) {
|
||||
if (ListModelEditor.this.itemEditor.isEmpty(lastItem)) {
|
||||
ListScrollingUtil.selectItem(list, ContainerUtil.indexOfIdentity(model.getItems(), lastItem));
|
||||
return;
|
||||
}
|
||||
@@ -51,7 +52,19 @@ public class ListModelEditor<T> extends ListModelEditorBase<T> {
|
||||
model.add(item);
|
||||
ListScrollingUtil.selectItem(list, ContainerUtil.indexOfIdentity(model.getItems(), item));
|
||||
}
|
||||
});
|
||||
})
|
||||
.setRemoveActionUpdater(new AnActionButtonUpdater() {
|
||||
@Override
|
||||
public boolean isEnabled(AnActionEvent e) {
|
||||
return areSelectedItemsRemovable(list.getSelectionModel());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ListModelEditor<T> disableUpDownActions() {
|
||||
toolbarDecorator.disableUpDownActions();
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
@@ -19,7 +19,6 @@ import com.intellij.openapi.options.ConfigurationException;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.ui.CollectionListModel;
|
||||
import com.intellij.ui.MutableCollectionComboBoxModel;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import gnu.trove.TObjectObjectProcedure;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -85,11 +84,7 @@ public abstract class ListModelEditorBase<T> extends CollectionModelEditor<T, Li
|
||||
@Override
|
||||
public boolean execute(T newItem, T oldItem) {
|
||||
itemEditor.applyModifiedProperties(newItem, oldItem);
|
||||
int index = ContainerUtil.indexOfIdentity(items, newItem);
|
||||
if (index == -1) {
|
||||
LOG.error("Inconsistent model", newItem.toString());
|
||||
}
|
||||
model.setElementAt(oldItem, index);
|
||||
silentlyReplaceItem(newItem, oldItem, -1);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
@@ -98,12 +93,24 @@ public abstract class ListModelEditorBase<T> extends CollectionModelEditor<T, Li
|
||||
return items;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void silentlyReplaceItem(@NotNull T oldItem, @NotNull T newItem, int index) {
|
||||
super.silentlyReplaceItem(oldItem, newItem, index);
|
||||
model.checkSelectionOnSilentReplace(oldItem, newItem);
|
||||
}
|
||||
|
||||
protected final class MyModel extends MutableCollectionComboBoxModel<T> {
|
||||
@NotNull
|
||||
final List<T> items() {
|
||||
return super.getInternalList();
|
||||
}
|
||||
|
||||
void checkSelectionOnSilentReplace(@NotNull T oldItem, @NotNull T newItem) {
|
||||
if (mySelection == oldItem) {
|
||||
mySelection = newItem;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void itemReplaced(@NotNull T existingItem, @Nullable T newItem) {
|
||||
helper.remove(existingItem);
|
||||
|
||||
@@ -98,6 +98,7 @@ public class TableModelEditor<T> extends CollectionModelEditor<T, CollectionItem
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public TableModelEditor<T> preferredScrollableViewportHeightInRows(int rows) {
|
||||
table.setPreferredScrollableViewportSize(new Dimension(200, table.getRowHeight() * rows));
|
||||
return this;
|
||||
@@ -151,11 +152,13 @@ public class TableModelEditor<T> extends CollectionModelEditor<T, CollectionItem
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public TableModelEditor<T> disableUpDownActions() {
|
||||
toolbarDecorator.disableUpDownActions();
|
||||
return this;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public TableModelEditor<T> enabled(boolean value) {
|
||||
table.setEnabled(value);
|
||||
return this;
|
||||
@@ -365,12 +368,7 @@ public class TableModelEditor<T> extends CollectionModelEditor<T, CollectionItem
|
||||
|
||||
@Override
|
||||
public boolean isEnabled(AnActionEvent e) {
|
||||
for (T item : table.getSelectedObjects()) {
|
||||
if (itemEditor.isRemovable(item)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return areSelectedItemsRemovable(table.getSelectionModel());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,5 +25,6 @@
|
||||
<orderEntry type="library" name="http-client" level="project" />
|
||||
<orderEntry type="module" module-name="jps-model-impl" scope="TEST" />
|
||||
<orderEntry type="library" name="gson" level="project" />
|
||||
<orderEntry type="library" scope="TEST" name="KotlinJavaRuntime" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -0,0 +1 @@
|
||||
<scheme name="1" data="true"/>
|
||||
@@ -0,0 +1 @@
|
||||
<scheme name="1" data="false"/>
|
||||
@@ -0,0 +1 @@
|
||||
<scheme name="1"/>
|
||||
@@ -0,0 +1 @@
|
||||
<scheme name="first"/>
|
||||
@@ -0,0 +1 @@
|
||||
<scheme name="second"/>
|
||||
@@ -0,0 +1 @@
|
||||
<scheme name="default"/>
|
||||
-288
@@ -1,288 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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;
|
||||
|
||||
import com.intellij.application.options.PathMacrosImpl;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.components.*;
|
||||
import com.intellij.openapi.components.impl.stores.*;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.vfs.CharsetToolkit;
|
||||
import com.intellij.testFramework.LightPlatformTestCase;
|
||||
import com.intellij.util.messages.MessageBus;
|
||||
import com.intellij.util.xmlb.XmlSerializerUtil;
|
||||
import gnu.trove.THashMap;
|
||||
import org.intellij.lang.annotations.Language;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
|
||||
public class ApplicationStoreTest extends LightPlatformTestCase {
|
||||
private File testAppConfig;
|
||||
private MyComponentStore componentStore;
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
String testAppConfigPath = System.getProperty("test.app.config.path");
|
||||
if (testAppConfigPath == null) {
|
||||
testAppConfig = FileUtil.createTempDirectory("testAppSettings", null);
|
||||
}
|
||||
else {
|
||||
testAppConfig = new File(FileUtil.expandUserHome(testAppConfigPath));
|
||||
}
|
||||
FileUtil.delete(testAppConfig);
|
||||
|
||||
componentStore = new MyComponentStore(testAppConfig.getAbsolutePath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tearDown() throws Exception {
|
||||
try {
|
||||
Disposer.dispose(componentStore);
|
||||
componentStore = null;
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
super.tearDown();
|
||||
}
|
||||
finally {
|
||||
FileUtil.delete(testAppConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testStreamProviderSaveIfSeveralStoragesConfigured() throws Throwable {
|
||||
SeveralStoragesConfigured component = new SeveralStoragesConfigured();
|
||||
|
||||
MyStreamProvider streamProvider = new MyStreamProvider();
|
||||
componentStore.getStateStorageManager().setStreamProvider(streamProvider);
|
||||
|
||||
componentStore.initComponent(component, false);
|
||||
component.foo = "newValue";
|
||||
StoreUtil.save(componentStore, null);
|
||||
|
||||
assertThat(
|
||||
streamProvider.data.get(RoamingType.PER_USER).get(StoragePathMacros.APP_CONFIG + "/proxy.settings.xml"),
|
||||
equalTo("<application>\n" +
|
||||
" <component name=\"HttpConfigurable\">\n" +
|
||||
" <option name=\"foo\" value=\"newValue\" />\n" +
|
||||
" </component>\n" +
|
||||
"</application>"));
|
||||
}
|
||||
|
||||
public void testLoadFromStreamProvider() throws Exception {
|
||||
SeveralStoragesConfigured component = new SeveralStoragesConfigured();
|
||||
|
||||
MyStreamProvider streamProvider = new MyStreamProvider();
|
||||
THashMap<String, String> map = new THashMap<String, String>();
|
||||
map.put(StoragePathMacros.APP_CONFIG + "/proxy.settings.xml", "<application>\n" +
|
||||
" <component name=\"HttpConfigurable\">\n" +
|
||||
" <option name=\"foo\" value=\"newValue\" />\n" +
|
||||
" </component>\n" +
|
||||
"</application>");
|
||||
streamProvider.data.put(RoamingType.PER_USER, map);
|
||||
|
||||
componentStore.getStateStorageManager().setStreamProvider(streamProvider);
|
||||
componentStore.initComponent(component, false);
|
||||
assertThat(component.foo, equalTo("newValue"));
|
||||
}
|
||||
|
||||
public void testRemoveDeprecatedStorageOnWrite() throws Exception {
|
||||
doRemoveDeprecatedStorageOnWrite(new SeveralStoragesConfigured());
|
||||
}
|
||||
|
||||
public void testRemoveDeprecatedStorageOnWrite2() throws Exception {
|
||||
doRemoveDeprecatedStorageOnWrite(new ActualStorageLast());
|
||||
}
|
||||
|
||||
private void doRemoveDeprecatedStorageOnWrite(@NotNull Foo component) throws IOException {
|
||||
File oldFile = saveConfig("other.xml", "<application>" +
|
||||
" <component name=\"HttpConfigurable\">\n" +
|
||||
" <option name=\"foo\" value=\"old\" />\n" +
|
||||
" </component>\n" +
|
||||
"</application>");
|
||||
|
||||
saveConfig("proxy.settings.xml", "<application>\n" +
|
||||
" <component name=\"HttpConfigurable\">\n" +
|
||||
" <option name=\"foo\" value=\"new\" />\n" +
|
||||
" </component>\n" +
|
||||
"</application>");
|
||||
|
||||
componentStore.initComponent(component, false);
|
||||
assertThat(component.foo, equalTo("new"));
|
||||
|
||||
component.foo = "new2";
|
||||
StoreUtil.save(componentStore, null);
|
||||
|
||||
assertThat(oldFile.exists(), equalTo(false));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private File saveConfig(@NotNull String fileName, @Language("XML") String data) throws IOException {
|
||||
File file = new File(testAppConfig, fileName);
|
||||
FileUtil.writeToFile(file, data);
|
||||
return file;
|
||||
}
|
||||
|
||||
private static class MyStreamProvider extends StreamProvider {
|
||||
public final Map<RoamingType, Map<String, String>> data = new THashMap<RoamingType, Map<String, String>>();
|
||||
|
||||
@Override
|
||||
public void saveContent(@NotNull String fileSpec,
|
||||
@NotNull byte[] content,
|
||||
int size,
|
||||
@NotNull RoamingType roamingType) {
|
||||
getMap(roamingType).put(fileSpec, new String(content, 0, size, CharsetToolkit.UTF8_CHARSET));
|
||||
}
|
||||
|
||||
private Map<String, String> getMap(@NotNull RoamingType roamingType) {
|
||||
Map<String, String> map = data.get(roamingType);
|
||||
if (map == null) {
|
||||
map = new THashMap<String, String>();
|
||||
data.put(roamingType, map);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public InputStream loadContent(@NotNull String fileSpec, @NotNull RoamingType roamingType) throws IOException {
|
||||
String data = getMap(roamingType).get(fileSpec);
|
||||
return data == null ? null : new ByteArrayInputStream(data.getBytes(CharsetToolkit.UTF8_CHARSET));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(@NotNull String fileSpec, @NotNull RoamingType roamingType) {
|
||||
Map<String, String> map = data.get(roamingType);
|
||||
if (map != null) {
|
||||
map.remove(fileSpec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static class MyComponentStore extends ComponentStoreImpl implements Disposable {
|
||||
private final StateStorageManager stateStorageManager;
|
||||
|
||||
MyComponentStore(@NotNull final String testAppConfigPath) {
|
||||
TrackingPathMacroSubstitutor macroSubstitutor = new ApplicationPathMacroManager().createTrackingSubstitutor();
|
||||
stateStorageManager = new StateStorageManagerImpl(macroSubstitutor, "application", this, ApplicationManager.getApplication().getPicoContainer()) {
|
||||
@NotNull
|
||||
@Override
|
||||
protected StorageData createStorageData(@NotNull String fileSpec, @NotNull String filePath) {
|
||||
return new StorageData("application");
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected String getOldStorageSpec(@NotNull Object component, @NotNull String componentName, @NotNull StateStorageOperation operation) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TrackingPathMacroSubstitutor getMacroSubstitutor(@NotNull final String fileSpec) {
|
||||
if (fileSpec.equals(StoragePathMacros.APP_CONFIG + "/" + PathMacrosImpl.EXT_FILE_NAME + ".xml")) {
|
||||
return null;
|
||||
}
|
||||
return super.getMacroSubstitutor(fileSpec);
|
||||
}
|
||||
};
|
||||
|
||||
stateStorageManager.addMacro(StoragePathMacros.APP_CONFIG, testAppConfigPath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void load() {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public StateStorageManager getStateStorageManager() {
|
||||
return stateStorageManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected PathMacroManager getPathMacroManagerForDefaults() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected MessageBus getMessageBus() {
|
||||
return ApplicationManager.getApplication().getMessageBus();
|
||||
}
|
||||
}
|
||||
|
||||
abstract static class Foo {
|
||||
public String foo = "defaultValue";
|
||||
}
|
||||
|
||||
@State(
|
||||
name = "HttpConfigurable",
|
||||
storages = {
|
||||
@Storage(file = StoragePathMacros.APP_CONFIG + "/proxy.settings.xml"),
|
||||
@Storage(file = StoragePathMacros.APP_CONFIG + "/other.xml", deprecated = true)
|
||||
}
|
||||
)
|
||||
static class SeveralStoragesConfigured extends Foo implements PersistentStateComponent<SeveralStoragesConfigured> {
|
||||
@Nullable
|
||||
@Override
|
||||
public SeveralStoragesConfigured getState() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadState(SeveralStoragesConfigured state) {
|
||||
XmlSerializerUtil.copyBean(state, this);
|
||||
}
|
||||
}
|
||||
|
||||
@State(
|
||||
name = "HttpConfigurable",
|
||||
storages = {
|
||||
@Storage(file = StoragePathMacros.APP_CONFIG + "/other.xml", deprecated = true),
|
||||
@Storage(file = StoragePathMacros.APP_CONFIG + "/proxy.settings.xml")
|
||||
}
|
||||
)
|
||||
static class ActualStorageLast extends Foo implements PersistentStateComponent<ActualStorageLast> {
|
||||
@Nullable
|
||||
@Override
|
||||
public ActualStorageLast getState() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadState(ActualStorageLast state) {
|
||||
XmlSerializerUtil.copyBean(state, this);
|
||||
}
|
||||
}
|
||||
}
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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
|
||||
|
||||
import com.intellij.application.options.PathMacrosImpl
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.*
|
||||
import com.intellij.openapi.components.impl.stores.*
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.vfs.CharsetToolkit
|
||||
import com.intellij.testFramework.LightPlatformTestCase
|
||||
import com.intellij.util.messages.MessageBus
|
||||
import com.intellij.util.xmlb.XmlSerializerUtil
|
||||
import gnu.trove.THashMap
|
||||
import org.hamcrest.CoreMatchers.equalTo
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.intellij.lang.annotations.Language
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
|
||||
public class ApplicationStoreTest : LightPlatformTestCase() {
|
||||
private var testAppConfig: File? = null
|
||||
private var componentStore: MyComponentStore? = null
|
||||
|
||||
throws(Exception::class)
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
|
||||
val testAppConfigPath = System.getProperty("test.app.config.path")
|
||||
if (testAppConfigPath == null) {
|
||||
testAppConfig = FileUtil.createTempDirectory("testAppSettings", null)
|
||||
}
|
||||
else {
|
||||
testAppConfig = File(FileUtil.expandUserHome(testAppConfigPath))
|
||||
}
|
||||
FileUtil.delete(testAppConfig!!)
|
||||
|
||||
componentStore = MyComponentStore(testAppConfig!!.getAbsolutePath())
|
||||
}
|
||||
|
||||
throws(Exception::class)
|
||||
override fun tearDown() {
|
||||
try {
|
||||
Disposer.dispose(componentStore!!)
|
||||
componentStore = null
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
super.tearDown()
|
||||
}
|
||||
finally {
|
||||
FileUtil.delete(testAppConfig!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public fun testStreamProviderSaveIfSeveralStoragesConfigured() {
|
||||
val component = SeveralStoragesConfigured()
|
||||
|
||||
val streamProvider = MyStreamProvider()
|
||||
componentStore!!.getStateStorageManager().setStreamProvider(streamProvider)
|
||||
|
||||
componentStore!!.initComponent(component, false)
|
||||
component.foo = "newValue"
|
||||
StoreUtil.save(componentStore!!, null)
|
||||
|
||||
assertThat<String>(streamProvider.data.get(RoamingType.PER_USER)!!.get(StoragePathMacros.APP_CONFIG + "/proxy.settings.xml"), equalTo("<application>\n" + " <component name=\"HttpConfigurable\">\n" + " <option name=\"foo\" value=\"newValue\" />\n" + " </component>\n" + "</application>"))
|
||||
}
|
||||
|
||||
public fun testLoadFromStreamProvider() {
|
||||
val component = SeveralStoragesConfigured()
|
||||
|
||||
val streamProvider = MyStreamProvider()
|
||||
val map = THashMap<String, String>()
|
||||
map.put(StoragePathMacros.APP_CONFIG + "/proxy.settings.xml", "<application>\n" + " <component name=\"HttpConfigurable\">\n" + " <option name=\"foo\" value=\"newValue\" />\n" + " </component>\n" + "</application>")
|
||||
streamProvider.data.put(RoamingType.PER_USER, map)
|
||||
|
||||
componentStore!!.getStateStorageManager().setStreamProvider(streamProvider)
|
||||
componentStore!!.initComponent(component, false)
|
||||
assertThat(component.foo, equalTo("newValue"))
|
||||
}
|
||||
|
||||
public fun testRemoveDeprecatedStorageOnWrite() {
|
||||
}
|
||||
|
||||
public fun testRemoveDeprecatedStorageOnWrite2() {
|
||||
doRemoveDeprecatedStorageOnWrite(ActualStorageLast())
|
||||
}
|
||||
|
||||
private fun doRemoveDeprecatedStorageOnWrite(component: Foo) {
|
||||
val oldFile = saveConfig("other.xml", "<application>" + " <component name=\"HttpConfigurable\">\n" + " <option name=\"foo\" value=\"old\" />\n" + " </component>\n" + "</application>")
|
||||
|
||||
saveConfig("proxy.settings.xml", "<application>\n" + " <component name=\"HttpConfigurable\">\n" + " <option name=\"foo\" value=\"new\" />\n" + " </component>\n" + "</application>")
|
||||
|
||||
componentStore!!.initComponent(component, false)
|
||||
assertThat(component.foo, equalTo("new"))
|
||||
|
||||
component.foo = "new2"
|
||||
StoreUtil.save(componentStore!!, null)
|
||||
|
||||
assertThat(oldFile.exists(), equalTo(false))
|
||||
}
|
||||
|
||||
throws(IOException::class)
|
||||
private fun saveConfig(fileName: String, Language("XML") data: String): File {
|
||||
val file = File(testAppConfig, fileName)
|
||||
FileUtil.writeToFile(file, data)
|
||||
return file
|
||||
}
|
||||
|
||||
private class MyStreamProvider : StreamProvider {
|
||||
public val data: MutableMap<RoamingType, MutableMap<String, String>> = THashMap()
|
||||
|
||||
override fun saveContent(fileSpec: String, content: ByteArray, size: Int, roamingType: RoamingType) {
|
||||
getMap(roamingType).put(fileSpec, String(content, 0, size, CharsetToolkit.UTF8_CHARSET))
|
||||
}
|
||||
|
||||
private fun getMap(roamingType: RoamingType): MutableMap<String, String> {
|
||||
var map = data.get(roamingType)
|
||||
if (map == null) {
|
||||
map = THashMap<String, String>()
|
||||
data.put(roamingType, map)
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
throws(IOException::class)
|
||||
override fun loadContent(fileSpec: String, roamingType: RoamingType): InputStream? {
|
||||
val data = getMap(roamingType).get(fileSpec)
|
||||
return if (data == null) null else ByteArrayInputStream(data.toByteArray(CharsetToolkit.UTF8_CHARSET))
|
||||
}
|
||||
|
||||
override fun delete(fileSpec: String, roamingType: RoamingType) {
|
||||
val map = data.get(roamingType)
|
||||
map?.remove(fileSpec)
|
||||
}
|
||||
}
|
||||
|
||||
class MyComponentStore(testAppConfigPath: String) : ComponentStoreImpl(), Disposable {
|
||||
private val stateStorageManager: StateStorageManager
|
||||
|
||||
init {
|
||||
val macroSubstitutor = ApplicationPathMacroManager().createTrackingSubstitutor()
|
||||
stateStorageManager = object : StateStorageManagerImpl(macroSubstitutor, "application", this, ApplicationManager.getApplication().getPicoContainer()) {
|
||||
override fun createStorageData(fileSpec: String, filePath: String): StorageData {
|
||||
return StorageData("application")
|
||||
}
|
||||
|
||||
override fun getOldStorageSpec(component: Any, componentName: String, operation: StateStorageOperation): String? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getMacroSubstitutor(fileSpec: String): TrackingPathMacroSubstitutor? {
|
||||
if (fileSpec == StoragePathMacros.APP_CONFIG + "/" + PathMacrosImpl.EXT_FILE_NAME + ".xml") {
|
||||
return null
|
||||
}
|
||||
return super.getMacroSubstitutor(fileSpec)
|
||||
}
|
||||
}
|
||||
|
||||
stateStorageManager.addMacro(StoragePathMacros.APP_CONFIG, testAppConfigPath)
|
||||
}
|
||||
|
||||
override fun load() {
|
||||
}
|
||||
|
||||
override fun getStateStorageManager(): StateStorageManager {
|
||||
return stateStorageManager
|
||||
}
|
||||
|
||||
override fun dispose() {
|
||||
}
|
||||
|
||||
override fun getPathMacroManagerForDefaults(): PathMacroManager? {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getMessageBus(): MessageBus {
|
||||
return ApplicationManager.getApplication().getMessageBus()
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Foo {
|
||||
public var foo: String = "defaultValue"
|
||||
}
|
||||
|
||||
State(name = "HttpConfigurable", storages = arrayOf(Storage(file = StoragePathMacros.APP_CONFIG + "/proxy.settings.xml"), Storage(file = StoragePathMacros.APP_CONFIG + "/other.xml", deprecated = true)))
|
||||
class SeveralStoragesConfigured : Foo(), PersistentStateComponent<SeveralStoragesConfigured> {
|
||||
override fun getState(): SeveralStoragesConfigured? {
|
||||
return this
|
||||
}
|
||||
|
||||
override fun loadState(state: SeveralStoragesConfigured) {
|
||||
XmlSerializerUtil.copyBean(state, this)
|
||||
}
|
||||
}
|
||||
|
||||
State(name = "HttpConfigurable", storages = arrayOf(Storage(file = StoragePathMacros.APP_CONFIG + "/other.xml", deprecated = true), Storage(file = StoragePathMacros.APP_CONFIG + "/proxy.settings.xml")))
|
||||
class ActualStorageLast : Foo(), PersistentStateComponent<ActualStorageLast> {
|
||||
override fun getState(): ActualStorageLast? {
|
||||
return this
|
||||
}
|
||||
|
||||
override fun loadState(state: ActualStorageLast) {
|
||||
XmlSerializerUtil.copyBean(state, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.intellij.options
|
||||
|
||||
import com.intellij.openapi.components.RoamingType
|
||||
import com.intellij.openapi.components.impl.stores.StreamProvider
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.util.SmartList
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.InputStream
|
||||
|
||||
class MockStreamProvider(private val myBaseDir: File) : StreamProvider {
|
||||
override fun isApplicable(fileSpec: String, roamingType: RoamingType) = roamingType === RoamingType.PER_USER
|
||||
|
||||
override fun saveContent(fileSpec: String, content: ByteArray, size: Int, roamingType: RoamingType) {
|
||||
FileUtil.writeToFile(File(myBaseDir, fileSpec), content, 0, size)
|
||||
}
|
||||
|
||||
override fun loadContent(fileSpec: String, roamingType: RoamingType): InputStream? {
|
||||
val file = File(myBaseDir, fileSpec)
|
||||
//noinspection IOResourceOpenedButNotSafelyClosed
|
||||
return if (file.exists()) FileInputStream(file) else null
|
||||
}
|
||||
|
||||
override fun listSubFiles(fileSpec: String, roamingType: RoamingType): Collection<String> {
|
||||
if (roamingType !== RoamingType.PER_USER) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val files = File(myBaseDir, fileSpec).listFiles() ?: return emptyList()
|
||||
val names = SmartList<String>()
|
||||
for (file in files) {
|
||||
names.add(file.getName())
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
override fun delete(fileSpec: String, roamingType: RoamingType) {
|
||||
FileUtil.delete(File(myBaseDir, fileSpec))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
package com.intellij.options
|
||||
|
||||
import com.intellij.openapi.application.invokeAndWaitIfNeed
|
||||
import com.intellij.openapi.components.RoamingType
|
||||
import com.intellij.openapi.options.BaseSchemeProcessor
|
||||
import com.intellij.openapi.options.ExternalizableScheme
|
||||
import com.intellij.openapi.options.SchemeManagerImpl
|
||||
import com.intellij.openapi.util.JDOMUtil
|
||||
import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.testFramework.PlatformTestUtil
|
||||
import com.intellij.util.SmartList
|
||||
import com.intellij.util.lang.CompoundRuntimeException
|
||||
import com.intellij.util.xmlb.SkipDefaultValuesSerializationFilters
|
||||
import com.intellij.util.xmlb.XmlSerializer
|
||||
import com.intellij.util.xmlb.annotations.Attribute
|
||||
import com.intellij.util.xmlb.annotations.Tag
|
||||
import com.intellij.util.xmlb.annotations.Transient
|
||||
import gnu.trove.THashMap
|
||||
import org.hamcrest.CoreMatchers.equalTo
|
||||
import org.hamcrest.CoreMatchers.not
|
||||
import org.hamcrest.CoreMatchers.notNullValue
|
||||
import org.hamcrest.CoreMatchers.sameInstance
|
||||
import org.hamcrest.MatcherAssert.assertThat
|
||||
import org.hamcrest.collection.IsMapContaining.hasKey
|
||||
import org.jdom.Element
|
||||
import org.jetbrains.testFramework.FixtureRule
|
||||
import org.jetbrains.testFramework.TemporaryDirectory
|
||||
import org.jetbrains.testFramework.exists
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import java.io.File
|
||||
|
||||
val FILE_SPEC = "REMOTE"
|
||||
|
||||
class SchemeManagerTest {
|
||||
private val fixtureManager = FixtureRule()
|
||||
public Rule fun getFixtureManager(): FixtureRule = fixtureManager
|
||||
|
||||
private val tempDirManager = TemporaryDirectory()
|
||||
public Rule fun getTemporaryFolder(): TemporaryDirectory = tempDirManager
|
||||
|
||||
private var localBaseDir: File? = null
|
||||
private var remoteBaseDir: File? = null
|
||||
|
||||
private fun getTestDataPath() = PlatformTestUtil.getCommunityPath().replace(File.separatorChar, '/') + "/platform/platform-tests/testData/options"
|
||||
|
||||
public Test fun testLoadSchemes() {
|
||||
doLoadSaveTest("options1", "1->first;2->second")
|
||||
}
|
||||
|
||||
public Test fun testLoadSimpleSchemes() {
|
||||
doLoadSaveTest("options", "1->1")
|
||||
}
|
||||
|
||||
public Test fun testDeleteScheme() {
|
||||
val manager = createAndLoad("options1")
|
||||
manager.removeScheme(TestScheme("first"))
|
||||
manager.save()
|
||||
|
||||
checkSchemes("2->second")
|
||||
}
|
||||
|
||||
public Test fun testRenameScheme() {
|
||||
val manager = createAndLoad("options1")
|
||||
|
||||
val scheme = manager.findSchemeByName("first")
|
||||
assertThat(scheme, notNullValue())
|
||||
assert(scheme != null)
|
||||
scheme!!.setName("renamed")
|
||||
manager.save()
|
||||
|
||||
checkSchemes("2->second;renamed->renamed")
|
||||
}
|
||||
|
||||
public Test fun testRenameScheme2() {
|
||||
val manager = createAndLoad("options1")
|
||||
|
||||
val first = manager.findSchemeByName("first")
|
||||
assertThat(first, notNullValue())
|
||||
assert(first != null)
|
||||
first!!.setName("2")
|
||||
val second = manager.findSchemeByName("second")
|
||||
assertThat(second, notNullValue())
|
||||
assert(second != null)
|
||||
second!!.setName("1")
|
||||
manager.save()
|
||||
|
||||
checkSchemes("1->1;2->2")
|
||||
}
|
||||
|
||||
public Test fun testDeleteRenamedScheme() {
|
||||
val manager = createAndLoad("options1")
|
||||
|
||||
val firstScheme = manager.findSchemeByName("first")
|
||||
assertThat(firstScheme, notNullValue())
|
||||
assert(firstScheme != null)
|
||||
firstScheme!!.setName("first_renamed")
|
||||
manager.save()
|
||||
|
||||
checkSchemes(File(remoteBaseDir, "REMOTE"), "first_renamed->first_renamed;2->second", true)
|
||||
checkSchemes(localBaseDir!!, "", false)
|
||||
|
||||
firstScheme.setName("first_renamed2")
|
||||
manager.removeScheme(firstScheme)
|
||||
manager.save()
|
||||
|
||||
checkSchemes(File(remoteBaseDir, "REMOTE"), "2->second", true)
|
||||
checkSchemes(localBaseDir!!, "", false)
|
||||
}
|
||||
|
||||
public Test fun testDeleteAndCreateSchemeWithTheSameName() {
|
||||
val manager = createAndLoad("options1")
|
||||
val firstScheme = manager.findSchemeByName("first")
|
||||
assertThat(firstScheme, notNullValue())
|
||||
|
||||
manager.removeScheme(firstScheme!!)
|
||||
manager.addScheme(TestScheme("first"))
|
||||
manager.save()
|
||||
checkSchemes("2->second;first->first")
|
||||
}
|
||||
|
||||
public Test fun testGenerateUniqueSchemeName() {
|
||||
val manager = createAndLoad("options1")
|
||||
val scheme = TestScheme("first")
|
||||
manager.addNewScheme(scheme, false)
|
||||
|
||||
assertThat("first2", equalTo(scheme.getName()))
|
||||
}
|
||||
|
||||
public Test fun `different extensions`() {
|
||||
val dir = tempDirManager.newDirectory()
|
||||
FileUtil.copyDir(File("${getTestDataPath()}/different-extensions"), dir, false)
|
||||
val schemesManager = SchemeManagerImpl<TestScheme, TestScheme>(FILE_SPEC, object: TestSchemesProcessor() {
|
||||
override fun isUpgradeNeeded() = true
|
||||
|
||||
override fun getSchemeExtension() = ".icls"
|
||||
}, RoamingType.PER_USER, null, dir)
|
||||
schemesManager.loadSchemes()
|
||||
val schemes = schemesManager.getAllSchemes()
|
||||
assertThat(schemes.size(), equalTo(1))
|
||||
val scheme = schemes.get(0)
|
||||
assertThat(scheme.data, equalTo("true"))
|
||||
|
||||
assertThat(File(dir, "1.icls"), exists())
|
||||
assertThat(File(dir, "1.xml"), exists())
|
||||
|
||||
scheme.data = "newTrue"
|
||||
save(schemesManager)
|
||||
|
||||
assertThat(File(dir, "1.icls"), exists())
|
||||
assertThat(File(dir, "1.xml"), not(exists()))
|
||||
}
|
||||
|
||||
public Test fun setSchemes() {
|
||||
val dir = tempDirManager.newDirectory()
|
||||
val schemesManager = createSchemeManager(dir)
|
||||
schemesManager.loadSchemes()
|
||||
assertThat(schemesManager.getAllSchemes().isEmpty(), equalTo(true))
|
||||
|
||||
val scheme = TestScheme("s1")
|
||||
schemesManager.setSchemes(listOf(scheme))
|
||||
|
||||
val schemes = schemesManager.getAllSchemes()
|
||||
assertThat(schemes.size(), equalTo(1))
|
||||
assertThat(schemes.get(0), sameInstance(scheme))
|
||||
|
||||
assertThat(File(dir, "s1.xml"), not(exists()))
|
||||
|
||||
scheme.data = "newTrue"
|
||||
save(schemesManager)
|
||||
|
||||
assertThat(File(dir, "s1.xml"), exists())
|
||||
|
||||
schemesManager.setSchemes(emptyList())
|
||||
|
||||
save(schemesManager)
|
||||
|
||||
assertThat(dir, not(exists()))
|
||||
}
|
||||
|
||||
public Test fun `save only if scheme differs from bundled`() {
|
||||
val dir = tempDirManager.newDirectory()
|
||||
var schemeManager = createSchemeManager(dir)
|
||||
val converter: (Element) -> TestScheme = { XmlSerializer.deserialize(it, javaClass<TestScheme>())!! }
|
||||
val bundledPath = "/bundledSchemes/default"
|
||||
schemeManager.loadBundledScheme(bundledPath, this, converter)
|
||||
var schemes = schemeManager.getAllSchemes()
|
||||
assertThat(schemes.size(), equalTo(1))
|
||||
val customScheme = TestScheme("default")
|
||||
assertThat(schemes.get(0), equalTo(customScheme))
|
||||
|
||||
save(schemeManager)
|
||||
assertThat(dir, not(exists()))
|
||||
|
||||
save(schemeManager)
|
||||
schemeManager.setSchemes(listOf(customScheme))
|
||||
assertThat(dir, not(exists()))
|
||||
|
||||
schemes = schemeManager.getAllSchemes()
|
||||
assertThat(schemes.size(), equalTo(1))
|
||||
assertThat(schemes.get(0), sameInstance(customScheme))
|
||||
|
||||
customScheme.data = "foo"
|
||||
save(schemeManager)
|
||||
val schemeFile = File(dir, "default.xml")
|
||||
assertThat(schemeFile, exists())
|
||||
|
||||
schemeManager = createSchemeManager(dir)
|
||||
schemeManager.loadBundledScheme(bundledPath, this, converter)
|
||||
schemeManager.loadSchemes()
|
||||
|
||||
schemes = schemeManager.getAllSchemes()
|
||||
assertThat(schemes.get(0), equalTo(customScheme))
|
||||
}
|
||||
|
||||
private fun createSchemeManager(dir: File) = SchemeManagerImpl<TestScheme, TestScheme>(FILE_SPEC, TestSchemesProcessor(), RoamingType.PER_USER, null, dir)
|
||||
|
||||
public Test fun `don't remove dir if no schemes but at least one non-hidden file exists`() {
|
||||
val dir = tempDirManager.newDirectory()
|
||||
val schemesManager = SchemeManagerImpl<TestScheme, TestScheme>(FILE_SPEC, TestSchemesProcessor(), RoamingType.PER_USER, null, dir)
|
||||
|
||||
val scheme = TestScheme("s1")
|
||||
schemesManager.setSchemes(listOf(scheme))
|
||||
|
||||
save(schemesManager)
|
||||
|
||||
val schemeFile = File(dir, "s1.xml")
|
||||
assertThat(schemeFile.exists(), equalTo(true))
|
||||
|
||||
schemesManager.setSchemes(emptyList())
|
||||
|
||||
FileUtil.writeToFile(File(dir, "empty"), byteArrayOf())
|
||||
|
||||
save(schemesManager)
|
||||
|
||||
assertThat(schemeFile.exists(), equalTo(false))
|
||||
assertThat(dir.exists(), equalTo(true))
|
||||
}
|
||||
|
||||
public Test fun rename() {
|
||||
val dir = tempDirManager.newDirectory()
|
||||
val schemesManager = SchemeManagerImpl<TestScheme, TestScheme>(FILE_SPEC, TestSchemesProcessor(), RoamingType.PER_USER, null, dir)
|
||||
schemesManager.loadSchemes()
|
||||
assertThat(schemesManager.getAllSchemes().isEmpty(), equalTo(true))
|
||||
|
||||
val scheme = TestScheme("s1")
|
||||
schemesManager.setSchemes(listOf(scheme))
|
||||
|
||||
val schemes = schemesManager.getAllSchemes()
|
||||
assertThat(schemes.size(), equalTo(1))
|
||||
assertThat(schemes.get(0), sameInstance(scheme))
|
||||
|
||||
assertThat(File(dir, "s1.xml").exists(), equalTo(false))
|
||||
|
||||
scheme.data = "newTrue"
|
||||
save(schemesManager)
|
||||
|
||||
assertThat(File(dir, "s1.xml").exists(), equalTo(true))
|
||||
|
||||
scheme.setName("s2")
|
||||
|
||||
save(schemesManager)
|
||||
|
||||
assertThat(File(dir, "s1.xml").exists(), equalTo(false))
|
||||
assertThat(File(dir, "s2.xml").exists(), equalTo(true))
|
||||
}
|
||||
|
||||
private fun save(schemeManager: SchemeManagerImpl<TestScheme, TestScheme>) {
|
||||
invokeAndWaitIfNeed {
|
||||
schemeManager.save()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createAndLoad(testData: String): SchemeManagerImpl<TestScheme, TestScheme> {
|
||||
createTempFiles(testData)
|
||||
return createAndLoad()
|
||||
}
|
||||
|
||||
private fun doLoadSaveTest(testData: String, expected: String, localExpected: String = "") {
|
||||
val schemesManager = createAndLoad(testData)
|
||||
schemesManager.save()
|
||||
checkSchemes(File(remoteBaseDir, "REMOTE"), expected, true)
|
||||
checkSchemes(localBaseDir!!, localExpected, false)
|
||||
}
|
||||
|
||||
private fun checkSchemes(expected: String) {
|
||||
checkSchemes(File(remoteBaseDir, "REMOTE"), expected, true)
|
||||
checkSchemes(localBaseDir!!, "", false)
|
||||
}
|
||||
|
||||
private fun createAndLoad(): SchemeManagerImpl<TestScheme, TestScheme> {
|
||||
val schemesManager = SchemeManagerImpl<TestScheme, TestScheme>(FILE_SPEC, TestSchemesProcessor(), RoamingType.PER_USER, MockStreamProvider(remoteBaseDir!!), localBaseDir!!)
|
||||
schemesManager.loadSchemes()
|
||||
return schemesManager
|
||||
}
|
||||
|
||||
private fun createTempFiles(testData: String) {
|
||||
val temp = tempDirManager.newDirectory()
|
||||
localBaseDir = File(temp, "__local")
|
||||
remoteBaseDir = temp
|
||||
FileUtil.copyDir(File("${getTestDataPath()}/$testData"), File(temp, "REMOTE"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkSchemes(baseDir: File, expected: String, ignoreDeleted: Boolean) {
|
||||
val filesToScheme = StringUtil.split(expected, ";")
|
||||
val fileToSchemeMap = THashMap<String, String>()
|
||||
for (fileToScheme in filesToScheme) {
|
||||
val index = fileToScheme.indexOf("->")
|
||||
fileToSchemeMap.put(fileToScheme.substring(0, index), fileToScheme.substring(index + 2))
|
||||
}
|
||||
|
||||
val files = baseDir.listFiles()
|
||||
if (files != null) {
|
||||
for (file in files) {
|
||||
val fileName = FileUtil.getNameWithoutExtension(file)
|
||||
if ("--deleted" == fileName && ignoreDeleted) {
|
||||
assertThat<Map<String, String>>(fileToSchemeMap, hasKey(fileName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (file in fileToSchemeMap.keySet()) {
|
||||
assertThat(File(baseDir, "$file.xml").isFile(), equalTo(true))
|
||||
}
|
||||
|
||||
if (files != null) {
|
||||
val schemesProcessor = TestSchemesProcessor()
|
||||
for (file in files) {
|
||||
val fileName = FileUtil.getNameWithoutExtension(file)
|
||||
val scheme = schemesProcessor.readScheme(JDOMUtil.load(file), true)
|
||||
assertThat(fileToSchemeMap.get(fileName), equalTo(scheme!!.getName()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public data Tag("scheme") class TestScheme(Attribute private var name: String = "", Attribute var data: String? = null) : ExternalizableScheme {
|
||||
override fun getName() = name
|
||||
|
||||
override Transient fun setName(newName: String) {
|
||||
name = newName
|
||||
}
|
||||
|
||||
@suppress("DEPRECATED_SYMBOL_WITH_MESSAGE")
|
||||
override fun getExternalInfo() = null
|
||||
}
|
||||
|
||||
public open class TestSchemesProcessor : BaseSchemeProcessor<TestScheme>() {
|
||||
override fun readScheme(element: Element) = XmlSerializer.deserialize(element, javaClass<TestScheme>())
|
||||
|
||||
override fun writeScheme(scheme: TestScheme) = scheme.serialize()
|
||||
}
|
||||
|
||||
fun SchemeManagerImpl<*, *>.save() {
|
||||
val errors = SmartList<Throwable>()
|
||||
save(errors)
|
||||
CompoundRuntimeException.doThrow(errors)
|
||||
}
|
||||
|
||||
public fun <T : Any> T.serialize(): Element = XmlSerializer.serialize(this, SkipDefaultValuesSerializationFilters())
|
||||
|
||||
public fun Element.toByteArray(): ByteArray {
|
||||
val out = BufferExposingByteArrayOutputStream(512)
|
||||
JDOMUtil.writeParent(this, out, "\n")
|
||||
return out.toByteArray()
|
||||
}
|
||||
@@ -88,7 +88,7 @@ class Generator(outputDir: String, rootPackage: String, requestClassName: String
|
||||
}
|
||||
|
||||
fun resolveType(typedObject: ItemDescriptor, scope: ResolveAndGenerateScope): TypeDescriptor {
|
||||
val optional = typedObject is ItemDescriptor.Named && (typedObject : ItemDescriptor.Named).optional()
|
||||
val optional = typedObject is ItemDescriptor.Named && typedObject.optional()
|
||||
return switchByType(typedObject, object : TypeVisitor<TypeDescriptor> {
|
||||
override fun visitRef(refName: String): TypeDescriptor {
|
||||
return TypeDescriptor(resolveRefType(scope.getDomainName(), refName, scope.getTypeDirection()), optional)
|
||||
@@ -120,7 +120,7 @@ class Generator(outputDir: String, rootPackage: String, requestClassName: String
|
||||
}
|
||||
|
||||
override fun visitArray(items: ProtocolMetaModel.ArrayItemType): TypeDescriptor {
|
||||
val type = scope.resolveType<ProtocolMetaModel.ArrayItemType>(items).type
|
||||
val type = scope.resolveType(items).type
|
||||
return TypeDescriptor(ListType(type), optional, false, type == BoxableType.ANY_STRING)
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ fun capitalizeFirstChar(s: String): String {
|
||||
}
|
||||
|
||||
fun <R> switchByType(typedObject: ItemDescriptor, visitor: TypeVisitor<R>): R {
|
||||
val refName = if (typedObject is ItemDescriptor.Referenceable) (typedObject : ItemDescriptor.Referenceable).ref() else null
|
||||
val refName = if (typedObject is ItemDescriptor.Referenceable) typedObject.ref() else null
|
||||
if (refName != null) {
|
||||
return visitor.visitRef(refName)
|
||||
}
|
||||
@@ -265,7 +265,7 @@ fun <R> switchByType(typedObject: ItemDescriptor, visitor: TypeVisitor<R>): R {
|
||||
return visitor.visitObject(null)
|
||||
}
|
||||
|
||||
val properties = (typedObject : ItemDescriptor.Type).properties()
|
||||
val properties = typedObject.properties()
|
||||
if (properties == null || properties.isEmpty()) {
|
||||
return visitor.visitMap()
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ data class NamePath(val lastComponent: String, val parent: NamePath? = null) {
|
||||
var current: NamePath? = this
|
||||
while (current != null) {
|
||||
res++
|
||||
current = current!!.parent
|
||||
current = current.parent
|
||||
}
|
||||
}
|
||||
return res
|
||||
|
||||
@@ -15,10 +15,7 @@ class TypeMap {
|
||||
private val typesToGenerate = ArrayList<StandaloneTypeBinding>()
|
||||
|
||||
fun resolve(domainName: String, typeName: String, direction: TypeData.Direction): BoxableType? {
|
||||
val domainGenerator = domainGeneratorMap!!.get(domainName)
|
||||
if (domainGenerator == null) {
|
||||
throw RuntimeException("Failed to find domain generator: " + domainName)
|
||||
}
|
||||
val domainGenerator = domainGeneratorMap!!.get(domainName) ?: throw RuntimeException("Failed to find domain generator: " + domainName)
|
||||
return direction.get(getTypeData(domainName, typeName)).resolve(this, domainGenerator)
|
||||
}
|
||||
|
||||
@@ -28,7 +25,7 @@ class TypeMap {
|
||||
|
||||
fun generateRequestedTypes() {
|
||||
// size may grow during iteration
|
||||
var list = typesToGenerate.copyToArray()
|
||||
var list = typesToGenerate.toTypedArray()
|
||||
typesToGenerate.clear()
|
||||
while (true) {
|
||||
for (binding in list) {
|
||||
@@ -39,7 +36,7 @@ class TypeMap {
|
||||
break
|
||||
}
|
||||
else {
|
||||
list = typesToGenerate.copyToArray()
|
||||
list = typesToGenerate.toTypedArray()
|
||||
typesToGenerate.clear()
|
||||
}
|
||||
}
|
||||
@@ -52,6 +49,6 @@ class TypeMap {
|
||||
result = TypeData(typeName)
|
||||
map.put(key, result)
|
||||
}
|
||||
return result!!
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,27 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.jetbrains.ide
|
||||
package org.jetbrains.testFramework
|
||||
|
||||
import com.intellij.openapi.application.WriteAction
|
||||
import com.intellij.openapi.application.invokeAndWaitIfNeed
|
||||
import com.intellij.testFramework.UsefulTestCase
|
||||
import com.intellij.testFramework.fixtures.IdeaTestFixtureFactory
|
||||
import org.junit.rules.ExternalResource
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
public fun invokeAndWaitIfNeed(runnable: () -> Unit) {
|
||||
if (SwingUtilities.isEventDispatchThread()) runnable() else SwingUtilities.invokeAndWait(runnable)
|
||||
}
|
||||
|
||||
public inline fun runWriteAction(runnable: () -> Unit) {
|
||||
val token = WriteAction.start()
|
||||
try {
|
||||
runnable()
|
||||
}
|
||||
finally {
|
||||
token.finish()
|
||||
}
|
||||
}
|
||||
|
||||
public class FixtureRule() : ExternalResource() {
|
||||
val projectFixture = IdeaTestFixtureFactory.getFixtureFactory().createLightFixtureBuilder().getFixture()
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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 org.jetbrains.testFramework
|
||||
|
||||
import com.intellij.openapi.util.io.FileUtil
|
||||
import com.intellij.openapi.util.io.FileUtilRt
|
||||
import com.intellij.util.SmartList
|
||||
import org.junit.rules.ExternalResource
|
||||
import java.io.File
|
||||
|
||||
public class TemporaryDirectory : ExternalResource() {
|
||||
private val files = SmartList<File>()
|
||||
|
||||
override fun after() {
|
||||
for (file in files) {
|
||||
FileUtil.delete(file)
|
||||
}
|
||||
files.clear()
|
||||
}
|
||||
|
||||
public fun newDirectory(): File {
|
||||
val file = FileUtilRt.generateRandomTemporaryPath()
|
||||
files.add(file)
|
||||
return file;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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 org.jetbrains.testFramework
|
||||
|
||||
import org.hamcrest.Description
|
||||
import org.hamcrest.Factory
|
||||
import org.hamcrest.Matcher
|
||||
import org.hamcrest.TypeSafeDiagnosingMatcher
|
||||
import java.io.File
|
||||
|
||||
public class FileExistenceMatcher(private val exists: Boolean) : TypeSafeDiagnosingMatcher<File>(javaClass<File>()) {
|
||||
override fun matchesSafely(file: File, mismatchDescription: Description): Boolean {
|
||||
if (exists == file.exists()) {
|
||||
return true
|
||||
}
|
||||
|
||||
mismatchDescription.appendText("is not a file")
|
||||
return false
|
||||
}
|
||||
|
||||
override fun describeTo(description: Description) {
|
||||
description.appendText("an existing file")
|
||||
}
|
||||
}
|
||||
|
||||
public Factory fun exists(): Matcher<File> = FileExistenceMatcher(true)
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
* Copyright 2000-2015 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.
|
||||
@@ -29,6 +29,13 @@ public class OrderedSet<T> extends ArrayList<T> implements Set<T>, RandomAccess
|
||||
this(ContainerUtil.<T>canonicalStrategy());
|
||||
}
|
||||
|
||||
public OrderedSet(@NotNull Collection<T> set) {
|
||||
super(set.size());
|
||||
|
||||
myHashSet = new OpenTHashSet<T>(set.size());
|
||||
addAll(set);
|
||||
}
|
||||
|
||||
public OrderedSet(@NotNull TObjectHashingStrategy<T> hashingStrategy) {
|
||||
this(hashingStrategy, 4);
|
||||
}
|
||||
@@ -38,6 +45,12 @@ public class OrderedSet<T> extends ArrayList<T> implements Set<T>, RandomAccess
|
||||
myHashSet = new OpenTHashSet<T>(capacity, hashingStrategy);
|
||||
}
|
||||
|
||||
public OrderedSet(int capacity) {
|
||||
super(capacity);
|
||||
|
||||
myHashSet = new OpenTHashSet<T>(capacity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(@NotNull Collection<?> c) {
|
||||
boolean removed = false;
|
||||
|
||||
@@ -26,7 +26,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Please use {@link SkipDefaultsSerializationFilter},
|
||||
* Please use {@link SkipDefaultsSerializationFilter} if state class doesn't implement "equals" (in Kotlin use data classes {@see http://kotlinlang.org/docs/reference/data-classes.html})
|
||||
*/
|
||||
public class SkipDefaultValuesSerializationFilters extends SerializationFilterBase {
|
||||
private final Map<Class<?>, Object> myDefaultBeans = new THashMap<Class<?>, Object>();
|
||||
|
||||
Reference in New Issue
Block a user