Merge remote-tracking branch 'origin/master'

This commit is contained in:
Vladimir.Orlov
2015-07-02 15:19:31 +03:00
47 changed files with 1990 additions and 1644 deletions
+11
View File
@@ -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;
}
}
+1
View File
@@ -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;
+1
View File
@@ -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>
@@ -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();
}
@@ -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);
}
@@ -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 "???";
@@ -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()
@@ -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"/>
@@ -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);
}
}
}
@@ -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
}
}
+2 -17
View File
@@ -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>();