diff --git a/java/idea-ui/src/com/intellij/facet/impl/ui/libraries/DownloadingOptionsDialog.java b/java/idea-ui/src/com/intellij/facet/impl/ui/libraries/DownloadingOptionsDialog.java index f3b5ca2b3d2d..e168c86972f7 100644 --- a/java/idea-ui/src/com/intellij/facet/impl/ui/libraries/DownloadingOptionsDialog.java +++ b/java/idea-ui/src/com/intellij/facet/impl/ui/libraries/DownloadingOptionsDialog.java @@ -79,7 +79,7 @@ public class DownloadingOptionsDialog extends DialogWrapper { myVersionComboBox.setRenderer(new ListCellRendererWrapper(myVersionComboBox) { @Override public void customize(JList list, FrameworkLibraryVersion value, int index, boolean selected, boolean hasFocus) { - setText(value.getVersionString()); + setText(value.getName() + value.getVersionString()); } }); myVersionComboBox.setSelectedItem(settings.getVersion()); diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/fs/RootDescriptor.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/fs/RootDescriptor.java index 128742e9a438..4274bdd236c1 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/fs/RootDescriptor.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/fs/RootDescriptor.java @@ -22,4 +22,14 @@ public final class RootDescriptor { this.isTestRoot = isTestRoot; this.isGeneratedSources = isGenerated; } + + @Override + public String toString() { + return "RootDescriptor{" + + "module='" + module + '\'' + + ", root=" + root + + ", test=" + isTestRoot + + ", generated=" + isGeneratedSources + + '}'; + } } diff --git a/jps/jps-builders/src/org/jetbrains/jps/incremental/java/JavaBuilder.java b/jps/jps-builders/src/org/jetbrains/jps/incremental/java/JavaBuilder.java index d3b59be07ecb..556898df5c1a 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/incremental/java/JavaBuilder.java +++ b/jps/jps-builders/src/org/jetbrains/jps/incremental/java/JavaBuilder.java @@ -760,24 +760,16 @@ public class JavaBuilder extends ModuleLevelBuilder { } private static Map> buildOutputDirectoriesMap(CompileContext context, ModuleChunk chunk) { - final Map> map = new HashMap>(); + final Map> map = new LinkedHashMap>(); final boolean compilingTests = context.isCompilingTests(); for (Module module : chunk.getModules()) { - final String outputPath; - final Collection srcPaths; - if (compilingTests) { - outputPath = module.getTestOutputPath(); - srcPaths = module.getTestRoots(); + final Set roots = new LinkedHashSet(); + for (RootDescriptor descriptor : context.getModuleRoots(module)) { + if (descriptor.isTestRoot == compilingTests) { + roots.add(descriptor.root); + } } - else { - outputPath = module.getOutputPath(); - srcPaths = module.getSourceRoots(); - } - final Set roots = new HashSet(); - for (String path : srcPaths) { - roots.add(new File(path)); - } - map.put(new File(outputPath), roots); + map.put(new File(compilingTests ? module.getTestOutputPath() : module.getOutputPath()), roots); } return map; } diff --git a/platform/core-impl/src/com/intellij/lang/impl/PsiBuilderImpl.java b/platform/core-impl/src/com/intellij/lang/impl/PsiBuilderImpl.java index f30e882568e3..85fa31f325ff 100644 --- a/platform/core-impl/src/com/intellij/lang/impl/PsiBuilderImpl.java +++ b/platform/core-impl/src/com/intellij/lang/impl/PsiBuilderImpl.java @@ -809,24 +809,17 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder, AS @Override public final boolean eof() { - if (!markTokenTypeChecked()) { + if (!myTokenTypeChecked) { + myTokenTypeChecked = true; skipWhitespace(); } return myCurrentLexeme >= myLexemeCount; } - public boolean markTokenTypeChecked() { - if (!myTokenTypeChecked) { - myTokenTypeChecked = true; - return false; - } - return true; - } - @SuppressWarnings({"SuspiciousMethodCalls"}) private void rollbackTo(Marker marker) { myCurrentLexeme = ((StartMarker)marker).myLexemeIndex; - markTokenTypeChecked(); + myTokenTypeChecked = true; int idx = myProduction.lastIndexOf(marker); if (idx < 0) { LOG.error("The marker must be added before rolled back to."); @@ -1072,7 +1065,7 @@ public class PsiBuilderImpl extends UserDataHolderBase implements PsiBuilder, AS @NotNull private StartMarker prepareLightTree() { - markTokenTypeChecked(); + myTokenTypeChecked = true; balanceWhiteSpaces(); if (myProduction.isEmpty()) { diff --git a/platform/core-impl/src/com/intellij/util/indexing/FileContentImpl.java b/platform/core-impl/src/com/intellij/util/indexing/FileContentImpl.java index b081fb55e4bb..442fac8f5ee2 100644 --- a/platform/core-impl/src/com/intellij/util/indexing/FileContentImpl.java +++ b/platform/core-impl/src/com/intellij/util/indexing/FileContentImpl.java @@ -181,6 +181,7 @@ public final class FileContentImpl extends UserDataHolderBase implements FileCon if (myContentAsText == null) { if (myContent != null) { myContentAsText = LoadTextUtil.getTextByBinaryPresentation(myContent, myCharset); + myContent = null; // help gc, indices are expected to use bytes or chars but not both } } return myContentAsText; diff --git a/platform/lang-impl/src/com/intellij/conversion/DetachFacetConversionProcessor.java b/platform/lang-impl/src/com/intellij/conversion/DetachFacetConversionProcessor.java new file mode 100644 index 000000000000..d49b1d51b49f --- /dev/null +++ b/platform/lang-impl/src/com/intellij/conversion/DetachFacetConversionProcessor.java @@ -0,0 +1,62 @@ +/* + * Copyright 2000-2012 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.conversion; + +import com.intellij.facet.FacetManagerImpl; +import org.jdom.Element; +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +public class DetachFacetConversionProcessor extends ConversionProcessor{ + private String[] myFacetNames; + + public DetachFacetConversionProcessor(@NotNull String... names) { + myFacetNames = names; + } + + @Override + public boolean isConversionNeeded(ModuleSettings moduleSettings) { + for (String facetName : myFacetNames) { + if (facetName != null && !moduleSettings.getFacetElements(facetName).isEmpty()) { + return true; + } + } + return false; + } + + @Override + public void process(ModuleSettings moduleSettings) throws CannotConvertException { + final Element facetManagerElement = moduleSettings.getComponentElement(FacetManagerImpl.COMPONENT_NAME); + if (facetManagerElement == null) return; + + final Element[] facetElements = getChildren(facetManagerElement, FacetManagerImpl.FACET_ELEMENT); + for (Element facetElement : facetElements) { + final String facetType = facetElement.getAttributeValue(FacetManagerImpl.TYPE_ATTRIBUTE); + for (String facetName : myFacetNames) { + if (facetName.equals(facetType)) { + facetElement.detach(); + } + } + } + } + + private static Element[] getChildren(Element parent, final String name) { + final List children = parent.getChildren(name); + return children.toArray(new Element[children.size()]); + } +} diff --git a/platform/lang-impl/src/com/intellij/find/impl/FindInProjectUtil.java b/platform/lang-impl/src/com/intellij/find/impl/FindInProjectUtil.java index 771bd0e36846..b2168ac187e5 100644 --- a/platform/lang-impl/src/com/intellij/find/impl/FindInProjectUtil.java +++ b/platform/lang-impl/src/com/intellij/find/impl/FindInProjectUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2012 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. @@ -163,9 +163,13 @@ public class FindInProjectUtil { @NotNull public static List findUsages(@NotNull final FindModel findModel, final PsiDirectory psiDirectory, @NotNull final Project project) { + return findUsages(findModel, psiDirectory, project, true); + } + @NotNull + public static List findUsages(@NotNull final FindModel findModel, final PsiDirectory psiDirectory, @NotNull final Project project, boolean showWarnings) { final CommonProcessors.CollectProcessor collector = new CommonProcessors.CollectProcessor(); - findUsages(findModel, psiDirectory, project, collector); + findUsages(findModel, psiDirectory, project, collector, showWarnings); return new ArrayList(collector.getResults()); } @@ -202,6 +206,15 @@ public class FindInProjectUtil { final PsiDirectory psiDirectory, @NotNull final Project project, @NotNull final Processor consumer) { + findUsages(findModel, psiDirectory, project, consumer, true); + } + + + public static void findUsages(@NotNull final FindModel findModel, + final PsiDirectory psiDirectory, + @NotNull final Project project, + @NotNull final Processor consumer, + boolean showWarnings) { final ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); final Collection psiFiles = getFilesToSearchIn(findModel, project, psiDirectory); @@ -252,7 +265,7 @@ public class FindInProjectUtil { } } - if (!largeFiles.isEmpty()) { + if (showWarnings && !largeFiles.isEmpty()) { @Language("HTML") String message = ""; if (largeFiles.size() == 1) { diff --git a/platform/lang-impl/src/com/intellij/internal/ImageDuplicateResultsDialog.java b/platform/lang-impl/src/com/intellij/internal/ImageDuplicateResultsDialog.java new file mode 100644 index 000000000000..fdeb4b3c6cec --- /dev/null +++ b/platform/lang-impl/src/com/intellij/internal/ImageDuplicateResultsDialog.java @@ -0,0 +1,415 @@ +/* + * Copyright 2000-2012 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.internal; + +import com.intellij.codeInsight.documentation.DocumentationManager; +import com.intellij.codeInsight.hint.ImplementationViewComponent; +import com.intellij.ide.DataManager; +import com.intellij.ide.util.PropertiesComponent; +import com.intellij.ide.util.PropertyName; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.module.ModuleUtil; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.DialogWrapper; +import com.intellij.openapi.ui.popup.JBPopup; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiManager; +import com.intellij.ui.*; +import com.intellij.ui.awt.RelativePoint; +import com.intellij.ui.components.JBList; +import com.intellij.ui.components.JBScrollPane; +import com.intellij.ui.popup.NotLookupOrSearchCondition; +import com.intellij.ui.treeStructure.Tree; +import com.intellij.util.Function; +import com.intellij.util.NotNullFunction; +import com.intellij.util.PlatformIcons; +import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.tree.TreeUtil; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.TreeSelectionEvent; +import javax.swing.event.TreeSelectionListener; +import javax.swing.tree.DefaultMutableTreeNode; +import javax.swing.tree.DefaultTreeModel; +import javax.swing.tree.TreePath; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.io.File; +import java.util.*; +import java.util.List; + +/** + * @author Konstantin Bulenkov + */ +@SuppressWarnings("UseOfObsoleteCollectionType") +public class ImageDuplicateResultsDialog extends DialogWrapper { + private final Project myProject; + private final List myImages; + private final Map> myDuplicates; + private Tree myTree; + private ResourceModules myResourceModules = new ResourceModules(); + + + public ImageDuplicateResultsDialog(Project project, List images, Map> duplicates) { + super(project); + myProject = project; + myImages = images; + PropertiesComponent.getInstance(myProject).loadFields(myResourceModules); + myDuplicates = duplicates; + setModal(false); + myTree = new Tree(new MyRootNode()); + myTree.setRootVisible(true); + myTree.setCellRenderer(new MyCellRenderer()); + init(); + TreeUtil.expandAll(myTree); + setTitle("Image Duplicates"); + TreeUtil.selectFirstNode(myTree); + } + + @Override + protected Action[] createActions() { + final Action[] actions = new Action[4]; + actions[0] = new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + } + }; + actions[0].putValue(Action.NAME, "Fix all"); + actions[0].putValue(DEFAULT_ACTION, Boolean.TRUE); + actions[0].putValue(FOCUSED_ACTION, Boolean.TRUE); + actions[1] = new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + } + }; + actions[1].putValue(Action.NAME, "Fix selected"); + actions[2] = getCancelAction(); + actions[3] = getHelpAction(); + //return actions; + return new Action[0]; + } + + @Override + protected JComponent createCenterPanel() { + final JPanel panel = new JPanel(new BorderLayout()); + DataManager.registerDataProvider(panel, new DataProvider() { + @Override + public Object getData(@NonNls String dataId) { + final TreePath path = myTree.getSelectionPath(); + if (path != null) { + Object component = path.getLastPathComponent(); + VirtualFile file = null; + if (component instanceof MyFileNode) { + component = ((MyFileNode)component).getParent(); + } + if (component instanceof MyDuplicatesNode) { + file = ((MyDuplicatesNode)component).getUserObject().iterator().next(); + } + if (PlatformDataKeys.VIRTUAL_FILE.is(dataId)) { + return file; + } + if (PlatformDataKeys.VIRTUAL_FILE_ARRAY.is(dataId) && file != null) { + return new VirtualFile[]{file}; + } + } + return null; + } + }); + + final JBList list = new JBList(new ResourceModules().getModuleNames()); + final NotNullFunction modulesRenderer = new NotNullFunction() { + @NotNull + @Override + public JComponent fun(Object dom) { + return new JLabel(dom instanceof Module ? ((Module)dom).getName() : dom.toString(), PlatformIcons.SOURCE_FOLDERS_ICON, SwingConstants.LEFT); + } + }; + list.installCellRenderer(modulesRenderer); + final JPanel modulesPanel = ToolbarDecorator.createDecorator(list) + .setAddAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton button) { + final Module[] all = ModuleManager.getInstance(myProject).getModules(); + Arrays.sort(all, new Comparator() { + @Override + public int compare(Module o1, Module o2) { + return o1.getName().compareTo(o2.getName()); + } + }); + final JBList modules = new JBList(all); + modules.installCellRenderer(modulesRenderer); + JBPopupFactory.getInstance().createListPopupBuilder(modules) + .setTitle("Add Resource Module") + .setFilteringEnabled(new Function() { + @Override + public String fun(Object o) { + return ((Module)o).getName(); + } + }) + .setItemChoosenCallback(new Runnable() { + @Override + public void run() { + final Object value = modules.getSelectedValue(); + if (value instanceof Module && !myResourceModules.contains((Module)value)) { + myResourceModules.add((Module)value); + ((DefaultListModel)list.getModel()).addElement(((Module)value).getName()); + } + ((DefaultTreeModel)myTree.getModel()).reload(); + TreeUtil.expandAll(myTree); + } + }).createPopup().show(button.getPreferredPopupPoint()); + } + }) + .setRemoveAction(new AnActionButtonRunnable() { + @Override + public void run(AnActionButton button) { + final Object[] values = list.getSelectedValues(); + for (Object value : values) { + myResourceModules.remove((String)value); + ((DefaultListModel)list.getModel()).removeElement(value); + } + ((DefaultTreeModel)myTree.getModel()).reload(); + TreeUtil.expandAll(myTree); + } + }) + .disableDownAction() + .disableUpAction() + .createPanel(); + modulesPanel.setPreferredSize(new Dimension(-1, 60)); + final JPanel top = new JPanel(new BorderLayout()); + top.add(new JLabel("Image modules:"), BorderLayout.NORTH); + top.add(modulesPanel, BorderLayout.CENTER); + + panel.add(top, BorderLayout.NORTH); + panel.add(new JBScrollPane(myTree), BorderLayout.CENTER); + new AnAction() { + + @Override + public void actionPerformed(AnActionEvent e) { + VirtualFile file = getFileFromSelection(); + if (file != null) { + final PsiFile psiFile = PsiManager.getInstance(myProject).findFile(file); + if (psiFile != null) { + final ImplementationViewComponent viewComponent = new ImplementationViewComponent(new PsiElement[]{psiFile}, 0); + final TreeSelectionListener listener = new TreeSelectionListener() { + @Override + public void valueChanged(TreeSelectionEvent e) { + final VirtualFile selection = getFileFromSelection(); + if (selection != null) { + final PsiFile newElement = PsiManager.getInstance(myProject).findFile(selection); + if (newElement != null) { + viewComponent.update(new PsiElement[]{newElement}, 0); + } + } + } + }; + myTree.addTreeSelectionListener(listener); + + final JBPopup popup = + JBPopupFactory.getInstance().createComponentPopupBuilder(viewComponent, viewComponent.getPrefferedFocusableComponent()) + .setRequestFocusCondition(myProject, NotLookupOrSearchCondition.INSTANCE) + .setProject(myProject) + .setDimensionServiceKey(myProject, DocumentationManager.JAVADOC_LOCATION_AND_SIZE, false) + .setResizable(true) + .setMovable(true) + .setRequestFocus(false) + .setCancelCallback(new Computable() { + @Override + public Boolean compute() { + myTree.removeTreeSelectionListener(listener); + return true; + } + }) + .setTitle("Image Preview") + .createPopup(); + + + final Window window = ImageDuplicateResultsDialog.this.getWindow(); + popup.show(new RelativePoint(window, new Point(window.getWidth(), 0))); + viewComponent.setHint(popup, "Image Preview"); + } + } + } + }.registerCustomShortcutSet(CustomShortcutSet.fromString("ENTER"), panel); + + int total = 0; + for (Set set : myDuplicates.values()) total+=set.size(); + total-=myDuplicates.size(); + final JLabel label = new JLabel( + "Press Enter to preview image
Total images found: " + myImages.size() + ". Total duplicates found: " + total+""); + panel.add(label, BorderLayout.SOUTH); + return panel; + } + + @Override + protected String getDimensionServiceKey() { + return "image.duplicates.dialog"; + } + + @Override + public JComponent getPreferredFocusedComponent() { + return myTree; + } + + @Nullable + private VirtualFile getFileFromSelection() { + final TreePath path = myTree.getSelectionPath(); + if (path != null) { + Object component = path.getLastPathComponent(); + VirtualFile file = null; + if (component instanceof MyFileNode) { + component = ((MyFileNode)component).getParent(); + } + if (component instanceof MyDuplicatesNode) { + file = ((MyDuplicatesNode)component).getUserObject().iterator().next(); + } + return file; + } + return null; + } + + + private class MyRootNode extends DefaultMutableTreeNode { + private MyRootNode() { + final Vector vector = new Vector(); + for (Set files : myDuplicates.values()) { + vector.add(new MyDuplicatesNode(this, files)); + } + children = vector; + } + } + + + private class MyDuplicatesNode extends DefaultMutableTreeNode { + private final Set myFiles; + + public MyDuplicatesNode(DefaultMutableTreeNode node, Set files) { + super(files); + myFiles = files; + setParent(node); + final Vector vector = new Vector(); + for (VirtualFile file : files) { + vector.add(new MyFileNode(this, file)); + } + children = vector; + } + + @Override + public Set getUserObject() { + return (Set)super.getUserObject(); + } + } + + private static class MyFileNode extends DefaultMutableTreeNode { + public MyFileNode(DefaultMutableTreeNode node, VirtualFile file) { + super(file); + setParent(node); + } + + @Override + public VirtualFile getUserObject() { + return (VirtualFile)super.getUserObject(); + } + } + + private class MyCellRenderer extends ColoredTreeCellRenderer { + @Override + public void customizeCellRenderer(JTree tree, + Object value, + boolean selected, + boolean expanded, + boolean leaf, + int row, + boolean hasFocus) { + if (value instanceof MyFileNode) { + final VirtualFile file = ((MyFileNode)value).getUserObject(); + final Module module = ModuleUtil.findModuleForFile(file, myProject); + if (module != null) { + setIcon(PlatformIcons.CONTENT_ROOT_ICON_CLOSED); + append("[" + module.getName() + "] ", new SimpleTextAttributes(SimpleTextAttributes.STYLE_BOLD, UIUtil.getTreeForeground())); + append(getRelativePathToProject(myProject, file)); + } + else { + append(getRelativePathToProject(myProject, file)); + } + } + else if (value instanceof MyDuplicatesNode) { + final Set files = ((MyDuplicatesNode)value).getUserObject(); + for (VirtualFile file : files) { + final Module module = ModuleUtil.findModuleForFile(file, myProject); + + if (module != null && myResourceModules.contains(module)) { + append("Icons can be replaced to "); + append(getRelativePathToProject(myProject, file), + new SimpleTextAttributes(SimpleTextAttributes.STYLE_BOLD, ColorUtil.fromHex("008000"))); + return; + } + } + append("Icon conflict"); + } else if (value instanceof MyRootNode) { + append("All conflicts"); + } + } + } + + private static String getRelativePathToProject(Project project, VirtualFile file) { + final String path = project.getBasePath(); + assert path != null; + final String result = FileUtil.getRelativePath(path, file.getPath().replace('/', File.separatorChar), File.separatorChar); + assert result != null; + return result; + } + + + + static class ResourceModules { + @PropertyName(value = "resource.modules", defaultValue = "icons") + public String modules; + + public List getModuleNames() { + return Arrays.asList(StringUtil.splitByLines(modules == null ? "icons" : modules)); + } + + public boolean contains(Module module) { + return getModuleNames().contains(module.getName()); + } + + public void add(Module module) { + if (StringUtil.isEmpty(modules)) { + modules = module.getName(); + } else { + modules += "\n" + module.getName(); + } + } + + public void remove(String value) { + final List names = new ArrayList(getModuleNames()); + names.remove(value); + modules = StringUtil.join(names, "\n"); + } + } +} diff --git a/platform/lang-impl/src/com/intellij/internal/ShowImageDuplicatesAction.java b/platform/lang-impl/src/com/intellij/internal/ShowImageDuplicatesAction.java new file mode 100644 index 000000000000..6f0bc72d5305 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/internal/ShowImageDuplicatesAction.java @@ -0,0 +1,155 @@ +/* + * Copyright 2000-2012 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.internal; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.search.FilenameIndex; + +import java.io.InputStream; +import java.security.MessageDigest; +import java.util.*; + +/** + * @author Konstantin Bulenkov + */ +public class ShowImageDuplicatesAction extends AnAction { + //FileTypeManager.getInstance().getFileTypeByExtension("png").getAllPossibleExtensions() ? + private static final List IMAGE_EXTENSIONS = Arrays.asList("png", "jpg", "jpeg", "gif", "tiff", "bmp"); + + @Override + public void actionPerformed(AnActionEvent e) { + final Project project = getEventProject(e); + assert project != null; + ProgressManager.getInstance().runProcessWithProgressSynchronously(new Runnable() { + @Override + public void run() { + collectAndShowDuplicates(project); + } + }, "Gathering images", true, project); + } + + private static void collectAndShowDuplicates(final Project project) { + final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + if (indicator != null && !indicator.isCanceled()) { + indicator.setText("Collecting project images..."); + indicator.setIndeterminate(false); + final List images = new ArrayList(); + for (String ext : IMAGE_EXTENSIONS) { + images.addAll(FilenameIndex.getAllFilesByExt(project, ext)); + } + + final Map> duplicates = new HashMap>(); + final Map all = new HashMap(); + for (int i = 0; i < images.size(); i++) { + indicator.setFraction((double)(i + 1) / (double)images.size()); + final VirtualFile file = images.get(i); + if (!(file.getFileSystem() instanceof LocalFileSystem)) continue; + final long length = file.getLength(); + if (all.containsKey(length)) { + if (!duplicates.containsKey(length)) { + final HashSet files = new HashSet(); + files.add(all.get(length)); + duplicates.put(length, files); + } + duplicates.get(length).add(file); + } else { + all.put(length, file); + } + indicator.checkCanceled(); + } + showResults(project, images, duplicates, all); + } + } + + private static void showResults(final Project project, final List images, + Map> duplicates, + Map all) { + final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + if (indicator == null || indicator.isCanceled()) return; + indicator.setText("MD5 check"); + + int count = 0; + for (Set set : duplicates.values()) count+=set.size(); + final Map> realDuplicates = new HashMap>(); + int seek = 0; + for (Set files : duplicates.values()) { + for (VirtualFile file : files) { + seek++; + indicator.setFraction((double)seek / (double)count); + try { + final String md5 = getMD5Checksum(file.getInputStream()); + if (realDuplicates.containsKey(md5)) { + realDuplicates.get(md5).add(file); + } else { + final HashSet set = new HashSet(); + set.add(file); + realDuplicates.put(md5, set); + } + } + catch (Exception ignored) { + } + } + } + count = 0; + for (String key : new ArrayList(realDuplicates.keySet())) { + final int size = realDuplicates.get(key).size(); + if (size == 1) { + realDuplicates.remove(key); + } else { + count+=size; + } + } + + ApplicationManager.getApplication().invokeLater(new Runnable() { + @Override + public void run() { + new ImageDuplicateResultsDialog(project, images, realDuplicates).show(); + } + }); + + } + + @Override + public void update(AnActionEvent e) { + e.getPresentation().setEnabledAndVisible(getEventProject(e) != null); + } + + public static byte[] createChecksum(InputStream fis) throws Exception { + byte[] buffer = new byte[1024]; + MessageDigest md5 = MessageDigest.getInstance("MD5"); + int read; + + while ((read = fis.read(buffer)) > 0) md5.update(buffer, 0, read); + + fis.close(); + return md5.digest(); + } + + public static String getMD5Checksum(InputStream fis) throws Exception { + byte[] bytes = createChecksum(fis); + String md5 = ""; + + for (byte b : bytes) md5 += Integer.toString((b & 0xff) + 0x100, 16).substring(1); + return md5; + } +} diff --git a/platform/lang-impl/src/com/intellij/openapi/module/impl/scopes/LibraryRuntimeClasspathScope.java b/platform/lang-impl/src/com/intellij/openapi/module/impl/scopes/LibraryRuntimeClasspathScope.java index 00c16f26dfcb..d02c7bf96610 100644 --- a/platform/lang-impl/src/com/intellij/openapi/module/impl/scopes/LibraryRuntimeClasspathScope.java +++ b/platform/lang-impl/src/com/intellij/openapi/module/impl/scopes/LibraryRuntimeClasspathScope.java @@ -46,20 +46,40 @@ public class LibraryRuntimeClasspathScope extends GlobalSearchScope { myIndex = ProjectRootManager.getInstance(project).getFileIndex(); final Set processedSdk = new THashSet(); final Set processedLibraries = new THashSet(); - final Set processedModules = new THashSet(); - final Condition condition = new Condition() { - @Override - public boolean value(OrderEntry orderEntry) { - if (orderEntry instanceof ModuleOrderEntry) { - final Module module = ((ModuleOrderEntry)orderEntry).getModule(); - return module != null && processedModules.add(module); - } - return true; - } - }; - for (Module module : modules) { - buildEntries(module, processedModules, processedLibraries, processedSdk, condition); - } + + ProjectRootManager.getInstance(project).orderEntries(modules).recursively().process(new RootPolicy>() { + public LinkedHashSet visitLibraryOrderEntry(final LibraryOrderEntry libraryOrderEntry, + final LinkedHashSet value) { + final Library library = libraryOrderEntry.getLibrary(); + if (library != null && processedLibraries.add(library)) { + ContainerUtil.addAll(value, libraryOrderEntry.getRootFiles(OrderRootType.CLASSES)); + } + return value; + } + + public LinkedHashSet visitModuleSourceOrderEntry(final ModuleSourceOrderEntry moduleSourceOrderEntry, + final LinkedHashSet value) { + ContainerUtil.addAll(value, moduleSourceOrderEntry.getFiles(OrderRootType.SOURCES)); + return value; + } + + @Override + public LinkedHashSet visitModuleOrderEntry(ModuleOrderEntry moduleOrderEntry, LinkedHashSet value) { + final Module depModule = moduleOrderEntry.getModule(); + if (depModule != null) { + ContainerUtil.addAll(value, ModuleRootManager.getInstance(depModule).getSourceRoots()); + } + return value; + } + + public LinkedHashSet visitJdkOrderEntry(final JdkOrderEntry jdkOrderEntry, final LinkedHashSet value) { + final Sdk jdk = jdkOrderEntry.getJdk(); + if (jdk != null && processedSdk.add(jdk)) { + ContainerUtil.addAll(value, jdkOrderEntry.getRootFiles(OrderRootType.CLASSES)); + } + return value; + } + }, myEntries); } public LibraryRuntimeClasspathScope(Project project, LibraryOrderEntry entry) { @@ -80,48 +100,6 @@ public class LibraryRuntimeClasspathScope extends GlobalSearchScope { return that.myEntries.equals(myEntries); } - private void buildEntries(@NotNull final Module module, - @NotNull final Set processedModules, - @NotNull final Set processedLibraries, - @NotNull final Set processedSdk, - Condition condition) { - if (!processedModules.add(module)) return; - - ModuleRootManager.getInstance(module).orderEntries().recursively().satisfying(condition).process(new RootPolicy>() { - public LinkedHashSet visitLibraryOrderEntry(final LibraryOrderEntry libraryOrderEntry, - final LinkedHashSet value) { - final Library library = libraryOrderEntry.getLibrary(); - if (library != null && processedLibraries.add(library)) { - ContainerUtil.addAll(value, libraryOrderEntry.getRootFiles(OrderRootType.CLASSES)); - } - return value; - } - - public LinkedHashSet visitModuleSourceOrderEntry(final ModuleSourceOrderEntry moduleSourceOrderEntry, - final LinkedHashSet value) { - ContainerUtil.addAll(value, moduleSourceOrderEntry.getFiles(OrderRootType.SOURCES)); - return value; - } - - @Override - public LinkedHashSet visitModuleOrderEntry(ModuleOrderEntry moduleOrderEntry, LinkedHashSet value) { - final Module depModule = moduleOrderEntry.getModule(); - if (depModule != null) { - ContainerUtil.addAll(value, ModuleRootManager.getInstance(depModule).getSourceRoots()); - } - return value; - } - - public LinkedHashSet visitJdkOrderEntry(final JdkOrderEntry jdkOrderEntry, final LinkedHashSet value) { - final Sdk jdk = jdkOrderEntry.getJdk(); - if (jdk != null && processedSdk.add(jdk)) { - ContainerUtil.addAll(value, jdkOrderEntry.getRootFiles(OrderRootType.CLASSES)); - } - return value; - } - }, myEntries); - } - public boolean contains(VirtualFile file) { return myEntries.contains(getFileRoot(file)); } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReference.java b/platform/lang-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReference.java index 2f743d22866a..9f7bdf2d1759 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReference.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReference.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2012 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. @@ -107,20 +107,25 @@ public class FileReference implements FileReferenceOwner, PsiPolyVariantReferenc @NotNull protected Collection getContexts() { final FileReference contextRef = getContextReference(); + ArrayList result = new ArrayList(); + if (contextRef == null) { Collection defaultContexts = myFileReferenceSet.getDefaultContexts(); for (PsiFileSystemItem context : defaultContexts) { LOG.assertTrue(context != null, myFileReferenceSet.getClass() + " provided a null context"); } - return defaultContexts; - } - ResolveResult[] resolveResults = contextRef.multiResolve(false); - ArrayList result = new ArrayList(); - for (ResolveResult resolveResult : resolveResults) { - if (resolveResult.getElement() != null) { - result.add((PsiFileSystemItem)resolveResult.getElement()); + result.addAll(defaultContexts); + } else { + ResolveResult[] resolveResults = contextRef.multiResolve(false); + for (ResolveResult resolveResult : resolveResults) { + if (resolveResult.getElement() != null) { + result.add((PsiFileSystemItem)resolveResult.getElement()); + } } } + + result.addAll(myFileReferenceSet.getExtraContexts()); + return result; } diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReferenceSet.java b/platform/lang-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReferenceSet.java index c52befbdd650..2d73b9b02f82 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReferenceSet.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReferenceSet.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2012 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. @@ -120,6 +120,10 @@ public class FileReferenceSet { return "/"; } + protected Collection getExtraContexts() { + return Collections.emptyList(); + } + public static FileReferenceSet createSet(PsiElement element, final boolean soft, boolean endingSlashNotAllowed, diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReferenceUtil.java b/platform/lang-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReferenceUtil.java index f401c935a18e..e80ee0db5ca3 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReferenceUtil.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReferenceUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * Copyright 2000-2012 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. @@ -19,6 +19,7 @@ import com.intellij.openapi.paths.PsiDynaReference; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiReference; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** @@ -64,4 +65,19 @@ public class FileReferenceUtil { } return null; } + + @Nullable + public static FileReference findFileReference(@NotNull PsiElement element) { + final PsiReference[] references = element.getReferences(); + for (int i = references.length - 1; i >= 0; i--) { + PsiReference ref = references[i]; + if (ref instanceof PsiDynaReference) { + ref = ((PsiDynaReference)ref).getLastFileReference(); + } + if (ref instanceof FileReference) { + return (FileReference)references[i]; + } + } + return null; + } } diff --git a/platform/lang-impl/src/com/intellij/ui/tabs/FileColorSettingsTable.java b/platform/lang-impl/src/com/intellij/ui/tabs/FileColorSettingsTable.java index f984c4d09ccd..1fbddd66a95d 100644 --- a/platform/lang-impl/src/com/intellij/ui/tabs/FileColorSettingsTable.java +++ b/platform/lang-impl/src/com/intellij/ui/tabs/FileColorSettingsTable.java @@ -30,6 +30,7 @@ import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; import java.awt.*; +import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.EventObject; import java.util.List; @@ -83,6 +84,7 @@ public abstract class FileColorSettingsTable extends JBTable { @Override public boolean editCellAt(int row, int column, EventObject e) { + if (e instanceof MouseEvent && ((MouseEvent)e).getClickCount() == 1) return false; final Object at = getModel().getValueAt(row, column); final FileColorConfigurationEditDialog dialog = new FileColorConfigurationEditDialog(myManager, ((FileColorConfiguration)at)); dialog.getScopeComboBox().setEnabled(false); @@ -90,11 +92,6 @@ public abstract class FileColorSettingsTable extends JBTable { return false; } - @Override - public boolean isCellEditable(int row, int column) { - return column == 1; - } - public boolean isModified() { final List current = getModel().getConfigurations(); diff --git a/platform/platform-api/src/com/intellij/execution/util/ExecUtil.java b/platform/platform-api/src/com/intellij/execution/util/ExecUtil.java index 0c899705c85c..31545a49ca65 100644 --- a/platform/platform-api/src/com/intellij/execution/util/ExecUtil.java +++ b/platform/platform-api/src/com/intellij/execution/util/ExecUtil.java @@ -27,6 +27,7 @@ import org.jetbrains.annotations.Nullable; import java.io.*; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; @@ -166,6 +167,9 @@ public class ExecUtil { final String script = "do shell script \"" + scriptPath + "\" with administrator privileges"; return execAndGetOutput(Arrays.asList(getOsascriptPath(), "-e", script), workDir); } + else if ("root".equals(System.getenv("USER"))) { + return execAndGetOutput(Collections.singletonList(scriptPath), workDir); + } else if (hasKdeSudo.getValue()) { return execAndGetOutput(Arrays.asList("kdesudo", "--comment", prompt, scriptPath), workDir); } diff --git a/platform/platform-impl/src/com/intellij/ide/actions/CreateDesktopEntryAction.java b/platform/platform-impl/src/com/intellij/ide/actions/CreateDesktopEntryAction.java index d1e12d19103d..5a70803f7af5 100644 --- a/platform/platform-impl/src/com/intellij/ide/actions/CreateDesktopEntryAction.java +++ b/platform/platform-impl/src/com/intellij/ide/actions/CreateDesktopEntryAction.java @@ -40,7 +40,6 @@ import com.intellij.util.PlatformUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import javax.script.ScriptException; import javax.swing.*; import java.io.File; import java.io.IOException; @@ -54,7 +53,7 @@ public class CreateDesktopEntryAction extends DumbAwareAction { private static final int MIN_ICON_SIZE = 32; public static boolean isAvailable() { - return SystemInfo.hasXdgOpen(); + return SystemInfo.isUnix && SystemInfo.hasXdgOpen(); } @Override @@ -202,19 +201,20 @@ public class CreateDesktopEntryAction extends DumbAwareAction { return null; } - private static void install(final File entryFile, final boolean globalEntry) throws IOException, ExecutionException, InterruptedException, ScriptException { + private static void install(final File entryFile, final boolean globalEntry) throws IOException, ExecutionException, InterruptedException { try { - final int result; if (globalEntry) { final String source = "#!/bin/sh\n" + - "xdg-desktop-menu install --mode system \"" + entryFile.getAbsolutePath() + "\""; + "xdg-desktop-menu install --mode system \"" + entryFile.getAbsolutePath() + "\""; final File script = ExecUtil.createTempExecutableScript("sudo", ".sh", source); - result = ExecUtil.sudoAndGetResult(script.getAbsolutePath(), ApplicationBundle.message("desktop.entry.sudo.prompt")); + script.deleteOnExit(); + final int result = ExecUtil.sudoAndGetResult(script.getAbsolutePath(), ApplicationBundle.message("desktop.entry.sudo.prompt")); + if (result != 0) throw new RuntimeException("'" + script.getAbsolutePath() + "' : " + result); } else { - result = ExecUtil.execAndGetResult("xdg-desktop-menu", "install", "--mode", "user", entryFile.getAbsolutePath()); + final int result = ExecUtil.execAndGetResult("xdg-desktop-menu", "install", "--mode", "user", entryFile.getAbsolutePath()); + if (result != 0) throw new RuntimeException("'" + entryFile.getAbsolutePath() + "' : " + result); } - if (result != 0) throw new RuntimeException("'" + entryFile.getAbsolutePath() + "' : " + result); } finally { if (!entryFile.delete()) LOG.error("Failed to delete temp file '" + entryFile + "'"); diff --git a/platform/platform-resources/src/idea/PlatformActions.xml b/platform/platform-resources/src/idea/PlatformActions.xml index 37d3ab3a7f40..99d73a5abdb3 100644 --- a/platform/platform-resources/src/idea/PlatformActions.xml +++ b/platform/platform-resources/src/idea/PlatformActions.xml @@ -527,6 +527,7 @@ + diff --git a/platform/util/src/com/intellij/util/io/PagedFileStorage.java b/platform/util/src/com/intellij/util/io/PagedFileStorage.java index 5dfdb24670a1..88c13ce23b9e 100644 --- a/platform/util/src/com/intellij/util/io/PagedFileStorage.java +++ b/platform/util/src/com/intellij/util/io/PagedFileStorage.java @@ -20,6 +20,7 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.SystemInfo; import com.intellij.util.SystemProperties; import com.intellij.util.containers.hash.LinkedHashMap; +import jsr166e.SequenceLock; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -34,7 +35,6 @@ import java.util.Comparator; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.locks.ReentrantLock; /** * @author max @@ -42,7 +42,7 @@ import java.util.concurrent.locks.ReentrantLock; public class PagedFileStorage implements Forceable { protected static final Logger LOG = Logger.getInstance("#com.intellij.util.io.PagedFileStorage"); - private static final int MB = 1024 * 1024; + public static final int MB = 1024 * 1024; private final static int LOWER_LIMIT; private final static int UPPER_LIMIT; @@ -604,11 +604,11 @@ public class PagedFileStorage implements Forceable { } public static class StorageLockContext { - private final ReentrantLock myReentrantLock; + private final SequenceLock myReentrantLock; private final StorageLock myLock; public StorageLockContext(StorageLock lock) { - myReentrantLock = new ReentrantLock(); + myReentrantLock = new SequenceLock(); myLock = lock; } } diff --git a/platform/util/src/com/intellij/util/io/PersistentEnumeratorBase.java b/platform/util/src/com/intellij/util/io/PersistentEnumeratorBase.java index 3eaec39499d2..0bf80964eabc 100644 --- a/platform/util/src/com/intellij/util/io/PersistentEnumeratorBase.java +++ b/platform/util/src/com/intellij/util/io/PersistentEnumeratorBase.java @@ -45,6 +45,9 @@ abstract class PersistentEnumeratorBase implements Forceable, Closeable { protected static final int DATA_START = META_DATA_OFFSET + 16; protected final ResizeableMappedFile myStorage; + private final byte[] myKeyStoreFileBuffer; + private volatile int myKeyStoreFileLength; + private volatile int myKeyStoreBufferPosition; private final ResizeableMappedFile myKeyStorage; private boolean myClosed = false; @@ -211,10 +214,13 @@ abstract class PersistentEnumeratorBase implements Forceable, Closeable { if (myDataDescriptor instanceof InlineKeyDescriptor) { myKeyStorage = null; myKeyReadStream = null; + myKeyStoreFileBuffer = null; } else { - myKeyStorage = new ResizeableMappedFile(keystreamFile(), initialSize, myStorage.getPagedFileStorage().getStorageLockContext(), -1, false); + myKeyStorage = new ResizeableMappedFile(keystreamFile(), initialSize, myStorage.getPagedFileStorage().getStorageLockContext(), PagedFileStorage.MB, false); myKeyReadStream = new MyDataIS(myKeyStorage); + myKeyStoreFileLength = (int)myKeyStorage.length(); + myKeyStoreFileBuffer = new byte[initialSize]; } } @@ -367,13 +373,27 @@ abstract class PersistentEnumeratorBase implements Forceable, Closeable { try { markDirty(true); - final int dataOff = myKeyStorage != null ? (int)myKeyStorage.length() : ((InlineKeyDescriptor)myDataDescriptor).toInt(value); + final int dataOff = myKeyStorage != null ? myKeyStoreBufferPosition + myKeyStoreFileLength : ((InlineKeyDescriptor)myDataDescriptor).toInt(value); if (myKeyStorage != null) { final BufferExposingByteArrayOutputStream bos = new BufferExposingByteArrayOutputStream(); DataOutput out = new DataOutputStream(bos); myDataDescriptor.save(out, value); - myKeyStorage.put(dataOff, bos.getInternalBuffer(), 0, bos.size()); + final int size = bos.size(); + final byte[] buffer = bos.getInternalBuffer(); + + if (size > myKeyStoreFileBuffer.length) { + flushKeyStoreBuffer(); + myKeyStorage.put(dataOff, buffer, 0, size); + myKeyStoreFileLength += size; + } else { + if (size > myKeyStoreFileBuffer.length - myKeyStoreBufferPosition) { + flushKeyStoreBuffer(); + } + // myKeyStoreFileBuffer will contain complete records + System.arraycopy(buffer, 0, myKeyStoreFileBuffer, myKeyStoreBufferPosition, size); + myKeyStoreBufferPosition += size; + } } return setupValueId(hashCode, dataOff); @@ -383,6 +403,14 @@ abstract class PersistentEnumeratorBase implements Forceable, Closeable { } } + private void flushKeyStoreBuffer() throws IOException { + if (myKeyStoreBufferPosition > 0) { + myKeyStorage.put(myKeyStoreFileLength, myKeyStoreFileBuffer, 0, myKeyStoreBufferPosition); + myKeyStoreFileLength += myKeyStoreBufferPosition; + myKeyStoreBufferPosition = 0; + } + } + protected int setupValueId(int hashCode, int dataOff) { final byte[] buf = myRecordHandler.getRecordBuffer(this); myRecordHandler.setupRecord(this, hashCode, dataOff, buf); @@ -399,10 +427,11 @@ abstract class PersistentEnumeratorBase implements Forceable, Closeable { throw new UnsupportedOperationException("Iteration over InlineIntegerKeyDescriptors is not supported"); } + flushKeyStoreBuffer(); myKeyStorage.force(); DataInputStream keysStream = new DataInputStream(new BufferedInputStream(new LimitedInputStream(new FileInputStream(keystreamFile()), - (int)myKeyStorage.length()))); + myKeyStoreFileLength))); try { try { while (true) { @@ -435,7 +464,11 @@ abstract class PersistentEnumeratorBase implements Forceable, Closeable { if (myKeyReadStream == null) return ((InlineKeyDescriptor)myDataDescriptor).fromInt(addr); - myKeyReadStream.setup(addr, myKeyStorage.length()); + if (myKeyStoreFileLength <= addr) { + return myDataDescriptor.read(new DataInputStream(new UnsyncByteArrayInputStream(myKeyStoreFileBuffer, addr - myKeyStoreFileLength, myKeyStoreBufferPosition))); + } + // we do not need to flushKeyBuffer since we store complete records + myKeyReadStream.setup(addr, myKeyStoreFileLength); return myDataDescriptor.read(myKeyReadStream); } catch (IOException io) { @@ -501,6 +534,7 @@ abstract class PersistentEnumeratorBase implements Forceable, Closeable { protected void doClose() throws IOException { try { if (myKeyStorage != null) { + flushKeyStoreBuffer(); myKeyStorage.close(); } flush(); @@ -542,6 +576,7 @@ abstract class PersistentEnumeratorBase implements Forceable, Closeable { try { if (myKeyStorage != null) { + flushKeyStoreBuffer(); myKeyStorage.force(); } flush(); diff --git a/platform/util/src/com/intellij/util/io/PersistentStringEnumerator.java b/platform/util/src/com/intellij/util/io/PersistentStringEnumerator.java index 89c46894993b..c269ed7bd8e4 100644 --- a/platform/util/src/com/intellij/util/io/PersistentStringEnumerator.java +++ b/platform/util/src/com/intellij/util/io/PersistentStringEnumerator.java @@ -15,16 +15,22 @@ */ package com.intellij.util.io; -import com.intellij.util.containers.ConcurrentSLRUMap; +import com.intellij.util.containers.SLRUMap; +import jsr166e.SequenceLock; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; +import java.util.concurrent.locks.Lock; public class PersistentStringEnumerator extends PersistentEnumeratorDelegate{ - @Nullable private final ConcurrentSLRUMap myIdToStringCache; - @Nullable private final ConcurrentSLRUMap myHashcodeToIdCache; + private static final int STRIPE_POWER = 4; + private static final int STRIPE_COUNT = 1 << STRIPE_POWER; + private static final int STRIPE_MASK = STRIPE_COUNT - 1; + @Nullable private final SLRUMap[] myHashcodeToIdCache; + @Nullable private final SLRUMap[] myIdToStringCache; + @Nullable private final Lock[] myStripeLocks; public PersistentStringEnumerator(@NotNull final File file) throws IOException { this(file, 1024 * 4); @@ -41,50 +47,111 @@ public class PersistentStringEnumerator extends PersistentEnumeratorDelegate(8192, 8192); - myHashcodeToIdCache = new ConcurrentSLRUMap(8192, 8192); + myIdToStringCache = new SLRUMap[STRIPE_COUNT]; + myHashcodeToIdCache = new SLRUMap[STRIPE_COUNT]; + myStripeLocks = new Lock[STRIPE_COUNT]; + int protectedSize = 8192; + int probationalSize = 8192; + + for(int i = 0; i < STRIPE_COUNT; ++i) { + myHashcodeToIdCache[i] = new SLRUMap(protectedSize / STRIPE_COUNT, probationalSize / STRIPE_COUNT); + myIdToStringCache[i] = new SLRUMap(protectedSize / STRIPE_COUNT, probationalSize / STRIPE_COUNT); + myStripeLocks[i] = new SequenceLock(); + } } else { myIdToStringCache = null; myHashcodeToIdCache = null; + myStripeLocks = null; } } @Override public int enumerate(@Nullable String value) throws IOException { - int valueHashCode = -1; + int valueHashCode =-1; + int stripe = -1; if (myHashcodeToIdCache != null && value != null) { - Integer cachedId = myHashcodeToIdCache.get(valueHashCode = value.hashCode()); + valueHashCode = value.hashCode(); + stripe = Math.abs(valueHashCode) & STRIPE_MASK; + + Integer cachedId; + + myStripeLocks[stripe].lock(); + try { + cachedId = myHashcodeToIdCache[stripe].get(valueHashCode); + } + finally { + myStripeLocks[stripe].unlock(); + } + if (cachedId != null) { - String s = myIdToStringCache.get(cachedId); - if (s != null && value.equals(s)) return cachedId.intValue(); + int stripe2 = idStripe(cachedId.intValue()); + myStripeLocks[stripe2].lock(); + try { + String s = myIdToStringCache[stripe2].get(cachedId); + if (s != null && value.equals(s)) return cachedId.intValue(); + } + finally { + myStripeLocks[stripe2].unlock(); + } } } int enumerate = super.enumerate(value); - Integer enumeratedInteger = null; - if (myHashcodeToIdCache != null) { - enumeratedInteger = enumerate; - if (value != null) myHashcodeToIdCache.put(valueHashCode, enumeratedInteger); + if (stripe != -1) { + Integer enumeratedInteger; + + myStripeLocks[stripe].lock(); + try { + enumeratedInteger = enumerate; + myHashcodeToIdCache[stripe].put(valueHashCode, enumeratedInteger); + } finally { + myStripeLocks[stripe].unlock(); + } + + int stripe2 = idStripe(enumerate); + myStripeLocks[stripe2].lock(); + try { + myIdToStringCache[stripe2].put(enumeratedInteger, value); + } finally { + myStripeLocks[stripe2].unlock(); + } } - if (myIdToStringCache != null) { - myIdToStringCache.put(enumeratedInteger, value); - } return enumerate; } + private int idStripe(int h) { + h ^= (h >>> 20) ^ (h >>> 12); + return Math.abs(h ^ (h >>> 7) ^ (h >>> 4)) & STRIPE_MASK; + } + @Nullable @Override public String valueOf(int idx) throws IOException { + int stripe = -1; if (myIdToStringCache != null) { - String s = myIdToStringCache.get(idx); - if (s != null) return s; + stripe = idStripe(idx); + myStripeLocks[stripe].lock(); + try { + String s = myIdToStringCache[stripe].get(idx); + if (s != null) return s; + } + finally { + myStripeLocks[stripe].unlock(); + } } String s = super.valueOf(idx); - if (myIdToStringCache != null && s != null) { - myIdToStringCache.put(idx, s); + + if (stripe != -1 && s != null) { + myStripeLocks[stripe].lock(); + try { + myIdToStringCache[stripe].put(idx, s); + } + finally { + myStripeLocks[stripe].unlock(); + } } return s; } @@ -94,11 +161,12 @@ public class PersistentStringEnumerator extends PersistentEnumeratorDelegateExcept for the lack of support for specified fairness policies, + * or {@link Condition} objects, a SequenceLock can be used in the + * same way as {@link ReentrantLock}. It provides similar status and + * monitoring methods, such as {@link #isHeldByCurrentThread}. + * SequenceLocks may be preferable in contexts in which multiple + * threads invoke short read-only methods much more frequently than + * fully locked methods. + * + *

Methods {@code awaitAvailability} and {@code getSequence} can + * be used together to define (partially) optimistic read-only methods + * that are usually more efficient than ReadWriteLocks when they + * apply. These methods should in general be structured as loops that + * await lock availability, then read {@code volatile} fields into + * local variables (and may further read other values derived from + * these, for example the {@code length} of a {@code volatile} array), + * and retry if the sequence number changed while doing so. + * Alternatively, because {@code awaitAvailability} accommodates + * reentrancy, a method can retry a bounded number of times before + * switching to locking mode. While conceptually straightforward, + * expressing these ideas can be verbose. For example: + * + *

 {@code
+ * class Point {
+ *   private volatile double x, y;
+ *   private final SequenceLock sl = new SequenceLock();
+ *
+ *   // an exclusively locked method
+ *   void move(double deltaX, double deltaY) {
+ *     sl.lock();
+ *     try {
+ *       x += deltaX;
+ *       y += deltaY;
+ *     } finally {
+ *       sl.unlock();
+ *     }
+ *   }
+ *
+ *   // A read-only method
+ *   double distanceFromOriginV1() {
+ *     double currentX, currentY;
+ *     long seq;
+ *     do {
+ *       seq = sl.awaitAvailability();
+ *       currentX = x;
+ *       currentY = y;
+ *     } while (sl.getSequence() != seq); // retry if sequence changed
+ *     return Math.sqrt(currentX * currentX + currentY * currentY);
+ *   }
+ *
+ *   // Uses bounded retries before locking
+ *   double distanceFromOriginV2() {
+ *     double currentX, currentY;
+ *     long seq;
+ *     int retries = RETRIES_BEFORE_LOCKING; // for example 8
+ *     try {
+ *       do {
+ *         if (--retries < 0)
+ *           sl.lock();
+ *         seq = sl.awaitAvailability();
+ *         currentX = x;
+ *         currentY = y;
+ *       } while (sl.getSequence() != seq);
+ *     } finally {
+ *       if (retries < 0)
+ *         sl.unlock();
+ *     }
+ *     return Math.sqrt(currentX * currentX + currentY * currentY);
+ *   }
+ * }}
+ * + * @since 1.8 + * @author Doug Lea + */ +public class SequenceLock implements Lock, java.io.Serializable { + private static final long serialVersionUID = 7373984872572414699L; + + static final class Sync extends AbstractQueuedLongSynchronizer { + static final long serialVersionUID = 2540673546047039555L; + + /** + * The number of times to spin in lock() and awaitAvailability(). + */ + final int spins; + + /** + * The number of reentrant holds on this lock. Uses a long for + * compatibility with other AbstractQueuedLongSynchronizer + * operations. Accessed only by lock holder. + */ + long holds; + + Sync(int spins) { this.spins = spins; } + + // overrides of AQLS methods + + public final boolean isHeldExclusively() { + return (getState() & 1L) != 0L && + getExclusiveOwnerThread() == Thread.currentThread(); + } + + public final boolean tryAcquire(long acquires) { + long c = getState(); + if ((c & 1L) == 0L) { + if (compareAndSetState(c, c + 1L)) { + holds = acquires; + setExclusiveOwnerThread(Thread.currentThread()); + return true; + } + } + else if (Thread.currentThread() == getExclusiveOwnerThread()) { + holds += acquires; + return true; + } + return false; + } + + public final boolean tryRelease(long releases) { + if (Thread.currentThread() != getExclusiveOwnerThread()) + throw new IllegalMonitorStateException(); + if ((holds -= releases) == 0L) { + setExclusiveOwnerThread(null); + setState(getState() + 1L); + return true; + } + return false; + } + + public final long tryAcquireShared(long unused) { + return (((getState() & 1L) == 0L) ? 1L : + (getExclusiveOwnerThread() == Thread.currentThread()) ? 0L: + -1L); + } + + public final boolean tryReleaseShared(long unused) { + return (getState() & 1L) == 0L; + } + + public final Condition newCondition() { + throw new UnsupportedOperationException(); + } + + // Other methods in support of SequenceLock + + final long getSequence() { + return getState(); + } + + final void lock() { + int k = spins; + while (!tryAcquire(1L)) { + if (k == 0) { + acquire(1L); + break; + } + --k; + } + } + + final long awaitAvailability() { + long s; + while (((s = getState()) & 1L) != 0L && + getExclusiveOwnerThread() != Thread.currentThread()) { + acquireShared(1L); + releaseShared(1L); + } + return s; + } + + final long tryAwaitAvailability(long nanos) + throws InterruptedException, TimeoutException { + Thread current = Thread.currentThread(); + for (;;) { + long s = getState(); + if ((s & 1L) == 0L || getExclusiveOwnerThread() == current) { + releaseShared(1L); + return s; + } + if (!tryAcquireSharedNanos(1L, nanos)) + throw new TimeoutException(); + // since tryAcquireSharedNanos doesn't return seq + // retry with minimal wait time. + nanos = 1L; + } + } + + final boolean isLocked() { + return (getState() & 1L) != 0L; + } + + final Thread getOwner() { + return (getState() & 1L) == 0L ? null : getExclusiveOwnerThread(); + } + + final long getHoldCount() { + return isHeldExclusively() ? holds : 0; + } + + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException { + s.defaultReadObject(); + holds = 0L; + setState(0L); // reset to unlocked state + } + } + + private final Sync sync; + + /** + * The default spin value for constructor. Future versions of this + * class might choose platform-specific values. Currently, except + * on uniprocessors, it is set to a small value that overcomes near + * misses between releases and acquires. + */ + static final int DEFAULT_SPINS = + Runtime.getRuntime().availableProcessors() > 1 ? 64 : 0; + + /** + * Creates an instance of {@code SequenceLock} with the default + * number of retry attempts to acquire the lock before blocking. + */ + public SequenceLock() { sync = new Sync(DEFAULT_SPINS); } + + /** + * Creates an instance of {@code SequenceLock} that will retry + * attempts to acquire the lock at least the given number times + * before blocking. + */ + public SequenceLock(int spins) { sync = new Sync(spins); } + + /** + * Returns the current sequence number of this lock. The sequence + * number is advanced upon each acquire or release action. When + * this value is odd, the lock is held; when even, it is released. + * + * @return the current sequence number + */ + public long getSequence() { return sync.getSequence(); } + + /** + * Returns the current sequence number when the lock is, or + * becomes, available. A lock is available if it is either + * released, or is held by the current thread. If the lock is not + * available, the current thread becomes disabled for thread + * scheduling purposes and lies dormant until the lock has been + * released by some other thread. + * + * @return the current sequence number + */ + public long awaitAvailability() { return sync.awaitAvailability(); } + + /** + * Returns the current sequence number if the lock is, or + * becomes, available within the specified waiting time. + * + *

If the lock is not available, the current thread becomes + * disabled for thread scheduling purposes and lies dormant until + * one of three things happens: + * + *

    + * + *
  • The lock becomes available, in which case the current + * sequence number is returned. + * + *
  • Some other thread {@linkplain Thread#interrupt interrupts} + * the current thread, in which case this method throws + * {@link InterruptedException}. + * + *
  • The specified waiting time elapses, in which case + * this method throws {@link TimeoutException}. + * + *
+ * + * @param timeout the time to wait for availability + * @param unit the time unit of the timeout argument + * @return the current sequence number if the lock is available + * upon return from this method + * @throws InterruptedException if the current thread is interrupted + * @throws TimeoutException if the lock was not available within + * the specified waiting time + * @throws NullPointerException if the time unit is null + */ + public long tryAwaitAvailability(long timeout, TimeUnit unit) + throws InterruptedException, TimeoutException { + return sync.tryAwaitAvailability(unit.toNanos(timeout)); + } + + /** + * Acquires the lock. + * + *

If the current thread already holds this lock then the hold count + * is incremented by one and the method returns immediately without + * incrementing the sequence number. + * + *

If this lock not held by another thread, this method + * increments the sequence number (which thus becomes an odd + * number), sets the lock hold count to one, and returns + * immediately. + * + *

If the lock is held by another thread then the current + * thread may retry acquiring this lock, depending on the {@code + * spin} count established in constructor. If the lock is still + * not acquired, the current thread becomes disabled for thread + * scheduling purposes and lies dormant until enabled by + * some other thread releasing the lock. + */ + public void lock() { sync.lock(); } + + /** + * Acquires the lock unless the current thread is + * {@linkplain Thread#interrupt interrupted}. + * + *

If the current thread already holds this lock then the hold count + * is incremented by one and the method returns immediately without + * incrementing the sequence number. + * + *

If this lock not held by another thread, this method + * increments the sequence number (which thus becomes an odd + * number), sets the lock hold count to one, and returns + * immediately. + * + *

If the lock is held by another thread then the current + * thread may retry acquiring this lock, depending on the {@code + * spin} count established in constructor. If the lock is still + * not acquired, the current thread becomes disabled for thread + * scheduling purposes and lies dormant until one of two things + * happens: + * + *

    + * + *
  • The lock is acquired by the current thread; or + * + *
  • Some other thread {@linkplain Thread#interrupt interrupts} the + * current thread. + * + *
+ * + *

If the lock is acquired by the current thread then the lock hold + * count is set to one and the sequence number is incremented. + * + *

If the current thread: + * + *

    + * + *
  • has its interrupted status set on entry to this method; or + * + *
  • is {@linkplain Thread#interrupt interrupted} while acquiring + * the lock, + * + *
+ * + * then {@link InterruptedException} is thrown and the current thread's + * interrupted status is cleared. + * + *

In this implementation, as this method is an explicit + * interruption point, preference is given to responding to the + * interrupt over normal or reentrant acquisition of the lock. + * + * @throws InterruptedException if the current thread is interrupted + */ + public void lockInterruptibly() throws InterruptedException { + sync.acquireInterruptibly(1L); + } + + /** + * Acquires the lock only if it is not held by another thread at the time + * of invocation. + * + *

If the current thread already holds this lock then the hold + * count is incremented by one and the method returns {@code true} + * without incrementing the sequence number. + * + *

If this lock not held by another thread, this method + * increments the sequence number (which thus becomes an odd + * number), sets the lock hold count to one, and returns {@code + * true}. + * + *

If the lock is held by another thread then this method + * returns {@code false}. + * + * @return {@code true} if the lock was free and was acquired by the + * current thread, or the lock was already held by the current + * thread; and {@code false} otherwise + */ + public boolean tryLock() { return sync.tryAcquire(1L); } + + /** + * Acquires the lock if it is not held by another thread within the given + * waiting time and the current thread has not been + * {@linkplain Thread#interrupt interrupted}. + * + *

If the current thread already holds this lock then the hold count + * is incremented by one and the method returns immediately without + * incrementing the sequence number. + * + *

If this lock not held by another thread, this method + * increments the sequence number (which thus becomes an odd + * number), sets the lock hold count to one, and returns + * immediately. + * + *

If the lock is held by another thread then the current + * thread may retry acquiring this lock, depending on the {@code + * spin} count established in constructor. If the lock is still + * not acquired, the current thread becomes disabled for thread + * scheduling purposes and lies dormant until one of three things + * happens: + * + *

    + * + *
  • The lock is acquired by the current thread; or + * + *
  • Some other thread {@linkplain Thread#interrupt interrupts} + * the current thread; or + * + *
  • The specified waiting time elapses + * + *
+ * + *

If the lock is acquired then the value {@code true} is returned and + * the lock hold count is set to one. + * + *

If the current thread: + * + *

    + * + *
  • has its interrupted status set on entry to this method; or + * + *
  • is {@linkplain Thread#interrupt interrupted} while + * acquiring the lock, + * + *
+ * then {@link InterruptedException} is thrown and the current thread's + * interrupted status is cleared. + * + *

If the specified waiting time elapses then the value {@code false} + * is returned. If the time is less than or equal to zero, the method + * will not wait at all. + * + *

In this implementation, as this method is an explicit + * interruption point, preference is given to responding to the + * interrupt over normal or reentrant acquisition of the lock, and + * over reporting the elapse of the waiting time. + * + * @param timeout the time to wait for the lock + * @param unit the time unit of the timeout argument + * @return {@code true} if the lock was free and was acquired by the + * current thread, or the lock was already held by the current + * thread; and {@code false} if the waiting time elapsed before + * the lock could be acquired + * @throws InterruptedException if the current thread is interrupted + * @throws NullPointerException if the time unit is null + * + */ + public boolean tryLock(long timeout, TimeUnit unit) + throws InterruptedException { + return sync.tryAcquireNanos(1L, unit.toNanos(timeout)); + } + + /** + * Attempts to release this lock. + * + *

If the current thread is the holder of this lock then the + * hold count is decremented. If the hold count is now zero then + * the sequence number is incremented (thus becoming an even + * number) and the lock is released. If the current thread is not + * the holder of this lock then {@link + * IllegalMonitorStateException} is thrown. + * + * @throws IllegalMonitorStateException if the current thread does not + * hold this lock + */ + public void unlock() { sync.release(1); } + + /** + * Throws UnsupportedOperationException. SequenceLocks + * do not support Condition objects. + * + * @throws UnsupportedOperationException + */ + public Condition newCondition() { + throw new UnsupportedOperationException(); + } + + /** + * Queries the number of holds on this lock by the current thread. + * + *

A thread has a hold on a lock for each lock action that is not + * matched by an unlock action. + * + *

The hold count information is typically only used for testing and + * debugging purposes. + * + * @return the number of holds on this lock by the current thread, + * or zero if this lock is not held by the current thread + */ + public long getHoldCount() { return sync.getHoldCount(); } + + /** + * Queries if this lock is held by the current thread. + * + * @return {@code true} if current thread holds this lock and + * {@code false} otherwise + */ + public boolean isHeldByCurrentThread() { return sync.isHeldExclusively(); } + + /** + * Queries if this lock is held by any thread. This method is + * designed for use in monitoring of the system state, + * not for synchronization control. + * + * @return {@code true} if any thread holds this lock and + * {@code false} otherwise + */ + public boolean isLocked() { return sync.isLocked(); } + + /** + * Returns the thread that currently owns this lock, or + * {@code null} if not owned. When this method is called by a + * thread that is not the owner, the return value reflects a + * best-effort approximation of current lock status. For example, + * the owner may be momentarily {@code null} even if there are + * threads trying to acquire the lock but have not yet done so. + * This method is designed to facilitate construction of + * subclasses that provide more extensive lock monitoring + * facilities. + * + * @return the owner, or {@code null} if not owned + */ + protected Thread getOwner() { return sync.getOwner(); } + + /** + * Queries whether any threads are waiting to acquire this lock. Note that + * because cancellations may occur at any time, a {@code true} + * return does not guarantee that any other thread will ever + * acquire this lock. This method is designed primarily for use in + * monitoring of the system state. + * + * @return {@code true} if there may be other threads waiting to + * acquire the lock + */ + public final boolean hasQueuedThreads() { + return sync.hasQueuedThreads(); + } + + /** + * Queries whether the given thread is waiting to acquire this + * lock. Note that because cancellations may occur at any time, a + * {@code true} return does not guarantee that this thread + * will ever acquire this lock. This method is designed primarily for use + * in monitoring of the system state. + * + * @param thread the thread + * @return {@code true} if the given thread is queued waiting for this lock + * @throws NullPointerException if the thread is null + */ + public final boolean hasQueuedThread(Thread thread) { + return sync.isQueued(thread); + } + + /** + * Returns an estimate of the number of threads waiting to + * acquire this lock. The value is only an estimate because the number of + * threads may change dynamically while this method traverses + * internal data structures. This method is designed for use in + * monitoring of the system state, not for synchronization + * control. + * + * @return the estimated number of threads waiting for this lock + */ + public final int getQueueLength() { + return sync.getQueueLength(); + } + + /** + * Returns a collection containing threads that may be waiting to + * acquire this lock. Because the actual set of threads may change + * dynamically while constructing this result, the returned + * collection is only a best-effort estimate. The elements of the + * returned collection are in no particular order. This method is + * designed to facilitate construction of subclasses that provide + * more extensive monitoring facilities. + * + * @return the collection of threads + */ + protected Collection getQueuedThreads() { + return sync.getQueuedThreads(); + } + + /** + * Returns a string identifying this lock, as well as its lock state. + * The state, in brackets, includes either the String {@code "Unlocked"} + * or the String {@code "Locked by"} followed by the + * {@linkplain Thread#getName name} of the owning thread. + * + * @return a string identifying this lock, as well as its lock state + */ + public String toString() { + Thread o = sync.getOwner(); + return super.toString() + ((o == null) ? + "[Unlocked]" : + "[Locked by thread " + o.getName() + "]"); + } + +} \ No newline at end of file diff --git a/plugins/android/src/org/jetbrains/android/actions/AndroidProcessChooserDialog.form b/plugins/android/src/org/jetbrains/android/actions/AndroidProcessChooserDialog.form index 5ad418dcba3d..8bd923fac266 100644 --- a/plugins/android/src/org/jetbrains/android/actions/AndroidProcessChooserDialog.form +++ b/plugins/android/src/org/jetbrains/android/actions/AndroidProcessChooserDialog.form @@ -1,6 +1,6 @@

- + @@ -19,8 +19,8 @@ - - + + @@ -35,6 +35,14 @@ + + + + + + + + diff --git a/plugins/android/src/org/jetbrains/android/actions/AndroidProcessChooserDialog.java b/plugins/android/src/org/jetbrains/android/actions/AndroidProcessChooserDialog.java index ee6416b7e0e9..9e7fefd48914 100644 --- a/plugins/android/src/org/jetbrains/android/actions/AndroidProcessChooserDialog.java +++ b/plugins/android/src/org/jetbrains/android/actions/AndroidProcessChooserDialog.java @@ -39,6 +39,7 @@ import com.intellij.psi.XmlRecursiveElementVisitor; import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlElement; import com.intellij.ui.JBDefaultTreeCellRenderer; +import com.intellij.ui.components.JBCheckBox; import com.intellij.ui.content.Content; import com.intellij.ui.treeStructure.Tree; import com.intellij.util.NotNullFunction; @@ -63,10 +64,7 @@ import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import java.awt.*; -import java.awt.event.KeyAdapter; -import java.awt.event.KeyEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; +import java.awt.event.*; import java.util.Collection; import java.util.List; import java.util.Set; @@ -78,12 +76,14 @@ public class AndroidProcessChooserDialog extends DialogWrapper { private static final Logger LOG = Logger.getInstance("#org.jetbrains.android.actions.AndroidProcessChooserDialog"); @NonNls private static final String DEBUGGABLE_PROCESS_PROPERTY = "DEBUGGABLE_PROCESS"; + @NonNls private static final String SHOW_ALL_PROCESSES_PROPERTY = "SHOW_ALL_PROCESSES"; @NonNls private static final String DEBUGGABLE_DEVICE_PROPERTY = "DEBUGGABLE_DEVICE"; @NonNls private static final String RUN_CONFIGURATION_NAME_PATTERN = "Android Debugger (%s)"; private final Project myProject; private JPanel myContentPanel; private Tree myProcessTree; + private JBCheckBox myShowAllProcessesCheckBox; private final MergingUpdateQueue myUpdatesQueue; private final AndroidDebugBridge.IClientChangeListener myClientChangeListener; @@ -97,7 +97,11 @@ public class AndroidProcessChooserDialog extends DialogWrapper { myUpdatesQueue = new MergingUpdateQueue("AndroidProcessChooserDialogUpdatingQueue", 500, true, MergingUpdateQueue.ANY_COMPONENT, myProject); - doUpdateTree(); + final String showAllProcessesStr = PropertiesComponent.getInstance(project).getValue(SHOW_ALL_PROCESSES_PROPERTY); + final boolean showAllProcesses = Boolean.parseBoolean(showAllProcessesStr); + myShowAllProcessesCheckBox.setSelected(showAllProcesses); + + doUpdateTree(showAllProcesses); myClientChangeListener = new AndroidDebugBridge.IClientChangeListener() { @Override @@ -125,6 +129,13 @@ public class AndroidProcessChooserDialog extends DialogWrapper { }; AndroidDebugBridge.addDeviceChangeListener(myDeviceChangeListener); + myShowAllProcessesCheckBox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + updateTree(); + } + }); + myProcessTree.addTreeSelectionListener(new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent e) { @@ -219,6 +230,8 @@ public class AndroidProcessChooserDialog extends DialogWrapper { } private void updateTree() { + final boolean showAllProcesses = myShowAllProcessesCheckBox.isSelected(); + myUpdatesQueue.queue(new Update(AndroidProcessChooserDialog.this) { @Override public void run() { @@ -234,7 +247,7 @@ public class AndroidProcessChooserDialog extends DialogWrapper { return; } - doUpdateTree(); + doUpdateTree(showAllProcesses); } @Override @@ -244,7 +257,7 @@ public class AndroidProcessChooserDialog extends DialogWrapper { }); } - private void doUpdateTree() { + private void doUpdateTree(boolean showAllProcesses) { final AndroidDebugBridge debugBridge = AndroidSdkUtils.getDebugBridge(myProject); final DefaultMutableTreeNode root = new DefaultMutableTreeNode(); @@ -275,7 +288,8 @@ public class AndroidProcessChooserDialog extends DialogWrapper { for (Client client : device.getClients()) { final String clientDescription = getClientDescription(client); - if (clientDescription != null && processNames.contains(clientDescription)) { + if (clientDescription != null && + (showAllProcesses || isRelatedProcess(processNames, clientDescription))) { final DefaultMutableTreeNode clientNode = new DefaultMutableTreeNode(client); deviceNode.add(clientNode); @@ -314,6 +328,17 @@ public class AndroidProcessChooserDialog extends DialogWrapper { }); } + private boolean isRelatedProcess(Set processNames, String clientDescription) { + final String lc = clientDescription.toLowerCase(); + + for (String processName : processNames) { + if (lc.startsWith(processName)) { + return true; + } + } + return false; + } + @NotNull private static Set collectAllProcessNames(Project project) { final List facets = ProjectFacetManager.getInstance(project).getFacets(AndroidFacet.ID); @@ -325,7 +350,7 @@ public class AndroidProcessChooserDialog extends DialogWrapper { final String packageName = manifest.getPackage().getValue(); if (packageName != null) { - result.add(packageName); + result.add(packageName.toLowerCase()); } final XmlElement xmlElement = manifest.getXmlElement(); @@ -352,7 +377,7 @@ public class AndroidProcessChooserDialog extends DialogWrapper { final String value = attribute.getValue(); if (value != null) { - result.add(value); + result.add(value.toLowerCase()); } } } @@ -382,6 +407,7 @@ public class AndroidProcessChooserDialog extends DialogWrapper { properties.setValue(DEBUGGABLE_DEVICE_PROPERTY, getPresentableName(selectedDevice)); properties.setValue(DEBUGGABLE_PROCESS_PROPERTY, selectedClient.getClientData().getClientDescription()); + properties.setValue(SHOW_ALL_PROCESSES_PROPERTY, Boolean.toString(myShowAllProcessesCheckBox.isSelected())); final String debugPort = Integer.toString(selectedClient.getDebuggerListenPort()); diff --git a/plugins/devkit/resources/META-INF/plugin.xml b/plugins/devkit/resources/META-INF/plugin.xml index 1a57d95de8a0..aa5791180304 100644 --- a/plugins/devkit/resources/META-INF/plugin.xml +++ b/plugins/devkit/resources/META-INF/plugin.xml @@ -75,6 +75,8 @@ + + diff --git a/plugins/devkit/src/references/IconsReferencesContributor.java b/plugins/devkit/src/references/IconsReferencesContributor.java new file mode 100644 index 000000000000..f7f98e86afcb --- /dev/null +++ b/plugins/devkit/src/references/IconsReferencesContributor.java @@ -0,0 +1,157 @@ +/* + * Copyright 2000-2012 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.idea.devkit.references; + +import com.intellij.find.FindModel; +import com.intellij.find.impl.FindInProjectUtil; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.module.ModuleUtil; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ModuleRootManager; +import com.intellij.openapi.util.IconLoader; +import com.intellij.openapi.util.ProperTextRange; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.patterns.*; +import com.intellij.psi.*; +import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReference; +import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReferenceSet; +import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReferenceUtil; +import com.intellij.psi.search.searches.ReferencesSearch; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.usageView.UsageInfo; +import com.intellij.util.ProcessingContext; +import com.intellij.util.Processor; +import com.intellij.util.QueryExecutor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static com.intellij.patterns.PsiJavaPatterns.*; + +/** + * @author Konstantin Bulenkov + */ +public class IconsReferencesContributor extends PsiReferenceContributor implements QueryExecutor { + @Override + public void registerReferenceProviders(PsiReferenceRegistrar registrar) { + final StringPattern methodName = string().oneOf("findIcon", "getIcon"); + final PsiMethodPattern method = psiMethod().withName(methodName).definedInClass(IconLoader.class.getName()); + final PsiJavaElementPattern.Capture javaFile + = literalExpression().and(psiExpression().methodCallParameter(0, method)); + + final XmlAttributeValuePattern pluginXml = XmlPatterns.xmlAttributeValue().withLocalName("icon"); + + registrar.registerReferenceProvider(or(javaFile, pluginXml), new PsiReferenceProvider() { + @NotNull + @Override + public PsiReference[] getReferencesByElement(@NotNull final PsiElement element, @NotNull ProcessingContext context) { + if (!isIdeaProject(element.getProject())) return PsiReference.EMPTY_ARRAY; + return new FileReferenceSet(element) { + @Override + protected Collection getExtraContexts() { + final Module icons = ModuleManager.getInstance(element.getProject()).findModuleByName("icons"); + if (icons != null) { + final ArrayList result = new ArrayList(); + final VirtualFile[] roots = ModuleRootManager.getInstance(icons).getSourceRoots(); + final PsiManager psiManager = element.getManager(); + for (VirtualFile root : roots) { + final PsiDirectory directory = psiManager.findDirectory(root); + if (directory != null) { + result.add(directory); + } + } + return result; + } + return super.getExtraContexts(); + } + }.getAllReferences(); + } + }); + } + + public static boolean isIdeaProject(@Nullable Project project) { + final VirtualFile baseDir; + return project != null + && ("IDEA".equals(project.getName()) || "community".equals(project.getName())) + && (baseDir = project.getBaseDir()) != null + && baseDir.findFileByRelativePath("plugins") != null; + } + + @Override + public boolean execute(@NotNull ReferencesSearch.SearchParameters queryParameters, @NotNull Processor consumer) { + final PsiElement file = queryParameters.getElementToSearch(); + if (file instanceof PsiBinaryFile) { + final Module module = ModuleUtil.findModuleForPsiElement(file); + final VirtualFile image = ((PsiBinaryFile)file).getVirtualFile(); + if (isImage(image) && isIconsModule(module)) { + final Project project = file.getProject(); + final FindModel model = new FindModel(); + final String path = getPathToImage(image, module); + if (path == null) return true; + model.setStringToFind(path); + model.setCaseSensitive(true); + model.setFindAll(true); + final List usages = FindInProjectUtil.findUsages(model, FindInProjectUtil.getPsiDirectory(model, project), project, false); + if (!usages.isEmpty()) { + for (UsageInfo usage : usages) { + final PsiElement element = usage.getElement(); + + final ProperTextRange textRange = usage.getRangeInElement(); + if (element != null && textRange != null) { + final PsiElement start = element.findElementAt(textRange.getStartOffset()); + final PsiElement end = element.findElementAt(textRange.getEndOffset()); + if (start != null && end != null) { + PsiElement value = PsiTreeUtil.findCommonParent(start, end); + if (value instanceof PsiJavaToken) { + value = value.getParent(); + } + if (value != null) { + final FileReference reference = FileReferenceUtil.findFileReference(value); + if (reference != null) { + consumer.process(reference); + } + } + } + } + } + } + } + } + return true; + } + + @Nullable + private static String getPathToImage(VirtualFile image, Module module) { + final String path = ModuleRootManager.getInstance(module).getSourceRoots()[0].getPath(); + return "/" + FileUtil.getRelativePath(path, image.getPath(), '/'); + } + + private static boolean isIconsModule(Module module) { + return module != null && "icons".equals(module.getName()) + && ModuleRootManager.getInstance(module).getSourceRoots().length == 1; + } + + private static boolean isImage(VirtualFile image) { + final FileTypeManager mgr = FileTypeManager.getInstance(); + return image != null && mgr.getFileTypeByFile(image) == mgr.getFileTypeByExtension("png"); + } +} diff --git a/plugins/groovy/rt/src/org/jetbrains/jps/incremental/groovy/GroovyBuilder.java b/plugins/groovy/rt/src/org/jetbrains/jps/incremental/groovy/GroovyBuilder.java index 0f775b2ec4f4..4c9c20e16ae9 100644 --- a/plugins/groovy/rt/src/org/jetbrains/jps/incremental/groovy/GroovyBuilder.java +++ b/plugins/groovy/rt/src/org/jetbrains/jps/incremental/groovy/GroovyBuilder.java @@ -128,7 +128,7 @@ public class GroovyBuilder extends ModuleLevelBuilder { private Map getGenerationOutputs(ModuleChunk chunk, Map finalOutputs) throws IOException { Map generationOutputs = new HashMap(); for (Module module : chunk.getModules()) { - generationOutputs.put(module, myForStubs ? FileUtil.createTempDirectory("groovyStubs", module.getName()).getPath() : finalOutputs.get(module)); + generationOutputs.put(module, myForStubs ? FileUtil.createTempDirectory("groovyStubs", "__" + module.getName()).getPath() : finalOutputs.get(module)); } return generationOutputs; } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/config/GroovyModuleConverter.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/config/GroovyModuleConverter.java index 727e74b3a941..d9e8d439ea7c 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/config/GroovyModuleConverter.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/config/GroovyModuleConverter.java @@ -16,33 +16,26 @@ package org.jetbrains.plugins.groovy.config; import com.intellij.conversion.CannotConvertException; -import com.intellij.conversion.ConversionProcessor; +import com.intellij.conversion.DetachFacetConversionProcessor; import com.intellij.conversion.ModuleSettings; -import com.intellij.facet.FacetManagerImpl; import com.intellij.openapi.module.StdModuleTypes; -import org.jdom.Element; - -import java.util.List; /** * @author peter */ -public class GroovyModuleConverter extends ConversionProcessor { +public class GroovyModuleConverter extends DetachFacetConversionProcessor { + + public GroovyModuleConverter() { + super("Grails", "Groovy"); + } + @Override public boolean isConversionNeeded(ModuleSettings moduleSettings) { if ("GRAILS_MODULE".equals(moduleSettings.getModuleType())) { return true; } - if (!moduleSettings.getFacetElements("Grails").isEmpty()) { - return true; - } - - if (!moduleSettings.getFacetElements("Groovy").isEmpty()) { - return true; - } - - return false; + return super.isConversionNeeded(moduleSettings); } @Override @@ -51,20 +44,6 @@ public class GroovyModuleConverter extends ConversionProcessor { moduleSettings.setModuleType(StdModuleTypes.JAVA.getId()); } - final Element facetManagerElement = moduleSettings.getComponentElement(FacetManagerImpl.COMPONENT_NAME); - if (facetManagerElement == null) return; - - final Element[] facetElements = getChildren(facetManagerElement, FacetManagerImpl.FACET_ELEMENT); - for (Element facetElement : facetElements) { - final String facetType = facetElement.getAttributeValue(FacetManagerImpl.TYPE_ATTRIBUTE); - if ("Grails".equals(facetType) || "Groovy".equals(facetType)) { - facetElement.detach(); - } - } - } - - private static Element[] getChildren(Element parent, final String name) { - final List children = parent.getChildren(name); - return children.toArray(new Element[children.size()]); + super.process(moduleSettings); } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/ControlFlowBuilderUtil.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/ControlFlowBuilderUtil.java index c8c6fe28610e..c5c80a0471d3 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/ControlFlowBuilderUtil.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/ControlFlowBuilderUtil.java @@ -66,7 +66,7 @@ public class ControlFlowBuilderUtil { int[] invpostorder = invPostorder(postorder); findReadsBeforeWrites(flow, definitelyAssigned, result, namesIndex, postorder, invpostorder); - + if (result.size() == 0) return ReadWriteVariableInstruction.EMPTY_ARRAY; return result.toArray(new ReadWriteVariableInstruction[result.size()]); } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/InstanceOfInstruction.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/InstanceOfInstruction.java index d0b95af4ba4f..a8e3951c8fef 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/InstanceOfInstruction.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/InstanceOfInstruction.java @@ -23,31 +23,28 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrExpres import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrInstanceOfExpression; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrReferenceExpression; import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement; +import org.jetbrains.plugins.groovy.lang.psi.controlFlow.impl.ConditionInstruction; import org.jetbrains.plugins.groovy.lang.psi.controlFlow.impl.InstructionImpl; /** * @author peter */ public class InstanceOfInstruction extends InstructionImpl implements MixinTypeInstruction { - private final boolean myNegate; + private final ConditionInstruction myCondition; - public InstanceOfInstruction(int num, GrExpression assertion, boolean negate) { + public InstanceOfInstruction(int num, GrExpression assertion, ConditionInstruction cond) { super(assertion, num); - myNegate = negate; - } - - public boolean isNegate() { - return myNegate; + myCondition = cond; } protected String getElementPresentation() { - return "instanceof: " + (myNegate ? "! " : "") + getElement().getText(); + return "instanceof: " + getElement().getText(); } @Nullable private GrInstanceOfExpression getApplicableInstanceof() { final PsiElement element = getElement(); - if (element instanceof GrInstanceOfExpression && !isNegate()) { + if (element instanceof GrInstanceOfExpression) { GrExpression operand = ((GrInstanceOfExpression)element).getOperand(); final GrTypeElement typeElement = ((GrInstanceOfExpression)element).getTypeElement(); if (operand instanceof GrReferenceExpression && ((GrReferenceExpression)operand).getQualifier() == null && typeElement != null) { @@ -85,4 +82,9 @@ public class InstanceOfInstruction extends InstructionImpl implements MixinTypeI return instanceOf.getOperand().getText(); } + + @Override + public ConditionInstruction getConditionInstruction() { + return myCondition; + } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/Instruction.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/Instruction.java index 5af9de47668e..83d18548353a 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/Instruction.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/Instruction.java @@ -30,6 +30,9 @@ public interface Instruction { int num(); + @Nullable + NegatingGotoInstruction getNegatingGotoInstruction(); + @Nullable PsiElement getElement(); diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/MixinTypeInstruction.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/MixinTypeInstruction.java index 8a8ce7e0e715..00b1c54eaca0 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/MixinTypeInstruction.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/MixinTypeInstruction.java @@ -17,6 +17,7 @@ package org.jetbrains.plugins.groovy.lang.psi.controlFlow; import com.intellij.psi.PsiType; import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.groovy.lang.psi.controlFlow.impl.ConditionInstruction; /** * @author Max Medvedev @@ -30,4 +31,7 @@ public interface MixinTypeInstruction extends Instruction { @Nullable String getVariableName(); + + @Nullable + ConditionInstruction getConditionInstruction(); } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/NegatingGotoInstruction.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/NegatingGotoInstruction.java new file mode 100644 index 000000000000..488987c2d739 --- /dev/null +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/NegatingGotoInstruction.java @@ -0,0 +1,44 @@ +/* + * Copyright 2000-2012 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.plugins.groovy.lang.psi.controlFlow; + +import com.intellij.psi.PsiElement; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.groovy.lang.psi.controlFlow.impl.ConditionInstruction; +import org.jetbrains.plugins.groovy.lang.psi.controlFlow.impl.InstructionImpl; + +/** + * @author Max Medvedev + */ +public class NegatingGotoInstruction extends InstructionImpl { + @NotNull private final ConditionInstruction myCondition; + + public NegatingGotoInstruction(@Nullable PsiElement element, int num, @NotNull ConditionInstruction condition) { + super(element, num); + myCondition = condition; + } + + @NotNull + public ConditionInstruction getCondition() { + return myCondition; + } + + @Override + protected String getElementPresentation() { + return " Negating goto instruction, condition=" + myCondition.num(); + } +} diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/ReadWriteVariableInstruction.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/ReadWriteVariableInstruction.java index e68eddd8fa56..37cb2f018c6f 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/ReadWriteVariableInstruction.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/ReadWriteVariableInstruction.java @@ -23,6 +23,8 @@ import org.jetbrains.plugins.groovy.lang.psi.controlFlow.impl.InstructionImpl; * @author ven */ public class ReadWriteVariableInstruction extends InstructionImpl { + public static final ReadWriteVariableInstruction[] EMPTY_ARRAY = new ReadWriteVariableInstruction[0]; + public static final int WRITE = -1; public static final int READ = 1; diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/ArgumentInstruction.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/ArgumentInstruction.java index c2d189dbe67c..44551b5cef4c 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/ArgumentInstruction.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/ArgumentInstruction.java @@ -100,6 +100,11 @@ public class ArgumentInstruction extends InstructionImpl implements MixinTypeIns return ((GrReferenceExpression)getElement()).getReferenceName(); } + @Override + public ConditionInstruction getConditionInstruction() { + return null; + } + @Override protected String getElementPresentation() { return "ARGUMENT " + super.getElementPresentation(); diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/ConditionInstruction.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/ConditionInstruction.java new file mode 100644 index 000000000000..5e6ea5dbbc2f --- /dev/null +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/ConditionInstruction.java @@ -0,0 +1,48 @@ +/* + * Copyright 2000-2012 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.plugins.groovy.lang.psi.controlFlow.impl; + +import com.intellij.psi.PsiElement; +import com.intellij.util.containers.HashSet; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.plugins.groovy.lang.psi.controlFlow.Instruction; + +import java.util.Set; + +/** + * @author Max Medvedev + */ +public class ConditionInstruction extends InstructionImpl implements Instruction { + private final Set myDependent = new HashSet(); + + public ConditionInstruction(@NotNull PsiElement element, int num) { + super(element, num); + myDependent.add(this); + } + + @Override + protected String getElementPresentation() { + return "Condition"; + } + + void addDependent(ConditionInstruction i) { + myDependent.add(i); + } + + public Set getDependentConditions() { + return myDependent; + } +} diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/ControlFlowBuilder.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/ControlFlowBuilder.java index 12bb6627500b..5724e15ccae4 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/ControlFlowBuilder.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/ControlFlowBuilder.java @@ -23,7 +23,6 @@ import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.containers.hash.HashSet; import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils; -import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes; import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase; import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; import org.jetbrains.plugins.groovy.lang.psi.GroovyRecursiveElementVisitor; @@ -31,7 +30,6 @@ import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrCondition; import org.jetbrains.plugins.groovy.lang.psi.api.statements.*; import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList; import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; -import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrCodeBlock; import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrOpenBlock; import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.*; import org.jetbrains.plugins.groovy.lang.psi.api.statements.clauses.GrCaseSection; @@ -53,6 +51,7 @@ import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; import java.util.*; +import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.*; import static org.jetbrains.plugins.groovy.lang.psi.controlFlow.ReadWriteVariableInstruction.READ; import static org.jetbrains.plugins.groovy.lang.psi.controlFlow.ReadWriteVariableInstruction.WRITE; @@ -83,6 +82,17 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { */ private Deque myCaughtExceptionInfos; + /** + * stack of current conditions + */ + private Deque myConditions; + + + /** + * stack of negating instructions + */ + private Deque myNegatingStack; + /** * count of finally blocks surrounding current statement */ @@ -92,8 +102,6 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { * last visited node */ private InstructionImpl myHead; - private boolean myNegate; - private boolean myAssertionsOnly; private GroovyPsiElement myLastInScope; /** @@ -150,6 +158,9 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { myInstructions = new ArrayList(); myProcessingStack = new ArrayDeque(); myCaughtExceptionInfos = new ArrayDeque(); + myConditions = new ArrayDeque(); + myNegatingStack = new ArrayDeque(); + myFinallyCount = 0; myPending = new ArrayList>(); myInstructionNumber = 0; @@ -193,27 +204,7 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { addNode(new ReadWriteVariableInstruction(parameter.getName(), parameter, myInstructionNumber++, WRITE)); } - final Set names = new LinkedHashSet(); - - closure.accept(new GroovyRecursiveElementVisitor() { - public void visitReferenceExpression(GrReferenceExpression refExpr) { - super.visitReferenceExpression(refExpr); - if (refExpr.getQualifierExpression() == null && !PsiUtil.isLValue(refExpr)) { - if (!(refExpr.getParent() instanceof GrCall)) { - final String refName = refExpr.getReferenceName(); - if (!hasDeclaredVariable(refName, closure, refExpr)) { - //names.add(refName); - } - } - } - } - }); - - names.add("owner"); - - for (String name : names) { - addNode(new ReadWriteVariableInstruction(name, closure.getLBrace(), myInstructionNumber++, WRITE)); - } + addNode(new ReadWriteVariableInstruction("owner", closure.getLBrace(), myInstructionNumber++, WRITE)); PsiElement child = closure.getFirstChild(); while (child != null) { @@ -229,17 +220,20 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { } } - private void addNode(InstructionImpl instruction) { + private T addNode(T instruction) { myInstructions.add(instruction); + instruction.setNegating(myNegatingStack.peek()); if (myHead != null) { addEdge(myHead, instruction); } myHead = instruction; + return instruction; } - private void addNodeAndCheckPending(InstructionImpl i) { + private T addNodeAndCheckPending(T i) { addNode(i); checkPending(i); + return i; } private static void addEdge(InstructionImpl begin, InstructionImpl end) { @@ -309,6 +303,7 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { public void visitAssertStatement(GrAssertStatement assertStatement) { final GrExpression assertion = assertStatement.getAssertion(); if (assertion != null) { + myConditions.push(addNodeAndCheckPending(new ConditionInstruction(assertion, myInstructionNumber++))); assertion.accept(this); final InstructionImpl assertInstruction = startNode(assertStatement); GrExpression errorMessage = assertStatement.getErrorMessage(); @@ -378,7 +373,7 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { public void visitAssignmentExpression(GrAssignmentExpression expression) { GrExpression lValue = expression.getLValue(); - if (expression.getOperationToken() != GroovyTokenTypes.mASSIGN) { + if (expression.getOperationToken() != mASSIGN) { if (lValue instanceof GrReferenceExpression) { String referenceName = ((GrReferenceExpression)lValue).getReferenceName(); if (referenceName != null) { @@ -404,22 +399,29 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { public void visitUnaryExpression(GrUnaryExpression expression) { final GrExpression operand = expression.getOperand(); if (operand != null) { - final boolean negation = expression.getOperationTokenType() == GroovyTokenTypes.mLNOT; - if (negation) { - myNegate = !myNegate; + ConditionInstruction cond = null; + if (expression.getOperationTokenType() == mLNOT) { + cond = new ConditionInstruction(expression, myInstructionNumber++); + addNodeAndCheckPending(cond); + registerCondition(cond); } operand.accept(this); - if (negation) { - myNegate = !myNegate; - } visitCall(expression); + + if (cond != null) { + myConditions.removeFirstOccurrence(cond); + myNegatingStack.push(addNodeAndCheckPending(new NegatingGotoInstruction(expression, myInstructionNumber++, cond))); + } } } @Override public void visitInstanceofExpression(GrInstanceOfExpression expression) { expression.getOperand().accept(this); - addNode(new InstanceOfInstruction(myInstructionNumber++, expression, myNegate)); + final ConditionInstruction cond = myConditions.peek(); + if (cond != null) { + addNode(new InstanceOfInstruction(myInstructionNumber++, expression, cond)); + } } public void visitReferenceExpression(GrReferenceExpression refExpr) { @@ -428,14 +430,14 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { String name = refExpr.getReferenceName(); if (name == null) return; - if (ControlFlowUtils.isIncOrDecOperand(refExpr) && !myAssertionsOnly) { + if (ControlFlowUtils.isIncOrDecOperand(refExpr)) { final InstructionImpl i = new ReadWriteVariableInstruction(name, refExpr, myInstructionNumber++, READ); addNodeAndCheckPending(i); addNode(new ReadWriteVariableInstruction(name, refExpr, myInstructionNumber++, WRITE)); } else { - boolean isWrite = !myAssertionsOnly && PsiUtil.isLValue(refExpr); - addNodeAndCheckPending(new ReadWriteVariableInstruction(name, refExpr, myInstructionNumber++, isWrite ? WRITE : READ)); + final int type = PsiUtil.isLValue(refExpr) ? WRITE : READ; + addNodeAndCheckPending(new ReadWriteVariableInstruction(name, refExpr, myInstructionNumber++, type)); if (refExpr.getParent() instanceof GrArgumentList && refExpr.getParent().getParent() instanceof GrCall) { addNodeAndCheckPending(new ArgumentInstruction(refExpr, myInstructionNumber++)); } @@ -476,23 +478,41 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { final GrExpression right = expression.getRightOperand(); final IElementType opType = expression.getOperationTokenType(); - InstructionImpl start = myHead; + final ConditionInstruction cond; + if (opType == mLOR || opType == mLAND) { + cond = addNodeAndCheckPending(new ConditionInstruction(expression, myInstructionNumber++)); + registerCondition(cond); + } + else { + cond = null; + } left.accept(this); - if (right != null) { - if (opType == GroovyTokenTypes.mLOR) { - addPendingEdge(expression, myHead); - myHead = start; + if (cond != null) { + myConditions.removeFirstOccurrence(cond); + } - myNegate = !myNegate; - left.accept(this); - myNegate = !myNegate; + NegatingGotoInstruction first = null; + if (right != null) { + if (cond != null) { + final InstructionImpl head = myHead; + if (opType == mLAND) { + first = addNodeAndCheckPending(new NegatingGotoInstruction(expression, myInstructionNumber++, cond)); + } + addPendingEdge(expression, myHead); + myHead = head; + if (opType == mLOR) { + myNegatingStack.push(addNodeAndCheckPending(new NegatingGotoInstruction(expression, myInstructionNumber++, cond))); + } } right.accept(this); } visitCall(expression); + if (first != null) { + myNegatingStack.push(first); + } } /** @@ -518,39 +538,65 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { public void visitIfStatement(GrIfStatement ifStatement) { InstructionImpl ifInstruction = startNode(ifStatement); - final GrCondition condition = ifStatement.getCondition(); final InstructionImpl head = myHead; + + final GrCondition condition = ifStatement.getCondition(); final GrStatement thenBranch = ifStatement.getThenBranch(); + final GrStatement elseBranch = ifStatement.getElseBranch(); + + InstructionImpl conditionEnd = null; InstructionImpl thenEnd = null; - if (thenBranch != null) { - if (condition != null) { - condition.accept(this); + InstructionImpl elseEnd = null; + + List pendingNegations = new ArrayList(); + ConditionInstruction conditionStart = null; + if (condition != null) { + final List> oldPending = myPending; + myPending = new ArrayList>(); + + conditionStart = addNodeAndCheckPending(new ConditionInstruction(condition, myInstructionNumber++)); + registerCondition(conditionStart); + condition.accept(this); + conditionEnd = myHead; + + List> pendingFromCondition = myPending; + myPending = oldPending; + for (Pair pair : pendingFromCondition) { + if (pair.first instanceof NegatingGotoInstruction) { + pendingNegations.add((NegatingGotoInstruction)pair.first); + } + else { + addPendingEdge(pair.second, pair.first); + } } + } + + if (thenBranch != null) { thenBranch.accept(this); handlePossibleReturn(thenBranch); thenEnd = myHead; + interruptFlow(); } - myHead = head; - final GrStatement elseBranch = ifStatement.getElseBranch(); - InstructionImpl elseEnd = null; - if (elseBranch != null) { - if (condition != null) { - myNegate = !myNegate; - final boolean old = myAssertionsOnly; - myAssertionsOnly = true; - condition.accept(this); - myNegate = !myNegate; - myAssertionsOnly = old; - } + if (condition != null) { + myHead = conditionEnd; + myNegatingStack.push(addNode(new NegatingGotoInstruction(condition, myInstructionNumber++, conditionStart))); + } + else { + myHead = head; + } + for (NegatingGotoInstruction negation : pendingNegations) { + assert condition != null; + addPendingEdge(condition, negation); + } + if (elseBranch != null) { elseBranch.accept(this); handlePossibleReturn(elseBranch); elseEnd = myHead; } - if (thenBranch != null || elseBranch != null) { final InstructionImpl end = new IfEndInstruction(ifStatement, myInstructionNumber++); addNode(end); @@ -560,6 +606,13 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { finishNode(ifInstruction); } + private void registerCondition(ConditionInstruction conditionStart) { + for (ConditionInstruction condition : myConditions) { + condition.addDependent(conditionStart); + } + myConditions.push(conditionStart); + } + public void visitForStatement(GrForStatement forStatement) { final GrForClause clause = forStatement.getClause(); if (clause instanceof GrTraditionalForClause) { @@ -963,31 +1016,4 @@ public class ControlFlowBuilder extends GroovyRecursiveElementVisitor { return null; } - private static boolean hasDeclaredVariable(String name, GrClosableBlock scope, PsiElement place) { - PsiElement prev = null; - while (place != null) { - if (place instanceof GrCodeBlock) { - GrStatement[] statements = ((GrCodeBlock)place).getStatements(); - for (GrStatement statement : statements) { - if (statement == prev) break; - if (statement instanceof GrVariableDeclaration) { - GrVariable[] variables = ((GrVariableDeclaration)statement).getVariables(); - for (GrVariable variable : variables) { - if (name.equals(variable.getName())) return true; - } - } - } - } - - if (place == scope) { - break; - } - else { - prev = place; - place = place.getParent(); - } - } - - return false; - } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/InstructionImpl.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/InstructionImpl.java index 25cc79aa38a2..e81218b67a98 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/InstructionImpl.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/controlFlow/impl/InstructionImpl.java @@ -20,6 +20,7 @@ import org.jetbrains.annotations.Nullable; import org.jetbrains.plugins.groovy.lang.psi.controlFlow.CallEnvironment; import org.jetbrains.plugins.groovy.lang.psi.controlFlow.CallInstruction; import org.jetbrains.plugins.groovy.lang.psi.controlFlow.Instruction; +import org.jetbrains.plugins.groovy.lang.psi.controlFlow.NegatingGotoInstruction; import java.util.Deque; import java.util.LinkedHashSet; @@ -33,6 +34,7 @@ public class InstructionImpl implements Instruction { PsiElement myPsiElement; private final int myNumber; + private NegatingGotoInstruction myNegating; @Nullable public PsiElement getElement() { @@ -91,6 +93,11 @@ public class InstructionImpl implements Instruction { return myNumber; } + @Override + public NegatingGotoInstruction getNegatingGotoInstruction() { + return myNegating; + } + public void addSuccessor(InstructionImpl instruction) { mySuccessors.add(instruction); } @@ -98,4 +105,8 @@ public class InstructionImpl implements Instruction { public void addPredecessor(InstructionImpl instruction) { myPredecessors.add(instruction); } + + void setNegating(NegatingGotoInstruction negating) { + myNegating = negating; + } } diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/DFAType.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/DFAType.java new file mode 100644 index 000000000000..eebf8bf17ad5 --- /dev/null +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/dataFlow/DFAType.java @@ -0,0 +1,154 @@ +/* + * Copyright 2000-2012 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.plugins.groovy.lang.psi.dataFlow; + +import com.intellij.psi.PsiIntersectionType; +import com.intellij.psi.PsiManager; +import com.intellij.psi.PsiType; +import com.intellij.psi.util.TypeConversionUtil; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.plugins.groovy.lang.psi.controlFlow.NegatingGotoInstruction; +import org.jetbrains.plugins.groovy.lang.psi.controlFlow.impl.ConditionInstruction; +import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil; + +import java.util.ArrayList; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; + +/** + * @author Max Medvedev + */ +public class DFAType { + private static class Mixin { + private final int ID; + + private PsiType myType; + private ConditionInstruction myCondition; + private boolean myNegated; + + private Mixin(PsiType type, ConditionInstruction condition, boolean negated) { + this(-1, type, condition, negated); + } + + private Mixin(int ID, PsiType type, ConditionInstruction condition, boolean negated) { + if (ID == -1) ID = hashCode(); + this.ID = ID; + myType = type; + myCondition = condition; + myNegated = negated; + } + + Mixin negate() { + return new Mixin(ID, myType, myCondition, !myNegated); + } + } + + private final PsiType primary; + + private final List mixins = new ArrayList(); + + private DFAType(@Nullable PsiType primary) { + this.primary = primary; + } + + public void addMixin(@Nullable PsiType mixin, ConditionInstruction instruction) { + mixins.add(new Mixin(mixin, instruction, false)); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof DFAType)) return false; + + final DFAType other = (DFAType)obj; + + if (!eq(primary, other.primary)) return false; + + if (mixins.size() != other.mixins.size()) return false; + for (Mixin mixin1 : mixins) { + boolean contains = false; + for (Mixin mixin2 : other.mixins) { + if (mixin1.ID == mixin2.ID) { + contains = mixin1.myNegated == mixin2.myNegated; + break; + } + } + if (!contains) return false; + } + + return true; + } + + public DFAType negate(NegatingGotoInstruction negating) { + final DFAType type = new DFAType(primary); + + for (Mixin mixin : mixins) { + type.mixins.add(mixin); + } + + for (; negating != null; negating = negating.getNegatingGotoInstruction()) { + final Set conditionsToNegate = negating.getCondition().getDependentConditions(); + + for (ListIterator iterator = type.mixins.listIterator(); iterator.hasNext(); ) { + Mixin mixin = iterator.next(); + if (conditionsToNegate.contains(mixin.myCondition)) { + iterator.set(mixin.negate()); + } + } + } + return type; + } + + @Nullable + public PsiType getType() { + if (mixins.size() == 0) return primary; + + List types = new ArrayList(); + if (primary != null) { + types.add(primary); + } + for (Mixin mixin : mixins) { + if (!mixin.myNegated) { + types.add(mixin.myType); + } + } + if (types.size() == 0) return null; + return PsiIntersectionType.createIntersection(types.toArray(new PsiType[types.size()])); + } + + public static DFAType create(@Nullable PsiType type) { + return new DFAType(type); + } + + private static boolean eq(PsiType t1, PsiType t2) { + return !TypeConversionUtil.erasure(t1).equals(TypeConversionUtil.erasure(t2)); + } + + @Nullable + public static DFAType create(DFAType t1, DFAType t2, PsiManager manager) { + final PsiType primary = TypesUtil.getLeastUpperBoundNullable(t1.primary, t2.primary, manager); + final DFAType type = new DFAType(primary); + + for (Mixin mixin1 : t1.mixins) { + for (Mixin mixin2 : t2.mixins) { + if (mixin1.ID == mixin2.ID && mixin1.myNegated == mixin2.myNegated) { + type.mixins.add(mixin1); + } + } + } + return type; + } +} diff --git a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/TypeInferenceHelper.java b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/TypeInferenceHelper.java index ac7d9bc23a9a..e0ccf7386a24 100644 --- a/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/TypeInferenceHelper.java +++ b/plugins/groovy/src/org/jetbrains/plugins/groovy/lang/psi/impl/TypeInferenceHelper.java @@ -21,7 +21,6 @@ import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.RecursionManager; import com.intellij.openapi.util.TextRange; import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiIntersectionType; import com.intellij.psi.PsiType; import com.intellij.psi.util.*; import gnu.trove.TIntHashSet; @@ -35,12 +34,10 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable; import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.*; import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter; import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement; -import org.jetbrains.plugins.groovy.lang.psi.controlFlow.InstanceOfInstruction; -import org.jetbrains.plugins.groovy.lang.psi.controlFlow.Instruction; -import org.jetbrains.plugins.groovy.lang.psi.controlFlow.MixinTypeInstruction; -import org.jetbrains.plugins.groovy.lang.psi.controlFlow.ReadWriteVariableInstruction; +import org.jetbrains.plugins.groovy.lang.psi.controlFlow.*; import org.jetbrains.plugins.groovy.lang.psi.controlFlow.impl.ArgumentInstruction; import org.jetbrains.plugins.groovy.lang.psi.dataFlow.DFAEngine; +import org.jetbrains.plugins.groovy.lang.psi.dataFlow.DFAType; import org.jetbrains.plugins.groovy.lang.psi.dataFlow.reachingDefs.ReachingDefinitionsDfaInstance; import org.jetbrains.plugins.groovy.lang.psi.dataFlow.reachingDefs.ReachingDefinitionsSemilattice; import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil; @@ -74,7 +71,9 @@ public class TypeInferenceHelper { return getInitializerType(refExpr); } - return getInferredType(refExpr.getReferenceName(), instruction, flow, scope); + final DFAType type = getInferredType(refExpr.getReferenceName(), instruction, flow, scope); + if (type == null) return null; + return type.getType(); } }); @@ -89,11 +88,12 @@ public class TypeInferenceHelper { Instruction instruction = findInstructionAt(place, flow); if (instruction == null) return null; - return getInferredType(variableName, instruction, flow, scope); + final DFAType type = getInferredType(variableName, instruction, flow, scope); + return type != null ? type.getType() : null; } public static boolean isTooComplexTooAnalyze(GrControlFlowOwner scope) { - return getDefUseMaps(scope).second == null; + return getDefUseMaps(scope) == null; } @Nullable @@ -134,7 +134,7 @@ public class TypeInferenceHelper { } @Nullable - private static PsiType getInferredType(@NotNull String varName, @NotNull Instruction instruction, @NotNull Instruction[] flow, @NotNull GrControlFlowOwner scope) { + private static DFAType getInferredType(@NotNull String varName, @NotNull Instruction instruction, @NotNull Instruction[] flow, @NotNull GrControlFlowOwner scope) { final Pair>> pair = getDefUseMaps(scope); List> dfaResult = pair.second; @@ -146,12 +146,17 @@ public class TypeInferenceHelper { final TIntHashSet varDefs = allDefs.get(varIndex); if (varDefs == null) return null; - PsiType result = null; + DFAType result = null; for (int defIndex : varDefs.toArray()) { - PsiType defType = getDefinitionType(flow[defIndex], flow, scope); + DFAType defType = getDefinitionType(flow[defIndex], flow, scope); + + final NegatingGotoInstruction negating = instruction.getNegatingGotoInstruction(); if (defType != null) { - defType = TypesUtil.boxPrimitiveType(defType, scope.getManager(), scope.getResolveScope()); - result = result == null ? defType : TypesUtil.getLeastUpperBound(result, defType, scope.getManager()); + defType = defType.negate(negating); + } + + if (defType != null) { + result = result == null ? defType : DFAType.create(defType, result, scope.getManager()); } } return result; @@ -165,10 +170,10 @@ public class TypeInferenceHelper { final ReachingDefinitionsDfaInstance dfaInstance = new ReachingDefinitionsDfaInstance(flow) { @Override public void fun(TIntObjectHashMap m, Instruction instruction) { - if (instruction instanceof InstanceOfInstruction) { //todo assertions are not defs, they just add to type intersection and don't overwrite it completely + if (instruction instanceof InstanceOfInstruction) { final InstanceOfInstruction instanceOfInstruction = (InstanceOfInstruction)instruction; final PsiElement element = instanceOfInstruction.getElement(); - if (element instanceof GrInstanceOfExpression && !instanceOfInstruction.isNegate()) { + if (element instanceof GrInstanceOfExpression) { final GrExpression operand = ((GrInstanceOfExpression)element).getOperand(); final GrTypeElement typeElement = ((GrInstanceOfExpression)element).getTypeElement(); if (typeElement != null) { @@ -197,11 +202,11 @@ public class TypeInferenceHelper { } @Nullable - private static PsiType getDefinitionType(Instruction instruction, Instruction[] flow, GrControlFlowOwner scope) { + private static DFAType getDefinitionType(Instruction instruction, Instruction[] flow, GrControlFlowOwner scope) { if (instruction instanceof ReadWriteVariableInstruction && ((ReadWriteVariableInstruction) instruction).isWrite()) { final PsiElement element = instruction.getElement(); if (element != null) { - return getInitializerType(element); + return DFAType.create(TypesUtil.boxPrimitiveType(getInitializerType(element), scope.getManager(), scope.getResolveScope())); } } if (instruction instanceof MixinTypeInstruction) { @@ -211,21 +216,24 @@ public class TypeInferenceHelper { } @Nullable - private static PsiType mixinType(final MixinTypeInstruction instruction, final Instruction[] flow, final GrControlFlowOwner scope) { - return RecursionManager.doPreventingRecursion(instruction, false, new NullableComputable() { + private static DFAType mixinType(final MixinTypeInstruction instruction, final Instruction[] flow, final GrControlFlowOwner scope) { + return RecursionManager.doPreventingRecursion(instruction, false, new NullableComputable() { @Override @Nullable - public PsiType compute() { + public DFAType compute() { String varName = instruction.getVariableName(); if (varName == null) return null; ReadWriteVariableInstruction originalInstr = instruction.getInstructionToMixin(flow); LOG.assertTrue(originalInstr != null, scope.getContainingFile().getName() + ":" + scope.getText()); - final PsiType original = getInferredType(varName, originalInstr, flow, scope); + + DFAType original = getInferredType(varName, originalInstr, flow, scope); final PsiType mixin = instruction.inferMixinType(); if (mixin == null) return original; - if (original == null) return mixin; - if (TypesUtil.isAssignableByMethodCallConversion(mixin, original, scope)) return original; - return PsiIntersectionType.createIntersection(mixin, original); + if (original == null) { + original = DFAType.create(null); + } + original.addMixin(mixin, instruction.getConditionInstruction()); + return original; } }); } @@ -291,4 +299,32 @@ public class TypeInferenceHelper { return null; } + + /*@Nullable + private static PsiType getInferredType(@NotNull String varName, + @NotNull Instruction instruction, + @NotNull GrControlFlowOwner scope) { + final ArrayList> dfaResult = getDefUseMaps(scope); + + if (dfaResult == null) return null; + + + final Map allDefs = dfaResult.get(instruction.num()); + final DFAType dfaType = allDefs.get(varName); + if (dfaType == null) return null; + + return dfaType.getType(); + } + + private static ArrayList> getDefUseMaps(final GrControlFlowOwner scope) { + return CachedValuesManager.getManager(scope.getProject()).getCachedValue(scope, new CachedValueProvider>>() { + @Override + public Result>> compute() { + final Instruction[] flow = scope.getControlFlow(); + final DFAEngine> engine = new DFAEngine>(flow, new TypeDFAInstance(), new TypesSemilattice(scope.getManager())); + final ArrayList> result = engine.performDFAWithTimeout(); + return Result.create(result, PsiModificationTracker.MODIFICATION_COUNT); + } + }); + }*/ } diff --git a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTest.groovy b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTest.groovy index 1c9263e1579a..7eb620851311 100644 --- a/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTest.groovy +++ b/plugins/groovy/test/org/jetbrains/plugins/groovy/lang/resolve/TypeInferenceTest.groovy @@ -478,10 +478,66 @@ use(Cat){ ''', "$JAVA_UTIL_ARRAY_LIST<$JAVA_UTIL_ARRAY_LIST<$JAVA_LANG_INTEGER>>") } + void testInstanceOfInferring1() { + doTest('''\ +def bar(oo) { + boolean b = oo instanceof String || oo != null + oo +} +''', null) + } + + void testInstanceOfInferring2() { + doTest('''\ +def bar(oo) { + boolean b = oo instanceof String || oo != null + oo +} +''', null) + } + + void testInstanceOfInferring3() { + doTest('''\ +def bar(oo) { + boolean b = oo instanceof String && oo != null + oo +} +''', String.canonicalName) + } + + void testInstanceOfInferring4() { + doTest('''\ +def bar(oo) { + boolean b = oo instanceof String && oo != null + oo +} +''', null) + } + + void testInstanceOfInferring5() { + doTest('''\ +def foo(def oo) { + if (oo instanceof String && oo instanceof CharSequence) { + oo + } + else { + oo + } + +} +''', null) + } + private void doTest(String text, String type) { def file = myFixture.configureByText('_.groovy', text) def ref = file.findReferenceAt(myFixture.editor.caretModel.offset) as GrReferenceExpression def actual = ref.type + if (type == null) { + assertNull(actual) + return + } + + assertNotNull(actual) if (actual instanceof PsiIntersectionType) { assertEquals(type, genIntersectionTypeText(actual)) } diff --git a/plugins/groovy/testdata/groovy/controlFlow/grvy1497.test b/plugins/groovy/testdata/groovy/controlFlow/grvy1497.test index acc55ec03c94..fdd11cd0a264 100644 --- a/plugins/groovy/testdata/groovy/controlFlow/grvy1497.test +++ b/plugins/groovy/testdata/groovy/controlFlow/grvy1497.test @@ -5,14 +5,16 @@ println blah ----- 0(1) element: null 1(2) WRITE blah -2(3,5) element: IF statement -3(4) READ blah -4(5) WRITE blah -5(6) element: IF statement -6(7) READ blah -7(8) WRITE blah -8(9) READ println -9(10) READ blah -10(11) ARGUMENT element: Reference expression -11(12) element: Call expression MAYBE_RETURN -12() element: null \ No newline at end of file +2(3) element: IF statement +3(4,6) Condition +4(5) READ blah +5(7) WRITE blah +6(7) Negating goto instruction, condition=3 +7(8) element: IF statement +8(9) READ blah +9(10) WRITE blah +10(11) READ println +11(12) READ blah +12(13) ARGUMENT element: Reference expression +13(14) element: Call expression MAYBE_RETURN +14() element: null \ No newline at end of file diff --git a/plugins/groovy/testdata/groovy/controlFlow/if1.test b/plugins/groovy/testdata/groovy/controlFlow/if1.test index 6432efeca933..f5a6183cee3a 100644 --- a/plugins/groovy/testdata/groovy/controlFlow/if1.test +++ b/plugins/groovy/testdata/groovy/controlFlow/if1.test @@ -5,10 +5,12 @@ if (true) { a = 2 ----- 0(1) element: null -1(2,4) element: IF statement -2(3) WRITE a -3(6) element: Assignment expression MAYBE_RETURN -4(5) WRITE a -5(6) element: Assignment expression MAYBE_RETURN -6(7) element: IF statement -7() element: null \ No newline at end of file +1(2) element: IF statement +2(3,5) Condition +3(4) WRITE a +4(8) element: Assignment expression MAYBE_RETURN +5(6) Negating goto instruction, condition=2 +6(7) WRITE a +7(8) element: Assignment expression MAYBE_RETURN +8(9) element: IF statement +9() element: null \ No newline at end of file diff --git a/plugins/groovy/testdata/groovy/controlFlow/ifInstanceofElse.test b/plugins/groovy/testdata/groovy/controlFlow/ifInstanceofElse.test index 33e7f5f2c2af..8a29021c09b1 100644 --- a/plugins/groovy/testdata/groovy/controlFlow/ifInstanceofElse.test +++ b/plugins/groovy/testdata/groovy/controlFlow/ifInstanceofElse.test @@ -3,22 +3,24 @@ else if (!(o instanceof Integer)) b = 2 else b = 3 ----- 0(1) element: null -1(2,6) element: IF statement -2(3) READ o -3(4) instanceof: o instanceof String -4(5) WRITE b -5(18) element: Assignment expression MAYBE_RETURN -6(7) READ o -7(8) instanceof: ! o instanceof String -8(9,13) element: IF statement -9(10) READ o -10(11) instanceof: ! o instanceof Integer -11(12) WRITE b -12(17) element: Assignment expression MAYBE_RETURN -13(14) READ o -14(15) instanceof: o instanceof Integer -15(16) WRITE b -16(17) element: Assignment expression MAYBE_RETURN -17(18) element: IF statement -18(19) element: IF statement -19() element: null \ No newline at end of file +1(2) element: IF statement +2(3) Condition +3(4) READ o +4(5,7) instanceof: o instanceof String +5(6) WRITE b +6(20) element: Assignment expression MAYBE_RETURN +7(8) Negating goto instruction, condition=2 +8(9) element: IF statement +9(10) Condition +10(11) Condition +11(12) READ o +12(13) instanceof: o instanceof Integer +13(14,16) Negating goto instruction, condition=10 +14(15) WRITE b +15(19) element: Assignment expression MAYBE_RETURN +16(17) Negating goto instruction, condition=9 +17(18) WRITE b +18(19) element: Assignment expression MAYBE_RETURN +19(20) element: IF statement +20(21) element: IF statement +21() element: null \ No newline at end of file diff --git a/plugins/groovy/testdata/groovy/controlFlow/nested.test b/plugins/groovy/testdata/groovy/controlFlow/nested.test index 83c08bb98285..f49ad96807b0 100644 --- a/plugins/groovy/testdata/groovy/controlFlow/nested.test +++ b/plugins/groovy/testdata/groovy/controlFlow/nested.test @@ -9,14 +9,16 @@ for (e in [1,2,3,4]) { 0(1) element: null 1(2) element: Labeled statement 2(3) WRITE e -3(4,13) element: For statement +3(4,15) element: For statement 4(5) element: Block statement 5(6) WRITE ee 6(7,3) element: For statement 7(8) element: Block statement -8(1,9) element: IF statement -9(10) element: IF statement -10(11) READ print -11(12) READ e -12(6) READ ee -13() element: null \ No newline at end of file +8(9) element: IF statement +9(1,10) Condition +10(11) Negating goto instruction, condition=9 +11(12) element: IF statement +12(13) READ print +13(14) READ e +14(6) READ ee +15() element: null \ No newline at end of file diff --git a/plugins/groovy/testdata/groovy/controlFlow/return.test b/plugins/groovy/testdata/groovy/controlFlow/return.test index 731fd83021a3..e720cd515a65 100644 --- a/plugins/groovy/testdata/groovy/controlFlow/return.test +++ b/plugins/groovy/testdata/groovy/controlFlow/return.test @@ -1,10 +1,12 @@ if (true) return a else return b ----- 0(1) element: null -1(2,4) element: IF statement -2(3) READ a -3(7) element: RETURN statement -4(5) READ b -5(7) element: RETURN statement -6(7) element: IF statement -7() element: null \ No newline at end of file +1(2) element: IF statement +2(3,5) Condition +3(4) READ a +4(9) element: RETURN statement +5(6) Negating goto instruction, condition=2 +6(7) READ b +7(9) element: RETURN statement +8(9) element: IF statement +9() element: null \ No newline at end of file diff --git a/plugins/groovy/testdata/groovy/controlFlow/try1.test b/plugins/groovy/testdata/groovy/controlFlow/try1.test index 6a516dd61fc9..42b3e0bacf11 100644 --- a/plugins/groovy/testdata/groovy/controlFlow/try1.test +++ b/plugins/groovy/testdata/groovy/controlFlow/try1.test @@ -7,19 +7,21 @@ print e ----- 0(1) element: null 1(2) element: Open block -2(3,5) element: IF statement -3(4) READ c -4(7) element: RETURN statement -5(9) element: IF statement -6(11) element: Finally clause -7(6,8) CALL 6 -8(17) AFTER CALL 7 -9(6,10) CALL 6 -10(13) AFTER CALL 9 -11(12) READ e -12(8,10) RETURN -13(14) READ print -14(15) READ e -15(16) ARGUMENT element: Reference expression -16(17) element: Call expression MAYBE_RETURN -17() element: null \ No newline at end of file +2(3) element: IF statement +3(4) Condition +4(5,6) READ c +5(9) element: RETURN statement +6(7) Negating goto instruction, condition=3 +7(11) element: IF statement +8(13) element: Finally clause +9(8,10) CALL 8 +10(19) AFTER CALL 9 +11(8,12) CALL 8 +12(15) AFTER CALL 11 +13(14) READ e +14(10,12) RETURN +15(16) READ print +16(17) READ e +17(18) ARGUMENT element: Reference expression +18(19) element: Call expression MAYBE_RETURN +19() element: null \ No newline at end of file diff --git a/plugins/groovy/testdata/groovy/controlFlow/try10.test b/plugins/groovy/testdata/groovy/controlFlow/try10.test index 7a5c36c02b02..215c5c8d8a72 100644 --- a/plugins/groovy/testdata/groovy/controlFlow/try10.test +++ b/plugins/groovy/testdata/groovy/controlFlow/try10.test @@ -10,13 +10,15 @@ cde 1(3) element: Open block 2(5) element: Finally clause 3(2,4) CALL 2 -4(11) AFTER CALL 3 +4(13) AFTER CALL 3 5(6) element: For statement 6(7) element: Block statement -7(8,9) element: IF statement -8(10) READ abc -9(5) element: IF statement -10(4) RETURN -11(12) READ cde -12(13) element: Reference expression MAYBE_RETURN -13() element: null \ No newline at end of file +7(8) element: IF statement +8(9) Condition +9(10,12) READ abc +10(11) Negating goto instruction, condition=8 +11(5) element: IF statement +12(4) RETURN +13(14) READ cde +14(15) element: Reference expression MAYBE_RETURN +15() element: null \ No newline at end of file diff --git a/plugins/groovy/testdata/groovy/controlFlow/try5.test b/plugins/groovy/testdata/groovy/controlFlow/try5.test index 111dd0292008..d3ec3937c95e 100644 --- a/plugins/groovy/testdata/groovy/controlFlow/try5.test +++ b/plugins/groovy/testdata/groovy/controlFlow/try5.test @@ -11,19 +11,21 @@ finally { fScript.previewTask(taskName) ----- 0(1) element: null -1(2,5) element: IF statement -2(3) READ ddd -3(4) READ fXRec -4(16) element: RETURN statement -5(6) element: IF statement -6(7) WRITE fScript -7(9) element: Open block -8(11) element: Finally clause -9(8,10) CALL 8 -10(12) AFTER CALL 9 -11(10) RETURN -12(13) READ fScript -13(14) READ taskName -14(15) ARGUMENT element: Reference expression -15(16) element: Method call MAYBE_RETURN -16() element: null \ No newline at end of file +1(2) element: IF statement +2(3) Condition +3(4,6) READ ddd +4(5) READ fXRec +5(18) element: RETURN statement +6(7) Negating goto instruction, condition=2 +7(8) element: IF statement +8(9) WRITE fScript +9(11) element: Open block +10(13) element: Finally clause +11(10,12) CALL 10 +12(14) AFTER CALL 11 +13(12) RETURN +14(15) READ fScript +15(16) READ taskName +16(17) ARGUMENT element: Reference expression +17(18) element: Method call MAYBE_RETURN +18() element: null \ No newline at end of file diff --git a/plugins/groovy/testdata/groovy/controlFlow/try6.test b/plugins/groovy/testdata/groovy/controlFlow/try6.test index d984d59e5c5d..94bcb21c160c 100644 --- a/plugins/groovy/testdata/groovy/controlFlow/try6.test +++ b/plugins/groovy/testdata/groovy/controlFlow/try6.test @@ -9,16 +9,18 @@ if (url == null) { return url ----- 0(1) element: null -1(2,10) element: IF statement -2(3) READ url -3(4) element: Open block -4(11) WRITE url -5(6) element: Catch clause -6(7) WRITE e -7(8) READ e -8(9) ARGUMENT element: Reference expression -9(13) THROW. element: THROW statement -10(11) element: IF statement -11(12) READ url -12(13) element: RETURN statement -13() element: null \ No newline at end of file +1(2) element: IF statement +2(3) Condition +3(4,11) READ url +4(5) element: Open block +5(13) WRITE url +6(7) element: Catch clause +7(8) WRITE e +8(9) READ e +9(10) ARGUMENT element: Reference expression +10(15) THROW. element: THROW statement +11(12) Negating goto instruction, condition=2 +12(13) element: IF statement +13(14) READ url +14(15) element: RETURN statement +15() element: null \ No newline at end of file diff --git a/plugins/groovy/testdata/groovy/controlFlow/while1.test b/plugins/groovy/testdata/groovy/controlFlow/while1.test index 493df467f932..32691ed75860 100644 --- a/plugins/groovy/testdata/groovy/controlFlow/while1.test +++ b/plugins/groovy/testdata/groovy/controlFlow/while1.test @@ -7,9 +7,11 @@ while (true) { 0(1) element: null 1(2) WRITE i 2(3) element: WHILE statement -3(4,5) element: IF statement -4(8) READ i -5(6) element: IF statement -6(7) READ i -7(2) WRITE i -8() element: null \ No newline at end of file +3(4) element: IF statement +4(5) Condition +5(6,10) READ i +6(7) Negating goto instruction, condition=4 +7(8) element: IF statement +8(9) READ i +9(2) WRITE i +10() element: null \ No newline at end of file diff --git a/plugins/groovy/testdata/groovy/controlFlow/while2.test b/plugins/groovy/testdata/groovy/controlFlow/while2.test index 0de510732776..1e98fb09882c 100644 --- a/plugins/groovy/testdata/groovy/controlFlow/while2.test +++ b/plugins/groovy/testdata/groovy/controlFlow/while2.test @@ -7,9 +7,11 @@ while (true) { 0(1) element: null 1(2) WRITE i 2(3) element: WHILE statement -3(4,5) element: IF statement -4(2) READ i -5(6) element: IF statement -6(7) READ i -7(2) WRITE j -8() element: null \ No newline at end of file +3(4) element: IF statement +4(5) Condition +5(2,6) READ i +6(7) Negating goto instruction, condition=4 +7(8) element: IF statement +8(9) READ i +9(2) WRITE j +10() element: null \ No newline at end of file diff --git a/plugins/groovy/testdata/groovy/controlFlow/whileNonConstant.test b/plugins/groovy/testdata/groovy/controlFlow/whileNonConstant.test index f1206ab03412..9ece4b7bdeea 100644 --- a/plugins/groovy/testdata/groovy/controlFlow/whileNonConstant.test +++ b/plugins/groovy/testdata/groovy/controlFlow/whileNonConstant.test @@ -7,10 +7,12 @@ while (condition()) { 0(1) element: null 1(2) WRITE i 2(3) element: WHILE statement -3(4,9) READ condition -4(5,6) element: IF statement -5(9) READ i -6(7) element: IF statement -7(8) READ i -8(2) WRITE i -9() element: null \ No newline at end of file +3(4,11) READ condition +4(5) element: IF statement +5(6) Condition +6(7,11) READ i +7(8) Negating goto instruction, condition=5 +8(9) element: IF statement +9(10) READ i +10(2) WRITE i +11() element: null \ No newline at end of file