From 12e372b368a4f2b03b047f5eee1319e364f46f26 Mon Sep 17 00:00:00 2001 From: Alexey Pegov Date: Mon, 22 Aug 2011 19:20:40 +0400 Subject: [PATCH 01/13] OC-1298 Creating new folder beachballs AppCode (in create from Xcode Template) --- .../ui/mac/MacFileChooserDialogImpl.java | 160 ++++++++++++------ .../com/intellij/ui/mac/MacMessagesImpl.java | 44 +---- .../intellij/ui/mac/foundation/MacUtil.java | 40 ++++- 3 files changed, 148 insertions(+), 96 deletions(-) diff --git a/platform/platform-impl/src/com/intellij/ui/mac/MacFileChooserDialogImpl.java b/platform/platform-impl/src/com/intellij/ui/mac/MacFileChooserDialogImpl.java index 1a2c18d9bfd0..78d48527b95e 100644 --- a/platform/platform-impl/src/com/intellij/ui/mac/MacFileChooserDialogImpl.java +++ b/platform/platform-impl/src/com/intellij/ui/mac/MacFileChooserDialogImpl.java @@ -51,48 +51,79 @@ public class MacFileChooserDialogImpl implements MacFileChooserDialog { private Project myProject; + private static final Callback SHOULD_ENABLE_URL = new Callback() { + public boolean callback(ID self, String selector, ID panel, ID url) { + return true; + } + }; + private static final Callback SHOULD_SHOW_FILENAME_CALLBACK = new Callback() { - public boolean callback(ID self, String selector, ID panel, ID filename) { - if (filename == null || filename.intValue() == 0) return false; - final String fileName = Foundation.toStringViaUTF8(filename); - final VirtualFile virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(fileName); - return virtualFile != null && (virtualFile.isDirectory() || getDescriptor().isFileSelectable(virtualFile)); + public boolean callback(ID self, String selector, ID panel, ID filename) { + if (filename == null || filename.intValue() == 0) return false; + final String fileName = Foundation.toStringViaUTF8(filename); + try { + SwingUtilities.invokeAndWait(new Runnable() { + @Override + public void run() { + LocalFileSystem.getInstance().refreshAndFindFileByPath(fileName); + } + }); } - }; + catch (Exception e) { + return false; + } + + final VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(fileName); + return virtualFile != null && (virtualFile.isDirectory() || getDescriptor().isFileSelectable(virtualFile)); + } + }; private static final Callback IS_VALID_FILENAME_CALLBACK = new Callback() { - public boolean callback(ID self, String selector, ID panel, ID filename) { - if (filename == null || filename.intValue() == 0) return false; - final String fileName = Foundation.toStringViaUTF8(filename); - final VirtualFile virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByPath(fileName); - return virtualFile != null && (!virtualFile.isDirectory() || getDescriptor().isFileSelectable(virtualFile)); + public boolean callback(ID self, String selector, ID panel, ID filename) { + if (filename == null || filename.intValue() == 0) return false; + final String fileName = Foundation.toStringViaUTF8(filename); + + try { + SwingUtilities.invokeAndWait(new Runnable() { + @Override + public void run() { + LocalFileSystem.getInstance().refreshAndFindFileByPath(fileName); + } + }); } - }; + catch (Exception e) { + return false; + } + + final VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(fileName); + return virtualFile != null && (!virtualFile.isDirectory() || getDescriptor().isFileSelectable(virtualFile)); + } + }; private static final Callback OPEN_PANEL_DID_END = new Callback() { - public void callback(ID self, String selector, ID openPanelDidEnd, ID returnCode, ID contextInfo) { - processResult(returnCode, openPanelDidEnd); + public void callback(ID self, String selector, ID openPanelDidEnd, ID returnCode, ID contextInfo) { + processResult(returnCode, openPanelDidEnd); - try { - if (myResultFiles != null) { - final VirtualFile[] chosenFiles = VfsUtil.toVirtualFileArray(myResultFiles); - final MacFileChooserCallback callback = mySheetCallback; - SwingUtilities.invokeLater(new Runnable() { - public void run() { - callback.onChosen(chosenFiles); - } - }); - } + try { + if (myResultFiles != null) { + final VirtualFile[] chosenFiles = VfsUtil.toVirtualFileArray(myResultFiles); + final MacFileChooserCallback callback = mySheetCallback; + SwingUtilities.invokeLater(new Runnable() { + public void run() { + callback.onChosen(chosenFiles); + } + }); } - finally { - myFileChooserActive = false; - myResultFiles = null; - mySheetCallback = null; - } - - Foundation.cfRelease(self); } - }; + finally { + myFileChooserActive = false; + myResultFiles = null; + mySheetCallback = null; + } + + Foundation.cfRelease(self); + } + }; private static final Callback MAIN_THREAD_RUNNABLE = new Callback() { @@ -105,11 +136,13 @@ public class MacFileChooserDialogImpl implements MacFileChooserDialog { invoke(chooser, "setAllowsMultipleSelection:", myChooserDescriptor.isChooseMultiple()); invoke(chooser, "setTreatsFilePackagesAsDirectories:", myChooserDescriptor.isChooseFolders()); //invoke(chooser, "setCanCreateDirectories:", true); - if (Foundation.isClassRespondsToSelector(Foundation.getClass("NSOpenPanel"), Foundation.createSelector("_setIncludeNewFolderButton:"))) { + if (Foundation + .isClassRespondsToSelector(Foundation.getClass("NSOpenPanel"), Foundation.createSelector("_setIncludeNewFolderButton:"))) { invoke(chooser, "_setIncludeNewFolderButton:", true); } - final Object showHiddenFiles = myChooserDescriptor.getUserData(MacFileChooserDialog.NATIVE_MAC_FILE_CHOOSER_SHOW_HIDDEN_FILES_ENABLED.getName()); + final Object showHiddenFiles = + myChooserDescriptor.getUserData(MacFileChooserDialog.NATIVE_MAC_FILE_CHOOSER_SHOW_HIDDEN_FILES_ENABLED.getName()); if (Registry.is("ide.mac.filechooser.showhidden.files") || Boolean.TRUE.equals(showHiddenFiles)) { if (Foundation.isClassRespondsToSelector(Foundation.getClass("NSOpenPanel"), Foundation.createSelector("setShowsHiddenFiles:"))) { @@ -124,12 +157,13 @@ public class MacFileChooserDialogImpl implements MacFileChooserDialog { final String toSelectPath = toSelect == null || toSelect.intValue() == 0 ? null : Foundation.toStringViaUTF8(toSelect); final VirtualFile toSelectFile = toSelectPath == null ? null : LocalFileSystem.getInstance().findFileByPath(toSelectPath); if (toSelectFile != null) { - if (toSelectFile.isDirectory()) { - directory = toSelect; - } else { - directory = Foundation.cfString(toSelectFile.getParent().getPath()); - file = Foundation.cfString(toSelectFile.getName()); - } + if (toSelectFile.isDirectory()) { + directory = toSelect; + } + else { + directory = Foundation.cfString(toSelectFile.getParent().getPath()); + file = Foundation.cfString(toSelectFile.getName()); + } } if (mySheetCallback != null) { @@ -138,7 +172,8 @@ public class MacFileChooserDialogImpl implements MacFileChooserDialog { String activeWindowTitle = null; if (activeWindow instanceof Frame) { activeWindowTitle = ((Frame)activeWindow).getTitle(); - } else if (activeWindow instanceof JDialog) { + } + else if (activeWindow instanceof JDialog) { activeWindowTitle = ((JDialog)activeWindow).getTitle(); } @@ -148,7 +183,8 @@ public class MacFileChooserDialogImpl implements MacFileChooserDialog { directory, file, null, focusedWindow, self, Foundation.createSelector("openPanelDidEnd:returnCode:contextInfo:"), null); } } - } else { + } + else { final ID result = invoke(chooser, "runModalForDirectory:file:", directory, file); processResult(result, chooser); } @@ -187,14 +223,22 @@ public class MacFileChooserDialogImpl implements MacFileChooserDialog { static { final ID delegateClass = Foundation.registerObjcClass(Foundation.getClass("NSObject"), "NSOpenPanelDelegate_"); - if (!Foundation.addMethod(delegateClass, Foundation.createSelector("panel:shouldShowFilename:"), SHOULD_SHOW_FILENAME_CALLBACK, "B*")) + if (!Foundation.addMethod(delegateClass, Foundation.createSelector("panel:shouldShowFilename:"), SHOULD_SHOW_FILENAME_CALLBACK, "B*")) { throw new RuntimeException("Unable to add method to objective-c delegate class!"); - if (!Foundation.addMethod(delegateClass, Foundation.createSelector("panel:isValidFilename:"), IS_VALID_FILENAME_CALLBACK, "B*")) + } + if (!Foundation.addMethod(delegateClass, Foundation.createSelector("panel:isValidFilename:"), IS_VALID_FILENAME_CALLBACK, "B*")) { throw new RuntimeException("Unable to add method to objective-c delegate class!"); - if (!Foundation.addMethod(delegateClass, Foundation.createSelector("showOpenPanel:"), MAIN_THREAD_RUNNABLE, "v*")) + } + if (!Foundation.addMethod(delegateClass, Foundation.createSelector("showOpenPanel:"), MAIN_THREAD_RUNNABLE, "v*")) { throw new RuntimeException("Unable to add method to objective-c delegate class!"); - if (!Foundation.addMethod(delegateClass, Foundation.createSelector("openPanelDidEnd:returnCode:contextInfo:"), OPEN_PANEL_DID_END, "v*i^void")) + } + if (!Foundation + .addMethod(delegateClass, Foundation.createSelector("openPanelDidEnd:returnCode:contextInfo:"), OPEN_PANEL_DID_END, "v*i")) { throw new RuntimeException("Unable to add method to objective-c delegate class!"); + } + if (!Foundation.addMethod(delegateClass, Foundation.createSelector("panel:shouldEnableURL:"), SHOULD_ENABLE_URL, "B@@")) { + throw new RuntimeException("Unable to add method to objective-c delegate class!"); + } Foundation.registerObjcClassPair(delegateClass); } @@ -248,8 +292,8 @@ public class MacFileChooserDialogImpl implements MacFileChooserDialog { } public void chooseWithSheet(@Nullable final VirtualFile toSelect, @Nullable final Project project, - @NotNull final MacFileChooserCallback callback) { - assert !myFileChooserActive: "Current native file chooser should finish before next usage!"; + @NotNull final MacFileChooserCallback callback) { + assert !myFileChooserActive : "Current native file chooser should finish before next usage!"; mySheetCallback = callback; SwingUtilities.invokeLater(new Runnable() { @@ -261,7 +305,7 @@ public class MacFileChooserDialogImpl implements MacFileChooserDialog { @NotNull public VirtualFile[] choose(@Nullable final VirtualFile toSelect, @Nullable final Project project) { - assert !myFileChooserActive: "Current native file chooser should finish before next usage!"; + assert !myFileChooserActive : "Current native file chooser should finish before next usage!"; myFileChooserActive = true; @@ -273,16 +317,18 @@ public class MacFileChooserDialogImpl implements MacFileChooserDialog { final Window parent = KeyboardFocusManager.getCurrentKeyboardFocusManager().getActiveWindow(); if (parent instanceof Frame) { - myFakeDialog = new JDialog((Frame) parent); - } else if (parent instanceof JDialog) { + myFakeDialog = new JDialog((Frame)parent); + } + else if (parent instanceof JDialog) { myFakeDialog = new JDialog(((JDialog)parent)); - } else { + } + else { myFakeDialog = new JDialog((JFrame)null); } myFakeDialog.setModal(true); myFakeDialog.setUndecorated(true); - myFakeDialog.getRootPane().putClientProperty( "Window.shadow", Boolean.FALSE ); + myFakeDialog.getRootPane().putClientProperty("Window.shadow", Boolean.FALSE); myFakeDialog.setSize(0, 0); myFakeDialog.setVisible(true); @@ -290,7 +336,8 @@ public class MacFileChooserDialogImpl implements MacFileChooserDialog { try { if (myResultFiles == null) { return new VirtualFile[0]; - } else { + } + else { return VfsUtil.toVirtualFileArray(myResultFiles); } } @@ -301,12 +348,13 @@ public class MacFileChooserDialogImpl implements MacFileChooserDialog { } private static VirtualFile getToSelect(VirtualFile toSelect, Project project) { - final VirtualFile[] selectFile = new VirtualFile[] {null}; + final VirtualFile[] selectFile = new VirtualFile[]{null}; if (toSelect == null) { if (project != null && project.getBaseDir() != null) { selectFile[0] = project.getBaseDir(); } - } else { + } + else { selectFile[0] = toSelect.isValid() ? toSelect : null; } return selectFile[0]; diff --git a/platform/platform-impl/src/com/intellij/ui/mac/MacMessagesImpl.java b/platform/platform-impl/src/com/intellij/ui/mac/MacMessagesImpl.java index f23395c79370..195a9dc496b5 100644 --- a/platform/platform-impl/src/com/intellij/ui/mac/MacMessagesImpl.java +++ b/platform/platform-impl/src/com/intellij/ui/mac/MacMessagesImpl.java @@ -50,7 +50,7 @@ public class MacMessagesImpl extends MacMessages{ if (window instanceof JFrame) { JFrame frame = (JFrame)window; JRootPane rootPane = frame.getRootPane(); - if (rootPane.getClientProperty(MAC_SHEET_ACTIVE) == Boolean.TRUE && + if (rootPane.getClientProperty(MacUtil.MAC_NATIVE_WINDOW_SHOWING) == Boolean.TRUE && fakeDialogTitle.equals(rootPane.getClientProperty(MAC_SHEET_ID))) { processResult(rootPane, returnCode.intValue(), suppressState.intValue()); break; @@ -59,7 +59,7 @@ public class MacMessagesImpl extends MacMessages{ else if (window instanceof JDialog) { JDialog dialog = (JDialog)window; JRootPane rootPane = dialog.getRootPane(); - if (rootPane.getClientProperty(MAC_SHEET_ACTIVE) == Boolean.TRUE && + if (rootPane.getClientProperty(MacUtil.MAC_NATIVE_WINDOW_SHOWING) == Boolean.TRUE && fakeDialogTitle.equals(rootPane.getClientProperty(MAC_SHEET_ID))) { processResult(rootPane, returnCode.intValue(), suppressState.intValue()); } @@ -107,10 +107,9 @@ public class MacMessagesImpl extends MacMessages{ rootPane.putClientProperty(MAC_SHEET_RESULT, returnCode); rootPane.putClientProperty(MAC_SHEET_SUPPRESS, suppressDialog == 1 ? Boolean.TRUE : Boolean.FALSE); rootPane.putClientProperty(MAC_SHEET_ID, null); - rootPane.putClientProperty(MAC_SHEET_ACTIVE, null); + rootPane.putClientProperty(MacUtil.MAC_NATIVE_WINDOW_SHOWING, null); } - private static final String MAC_SHEET_ACTIVE = "mac_sheet_active"; private static final String MAC_SHEET_RESULT = "mac_sheet_result"; private static final String MAC_SHEET_SUPPRESS = "mac_sheet_suppress"; private static final String MAC_SHEET_ID = "mac_sheet_id"; @@ -244,10 +243,10 @@ public class MacMessagesImpl extends MacMessages{ } if (fakeTitle != null) { - pane.putClientProperty(MAC_SHEET_ACTIVE, Boolean.TRUE); + pane.putClientProperty(MacUtil.MAC_NATIVE_WINDOW_SHOWING, Boolean.TRUE); pane.putClientProperty(MAC_SHEET_ID, fakeTitle); - startModal(pane); + MacUtil.startModal(pane); Integer result = (Integer)pane.getClientProperty(MAC_SHEET_RESULT); boolean suppress = Boolean.TRUE == pane.getClientProperty(MAC_SHEET_SUPPRESS); @@ -330,37 +329,4 @@ public class MacMessagesImpl extends MacMessages{ @Nullable Window window) { return showAlertDialog(title, okText, alternateText, cancelText, message, window, false, null); } - - private static synchronized void startModal(JRootPane pane) { - try { - if (SwingUtilities.isEventDispatchThread()) { - EventQueue theQueue = pane.getToolkit().getSystemEventQueue(); - - while (pane.getClientProperty(MAC_SHEET_ACTIVE) == Boolean.TRUE) { - AWTEvent event = theQueue.getNextEvent(); - Object source = event.getSource(); - if (event instanceof ActiveEvent) { - ((ActiveEvent)event).dispatch(); - } - else if (source instanceof Component) { - ((Component)source).dispatchEvent(event); - } - else if (source instanceof MenuComponent) { - ((MenuComponent)source).dispatchEvent(event); - } - else { - System.err.println("Unable to dispatch: " + event); - } - } - } - else { - while (pane.getClientProperty(MAC_SHEET_ACTIVE) == Boolean.TRUE) { - // TODO: - //wait(); - } - } - } - catch (InterruptedException ignored) { - } - } } diff --git a/platform/util/src/com/intellij/ui/mac/foundation/MacUtil.java b/platform/util/src/com/intellij/ui/mac/foundation/MacUtil.java index 63f21d146f91..8664790af20d 100644 --- a/platform/util/src/com/intellij/ui/mac/foundation/MacUtil.java +++ b/platform/util/src/com/intellij/ui/mac/foundation/MacUtil.java @@ -17,6 +17,9 @@ package com.intellij.ui.mac.foundation; import org.jetbrains.annotations.Nullable; +import javax.swing.*; +import java.awt.*; + import static com.intellij.ui.mac.foundation.Foundation.invoke; import static com.intellij.ui.mac.foundation.Foundation.toStringViaUTF8; @@ -24,7 +27,8 @@ import static com.intellij.ui.mac.foundation.Foundation.toStringViaUTF8; * @author pegov */ public class MacUtil { - + public static final String MAC_NATIVE_WINDOW_SHOWING = "MAC_NATIVE_WINDOW_SHOWING"; + private MacUtil() { } @@ -63,5 +67,39 @@ public class MacUtil { return focusedWindow; } + public static synchronized void startModal(JComponent component) { + try { + if (SwingUtilities.isEventDispatchThread()) { + EventQueue theQueue = component.getToolkit().getSystemEventQueue(); + + while (component.getClientProperty(MAC_NATIVE_WINDOW_SHOWING) == Boolean.TRUE) { + AWTEvent event = theQueue.getNextEvent(); + Object source = event.getSource(); + if (event instanceof ActiveEvent) { + ((ActiveEvent)event).dispatch(); + } + else if (source instanceof Component) { + ((Component)source).dispatchEvent(event); + } + else if (source instanceof MenuComponent) { + ((MenuComponent)source).dispatchEvent(event); + } + else { + System.err.println("Unable to dispatch: " + event); + } + } + } + else { + assert false: "Should be called from Event-Dispatch Thread only!"; + while (component.getClientProperty(MAC_NATIVE_WINDOW_SHOWING) == Boolean.TRUE) { + // TODO: + //wait(); + } + } + } + catch (InterruptedException ignored) { + } + } + } From c7effe47af91aead6771daa6684ca023166b913e Mon Sep 17 00:00:00 2001 From: Anton Makeev Date: Mon, 22 Aug 2011 17:27:32 +0200 Subject: [PATCH 02/13] Maven: write action --- .../idea/maven/utils/MavenEnvironmentRegistrar.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenEnvironmentRegistrar.java b/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenEnvironmentRegistrar.java index 134029336e6e..b778be3751be 100644 --- a/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenEnvironmentRegistrar.java +++ b/plugins/maven/src/main/java/org/jetbrains/idea/maven/utils/MavenEnvironmentRegistrar.java @@ -42,7 +42,12 @@ public class MavenEnvironmentRegistrar implements ApplicationComponent { private void registerFileTypes() { // we should not change file types in unit test mode if (ApplicationManager.getApplication().isUnitTestMode()) return; - FileTypeManager.getInstance().associateExtension(XmlFileType.INSTANCE, MavenConstants.POM_EXTENSION); + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + FileTypeManager.getInstance().associateExtension(XmlFileType.INSTANCE, MavenConstants.POM_EXTENSION); + } + }); } private void registerPathVariable() { From 329fe66a6507edd4a8e6e2e28c5413aef454d140 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Mon, 22 Aug 2011 17:27:10 +0200 Subject: [PATCH 03/13] Symlink support for Windows --- .../com/intellij/openapi/vfs/VirtualFile.java | 19 ++ .../intellij/openapi/vfs/LocalFileSystem.java | 21 ++ .../openapi/vfs/impl/local/FileWatcher.java | 10 +- .../vfs/impl/local/LocalFileSystemBase.java | 18 +- .../vfs/impl/local/LocalFileSystemImpl.java | 7 + .../openapi/vfs/impl/local/SymLinkUtil.java | 141 -------- .../newvfs/impl/VirtualFileSystemEntry.java | 6 + .../vfs/local/SymLinkHandlingTest.java | 31 +- .../com/intellij/openapi/util/SystemInfo.java | 4 +- .../intellij/openapi/util/io/FileUtil.java | 23 +- .../intellij/openapi/util/io/SymLinkUtil.java | 320 ++++++++++++++++++ 11 files changed, 421 insertions(+), 179 deletions(-) delete mode 100644 platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/SymLinkUtil.java create mode 100644 platform/util/src/com/intellij/openapi/util/io/SymLinkUtil.java diff --git a/platform/core-api/src/com/intellij/openapi/vfs/VirtualFile.java b/platform/core-api/src/com/intellij/openapi/vfs/VirtualFile.java index 7388140e6dc2..8f1e8da849e8 100644 --- a/platform/core-api/src/com/intellij/openapi/vfs/VirtualFile.java +++ b/platform/core-api/src/com/intellij/openapi/vfs/VirtualFile.java @@ -200,10 +200,29 @@ public abstract class VirtualFile extends UserDataHolderBase implements Modifica */ public abstract boolean isDirectory(); + /** + * Checks whether this file is a symbolic link. + * + * @since 11.0 + * @return true if this file is a symbolic link, false otherwise + */ public boolean isSymLink() { return false; } + /** + * Attempts to resolve a symbolic link represented by this file and returns link target. + * + * @since 11.0 + * @return this if the file isn't a symbolic link; + * instance of VirtualFile if the link was successfully resolved; + * null otherwise + */ + @Nullable + public VirtualFile getRealFile() { + return this; + } + /** * Checks whether this VirtualFile is valid. File can be invalidated either by deleting it or one of its * parents with {@link #delete} method or by an external change. diff --git a/platform/platform-api/src/com/intellij/openapi/vfs/LocalFileSystem.java b/platform/platform-api/src/com/intellij/openapi/vfs/LocalFileSystem.java index b03a30718020..8c7b28a6a98f 100644 --- a/platform/platform-api/src/com/intellij/openapi/vfs/LocalFileSystem.java +++ b/platform/platform-api/src/com/intellij/openapi/vfs/LocalFileSystem.java @@ -40,10 +40,31 @@ public abstract class LocalFileSystem extends NewVirtualFileSystem { return LocalFileSystemHolder.ourInstance; } + /** + * Checks whether given file is a symbolic link. + * + * @param file a file to check. + * @return true if the file is a symbolic link, false otherwise + * @since 11.0 + */ public boolean isSymLink(@NotNull final VirtualFile file) { return false; } + /** + * Attempts to resolve a symbolic link represented by given file and returns link target. + * + * @since 11.0 + * @param file a file to resolve. + * @return this if the file isn't a symbolic link; + * instance of VirtualFile if the link was successfully resolved; + * null otherwise + */ + @Nullable + public VirtualFile getRealFile(@NotNull final VirtualFile file) { + return file; + } + @Nullable public abstract VirtualFile findFileByIoFile(File file); diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/FileWatcher.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/FileWatcher.java index 422a3f34beee..883850311c12 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/FileWatcher.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/FileWatcher.java @@ -26,6 +26,7 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.io.SymLinkUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.newvfs.ManagingFS; import com.intellij.openapi.vfs.newvfs.NewVirtualFile; @@ -214,13 +215,8 @@ public class FileWatcher { } private static String getCanonicalPath(final String path) { - try { - return new File(path).getCanonicalPath(); - } - catch (IOException e) { - LOG.warn(e.getMessage() + ": " + path); - return path; - } + final String realPath = SymLinkUtil.resolveSymLink(path); + return realPath != null ? realPath : path; } private boolean isAlive() { diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemBase.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemBase.java index bca4f936c4db..96fe8bb9d867 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemBase.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemBase.java @@ -20,6 +20,7 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.io.SymLinkUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.*; import com.intellij.openapi.vfs.ex.VirtualFileManagerEx; @@ -170,22 +171,21 @@ public abstract class LocalFileSystemBase extends LocalFileSystem { } } - File ioFile = convertToIOFile(file); - if (file.isSymLink() && isRecursiveSymLink(ioFile)) { + if (isInvalidSymLink(file)) { return ArrayUtil.EMPTY_STRING_ARRAY; } + final File ioFile = convertToIOFile(file); final String[] names = ioFile.list(); return names != null ? names : ArrayUtil.EMPTY_STRING_ARRAY; } - protected static boolean isRecursiveSymLink(File ioFile) { - try { - if (FileUtil.isAncestor(ioFile.getCanonicalFile(), ioFile, true)) return true; - } - catch (IOException ignore) { - } - return false; + protected static boolean isInvalidSymLink(@NotNull final VirtualFile file) { + if (!file.isSymLink()) return false; + final VirtualFile realFile = file.getRealFile(); + return realFile == null || + realFile == file || + FileUtil.isAncestor(convertToIOFile(realFile), convertToIOFile(file), true); } @NotNull diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemImpl.java index 6d45f014f470..f6c9b2da036e 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/LocalFileSystemImpl.java @@ -22,6 +22,7 @@ import com.intellij.openapi.components.ApplicationComponent; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.io.SymLinkUtil; import com.intellij.openapi.vfs.JarFileSystem; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; @@ -471,6 +472,12 @@ public final class LocalFileSystemImpl extends LocalFileSystemBase implements Ap return SymLinkUtil.isSymLink(file.getPath()); } + @Override + public VirtualFile getRealFile(@NotNull final VirtualFile file) { + final String realPath = SymLinkUtil.resolveSymLink(file.getPath()); + return realPath != null ? findFileByPath(realPath) : null; + } + public boolean isWritable(@NotNull final VirtualFile file) { if (myNativeFileSystem == null) return super.isWritable(file); else return myNativeFileSystem.isWritable(file); diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/SymLinkUtil.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/SymLinkUtil.java deleted file mode 100644 index ee8e557327e9..000000000000 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/SymLinkUtil.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2000-2011 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.vfs.impl.local; - -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.util.SystemInfo; -import com.intellij.util.ArrayUtil; -import com.sun.jna.Library; -import com.sun.jna.Memory; -import com.sun.jna.Native; -import com.sun.jna.Pointer; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.lang.reflect.Method; - -// todo[r.sh] use NIO2 API after migration to JDK 7 -public class SymLinkUtil { - private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.impl.local.SymLinkUtil"); - - @Nullable - private static final Mediator ourMediator; - - static { - Mediator mediator = null; - if (SystemInfo.isJavaVersionAtLeast("1.7")) { - try { - mediator = new Jdk7MediatorImpl(); - mediator.isSymLink("/"); // quick test - } - catch (Exception e) { - LOG.error(e); - mediator = null; - } - } - if (mediator == null && (SystemInfo.isLinux || SystemInfo.isMac || SystemInfo.isSolaris)) { - try { - mediator = new JnaMediatorImpl(); - mediator.isSymLink("/"); // quick test - } - catch (Exception e) { - LOG.error(e); - mediator = null; - } - } - ourMediator = mediator; - } - - private SymLinkUtil() { } - - public static boolean isSymLink(@NotNull final File file) { - return isSymLink(file.getAbsolutePath()); - } - - public static boolean isSymLink(@NotNull final String path) { - try { - return ourMediator != null && ourMediator.isSymLink(path); - } - catch (Exception e) { - LOG.error(e); - return false; - } - } - - private interface Mediator { - boolean isSymLink(@NotNull final String path) throws Exception; - } - - private static class Jdk7MediatorImpl implements Mediator { - private final Method myGetDefault; - private final Method myGetPath; - private final Method myIsSymbolicLink; - - private Jdk7MediatorImpl() throws Exception { - myGetDefault = Class.forName("java.nio.file.FileSystems").getMethod("getDefault"); - myGetPath = Class.forName("java.nio.file.FileSystem").getMethod("getPath", String.class, String[].class); - myIsSymbolicLink = Class.forName("java.nio.file.Files").getMethod("isSymbolicLink", Class.forName("java.nio.file.Path")); - } - - @Override - public boolean isSymLink(@NotNull final String path) throws Exception { - final Object fileSystem = myGetDefault.invoke(null); - final Object pathObj = myGetPath.invoke(fileSystem, path, ArrayUtil.EMPTY_STRING_ARRAY); - return (Boolean)myIsSymbolicLink.invoke(null, pathObj); - } - } - - // thanks to SVNKit for the idea - @SuppressWarnings("OctalInteger") - private static class JnaMediatorImpl implements Mediator { - private interface LibC extends Library { - int S_MASK = 0177777; - int S_IFLNK = 0120000; - - int lstat(String path, Pointer stat); - int __lxstat64(int ver, String path, Pointer stat); - } - - private final LibC myLibC; - private final Memory mySharedMem; - private final int myOffset; - - private JnaMediatorImpl() throws Exception { - myLibC = (LibC)Native.loadLibrary("c", LibC.class); - mySharedMem = new Memory(512); - myOffset = SystemInfo.isLinux ? (SystemInfo.is32Bit ? 16 : 24) : - SystemInfo.isMac ? 8 : - SystemInfo.isSolaris ? (SystemInfo.is32Bit ? 20 : 16) : - -1; - if (myOffset < 0) throw new IllegalStateException("Unsupported OS: " + SystemInfo.OS_NAME); - } - - @Override - public synchronized boolean isSymLink(@NotNull final String path) throws Exception { - mySharedMem.clear(); - final int res = SystemInfo.isLinux ? myLibC.__lxstat64(0, path, mySharedMem) : myLibC.lstat(path, mySharedMem); - if (res == 0) { - final int mode = (SystemInfo.isLinux ? mySharedMem.getInt(myOffset) : mySharedMem.getShort(myOffset)) & LibC.S_MASK; - return (mode & LibC.S_IFLNK) == LibC.S_IFLNK; - } - else { - //LOG.warn("lstat(" + path + "): " + res); - return false; - } - } - } -} diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileSystemEntry.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileSystemEntry.java index f728f6f344da..c7c5d5595caf 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileSystemEntry.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/impl/VirtualFileSystemEntry.java @@ -425,4 +425,10 @@ public abstract class VirtualFileSystemEntry extends NewVirtualFile { final NewVirtualFileSystem fs = getFileSystem(); return fs instanceof LocalFileSystem && ((LocalFileSystem)fs).isSymLink(this); } + + @Override + public VirtualFile getRealFile() { + final NewVirtualFileSystem fs = getFileSystem(); + return fs instanceof LocalFileSystem ? ((LocalFileSystem)fs).getRealFile(this) : super.getRealFile(); + } } diff --git a/platform/platform-impl/testSrc/com/intellij/openapi/vfs/local/SymLinkHandlingTest.java b/platform/platform-impl/testSrc/com/intellij/openapi/vfs/local/SymLinkHandlingTest.java index 1985ec96ff23..95c3aae8c9e1 100644 --- a/platform/platform-impl/testSrc/com/intellij/openapi/vfs/local/SymLinkHandlingTest.java +++ b/platform/platform-impl/testSrc/com/intellij/openapi/vfs/local/SymLinkHandlingTest.java @@ -67,12 +67,17 @@ public class SymLinkHandlingTest extends LightPlatformTestCase { final VirtualFile linkVDir = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(linkDir); assertTrue("link=" + linkDir + ", vLink=" + linkVDir, linkVDir != null && linkVDir.isDirectory() && linkVDir.isSymLink()); - assertTrue(targetDir.getAbsolutePath(), targetDir.setWritable(true, false) && targetDir.canWrite()); - linkVDir.refresh(false, true); - assertTrue(linkVDir.getPath(), linkVDir.isWritable()); - assertTrue(targetDir.getAbsolutePath(), targetDir.setWritable(false, false) && !targetDir.canWrite()); - linkVDir.refresh(false, true); - assertFalse(linkVDir.getPath(), linkVDir.isWritable()); + if (!SystemInfo.isWindows) { + assertTrue(targetDir.getAbsolutePath(), targetDir.setWritable(true, false) && targetDir.canWrite()); + linkVDir.refresh(false, true); + assertTrue(linkVDir.getPath(), linkVDir.isWritable()); + assertTrue(targetDir.getAbsolutePath(), targetDir.setWritable(false, false) && !targetDir.canWrite()); + linkVDir.refresh(false, true); + assertFalse(linkVDir.getPath(), linkVDir.isWritable()); + } + else { + assertEquals(linkVDir.getPath(), targetDir.canWrite(), linkVDir.isWritable()); + } } public void testLinkDeleteIsSafe() throws Exception { @@ -122,14 +127,20 @@ public class SymLinkHandlingTest extends LightPlatformTestCase { final File parentDir = linkFile.getParentFile(); assertTrue("link=" + link + ", parent=" + parentDir, parentDir != null && (parentDir.isDirectory() || parentDir.mkdirs())); - final ProcessBuilder builder = new ProcessBuilder("ln", "-s", target, linkFile.getAbsolutePath()); + final ProcessBuilder builder; + if (SystemInfo.isWindows) { + builder = new File(target).isDirectory() + ? new ProcessBuilder("cmd", "/C", "mklink", "/D", linkFile.getAbsolutePath(), target) + : new ProcessBuilder("cmd", "/C", "mklink", linkFile.getAbsolutePath(), target); + } + else { + builder = new ProcessBuilder("ln", "-s", target, linkFile.getAbsolutePath()); + } final Process process = builder.start(); final int res = process.waitFor(); assertTrue(builder.command() + ": " + res, res == 0); final File targetFile = new File(target); - assertTrue("target=" + target + ", link=" + linkFile, - linkFile.exists() == targetFile.exists() && - linkFile.getCanonicalPath().equals(targetFile.getAbsolutePath()) == targetFile.exists()); + assertEquals("target=" + target + ", link=" + linkFile, targetFile.exists(), linkFile.exists()); return linkFile; } } diff --git a/platform/util/src/com/intellij/openapi/util/SystemInfo.java b/platform/util/src/com/intellij/openapi/util/SystemInfo.java index 79cd594314d5..aad3b4845727 100644 --- a/platform/util/src/com/intellij/openapi/util/SystemInfo.java +++ b/platform/util/src/com/intellij/openapi/util/SystemInfo.java @@ -50,12 +50,12 @@ public class SystemInfo { public static final boolean isMacSystemMenu = isMac && "true".equals(System.getProperty("apple.laf.useScreenMenuBar")); public static final boolean isFileSystemCaseSensitive = !isWindows && !isOS2 && !isMac; - public static final boolean areSymLinksSupported = isUnix; + public static final boolean areSymLinksSupported = isUnix || + isWindows && OS_VERSION.compareTo("6.0") >= 0 && isJavaVersionAtLeast("1.7"); public static final boolean is32Bit = ARCH_DATA_MODEL == null || ARCH_DATA_MODEL.equals("32"); public static final boolean is64Bit = !is32Bit; public static final boolean isAMD64 = "amd64".equals(OS_ARCH); - public static final boolean isMacIntel64 = isMac && "x86_64".equals(OS_ARCH); public static final String nativeFileManagerName = isMac ? "Finder" : isGnome ? "Nautilus" : isKDE ? "Konqueror" : "Explorer"; diff --git a/platform/util/src/com/intellij/openapi/util/io/FileUtil.java b/platform/util/src/com/intellij/openapi/util/io/FileUtil.java index 0bad8df44f64..562fa027e7b2 100644 --- a/platform/util/src/com/intellij/openapi/util/io/FileUtil.java +++ b/platform/util/src/com/intellij/openapi/util/io/FileUtil.java @@ -42,8 +42,10 @@ import java.util.regex.Pattern; @SuppressWarnings({"UtilityClassWithoutPrivateConstructor"}) public class FileUtil { public static final int MEGABYTE = 1024 * 1024; + public static final String ASYNC_DELETE_EXTENSION = ".__del__"; private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.io.FileUtil"); + private static final ThreadLocal BUFFER = new ThreadLocal() { protected byte[] initialValue() { return new byte[1024 * 20]; @@ -51,9 +53,9 @@ public class FileUtil { }; // do not use channels to copy files larger than 5 Mb because of possible MapFailed error - private static final long CHANNELS_COPYING_LIMIT = 5L * 1024L * 1024L; + private static final long CHANNELS_COPYING_LIMIT = 5L * MEGABYTE; private static String ourCanonicalTempPathCache = null; - public static final String ASYNC_DELETE_EXTENSION = ".__del__"; + private static final int MAX_FILE_DELETE_ATTEMPTS = 10; @Nullable public static String getRelativePath(File base, File file) { @@ -541,21 +543,22 @@ public class FileUtil { } public static boolean delete(@NotNull File file) { - File[] files = file.listFiles(); - if (files != null) { - for (File file1 : files) { - if (!delete(file1)) return false; + if (!SymLinkUtil.isSymLink(file)) { + File[] files = file.listFiles(); + if (files != null) { + for (File child : files) { + if (!delete(child)) return false; + } } } - for (int i = 0; i < 10; i++) { + for (int i = 0; i < MAX_FILE_DELETE_ATTEMPTS; i++) { if (file.delete() || !file.exists()) return true; try { + //noinspection BusyWait Thread.sleep(10); } - catch (InterruptedException ignored) { - - } + catch (InterruptedException ignored) { } } return false; } diff --git a/platform/util/src/com/intellij/openapi/util/io/SymLinkUtil.java b/platform/util/src/com/intellij/openapi/util/io/SymLinkUtil.java new file mode 100644 index 000000000000..17829dda6468 --- /dev/null +++ b/platform/util/src/com/intellij/openapi/util/io/SymLinkUtil.java @@ -0,0 +1,320 @@ +/* + * Copyright 2000-2011 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.util.io; + +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.util.ArrayUtil; +import com.sun.jna.Library; +import com.sun.jna.Memory; +import com.sun.jna.Native; +import com.sun.jna.Pointer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.lang.reflect.Array; +import java.lang.reflect.Method; + +// todo[r.sh] use NIO2 API after migration to JDK 7 +public class SymLinkUtil { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.io.SymLinkUtil"); + + @Nullable + private static final Mediator ourMediator; + + static { + Mediator mediator = null; + if (SystemInfo.areSymLinksSupported) { + if (SystemInfo.isJavaVersionAtLeast("1.7")) { + try { + mediator = new Jdk7MediatorImpl(); + mediator.isSymLink("/"); // quick test + } + catch (Throwable t) { + LOG.error(t); + mediator = null; + } + } + if (mediator == null) { + if (SystemInfo.isLinux || SystemInfo.isMac || SystemInfo.isSolaris) { + try { + mediator = new JnaUnixMediatorImpl(); + mediator.isSymLink("/"); // quick test + } + catch (Throwable t) { + LOG.error(t); + mediator = null; + } + } + /*else if (SystemInfo.isWindows) { + try { + mediator = new JnaWindowsMediatorImpl(); + mediator.isSymLink("/"); // quick test + } + catch (Throwable t) { + LOG.error(t); + mediator = null; + } + }*/ + } + } + ourMediator = mediator; + } + + private SymLinkUtil() { } + + public static boolean isSymLink(@NotNull final File file) { + return isSymLink(file.getAbsolutePath()); + } + + public static boolean isSymLink(@NotNull final String path) { + try { + return ourMediator != null && ourMediator.isSymLink(path); + } + catch (Exception e) { + LOG.warn(e); + return false; + } + } + + @Nullable + public static String resolveSymLink(@NotNull final File file) { + return resolveSymLink(file.getAbsolutePath()); + } + + @Nullable + public static String resolveSymLink(@NotNull final String path) { + if (ourMediator != null) { + try { + final String realPath = ourMediator.resolveSymLink(path); + if (realPath != null && new File(realPath).exists()) { + return realPath; + } + } + catch (Exception e) { + LOG.warn(e); + } + } + return null; + } + + private interface Mediator { + boolean isSymLink(@NotNull final String path) throws Exception; + + @Nullable + String resolveSymLink(@NotNull final String path) throws Exception; + } + + private static class Jdk7MediatorImpl implements Mediator { + private final Object myDefaultFileSystem; + private final Method myGetPath; + private final Method myIsSymbolicLink; + private final Object myLinkOptions; + + private Jdk7MediatorImpl() throws Exception { + myDefaultFileSystem = Class.forName("java.nio.file.FileSystems").getMethod("getDefault").invoke(null); + myGetPath = Class.forName("java.nio.file.FileSystem").getMethod("getPath", String.class, String[].class); + myGetPath.setAccessible(true); + myIsSymbolicLink = Class.forName("java.nio.file.Files").getMethod("isSymbolicLink", Class.forName("java.nio.file.Path")); + myIsSymbolicLink.setAccessible(true); + myLinkOptions = Array.newInstance(Class.forName("java.nio.file.LinkOption"), 0); + } + + @Override + public boolean isSymLink(@NotNull final String path) throws Exception { + final Object pathObj = myGetPath.invoke(myDefaultFileSystem, path, ArrayUtil.EMPTY_STRING_ARRAY); + return (Boolean)myIsSymbolicLink.invoke(null, pathObj); + } + + @Override + public String resolveSymLink(@NotNull final String path) throws Exception { + final Object pathObj = myGetPath.invoke(myDefaultFileSystem, path, ArrayUtil.EMPTY_STRING_ARRAY); + final Method toRealPath = pathObj.getClass().getMethod("toRealPath", myLinkOptions.getClass()); + toRealPath.setAccessible(true); + return toRealPath.invoke(pathObj, myLinkOptions).toString(); + } + } + + // thanks to SVNKit for the idea + @SuppressWarnings("OctalInteger") + private static class JnaUnixMediatorImpl implements Mediator { + private interface LibC extends Library { + int S_MASK = 0177777; + int S_IFLNK = 0120000; + + int lstat(String path, Pointer stat); + int __lxstat64(int ver, String path, Pointer stat); + } + + private final LibC myLibC; + private final Memory mySharedMem; + private final int myOffset; + + private JnaUnixMediatorImpl() throws Exception { + myLibC = (LibC)Native.loadLibrary("c", LibC.class); + mySharedMem = new Memory(512); + myOffset = SystemInfo.isLinux ? (SystemInfo.is32Bit ? 16 : 24) : + SystemInfo.isMac ? 8 : + SystemInfo.isSolaris ? (SystemInfo.is32Bit ? 20 : 16) : + -1; + if (myOffset < 0) throw new IllegalStateException("Unsupported OS: " + SystemInfo.OS_NAME); + } + + @Override + public synchronized boolean isSymLink(@NotNull final String path) throws Exception { + mySharedMem.clear(); + final int res = SystemInfo.isLinux ? myLibC.__lxstat64(0, path, mySharedMem) : myLibC.lstat(path, mySharedMem); + if (res == 0) { + final int mode = (SystemInfo.isLinux ? mySharedMem.getInt(myOffset) : mySharedMem.getShort(myOffset)) & LibC.S_MASK; + return (mode & LibC.S_IFLNK) == LibC.S_IFLNK; + } + else { + LOG.debug("lstat(" + path + "): " + res); + return false; + } + } + + @Override + public String resolveSymLink(@NotNull final String path) throws Exception { + return new File(path).getCanonicalPath(); + } + } + + /*private static class JnaWindowsMediatorImpl implements Mediator { + private interface Kernel32 extends StdCallLibrary { + int IO_REPARSE_TAG_SYMLINK = 0xA000000C; + int FILE_ACCESS_FLAGS = 0x0080; + int FILE_SHARE_FLAGS = 0x00000001 | 0x00000002 | 0x00000004; + int OPEN_EXISTING = 3; + int FILE_OPEN_FLAGS = 0x02000000 | 0x00200000; + int FSCTL_GET_REPARSE_POINT = 0x000900A8; + int SYMLINK_FLAG_RELATIVE = 0x00000001; + + @SuppressWarnings({"UnusedDeclaration", "MultipleVariablesInDeclaration"}) + class Win32FindData extends Structure implements Structure.ByReference { + public int dwFileAttributes; + public int ftCreationTimeL, ftCreationTimeH; + public int ftLastAccessTimeL, ftLastAccessTimeH; + public int ftLastWriteTimeL, ftLastWriteTimeH; + public int lFileSizeH, lFileSizeL; + public int dwReserved0; + public int dwReserved1; + public char[] cFileName = new char[260]; + public char[] cAlternateFileName = new char[14]; + } + + int MAX_SUPPORTED_TARGET_LENGTH = 4 * 1024; + @SuppressWarnings({"UnusedDeclaration", "MultipleVariablesInDeclaration"}) + class ReparseDataBuffer extends Structure implements Structure.ByReference { + public NativeLong ReparseTag; + public short ReparseDataLength; + public short Reserved; + public short SubstituteNameOffset, SubstituteNameLength; + public short PrintNameOffset, PrintNameLength; + public NativeLong Flags; + public char[] PathBuffer = new char[MAX_SUPPORTED_TARGET_LENGTH]; + } + + Pointer INVALID_HANDLE = Pointer.createConstant(-1); + + Pointer FindFirstFile(String lpFileName, Win32FindData lpFindFileData); + + boolean FindClose(Pointer hFindFile); + + Pointer CreateFile(String lpFileName, + int dwDesiredAccess, + int dwShareMode, + @Nullable Pointer lpSecurityAttributes, + int dwCreationDisposition, + int dwFlagsAndAttributes, + @Nullable Pointer hTemplateFile); + + boolean CloseHandle(Pointer hObject); + + boolean DeviceIoControl(Pointer hDevice, + int dwIoControlCode, + @Nullable Structure.ByReference lpInBuffer, + int nInBufferSize, + @Nullable Structure.ByReference lpOutBuffer, + int nOutBufferSize, + IntByReference lpBytesReturned, + @Nullable Pointer lpOverlapped); + } + + private final Kernel32 myKernel32; + private final Kernel32.Win32FindData myFindData; + private final Kernel32.ReparseDataBuffer myReparseData; + + private JnaWindowsMediatorImpl() throws Exception { + myKernel32 = (Kernel32) Native.loadLibrary("kernel32", Kernel32.class, W32APIOptions.UNICODE_OPTIONS); + myFindData = new Kernel32.Win32FindData(); + myReparseData = new Kernel32.ReparseDataBuffer(); + } + + @SuppressWarnings("NonPrivateFieldAccessedInSynchronizedContext") + @Override + public synchronized boolean isSymLink(@NotNull final String path) throws Exception { + synchronized (myFindData) { + myFindData.dwReserved0 = 0; + final Pointer handle = myKernel32.FindFirstFile(path, myFindData); + if (Kernel32.INVALID_HANDLE.equals(handle)) { + LOG.debug("FindFirstFile(" + path + "): " + handle); + return false; + } + myKernel32.FindClose(handle); + return (myFindData.dwReserved0 & Kernel32.IO_REPARSE_TAG_SYMLINK) == Kernel32.IO_REPARSE_TAG_SYMLINK; + } + } + + @SuppressWarnings("NonPrivateFieldAccessedInSynchronizedContext") + @Override + public String resolveSymLink(@NotNull final String path) throws Exception { + final Pointer handle = myKernel32.CreateFile(path, Kernel32.FILE_ACCESS_FLAGS, Kernel32.FILE_SHARE_FLAGS, null, + Kernel32.OPEN_EXISTING, Kernel32.FILE_OPEN_FLAGS, null); + if (Kernel32.INVALID_HANDLE.equals(handle)) { + LOG.debug("CreateFile(" + path + "): " + handle); + return null; + } + synchronized (myReparseData) { + try { + myReparseData.ReparseTag.setValue(0); + myReparseData.SubstituteNameOffset = myReparseData.SubstituteNameLength = 0; + myReparseData.Flags.setValue(0); + final boolean result = myKernel32.DeviceIoControl(handle, Kernel32.FSCTL_GET_REPARSE_POINT, null, 0, + myReparseData, myReparseData.size(), new IntByReference(), null); + if (!result || myReparseData.ReparseTag.intValue() != Kernel32.IO_REPARSE_TAG_SYMLINK) { + LOG.debug("DeviceIoControl(" + path + "): " + result + "," + myReparseData.ReparseTag); + return null; + } + String target = new String(myReparseData.PathBuffer, myReparseData.SubstituteNameOffset / 2, myReparseData.SubstituteNameLength / 2); + if ((myReparseData.Flags.intValue() & Kernel32.SYMLINK_FLAG_RELATIVE) == Kernel32.SYMLINK_FLAG_RELATIVE) { + return new File(new File(path).getParent(), target).getCanonicalPath(); + } + else { + if (target.startsWith("\\??\\") || target.startsWith("\\\\?\\")) { + target = target.substring(4); + } + return new File(target).getCanonicalPath(); + } + } + finally { + myKernel32.CloseHandle(handle); + } + } + } + }*/ +} From c9412e967855182b808d2fa1c836adee27d61159 Mon Sep 17 00:00:00 2001 From: anna Date: Sun, 21 Aug 2011 20:21:35 +0200 Subject: [PATCH 04/13] search for all classes in the whole scope (IDEA-69086) --- .../findUsages/JavaFindUsagesHandler.java | 27 ++++--------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/java/java-impl/src/com/intellij/find/findUsages/JavaFindUsagesHandler.java b/java/java-impl/src/com/intellij/find/findUsages/JavaFindUsagesHandler.java index 3218b2dd73d2..e9381367f664 100644 --- a/java/java-impl/src/com/intellij/find/findUsages/JavaFindUsagesHandler.java +++ b/java/java-impl/src/com/intellij/find/findUsages/JavaFindUsagesHandler.java @@ -402,19 +402,6 @@ public class JavaFindUsagesHandler extends FindUsagesHandler{ private static void addClassesUsages(PsiPackage aPackage, final Processor results, final JavaPackageFindUsagesOptions options) { - final HashSet filesSet = new HashSet(); - final ArrayList files = new ArrayList(); - ReferencesSearch.search(new ReferencesSearch.SearchParameters(aPackage, options.searchScope, false, options.fastTrack)).forEach(new ReadActionProcessor() { - public boolean processInReadAction(final PsiReference psiReference) { - PsiElement ref = psiReference.getElement(); - PsiFile file = ref.getContainingFile(); - if (filesSet.add(file)) { - files.add(file); - } - return true; - } - }); - ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); if (progress != null){ progress.pushState(); @@ -430,17 +417,13 @@ public class JavaFindUsagesHandler extends FindUsagesHandler{ return aClass.getName(); } }))); + progress.checkCanceled(); } - for (PsiFile file : files) { - if (progress != null) { - progress.checkCanceled(); + ReferencesSearch.search(new ReferencesSearch.SearchParameters(aClass, options.searchScope, false, options.fastTrack)).forEach(new ReadActionProcessor() { + public boolean processInReadAction(final PsiReference psiReference) { + return addResult(results, psiReference, options); } - ReferencesSearch.search(new ReferencesSearch.SearchParameters(aClass, new LocalSearchScope(file), false, options.fastTrack)).forEach(new ReadActionProcessor() { - public boolean processInReadAction(final PsiReference psiReference) { - return addResult(results, psiReference, options); - } - }); - } + }); } if (progress != null){ From 49fb929e81d194393e14d33263f82dd56245730a Mon Sep 17 00:00:00 2001 From: anna Date: Mon, 22 Aug 2011 11:23:47 +0200 Subject: [PATCH 05/13] NPE (e.g. invoked on a package statement) --- .../intellij/codeInsight/hint/ImplementationViewComponent.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hint/ImplementationViewComponent.java b/platform/lang-impl/src/com/intellij/codeInsight/hint/ImplementationViewComponent.java index 44e4f564534b..17a108f1e1df 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hint/ImplementationViewComponent.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/hint/ImplementationViewComponent.java @@ -87,7 +87,7 @@ public class ImplementationViewComponent extends JPanel { } public boolean hasElementsToShow() { - return myElements.length > 0; + return myElements != null && myElements.length > 0; } private static class FileDescriptor { From c763ca289f3204f9ab81dd5d2dd8a4c35bd8b7c7 Mon Sep 17 00:00:00 2001 From: anna Date: Mon, 22 Aug 2011 11:35:30 +0200 Subject: [PATCH 06/13] can be final: should not trigger static fields with initializer in a constructor (IDEA-69011) --- .../canBeFinal/CanBeFinalAnnotator.java | 7 +++++-- .../inspection/canBeFinal/staticFields/expected.xml | 9 +++++++++ .../inspection/canBeFinal/staticFields/src/Foo.java | 13 +++++++++++++ .../com/intellij/codeInspection/CanBeFinalTest.java | 4 ++++ 4 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 java/java-tests/testData/inspection/canBeFinal/staticFields/expected.xml create mode 100644 java/java-tests/testData/inspection/canBeFinal/staticFields/src/Foo.java diff --git a/java/java-impl/src/com/intellij/codeInspection/canBeFinal/CanBeFinalAnnotator.java b/java/java-impl/src/com/intellij/codeInspection/canBeFinal/CanBeFinalAnnotator.java index 687f0c3060ce..0925fccff830 100644 --- a/java/java-impl/src/com/intellij/codeInspection/canBeFinal/CanBeFinalAnnotator.java +++ b/java/java-impl/src/com/intellij/codeInspection/canBeFinal/CanBeFinalAnnotator.java @@ -99,8 +99,11 @@ class CanBeFinalAnnotator extends RefGraphAnnotatorEx { boolean forReading, boolean forWriting) { if (!(refWhat instanceof RefField)) return; - if (!(refFrom instanceof RefMethod) || !((RefMethod)refFrom).isConstructor() || ((PsiField)refWhat.getElement()).hasInitializer() || - ((RefMethod)refFrom).getOwnerClass() != ((RefField)refWhat).getOwnerClass()) { + if (!(refFrom instanceof RefMethod) || + !((RefMethod)refFrom).isConstructor() || + ((PsiField)refWhat.getElement()).hasInitializer() || + ((RefMethod)refFrom).getOwnerClass() != ((RefField)refWhat).getOwnerClass() || + ((RefField)refWhat).isStatic()) { if (!referencedFromClassInitializer && forWriting) { ((RefFieldImpl)refWhat).setFlag(false, CAN_BE_FINAL_MASK); } diff --git a/java/java-tests/testData/inspection/canBeFinal/staticFields/expected.xml b/java/java-tests/testData/inspection/canBeFinal/staticFields/expected.xml new file mode 100644 index 000000000000..96969d3cdd3f --- /dev/null +++ b/java/java-tests/testData/inspection/canBeFinal/staticFields/expected.xml @@ -0,0 +1,9 @@ + + + + Foo.java + 2 + final + + + diff --git a/java/java-tests/testData/inspection/canBeFinal/staticFields/src/Foo.java b/java/java-tests/testData/inspection/canBeFinal/staticFields/src/Foo.java new file mode 100644 index 000000000000..c96e20164735 --- /dev/null +++ b/java/java-tests/testData/inspection/canBeFinal/staticFields/src/Foo.java @@ -0,0 +1,13 @@ +public final class Foo { + private static Object f1 = new Object(); // Can be final but unused + private static Object object; // can't be final + + Foo() { + object = new Object(); + } + + public static void main(String[] args) { + System.out.println(Foo.f1); + System.out.println(Foo.object); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInspection/CanBeFinalTest.java b/java/java-tests/testSrc/com/intellij/codeInspection/CanBeFinalTest.java index 4f216bfc550b..42ca7e6c352c 100644 --- a/java/java-tests/testSrc/com/intellij/codeInspection/CanBeFinalTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInspection/CanBeFinalTest.java @@ -82,6 +82,10 @@ public class CanBeFinalTest extends InspectionTestCase { doTest(); } + public void teststaticFields() throws Exception { + doTest(); + } + public void testSCR7737() throws Exception { CanBeFinalInspection tool = new CanBeFinalInspection(); tool.REPORT_CLASSES = false; From 88d4c3d6fbbb1f166523d718113d8ef2b50fbb8b Mon Sep 17 00:00:00 2001 From: anna Date: Mon, 22 Aug 2011 11:55:26 +0200 Subject: [PATCH 07/13] disable add static import intention for classes in default package (->IDEA-26971 ) --- .../intention/impl/AddOnDemandStaticImportAction.java | 3 +++ .../intention/impl/AddSingleMemberStaticImportAction.java | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/java/java-impl/src/com/intellij/codeInsight/intention/impl/AddOnDemandStaticImportAction.java b/java/java-impl/src/com/intellij/codeInsight/intention/impl/AddOnDemandStaticImportAction.java index efe0b105545e..645d5b0b29fc 100644 --- a/java/java-impl/src/com/intellij/codeInsight/intention/impl/AddOnDemandStaticImportAction.java +++ b/java/java-impl/src/com/intellij/codeInsight/intention/impl/AddOnDemandStaticImportAction.java @@ -24,6 +24,8 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.colors.EditorColors; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; @@ -65,6 +67,7 @@ public class AddOnDemandStaticImportAction extends PsiElementBaseIntentionAction return null; } PsiClass psiClass = (PsiClass)resolved; + if (Comparing.strEqual(psiClass.getName(), psiClass.getQualifiedName())) return null; PsiFile file = refExpr.getContainingFile(); if (!(file instanceof PsiJavaFile)) return null; PsiImportList importList = ((PsiJavaFile)file).getImportList(); diff --git a/java/java-impl/src/com/intellij/codeInsight/intention/impl/AddSingleMemberStaticImportAction.java b/java/java-impl/src/com/intellij/codeInsight/intention/impl/AddSingleMemberStaticImportAction.java index 87b3b2046ee5..190e54faf35f 100644 --- a/java/java-impl/src/com/intellij/codeInsight/intention/impl/AddSingleMemberStaticImportAction.java +++ b/java/java-impl/src/com/intellij/codeInsight/intention/impl/AddSingleMemberStaticImportAction.java @@ -25,7 +25,9 @@ import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.util.PsiUtil; @@ -63,7 +65,7 @@ public class AddSingleMemberStaticImportAction extends PsiElementBaseIntentionAc PsiClass aClass = ((PsiMember)resolved).getContainingClass(); if (aClass != null && !PsiTreeUtil.isAncestor(aClass, element, true)) { String qName = aClass.getQualifiedName(); - if (qName != null) { + if (qName != null && !Comparing.strEqual(qName, aClass.getName())) { qName = qName + "." +refExpr.getReferenceName(); if (file instanceof PsiJavaFile) { PsiImportList importList = ((PsiJavaFile)file).getImportList(); From 507e4efb9316c82512becadcc8394fbbb743fa3f Mon Sep 17 00:00:00 2001 From: anna Date: Mon, 22 Aug 2011 12:16:13 +0200 Subject: [PATCH 08/13] read access --- .../uiDesigner/binding/FormReferencesSearcher.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugins/ui-designer/src/com/intellij/uiDesigner/binding/FormReferencesSearcher.java b/plugins/ui-designer/src/com/intellij/uiDesigner/binding/FormReferencesSearcher.java index 9025926aa340..4256cabf6b3c 100644 --- a/plugins/ui-designer/src/com/intellij/uiDesigner/binding/FormReferencesSearcher.java +++ b/plugins/ui-designer/src/com/intellij/uiDesigner/binding/FormReferencesSearcher.java @@ -227,7 +227,12 @@ public class FormReferencesSearcher implements QueryExecutor() { + @Override + public String compute() { + return property.getName(); + } + }); if (name == null) return true; manager.startBatchFilesProcessingMode(); From e23fd301ad3cbf114a70fbe89f43dc18ea367ac5 Mon Sep 17 00:00:00 2001 From: anna Date: Mon, 22 Aug 2011 13:52:05 +0200 Subject: [PATCH 09/13] forbid default package classes in static imports (IDEA-26971 ) --- .../daemon/impl/analysis/HighlightUtil.java | 7 +++++++ .../DefaultPackageClassInStaticImport.java | 11 +++++++++++ .../codeInsight/daemon/LightAdvHighlightingTest.java | 1 + 3 files changed, 19 insertions(+) create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/DefaultPackageClassInStaticImport.java diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java index 4c8d53375291..243103137edb 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/analysis/HighlightUtil.java @@ -2173,6 +2173,13 @@ public class HighlightUtil { } if ((resolved instanceof PsiLocalVariable || resolved instanceof PsiParameter) && !(resolved instanceof ImplicitVariable)) { highlightInfo = HighlightControlFlowUtil.checkVariableMustBeFinal((PsiVariable)resolved, ref); + } else if (resolved instanceof PsiClass) { + if (Comparing.strEqual(((PsiClass)resolved).getQualifiedName(), ((PsiClass)resolved).getName())) { + final PsiElement parent = ref.getParent(); + if (parent instanceof PsiImportStaticReferenceElement || parent instanceof PsiImportStaticStatement) { + return HighlightInfo.createHighlightInfo(HighlightInfoType.WRONG_REF, refName, JavaErrorMessages.message("cannot.resolve.symbol", refName.getText())); + } + } } return highlightInfo; } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/DefaultPackageClassInStaticImport.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/DefaultPackageClassInStaticImport.java new file mode 100644 index 000000000000..43a1da2c223c --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/DefaultPackageClassInStaticImport.java @@ -0,0 +1,11 @@ +import static MyConstants.*; + +public class MyClient +{ + private int field = MyConstants.CONSTANT; +} + +class MyConstants +{ + public static final int CONSTANT = 1; +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/LightAdvHighlightingTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/LightAdvHighlightingTest.java index 0fbc7173173f..8fac2b46da3c 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/LightAdvHighlightingTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/LightAdvHighlightingTest.java @@ -175,6 +175,7 @@ public class LightAdvHighlightingTest extends LightDaemonAnalyzerTestCase { public void testIDEADEV13249() throws Exception { doTest(false, false); } public void testIDEADEV11919() throws Exception { doTest(false, false); } public void testMethodCannotBeApplied() throws Exception { doTest(false, false); } + public void testDefaultPackageClassInStaticImport() throws Exception { doTest(false, false); } public void testUnusedParamsOfPublicMethod() throws Exception { doTest(true, false); } From f087cd8c25b159eebef013e1f306afc634f99bb5 Mon Sep 17 00:00:00 2001 From: anna Date: Mon, 22 Aug 2011 14:18:21 +0200 Subject: [PATCH 10/13] generate private enum constructor everywhere (IDEA-69128 ) --- .../daemon/impl/quickfix/AddDefaultConstructorFix.java | 7 ++++++- .../generation/GenerateConstructorHandler.java | 6 ++++-- .../createConstructorParameterFromField/after1.java | 2 +- .../afterAfterSemi.java | 2 +- .../afterGenerateEnumPrivateConstructor.java | 10 ++++++++++ .../afterMultipleFieldsNoConstructorNoUsages.java | 2 +- .../createConstructorParameterFromField/before1.java | 2 +- .../beforeAfterSemi.java | 2 +- .../beforeGenerateEnumPrivateConstructor.java | 6 ++++++ .../beforeMultipleFieldsNoConstructorNoUsages.java | 2 +- .../afterEnumWithAbstractMethod.java | 2 +- 11 files changed, 33 insertions(+), 10 deletions(-) create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/afterGenerateEnumPrivateConstructor.java create mode 100644 java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/beforeGenerateEnumPrivateConstructor.java diff --git a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddDefaultConstructorFix.java b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddDefaultConstructorFix.java index 76f23c735310..24a25cf4ee32 100644 --- a/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddDefaultConstructorFix.java +++ b/java/java-impl/src/com/intellij/codeInsight/daemon/impl/quickfix/AddDefaultConstructorFix.java @@ -16,15 +16,20 @@ package com.intellij.codeInsight.daemon.impl.quickfix; import com.intellij.codeInsight.daemon.QuickFixBundle; +import com.intellij.codeInsight.generation.GenerateConstructorHandler; import com.intellij.psi.PsiClass; import org.jetbrains.annotations.NotNull; public class AddDefaultConstructorFix extends AddMethodFix { public AddDefaultConstructorFix(PsiClass aClass) { - super("public " + aClass.getName() + "() {}", aClass); + super(generateConstructor(aClass), aClass); setText(QuickFixBundle.message("add.default.constructor.text", aClass.getName())); } + private static String generateConstructor(PsiClass aClass) { + final String constructorModifier = GenerateConstructorHandler.getConstructorModifier(aClass); + return constructorModifier + (constructorModifier.isEmpty() ? "" : " ") + aClass.getName() + "() {}"; + } @NotNull public String getFamilyName() { diff --git a/java/java-impl/src/com/intellij/codeInsight/generation/GenerateConstructorHandler.java b/java/java-impl/src/com/intellij/codeInsight/generation/GenerateConstructorHandler.java index 148ca384b19a..b5b40fc52bf6 100644 --- a/java/java-impl/src/com/intellij/codeInsight/generation/GenerateConstructorHandler.java +++ b/java/java-impl/src/com/intellij/codeInsight/generation/GenerateConstructorHandler.java @@ -267,8 +267,7 @@ public class GenerateConstructorHandler extends GenerateMembersHandlerBase { } @Modifier - @Nullable - private static String getConstructorModifier(final PsiClass aClass) { + public static String getConstructorModifier(final PsiClass aClass) { @Modifier String modifier = PsiModifier.PUBLIC; if (aClass.hasModifierProperty(PsiModifier.ABSTRACT) && !aClass.isEnum()) { @@ -280,6 +279,9 @@ public class GenerateConstructorHandler extends GenerateMembersHandlerBase { else if (aClass.hasModifierProperty(PsiModifier.PRIVATE)) { modifier = PsiModifier.PRIVATE; } + else if (aClass.isEnum()) { + modifier = PsiModifier.PRIVATE; + } return modifier; } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/after1.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/after1.java index 9c2ab49c2900..5ffc5699bbe8 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/after1.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/after1.java @@ -1,5 +1,5 @@ // "Add constructor parameter" "true" -class A { +public class A { private int field; public A(int field) { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/afterAfterSemi.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/afterAfterSemi.java index 6e4cbf72850c..fa6f0109bb95 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/afterAfterSemi.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/afterAfterSemi.java @@ -1,5 +1,5 @@ // "Add constructor parameter" "true" -class A { +public class A { private final A field; public A(A field) { diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/afterGenerateEnumPrivateConstructor.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/afterGenerateEnumPrivateConstructor.java new file mode 100644 index 000000000000..53ec37bf6f57 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/afterGenerateEnumPrivateConstructor.java @@ -0,0 +1,10 @@ +// "Add constructor parameter" "true" +public enum MyEnum +{ + VALUE_ONE(myField); + private final int myField; + + private MyEnum(int myField) { + this.myField = myField; + } +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/afterMultipleFieldsNoConstructorNoUsages.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/afterMultipleFieldsNoConstructorNoUsages.java index 3766c72ffa57..e142c903f5ff 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/afterMultipleFieldsNoConstructorNoUsages.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/afterMultipleFieldsNoConstructorNoUsages.java @@ -1,5 +1,5 @@ // "Add constructor parameters" "true" -class A { +public class A { private final int field; private final Object o; private final Runnable runnable; diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/before1.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/before1.java index 95bf697f21bd..45de2162576b 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/before1.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/before1.java @@ -1,4 +1,4 @@ // "Add constructor parameter" "true" -class A { +public class A { private int field; } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/beforeAfterSemi.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/beforeAfterSemi.java index d35c2c7e2c9e..490babd32d1e 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/beforeAfterSemi.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/beforeAfterSemi.java @@ -1,4 +1,4 @@ // "Add constructor parameter" "true" -class A { +public class A { private final A field; } \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/beforeGenerateEnumPrivateConstructor.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/beforeGenerateEnumPrivateConstructor.java new file mode 100644 index 000000000000..57ca0439c9f2 --- /dev/null +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/beforeGenerateEnumPrivateConstructor.java @@ -0,0 +1,6 @@ +// "Add constructor parameter" "true" +public enum MyEnum +{ + VALUE_ONE; + private final int myField; +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/beforeMultipleFieldsNoConstructorNoUsages.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/beforeMultipleFieldsNoConstructorNoUsages.java index e54ed70e1c00..df96b56ca046 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/beforeMultipleFieldsNoConstructorNoUsages.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/quickFix/createConstructorParameterFromField/beforeMultipleFieldsNoConstructorNoUsages.java @@ -1,5 +1,5 @@ // "Add constructor parameters" "true" -class A { +public class A { private final int field; private final Object o; private final Runnable runnable; diff --git a/java/java-tests/testData/codeInsight/generateConstructor/afterEnumWithAbstractMethod.java b/java/java-tests/testData/codeInsight/generateConstructor/afterEnumWithAbstractMethod.java index 8a38e2b50e73..f12bc94ae242 100644 --- a/java/java-tests/testData/codeInsight/generateConstructor/afterEnumWithAbstractMethod.java +++ b/java/java-tests/testData/codeInsight/generateConstructor/afterEnumWithAbstractMethod.java @@ -7,6 +7,6 @@ public enum Operation { abstract int eval(int x, int y); - Operation() { + private Operation() { } } \ No newline at end of file From 613f9fddae76f566c072cb616ee19b2a8ea0723e Mon Sep 17 00:00:00 2001 From: anna Date: Mon, 22 Aug 2011 15:33:53 +0200 Subject: [PATCH 11/13] space (IDEA-67070) --- .../OperatorApplicability.java | 46 +++++++++---------- .../genericsHighlighting/Autoboxing.java | 4 +- .../ReferenceTypeParams.java | 2 +- .../src/messages/JavaErrorMessages.properties | 2 +- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/OperatorApplicability.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/OperatorApplicability.java index bcb1caa5ca6a..e60c440b6f69 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/OperatorApplicability.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlighting/OperatorApplicability.java @@ -2,36 +2,36 @@ public class a { int f(int ik) { - if (1 < null) {} - if (null == 'c') {} + if (1 < null) {} + if (null == 'c') {} Object o = null; - if (1.2 >= o) {} - if (1L != "null") {} - if ((1==2) == 3) {} + if (1.2 >= o) {} + if (1L != "null") {} + if ((1==2) == 3) {} - int i = (1 + null); - i = o/o; - i = null - 1.2; - i = true % 4; + int i = (1 + null); + i = o/o; + i = null - 1.2; + i = true % 4; - i = i << o; - i = (i==2) >> null; - i = i >>> 2.2; + i = i << o; + i = (i==2) >> null; + i = i >>> 2.2; - i = i & o; - i = true | 2.1; - i = 2 && 3; - i = 3.8 || 2L; - i = null || o; + i = i & o; + i = true | 2.1; + i = 2 && 3; + i = 3.8 || 2L; + i = null || o; - i |= null; + i |= null; double d = 0; - d &= i; - o /= 3; + d &= i; + o /= 3; - String sss2 = "" + fvoid(); - int sss1 = fvoid() + 2; + String sss2 = "" + fvoid(); + int sss1 = fvoid() + 2; int ia[] = null; boolean b = 1==3 || 3 < '4' && (1>3.5) == (o == null) || false || (o == "d"); @@ -44,7 +44,7 @@ public class a { ia[~i | (i+=(!b?2:i))] -= i + 3.3; // Object += String - o += o + "string"; + o += o + "string"; return 0; } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/Autoboxing.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/Autoboxing.java index 5e02b2774de3..8ecc8646e0be 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/Autoboxing.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/Autoboxing.java @@ -4,7 +4,7 @@ public class Autoboxing { } public boolean compare(Short s, Integer i) { - return i == s; //comparing as references + return i == s; //comparing as references } void f(Integer i) { @@ -15,7 +15,7 @@ public class Autoboxing { { Object data = 1; - boolean is1 = data == 1; + boolean is1 = data == 1; } //IDEADEV-5549: Short and double are convertible diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/ReferenceTypeParams.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/ReferenceTypeParams.java index 2831950aba69..9466c30afb77 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/ReferenceTypeParams.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/genericsHighlighting/ReferenceTypeParams.java @@ -78,7 +78,7 @@ class mylist {} class myAList extends mylist { { mylist l = (mylist) new myAList(); - boolean b = new myAList() == new myAList(); + boolean b = new myAList() == new myAList(); if (l instanceof myAList); Object o = new Object(); diff --git a/resources-en/src/messages/JavaErrorMessages.properties b/resources-en/src/messages/JavaErrorMessages.properties index 93a57976ab2e..532c09c48473 100644 --- a/resources-en/src/messages/JavaErrorMessages.properties +++ b/resources-en/src/messages/JavaErrorMessages.properties @@ -178,7 +178,7 @@ instance.method.cannot.override.static.method=Instance method ''{0}'' in ''{1}'' static.method.cannot.override.instance.method=Static method ''{0}'' in ''{1}'' cannot override instance method ''{2}'' in ''{3}'' inconvertible.type.cast=Inconvertible types; cannot cast ''{0}'' to ''{1}'' variable.expected=Variable expected -binary.operator.not.applicable=Operator ''{0}'' cannot be applied to ''{1}'',''{2}'' +binary.operator.not.applicable=Operator ''{0}'' cannot be applied to ''{1}'', ''{2}'' unary.operator.not.applicable=Operator ''{0}'' cannot be applied to ''{1}'' return.outside.method=Return outside method return.from.void.method=Cannot return a value from a method with void result type From 36aa700987a6dd63129646b324e49bca86380417 Mon Sep 17 00:00:00 2001 From: anna Date: Mon, 22 Aug 2011 16:03:42 +0200 Subject: [PATCH 12/13] escape field initializer (IDEA-66703 ) --- .../src/com/intellij/lang/java/JavaDocumentationProvider.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/java-impl/src/com/intellij/lang/java/JavaDocumentationProvider.java b/java/java-impl/src/com/intellij/lang/java/JavaDocumentationProvider.java index 5efe09d36d98..b5ef7b810442 100644 --- a/java/java-impl/src/com/intellij/lang/java/JavaDocumentationProvider.java +++ b/java/java-impl/src/com/intellij/lang/java/JavaDocumentationProvider.java @@ -34,6 +34,7 @@ import com.intellij.openapi.module.ModuleUtil; import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.*; +import com.intellij.openapi.util.JDOMUtil; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.JarFileSystem; @@ -112,7 +113,7 @@ public class JavaDocumentationProvider implements CodeDocumentationProvider, Ext boolean trunc = index < text.length(); text = text.substring(0, index); buffer.append(" = "); - buffer.append(text); + buffer.append(StringUtil.escapeXml(text)); if (trunc) { buffer.append("..."); } From ce5fb1fc794c167faebd23a3d51b20c877f8a088 Mon Sep 17 00:00:00 2001 From: anna Date: Mon, 22 Aug 2011 17:08:58 +0200 Subject: [PATCH 13/13] implementations view: show additional info about text in editor e.g. containing inner class if present (IDEA-65212 ) --- .../daemon/ImplementationsViewTest.java | 103 ++++++++++++++++++ .../hint/ImplementationViewComponent.java | 25 ++++- 2 files changed, 126 insertions(+), 2 deletions(-) diff --git a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/ImplementationsViewTest.java b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/ImplementationsViewTest.java index 3dc1bfa4dd54..9533a9e78cca 100644 --- a/java/java-tests/testSrc/com/intellij/codeInsight/daemon/ImplementationsViewTest.java +++ b/java/java-tests/testSrc/com/intellij/codeInsight/daemon/ImplementationsViewTest.java @@ -2,8 +2,16 @@ package com.intellij.codeInsight.daemon; import com.intellij.codeInsight.TargetElementUtilBase; import com.intellij.codeInsight.hint.ImplementationViewComponent; +import com.intellij.openapi.util.Disposer; +import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.search.searches.ClassInheritorsSearch; +import com.intellij.psi.search.searches.OverridingMethodsSearch; import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; +import org.junit.Assert; + +import java.util.*; /** * User: anna @@ -72,4 +80,99 @@ public class ImplementationsViewTest extends LightCodeInsightFixtureTestCase { " return \"text\";\n" + " }", newText); } + + public void testInnerClasses() { + myFixture.configureByText("a.java", "abstract class AFoo{\n" + + " abstract boolean aaa();\n" + + " static class AFoo1 extends AFoo {\n" + + " @Override\n" + + " boolean aaa() {\n" + + " return false;\n" + + " }\n" + + " }\n" + + " static class AFoo3 extends AFoo {\n" + + " @Override\n" + + " boolean aaa() {\n" + + " return false;\n" + + " }\n" + + " }\n" + + " static class AFoo2 extends AFoo {\n" + + " @Override\n" + + " boolean aaa() {\n" + + " return false;\n" + + " }\n" + + " }\n" + + " \n" + + "}"); + PsiClass psiClass = + (PsiClass)TargetElementUtilBase.findTargetElement(myFixture.getEditor(), TargetElementUtilBase.getInstance().getAllAccepted()); + + assert psiClass != null; + final Collection classes = ClassInheritorsSearch.search(psiClass).findAll(); + List all = new ArrayList(); + all.add(psiClass); + all.addAll(classes); + final ImplementationViewComponent component = + new ImplementationViewComponent(all.toArray(new PsiElement[all.size()]), 0); + try { + final String[] visibleFiles = component.getVisibleFiles(); + Assert.assertArrayEquals(Arrays.toString(visibleFiles), + new String[]{"a.java (AFoo)", "a.java (AFoo1 in AFoo)", "a.java (AFoo3 in AFoo)", "a.java (AFoo2 in AFoo)"}, visibleFiles); + } + finally { + component.removeNotify(); + } + } + + public void testMethodsInInnerClasses() { + myFixture.configureByText("a.java", "abstract class AFoo{\n" + + " abstract boolean aaa();\n" + + " static class AFoo1 extends AFoo {\n" + + " @Override\n" + + " boolean aaa() {\n" + + " return false;\n" + + " }\n" + + " }\n" + + " static class AFoo3 extends AFoo {\n" + + " @Override\n" + + " boolean aaa() {\n" + + " return false;\n" + + " }\n" + + " }\n" + + " static class AFoo2 extends AFoo {\n" + + " @Override\n" + + " boolean aaa() {\n" + + " return false;\n" + + " }\n" + + " }\n" + + " \n" + + "}"); + PsiMethod psiMethod = + (PsiMethod)TargetElementUtilBase.findTargetElement(myFixture.getEditor(), TargetElementUtilBase.getInstance().getAllAccepted()); + + assert psiMethod != null; + final Collection methods = OverridingMethodsSearch.search(psiMethod).findAll(); + List all = new ArrayList(); + all.add(psiMethod); + all.addAll(methods); + + //make sure they are in predefined order + Collections.sort(all, new Comparator() { + @Override + public int compare(PsiMethod o1, PsiMethod o2) { + return o1.getContainingClass().getQualifiedName() + .compareTo(o2.getContainingClass().getQualifiedName()); + } + }); + final ImplementationViewComponent component = + new ImplementationViewComponent(all.toArray(new PsiElement[all.size()]), 0); + try { + final String[] visibleFiles = component.getVisibleFiles(); + Assert.assertArrayEquals(Arrays.toString(visibleFiles), + new String[]{"a.java (AFoo)", "a.java (AFoo1 in AFoo)", "a.java (AFoo2 in AFoo)", "a.java (AFoo3 in AFoo)"}, visibleFiles); + } + finally { + component.removeNotify(); + } + } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hint/ImplementationViewComponent.java b/platform/lang-impl/src/com/intellij/codeInsight/hint/ImplementationViewComponent.java index 17a108f1e1df..8ee54bd6b9f1 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hint/ImplementationViewComponent.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/hint/ImplementationViewComponent.java @@ -38,6 +38,7 @@ import com.intellij.openapi.util.IconLoader; import com.intellij.openapi.vcs.FileStatusManager; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; +import com.intellij.psi.presentation.java.SymbolPresentationUtil; import com.intellij.psi.util.PsiUtilBase; import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.SideBorder; @@ -49,6 +50,7 @@ import com.intellij.usages.UsageViewManager; import com.intellij.usages.UsageViewPresentation; import com.intellij.util.PairFunction; import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.TestOnly; import javax.swing.*; import javax.swing.border.CompoundBorder; @@ -57,7 +59,9 @@ import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; public class ImplementationViewComponent extends JPanel { @NonNls private static final String TEXT_PAGE_KEY = "Text"; @@ -96,7 +100,7 @@ public class ImplementationViewComponent extends JPanel { public FileDescriptor(PsiFile file, PsiElement element) { myFile = file; - myElementPresentation = element instanceof PsiNamedElement ? ((PsiNamedElement)element).getName() : null; + myElementPresentation = SymbolPresentationUtil.getSymbolPresentableText(element); } public String getPresentableName(VirtualFile vFile) { @@ -237,6 +241,17 @@ public class ImplementationViewComponent extends JPanel { }); } + @TestOnly + public String[] getVisibleFiles() { + final ComboBoxModel model = myFileChooser.getModel(); + String[] result = new String[model.getSize()]; + for (int i = 0; i < model.getSize(); i++) { + FileDescriptor o = (FileDescriptor)model.getElementAt(i); + result[i] = o.getPresentableName(o.myFile.getVirtualFile()); + } + return result; + } + public void update(final PsiElement[] elements, final int index) { update(elements, new PairFunction, Boolean>() { @Override @@ -296,11 +311,17 @@ public class ImplementationViewComponent extends JPanel { private static void update(final PsiElement[] elements, final PairFunction, Boolean> fun) { List candidates = new ArrayList(elements.length); List files = new ArrayList(elements.length); + final Set names = new HashSet(); + for (PsiElement element : elements) { + if (element instanceof PsiNamedElement) { + names.add(((PsiNamedElement)element).getName()); + } + } for (PsiElement element : elements) { PsiFile file = getContainingFile(element); if (file == null) continue; final PsiElement parent = element.getParent(); - files.add(new FileDescriptor(file, parent == file ? element : parent)); + files.add(new FileDescriptor(file, names.size() > 1 || parent == file ? element : parent)); candidates.add(element.getNavigationElement()); }