diff options
author | Tor Norbye <tnorbye@google.com> | 2013-10-15 19:49:27 -0700 |
---|---|---|
committer | Tor Norbye <tnorbye@google.com> | 2013-10-15 19:49:46 -0700 |
commit | 767536605379e492929a763f4a585cb4f499b9f6 (patch) | |
tree | b82400bd5a1609a29dfb9d40d4b54cfac2daaabd /platform | |
parent | 32218cc024d27dc92563b5b45007c94057c1c423 (diff) | |
download | idea-767536605379e492929a763f4a585cb4f499b9f6.tar.gz |
Snapshot 246ebf4786ef75849985fa5fba6015155ebac527 from idea/132.637 of git://git.jetbrains.org/idea/community.git
246ebf4: better gradient colors for Darcula
8833be9: should be opaque
9a1a2f2: fix darcula text fields
001ed5d: add "perflib" from tools-base module to build
c348af4: Github: do not provide Git AuthData for wrong host
7424aca: Github: compare hosts case-insensitive
7ce90f4: fix double Shift on Windows
050b38c: check value of Required annotation; do not check required attributes of dom elements in XmlHighlightingVisitor; revive testing of required attribute validation in PluginXmlFunctionalTest
59d7274: IDEA-114873 (Add Package Info item to New submenu)
c4d7c3e: codestyle fix
9f3e60a: trace about substitutor once per derived class, in order to avoid excessive logging
27952b2: moved FinderRecursivePanel to platform-impl
94b9f06: IDEA-114850 Unreadable "No files are open" text
1fd5b2d: removing nested frameworks
4098566: capitalization (IDEA-81968)
10f8420: diagnose incorrect MixinEP registration (IDEA-113506)
35689ca: add apiVersion parameter to build.xml so that Android Studio can use exact platform build they're compatible with (IDEA-114596)
cb53ae1: Multiline support for regexp editor
ddef70e: Lens mode (hint location fixes)
f8d6bf8: disable escaper for balloons
99d486d: check for language
26fa119: support for java regexp modifiers
925000e: Ability to handle regexps with modifiers
67f1c14: automake is run only for the project where current user activity is going on (IDEA-104064)
d5cad13: fix naturalCompare() edge case
c1f7724: Merge remote-tracking branch 'origin/master'
862fb67: proper bounds for search everywhere button
d21dc3f: new shortcut for search everywhere
4283182: completely new UI
50e5c52: colors for white theme
08f4c9e: don't paint parent's background
0ed4058: active icon for Search Everywhere
17a6674: don't reset row height
52f84a7: IDEA-114694 Shift-Space is a terrible default shortcut for Search Everywhere
099ead0: dfa: exception handling reworked no more gosub-return causing equal states differing by offset stack only don't visit outer finally without visiting inner one
3744d2c: IDEA-114736 Bug in code completion inside instanceof block
377acad: correctly initialize semaphore for all BasicFuture implementations
81bfb1e: Lens mode (mouse wheel support & many fixes)
81d90a7: Get rid of extra UISettings notifications
2fb0092: Github: fix UrlToHost function
75f4e1f3: plugin advertisement: show disabled plugin name
8ed1b68: plugins advertisement: enabled installed plugin
53ad76d: IDEA-114708 Wrong Up/Down action interpretation in consoles (irb/Bash)
e818b6e: use fileEditor's editor instead of some random editor from dataContext
bd152a4: IDEA-89857 automatically adds required attributes in dom-based xml; insert correct endings for View/ViewGroup tags
7988d09: test for not building stubs for java files in module but not under source roots, in library but not in class root
131353b: IDEA-112134 $SELECTION$ variable doesn't work as true variable, but as $END$
cac9208f: item 1 of IDEA-95649 (Darcula: Settings -> File Templates doesn't follow theme colors)
ab2d2ca: remove color to display file template descriptions nicely under Darcula
7fb7911: use correct company name in version info (IDEA-82623)
5bd4249: WEB-8027 long single line values in debugger hang IntelliJ + review
4710a08: show editor notification for text mapped files about known plugins from the repository
62c87af: logging for incorrect move
eab3d4c: plugin advertising: stop if update was disabled
25584ad: Add MONTH_NAME_SHORT and MONTH_NAME_FULL definition & improve file template descriptions
4f10bd1: CR-IC-2706 (logging added)
eef188b: Add diagnostic info of invalid template ranges
fd7c9d0: EA-50616
618a573: license added?
d29c66e: Drivers management refactored
f280f8b: update netty (attempt to understand why nodejs disconnected on second run, but it was not netty fault)
15d9033: selecting right project type
4111497: http://ea.jetbrains.com/browser/ea_problems/50978
65eff28: EA-50605 - NPE: AbstractExternalSystemTaskConfigurationType.generateName
f43865d: Slightly reworked formatter markers' UI in accordance with IDEA-114583
8e51528: give file template description a bit more room
9d2f4f9: CR-IC-2705, fixed for Mac
55b6603: fix default file template descriptions
6fd90c1: Revert optimization, because MAC tests fail.
d11966f: IDEA-114766 Gradle: module dependency scope ignored while project info resolving
359ddf8: IDEA-114647 (fsnotifier size check updated)
0f10404: license added
780fa6e: allow watching exFAT (IDEA-114647)
f8dc1eb: lib license added
efb3281: testdata for IDEA-67591
993fb34: highlighting for incompatible return types in type parameter inheritors (IDEA-57274)
e03245e: Cleanup (comment)
51a77de: platform: garbage production by FIleWatcher reduced
06b198e: Cleanup (typo; dead code)
d8729d5: platform: partial refresh fixed
af0637c: Merge remote-tracking branch 'origin/master'
121ee3e: Add helper method for pretty-printing JSON in TaskUtil class. Improve logging in GenericRepository.
461c6d7: testng: parameterized template (IDEA-114746)
5a50ed8: do not detach copyright updater on project closed (IDEA-114186)
f06771b: restart infer nullity when library already exist (IDEA-114163)
b33fa7f: effectively final: check flow at variable start (IDEA-114737)
46e9f4b: testdata for IDEA-57252
82b6b16: testdata for IDEA-67518
6a9313c: type parameter in bounds check: separate wildcards with type param in bound
efc19ab: new inference: do not ignore meaningful upper bounds
cbff339: Optimization: don't use FileUtil.pathsEqual() , paths returned by FileWatcher is already canonical.
2723de7: don't fold already concise getters (EA-50804)
17dae78: IDEA-114699 Live template without description shows "NULL"
56e5602: a simpler way to setup live templates in tests
3ea2e38: Jediterm updated.
a47f9d3: Change cache lookup logic in XML and JSON response handlers
4ce5dec: Don't urlencode "serverUrl" template variable in GenericRepository fields
38c05d9: IDEA-114076 Unit tests can't be executed with a JRE 1.5 because junit-rt.jar is compiled with JRE 6.0 as target
bb8bb2a: moved to analysis
376fe99: moved to analysis
ff48d6d: moved to analysis
3fcf2d5: moved to analysis
1825351: moved to analysis
563ffda: moved to analysis
dc477a1: moved to analysis
3c18086: moved to analysis
472bab7: moved to analysis
473e45d: moved to analysis
65f35d7: moved to analysis
a36aaf6: moved to analysis
5f10c64: moved to analysis
c3bf35d: moved to analysis
b6fbbb6: moved to analysis
6a9d91b: moved to analysis
7582706: moved to analysis
ef37a6c: moved to analysis
1ea667f: moved to analysis
8985a7e: moved to analysis
37691e0: moved to analysis
08ba033: moved to analysis
f069df4: moved to analysis
3ac7210: moved to analysis
5e9a1e1: moved to analysis
74ecd51: moved to analysis
33f20b1: moved to analysis
936f232: read action
da37289: moved to analysis
e0e3724: moved to analysis
53e0050: moved to analysis
5240818: moved to analysis
50f7722: moved to analysis
8b64b6d: moved to analysis
62c27c4: moved to analysis
0dba483: moved to analysis
c7bfbeb: moved to analysis
6bf16de: moved to analysis
180ea57: moved to analysis
b8fcd01: moved to analysis
b19c765: moved to analysis
636378c: moved to analysis
7c63c331: moved to analysis
b47e90d: moved to analysis
44e0bd5: moved to analysis
4957a2c: moved to analysis
bad582a: moved to analysis
2a4c6df: moved to analysis
bf5ae61: moved to analysis
f547ee5: moved to analysis
c6d5ca6: moved to analysis
c3dc3f2: moved to analysis
27f40d2: moved to analysis
82cc0c1: moved to analysis
e309872: moved to analysis
c6806d7: moved to analysis
66b131c: test fix
5819121: moved to analysis
ace2035: moved to analysis
300fed6: moved to analysis
73d5646: moved to analysis
1efaa3b: moved to analysis
930eabe: moved to analysis
34c381d: moved to analysis
8094570: moved to analysis
bb3273c: moved to analysis
aec65a6: moved to analysis
cbc892a: moved to analysis
b42dc77: moved to analysis
ba6923d: moved to analysis
2283aa0: moved to analysis
1aeb0d3: moved to analysis
e08c9f1: moved to analysis
03cd373: moved to analysis
caaf810: moved to analysis
0e924cf: moved to analysis
f3e0d0a: moved to analysis
51ec5ea: moved to analysis
94fb66f: moved to analysis
6aaaaae: moved to analysis
08e51ae: moved to analysis
cf12745: moved to analysis
b34f44f: moved to analysis
37f3cdc: moved to analysis
036a2b6: moved to analysis
4931ffa: moved to analysis
8e59ca1: moved to analysis
02c0c01: moved to analysis
89a55b0: moved to analysis
cba23f4: moved to analysis
1b1fb1c: moved to analysis
bd725cd: moved to analysis
dfcc51d: moved to analysis
fad8af3: moved to analysis
827e5a5: moved to analysis
2360dcd: moved to analysis
008c56a: moved to analysis
758ba60: moved to analysis
731c642: moved to analysis
d5adf28: moved to analysis
b892f8b: moved to analysis
7513254: moved to analysis
e3a752c: moved to analysis
03219ce: moved to analysis
f06ad8a: moved to analysis
2c4614a: moved to analysis
4e076dd: moved to analysis
7f3ee23: moved to analysis
50835cc: moved to analysis
ff01a67: moved to analysis
084113a: moved to analysis
e197319: moved to analysis
1d7f251: moved to analysis
e2fc894: moved to analysis
d48b36b: EA-50870 - IAE: GroovyPropertyUtils.getGetterNameNonBoolean
0fb3c6e: IDEA-57747 (Idea X - CTRL+SHIFT+Left/Right Arrow functionality)
2cbff34: Github: use standard YesNoDialog
53782b5: Github: use strict class check
a42b1d9: when launching build process or javac server suppress libraries from 'ext' directories to ensure compiler is loaded from correct location
f40dfd1: OC-8512 Exception when typing on empty line at non-zero column during indexing
917a3a1: cleanup
c821ba0: moved to analysis
56da4ee: moved to core
38dbce1: cleanup
d8bd506: ForkJoin support
9f4a9d0: moved to analysis
da9996b: moved to analysis
f44d265: fix due to CR-IC-2640 comment
75b7026: Merge remote-tracking branch 'origin/master'
48e70d3: used lower level char buffer instead of document.getText() due to CR-IC-2640 comment
1271549: project structure: increased default size of source roots tree
a24ebae: Merge remote-tracking branch 'origin/master'
a9fc3c6: cleanup
adb2f16: diagnostics for EA-50397 - assert: TextRange.<init>
2244946: EA-50612 - NPE: EditorWindow$.run
a64cbc3: Logger renamed to more specific name
2cbf40b: EA-50756 - NPE: DefaultXmlExtension.getAvailableTagNames diagnostics
c25431e: new inference: skip inference for nested call when nested call was initially requested to be inferred
5fda195: detailed diagnostic for compiler class loading problems
c178ebf: workaround for EA-49915, EA-50599
5e7f578: go to the errors first by default
42ad23d: when matching local variables slots and variable names from sources take into account loop and catch statements
69424d4: run test method from abstract class: ignore non-runnable inheritors; try to retrieve from context implicitly selected inheritor (IDEA-114551)
8e51c80: override: fix param names in javadoc (IDEA-114557)
977e332: PY-11027 (ignore placeholder argument from non-compliant DEs)
c3af1f1: Merge remote-tracking branch 'origin/master'
fa5fa39: Test skipped for Win XP
bad439d: execution tests probably fixed
1e54387: range changed (IDEA-114528)
93c8631: infer nullity: more pessimistic check: check variables first (IDEA-114630)
6fb01cd: global inspections documentation cleanup
087b52a: WEB-445 "Select word at caret" a bit to grabby in class attributes
046efd8: highlight missing package-info.java in editor too
b525330: Errors
beef6c6: GroovyDirectInheritorsSearcher: avoid unnecessary collection->array copying
0596f3d: remove @NotNull from SortedMap#comparator (IDEA-114655)
eb9828f: IDEA-114203: Add new "Options" tab in YouTrack repository editor where user can customize states names
4371063: Delete repository problem notification
b8d1054: CR-IC-2693
3f3b474: CR-IC-2690
0ce5ae9: displaying version number in tree
2271eaf: IDEA-114633 (symlink deletion regression fixed)
569a70d: Cleanup (FileUtil tests grouped)
9fdcf65: Cleanup (formatting)
ca49f7d: Cleanup (unnecessary condition)
46d04cb: FrameworkLibraryVersion separated from FrameworkVersion
a8fc158: recognize IBM JDK from WebSphere 8 (IDEA-90661)
65f1157: Merge branch 'svn1_8_new'
f165eb7: named NodeRendererImpl constructor; adjust test expectations
117f86f: corrected
e173999: FrameworkGroupVersion dropped
9948d9d: Merge remote-tracking branch 'origin/master'
76f6788: fix exception on some linux machines
f6f6664: IDEA-114517 Use server url (and not repository url) as authentication realm for https server certificate
5e77c10: IDEA-114517 Force certificate errors checking before credentials error checking
13c6c0a: Merge remote-tracking branch 'origin/master'
784bfa5: Merge remote-tracking branch 'origin/master'
9f25ff8: jdk8 compilation
80265c1: plugin advertiser: escape # in feature implementation
efc8a3d: EA-50528 - MJE: JsonReader.syntaxError
2e10f33: IDEA-99241 (Report package naming convention violations in the editor)
1cc2fa4: made fields final
4374c69: replace exception-driven control flow with the proper one
f0cba21: moved to analysis
d9b6c0f: moved to psi
f8c15cf: moved to psi
836e381: cleanup
2e0029d: moved to analysis
6484602: make TempFileSystem extends LocalFileSystem to simplify clients and tests
662eb80: cleanup
0ebcf72: moved to analysis
250fff4: moved to analysis
3906227: moved to analysis
a24ae6d: moved to analysis
e8fc8c6: moved to psi
7a468ca: moved method to core
82060fd: cleanup
04c524c: reduce dependency on LocalFileSystem
8cdab66: frameworks tree refactored
9b45d86: WEB-9563 There is a bug with Emmet and BEM namings
29ecf27: guess DTD by public id #WEB-9546 fixed
c697898: JavaEE frameworks sorted out
6dc8386: less side effects
99e0eae: test is back
e2d2093: Gradle: gradle-native deps added
7507dc5: do not autoscroll when in focus, e.g. selection changing action may be triggered directly from structure
346c501: add model change support & some formatting
d6ff1a1: Merge remote-tracking branch 'origin/master'
b6a044a: Respect auto-save and auto-sync settings.
e1f4afb: IDEA-60234 Automatically unwrap groovy.lang.Reference in the debugger
0dcb367: make topic final
c5ce0b8: IDEA-114604 Evaluate Expression doesn't change context after navigating call stack +review
f9411d3: cleanup
89d03b1: overrides
147d5ea: cleanup
aef6c9e: extracted method to avoid code duplication
4cffaf3: on rebuild clear system caches for compilers run internally in IDEA
f72bc3d: add darcula green color
31d24b4: better colors for darcula
4ef7b0c: glow color for IntelliJ laf
36aad83: refactor button and checkbox
9181562: search field style for IntelliJ LaF
cff6f4b: use triangles in IntelliJ LaF trees
c4ebcef: add IntelliJ LaF
dee1ff7: IntelliJ laf
a152352: customize Darcula LaF from IntelliJ LaF
6e97b12: tree icons for IntelliJ LaF
69dbc89: inference: do not check return type during inference; separate highlighting check
9e060c7: cast
51f471a: Format pom.xml on create new module.
2624ec6: IDEA-114593 Debugger: Show variable information when no debug info
1918d6e: overrides, nullability
c463545: platform: race between JNA and environment loading fixed
943cea6: Accidentally forget to add annotator to plugin.xml
0dc87d5: IDEA-50801 Evaluate expression: expand root node automatically +review
79c7ba5: overrides
5e841a7: fix testdata
7c84b97: Update JqlAnnotator to highlight JqlIdentifiers as constants, not as strings, to be consistent with YouTrack queries colors
e0ac1f1: Merge remote-tracking branch 'origin/master'
dfc9912: fixed PY-10964 Extract variable doesn't work as expected inside brackets fixed PY-10221 Refactor->Extract->Variable may break square bracket symmetry and may break user input
9c38ae3: Extract pretty-printing of XML responses to helper method in TaskUtil, use StringUtil.notNullize in YouTrackIntellisense models.
6c49b66: IDEA-114523 Fixed svn executable version parsing - support arbitrary text after version (in executable output)
7157489: Make myWorkspaceMap and myUnresolvedCollector volatile.
35e76f4: Comment for getNameThroughCopies method added
cb377a5: spelling fixed
b5eeae5: svn: Refactored RootUrlInfo - utilize underlying Node instance (instead of just copying fields)
b615000: IDEA-113670 Reformat+rearrange per-language setting [CR-IC-2682]
b37e22e: Merge remote-tracking branch 'origin/master'
7a40a2c: svn: @NotNull/@Nullable for NestedCopyInfo and RootUrlInfo parameters
05e6734: cleanup
60b90f9: cleanup
6e534ce: update netty (07 oct 4.1 Alpha build) Updated, but ref error was our fault, it is not netty bug.
b1d9994: inference: filter out inaccessible for method ref methods
56be1b2: svn: Updated detection if svn roots were changed - added error roots tracking
265806c: svn: Simplified svn executable validation logic - removed explicit major version check
3aadab0: svn: Removed check "if working copy format of project root is compatible with command line client" (during svn executable validation)
be91b71: svn: Added notification about errors during svn roots detection
7e742f4: Merge remote-tracking branch 'origin/master'
1c1e2ee: svn: Extracted showing "Subversion Working Copies Information" panel to separate method
16b2321: EA-50707 - assert: TextRange.<init>
35546a0: new inference: ignore captured variables incorporation phase
8c0d7a1: testRunner: wording (IDEA-114500) as attached state is everywhere checked separately
9641ede: plugins notification: hide notification before dialog is shown
3ecdadc: ctors closed
199a379: framework roles
2d68bc5: platform: hidden Windows root dir with NIO2 attribute reader fixed
5ff5b2e: IDEA-113961 (ownership checking)
498d04f: IDEA-113961 (permissions in file attributes deprecated and interned)
d9f1d4f: Cleanup (pointless assertion)
415b782: svn: Implemented ability to view svn roots detection errors in "Subversion Working Copies Information" panel
f1ecb53: svn: Refactored RootUrlInfo - utilize Node instance on which RootUrlInfo is based
b726526: svn: Implemented ability to detect svn roots when working copy format does not correspond to used client (SVNKit or executable could not perform commands on the working copy - for instance, when client is old or working copy upgrade required)
67bdd3a: svn: Implemented ability to resolve client factory and working copy format without checking detected svn roots
b15784f: add some ways for reuse in DB part & cleanup
8dae62f: do not proceed with painting if placeHolder text was really painted, i.e. skip error markup, selection, etc
5795402: new inference: reject explicitly typed lambda as pertinent to applicability if its return statements are poly
ae3b46c: new inference: temp: ignore inferred Object at first stage
8d2b3e3: new inference: accept methods in lambda return statement as poly without additional checks
5c40157: fixed NPE
5bf3698: Reimplement select word tests for CSS, CoffeeScript, Ruby, Python, Sass, Scss and Less
a99db1d: Add test utility method for select word action testing
203e791: prefer variants without leading underscores when case sensitivity=first letter (OC-3547)
4af89f65: IDEA-114514 Grammar error in "Library already exists" dialog
215044f: Allow current element selection in StructureViewer for non-editors.
4ddd1f8: dont' copy editor markup model in language console
f2ec48d: extends and implements clauses should always exist in type definitions
c3ef7a1: GroovyBlockWithRange
f0b2208: Merge remote-tracking branch 'origin/master'
c495919: Merge remote-tracking branch 'origin/master'
7ae82a0: Save documents on switch to terminal refresh VFS on switch from it (PY-10944).
14cc2d5: IDEA-114534 Gradle: cancelation of gradle tasks for "out-of-process" mode
4684ba7: Add package-info.java checking to "Declaration has problems in Javadoc references" inspection
53c1eff: [log] Move logProvider extension point to the right place: vcs-log.xml
b667798: decode file name for quickfix #WEB-9559 fixed
bb34511: new inference: ignore inferred wildcards in return position
74aa35f: new inference: postpone resolve as long as possible
506188d: new inference: mark unresolved calls as poly expressions
3b99b77: Refactor annotator to use visitor
57f9383: Add annotator for inspecting some common errors in JQL queries
c889c0a: svn: Refactored vcs roots detection - CopiesDetector moved/renamed to separate class, detection output extracted to separate class
cf66975: svn: Forced status command result contain repository root url (resolved with corresponding info command)
2a7389c: svn: Refactored Node class to have notnull fields
69d918e: svn: Refactored WCInfo - utilize RootUrlInfo on which WCInfo is based (instead of just copying fields)
a9da4bc: svn: Refactored RootUrlInfo - removed setType() usages where possible
5f09493: svn: Refactored nested copy detection - removed unused code
6eee591: svn: Refactored nested copy detection - extractions, renames, simplified logic
b3eeb5e: svn: Refactored nested copy detection - use instance instead of static methods
238c879: svn: Refactored nested copy detection - removed Real class
38db9bf: svn: Refactored nested copy detection - moves, renames
dd8b94c: svn: Refactored nested copy detection - simplified data structures for storing nested copies (while collecting changes with ChangeProvider)
8fbc758: svn: Refactored filtering roots that are not actually working copies - methods extracted, logic simplified
66608ee: svn: Refactored inner/external working copies detection - removed unused code
06f90ae: svn: Refactored inner/external working copies detection - methods extracted, logic simplified
b44d86f: svn: Refactored nested copies detection by folder status - simplified code flow
e397390: svn: Replaced working copy format detection using SVNStatus.getWorkingCopyFormat() with SvnVcs.getWorkingCopyFormat() (1.7 and 1.8 both have "12" entries file format and previously 1.7 was always returned for command line status command)
f0a4f49: svn: Refactored working copy format detection for rollback missing files/directories - do not use SVNStatus.getWorkingCopyFormat()
0bdd518: svn: Refactored getting changelist name from SVNStatus - removed duplication and check if working copy format supports change lists
054bf7f: svn: Refactored CommandUtil.escape (simplified)
e211be2: svn: Removed unused WorkingCopyFormat parameter from StatusReceiver
8d5fffd: svn: Fixed "upgrade working copy format" dialog - always make current working copy format enabled
Change-Id: I928a2caf9bf9d566fb9f556ce39acb5111119c9c
Diffstat (limited to 'platform')
218 files changed, 11190 insertions, 2939 deletions
diff --git a/platform/analysis-api/src/com/intellij/codeInspection/GlobalInspectionTool.java b/platform/analysis-api/src/com/intellij/codeInspection/GlobalInspectionTool.java index 5e72c932b71c..077afec102b8 100644 --- a/platform/analysis-api/src/com/intellij/codeInspection/GlobalInspectionTool.java +++ b/platform/analysis-api/src/com/intellij/codeInspection/GlobalInspectionTool.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -30,6 +30,10 @@ import org.jetbrains.annotations.Nullable; * complete graph of references between classes, methods and other elements in the scope * selected for the analysis. * + * Global inspections can use a shared local inspection tool for highlighting the cases + * that do not need global analysis in the editor by implementing {@link #getSharedLocalInspectionTool()} + * The shared local inspection tools shares settings and documentation with the global inspection tool. + * * @author anna * @see LocalInspectionTool * @since 6.0 @@ -193,9 +197,25 @@ public abstract class GlobalInspectionTool extends InspectionProfileEntry { return null; } - // In some cases we can do highlighting in annotator or high. visitor based on global inspection + /** + * In some cases we can do highlighting in annotator or high. visitor based on global inspection or use a shared local inspection tool + */ public boolean worksInBatchModeOnly() { - return true; + return getSharedLocalInspectionTool() != null; + } + + /** + * Returns the local inspection tool used for highlighting in the editor. Meant for global inspections which have a local component. + * The local inspection tool is not required to report on the exact same problems, and naturally can't use global analysis. The local + * inspection tool is not used in batch mode. + * + * For example a global inspection that reports a package could have a local inspection tool which highlights + * the package statement in a file. + * @return + */ + @Nullable + public LocalInspectionTool getSharedLocalInspectionTool() { + return null; } public void initialize(@NotNull GlobalInspectionContext context) { diff --git a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/DaemonCodeAnalyzerSettings.java b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/DaemonCodeAnalyzerSettings.java index 4cc19cebef7b..0b3d945f85f3 100644 --- a/platform/analysis-impl/src/com/intellij/codeInsight/daemon/DaemonCodeAnalyzerSettings.java +++ b/platform/analysis-impl/src/com/intellij/codeInsight/daemon/DaemonCodeAnalyzerSettings.java @@ -24,7 +24,7 @@ public class DaemonCodeAnalyzerSettings { return ServiceManager.getService(DaemonCodeAnalyzerSettings.class); } - public boolean NEXT_ERROR_ACTION_GOES_TO_ERRORS_FIRST = false; + public boolean NEXT_ERROR_ACTION_GOES_TO_ERRORS_FIRST = true; public int AUTOREPARSE_DELAY = 300; public boolean SHOW_ADD_IMPORT_HINTS = true; @NonNls public String NO_AUTO_IMPORT_PATTERN = "[a-z].?"; diff --git a/platform/analysis-impl/src/com/intellij/codeInspection/SuppressionUtil.java b/platform/analysis-impl/src/com/intellij/codeInspection/SuppressionUtil.java index b2f0c8ee2cc7..869d1f220b87 100644 --- a/platform/analysis-impl/src/com/intellij/codeInspection/SuppressionUtil.java +++ b/platform/analysis-impl/src/com/intellij/codeInspection/SuppressionUtil.java @@ -41,9 +41,7 @@ import java.util.regex.Pattern; /** * @author yole */ -public class SuppressionUtil { - @NonNls public static final String SUPPRESS_INSPECTIONS_TAG_NAME = "noinspection"; - +public class SuppressionUtil extends SuppressionUtilCore { /** * Common part of regexp for suppressing in line comments for different languages. * Comment start prefix isn't included, e.g. add '//' for Java/C/JS or '#' for Ruby diff --git a/platform/analysis-impl/src/com/intellij/codeInspection/ex/GlobalInspectionToolWrapper.java b/platform/analysis-impl/src/com/intellij/codeInspection/ex/GlobalInspectionToolWrapper.java index 5fb8e9442308..8f6e87c3c495 100644 --- a/platform/analysis-impl/src/com/intellij/codeInspection/ex/GlobalInspectionToolWrapper.java +++ b/platform/analysis-impl/src/com/intellij/codeInspection/ex/GlobalInspectionToolWrapper.java @@ -18,10 +18,12 @@ package com.intellij.codeInspection.ex; import com.intellij.codeInspection.GlobalInspectionContext; import com.intellij.codeInspection.GlobalInspectionTool; import com.intellij.codeInspection.InspectionEP; +import com.intellij.codeInspection.LocalInspectionTool; import com.intellij.codeInspection.reference.RefGraphAnnotator; import com.intellij.codeInspection.reference.RefManagerImpl; import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * User: anna @@ -72,4 +74,13 @@ public class GlobalInspectionToolWrapper extends InspectionToolWrapper<GlobalIns public boolean worksInBatchModeOnly() { return getTool().worksInBatchModeOnly(); } + + @Nullable + public LocalInspectionToolWrapper getSharedLocalInspectionToolWrapper() { + final LocalInspectionTool sharedTool = getTool().getSharedLocalInspectionTool(); + if (sharedTool == null) { + return null; + } + return new LocalInspectionToolWrapper(sharedTool); + } } diff --git a/platform/platform-impl/src/com/intellij/codeStyle/InconsistentLineSeparatorsInspection.java b/platform/analysis-impl/src/com/intellij/codeStyle/InconsistentLineSeparatorsInspection.java index de31bc2d7d92..97e0261987e4 100644 --- a/platform/platform-impl/src/com/intellij/codeStyle/InconsistentLineSeparatorsInspection.java +++ b/platform/analysis-impl/src/com/intellij/codeStyle/InconsistentLineSeparatorsInspection.java @@ -19,6 +19,7 @@ import com.intellij.codeInspection.LocalInspectionTool; import com.intellij.codeInspection.LocalQuickFix; import com.intellij.codeInspection.ProblemDescriptor; import com.intellij.codeInspection.ProblemsHolder; +import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.impl.LoadTextUtil; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; @@ -46,10 +47,7 @@ public class InconsistentLineSeparatorsInspection extends LocalInspectionTool { } final Project project = holder.getProject(); - final String projectLineSeparator = CodeStyleFacade.getInstance(project).getLineSeparator(); - if (projectLineSeparator == null) { - return; - } + final String projectLineSeparator = FileDocumentManager.getInstance().getLineSeparator(null, project); final VirtualFile virtualFile = file.getVirtualFile(); if (virtualFile == null || !AbstractConvertLineSeparatorsAction.shouldProcess(virtualFile, project)) { @@ -88,10 +86,7 @@ public class InconsistentLineSeparatorsInspection extends LocalInspectionTool { return; } - final String lineSeparator = CodeStyleFacade.getInstance(project).getLineSeparator(); - if (lineSeparator == null) { - return; - } + final String lineSeparator = FileDocumentManager.getInstance().getLineSeparator(null, project); final VirtualFile virtualFile = ((PsiFile)psiElement).getVirtualFile(); if (virtualFile != null) { diff --git a/platform/analysis-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReference.java b/platform/analysis-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReference.java index 582c728af173..69c2b0cdf326 100644 --- a/platform/analysis-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReference.java +++ b/platform/analysis-impl/src/com/intellij/psi/impl/source/resolve/reference/impl/providers/FileReference.java @@ -198,7 +198,7 @@ public class FileReference implements PsiFileReference, FileReferenceOwner, PsiP } public String getFileNameToCreate() { - return getCanonicalText(); + return decode(getCanonicalText()); } public diff --git a/platform/bootstrap/src/com/intellij/idea/Main.java b/platform/bootstrap/src/com/intellij/idea/Main.java index b095fabae055..c9a3f8e68004 100644 --- a/platform/bootstrap/src/com/intellij/idea/Main.java +++ b/platform/bootstrap/src/com/intellij/idea/Main.java @@ -40,13 +40,18 @@ public class Main { private static final String AWT_HEADLESS = "java.awt.headless"; private static final String PLATFORM_PREFIX_PROPERTY = "idea.platform.prefix"; + private static final String[] NO_ARGS = {}; private static boolean isHeadless; private static boolean isCommandLine; private Main() { } - public static void main(final String[] args) { + public static void main(String[] args) { + if (args.length == 1 && "%f".equals(args[0])) { + args = NO_ARGS; + } + setFlags(args); if (isHeadless()) { diff --git a/platform/core-api/src/com/intellij/concurrency/Job.java b/platform/core-api/src/com/intellij/concurrency/Job.java index 488ec8aa6947..f20917d2e703 100644 --- a/platform/core-api/src/com/intellij/concurrency/Job.java +++ b/platform/core-api/src/com/intellij/concurrency/Job.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 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,10 +19,13 @@ */ package com.intellij.concurrency; +import com.intellij.util.IncorrectOperationException; import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; public interface Job<T> { // the lower the priority the more important the task is @@ -46,6 +49,7 @@ public interface Job<T> { boolean isDone(); + void waitForCompletion(int millis) throws InterruptedException, ExecutionException, TimeoutException; @NotNull Job NULL_JOB = new Job() { @@ -55,6 +59,11 @@ public interface Job<T> { } @Override + public void waitForCompletion(int millis) { + + } + + @Override public void cancel() { } @@ -65,32 +74,32 @@ public interface Job<T> { @Override public void addTask(@NotNull Callable task) { - + throw new IncorrectOperationException(); } @Override public void addTask(@NotNull Runnable task, Object result) { - + throw new IncorrectOperationException(); } @Override public void addTask(@NotNull Runnable task) { - + throw new IncorrectOperationException(); } @Override public List scheduleAndWaitForResults() throws Throwable { - return null; + throw new IncorrectOperationException(); } @Override public boolean isCanceled() { - return false; + return true; } @Override public void schedule() { - + throw new IncorrectOperationException(); } }; diff --git a/platform/core-api/src/com/intellij/openapi/vfs/VfsUtilCore.java b/platform/core-api/src/com/intellij/openapi/vfs/VfsUtilCore.java index 71a830a4c007..9734068a9dc0 100644 --- a/platform/core-api/src/com/intellij/openapi/vfs/VfsUtilCore.java +++ b/platform/core-api/src/com/intellij/openapi/vfs/VfsUtilCore.java @@ -27,6 +27,7 @@ import com.intellij.util.Function; import com.intellij.util.PathUtil; import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.Convertor; import com.intellij.util.containers.DistinctRootsCollection; import com.intellij.util.io.URLUtil; import com.intellij.util.text.StringFactory; @@ -550,4 +551,27 @@ public class VfsUtilCore { return VfsUtilCore.isAncestor(ancestor, virtualFile, false); } } + + public static void processFilesRecursively(@NotNull VirtualFile root, + @NotNull Processor<VirtualFile> processor, + @NotNull Convertor<VirtualFile, Boolean> directoryFilter) { + if (!processor.process(root)) return; + + if (root.isDirectory() && directoryFilter.convert(root)) { + final LinkedList<VirtualFile[]> queue = new LinkedList<VirtualFile[]>(); + + queue.add(root.getChildren()); + + do { + final VirtualFile[] files = queue.removeFirst(); + + for (VirtualFile file : files) { + if (!processor.process(file)) return; + if (file.isDirectory() && directoryFilter.convert(file)) { + queue.add(file.getChildren()); + } + } + } while (!queue.isEmpty()); + } + } } diff --git a/platform/core-api/src/com/intellij/psi/util/PsiUtilCore.java b/platform/core-api/src/com/intellij/psi/util/PsiUtilCore.java index 1948ff4d95ab..eaf72d5504f7 100644 --- a/platform/core-api/src/com/intellij/psi/util/PsiUtilCore.java +++ b/platform/core-api/src/com/intellij/psi/util/PsiUtilCore.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2011 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -483,4 +483,29 @@ public class PsiUtilCore { } return parent; } + + @NotNull + public static Language findLanguageFromElement(final PsiElement elt) { + if (elt.getFirstChild() == null) { //is leaf + final PsiElement parent = elt.getParent(); + if (parent != null) { + return parent.getLanguage(); + } + } + + return elt.getLanguage(); + } + + @NotNull + public static Language getLanguageAtOffset (@NotNull PsiFile file, int offset) { + final PsiElement elt = file.findElementAt(offset); + if (elt == null) return file.getLanguage(); + if (elt instanceof PsiWhiteSpace) { + final int decremented = elt.getTextRange().getStartOffset() - 1; + if (decremented >= 0) { + return getLanguageAtOffset(file, decremented); + } + } + return findLanguageFromElement(elt); + } } diff --git a/platform/core-impl/src/com/intellij/codeInspection/SuppressionUtilCore.java b/platform/core-impl/src/com/intellij/codeInspection/SuppressionUtilCore.java new file mode 100644 index 000000000000..55e33a400594 --- /dev/null +++ b/platform/core-impl/src/com/intellij/codeInspection/SuppressionUtilCore.java @@ -0,0 +1,22 @@ +/* + * Copyright 2000-2013 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.codeInspection; + +import org.jetbrains.annotations.NonNls; + +public class SuppressionUtilCore { + @NonNls public static final String SUPPRESS_INSPECTIONS_TAG_NAME = "noinspection"; +} diff --git a/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java b/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java index f07fd284f13a..66062ee4766c 100644 --- a/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java +++ b/platform/core-impl/src/com/intellij/ide/plugins/PluginManagerCore.java @@ -150,6 +150,18 @@ public class PluginManagerCore { return true; } + public static boolean enablePlugin(String id) { + if (!getDisabledPlugins().contains(id)) return false; + getDisabledPlugins().remove(id); + try { + saveDisabledPlugins(getDisabledPlugins(), false); + } + catch (IOException e) { + return false; + } + return true; + } + public static void saveDisabledPlugins(Collection<String> ids, boolean append) throws IOException { File plugins = new File(PathManager.getConfigPath(), DISABLED_PLUGINS_FILENAME); savePluginsList(ids, append, plugins); diff --git a/platform/core-impl/src/com/intellij/openapi/progress/util/ProgressWrapper.java b/platform/core-impl/src/com/intellij/openapi/progress/util/ProgressWrapper.java index f8520610ffda..a192ae0fd258 100644 --- a/platform/core-impl/src/com/intellij/openapi/progress/util/ProgressWrapper.java +++ b/platform/core-impl/src/com/intellij/openapi/progress/util/ProgressWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ import org.jetbrains.annotations.Nullable; public class ProgressWrapper extends AbstractProgressIndicatorBase { private final ProgressIndicator myOriginal; - private ProgressWrapper(@NotNull ProgressIndicator original) { + protected ProgressWrapper(@NotNull ProgressIndicator original) { myOriginal = original; } diff --git a/platform/editor-ui-api/src/com/intellij/ide/ui/UISettings.java b/platform/editor-ui-api/src/com/intellij/ide/ui/UISettings.java index 62799195b045..b7fb408d6318 100644 --- a/platform/editor-ui-api/src/com/intellij/ide/ui/UISettings.java +++ b/platform/editor-ui-api/src/com/intellij/ide/ui/UISettings.java @@ -69,6 +69,7 @@ public class UISettings implements PersistentStateComponent<UISettings>, Exporta public boolean WIDESCREEN_SUPPORT = false; public boolean LEFT_HORIZONTAL_SPLIT = false; public boolean RIGHT_HORIZONTAL_SPLIT = false; + public boolean SHOW_EDITOR_TOOLTIP = true; public boolean SHOW_MEMORY_INDICATOR = false; public boolean ALLOW_MERGE_BUTTONS = true; public boolean SHOW_MAIN_TOOLBAR = false; diff --git a/platform/platform-impl/src/com/intellij/codeStyle/AbstractConvertLineSeparatorsAction.java b/platform/editor-ui-ex/src/com/intellij/codeStyle/AbstractConvertLineSeparatorsAction.java index d09b3a2643a9..8f54a068385a 100644 --- a/platform/platform-impl/src/com/intellij/codeStyle/AbstractConvertLineSeparatorsAction.java +++ b/platform/editor-ui-ex/src/com/intellij/codeStyle/AbstractConvertLineSeparatorsAction.java @@ -21,12 +21,12 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.impl.LoadTextUtil; -import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.fileTypes.FileTypeRegistry; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.FileIndexFacade; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.LineSeparator; import com.intellij.util.Processor; @@ -40,14 +40,13 @@ import java.io.IOException; * @author Nikolai Matveev */ public abstract class AbstractConvertLineSeparatorsAction extends AnAction { - - private static Logger LOG = Logger.getInstance("#com.intellij.codeStyle.AbstractConvertLineSeparatorsAction"); + private static final Logger LOG = Logger.getInstance("#com.intellij.codeStyle.AbstractConvertLineSeparatorsAction"); @NotNull private final String mySeparator; protected AbstractConvertLineSeparatorsAction(@Nullable String text, @NotNull LineSeparator separator) { - this(separator.toString() + " - " + text, separator.getSeparatorString()); + this(separator + " - " + text, separator.getSeparatorString()); } protected AbstractConvertLineSeparatorsAction(@Nullable String text, @NotNull String separator) { @@ -98,9 +97,9 @@ public abstract class AbstractConvertLineSeparatorsAction extends AnAction { projectVirtualDirectory = null; } - final FileTypeManager fileTypeManager = FileTypeManager.getInstance(); + final FileTypeRegistry fileTypeManager = FileTypeRegistry.getInstance(); for (VirtualFile file : virtualFiles) { - VfsUtil.processFilesRecursively( + VfsUtilCore.processFilesRecursively( file, new Processor<VirtualFile>() { @Override @@ -115,7 +114,7 @@ public abstract class AbstractConvertLineSeparatorsAction extends AnAction { @Override public Boolean convert(VirtualFile dir) { return !dir.equals(projectVirtualDirectory) - && !fileTypeManager.isFileIgnored(dir.getName()); // Exclude files like '.git' + && !fileTypeManager.isFileIgnored(dir); // Exclude files like '.git' } } ); @@ -125,7 +124,7 @@ public abstract class AbstractConvertLineSeparatorsAction extends AnAction { public static boolean shouldProcess(@NotNull VirtualFile file, @NotNull Project project) { if (file.isDirectory() || !file.isWritable() - || FileTypeManager.getInstance().isFileIgnored(file) + || FileTypeRegistry.getInstance().isFileIgnored(file) || file.getFileType().isBinary() || file.equals(project.getProjectFile()) || file.equals(project.getWorkspaceFile())) diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/ExternalSystemTask.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/ExternalSystemTask.java index 4c93222793fc..2329fd38e67d 100644 --- a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/ExternalSystemTask.java +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/ExternalSystemTask.java @@ -38,6 +38,21 @@ public interface ExternalSystemTask { void execute(@NotNull ExternalSystemTaskNotificationListener... listeners); /** + * Cancels current task and updates given indicator's {@link ProgressIndicator#setText2(String) status} during that. + * + * @param indicator target progress indicator + * @param listeners callbacks to be notified on task execution update + */ + void cancel(@NotNull ProgressIndicator indicator, @NotNull ExternalSystemTaskNotificationListener... listeners); + + /** + * Cancels current task at the calling thread, i.e. the call to this method blocks. + * + * @param listeners callbacks to be notified about the task execution update + */ + void cancel(@NotNull ExternalSystemTaskNotificationListener... listeners); + + /** * Forces current task to refresh {@link #getState() its state}. */ void refreshState(); diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/ExternalSystemTaskState.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/ExternalSystemTaskState.java index 315a7a6fecf6..2df96affc604 100644 --- a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/ExternalSystemTaskState.java +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/ExternalSystemTaskState.java @@ -21,5 +21,5 @@ package com.intellij.openapi.externalSystem.model.task; */ public enum ExternalSystemTaskState { - NOT_STARTED, IN_PROGRESS, FINISHED, FAILED + NOT_STARTED, IN_PROGRESS, FINISHED, FAILED, CANCELING, CANCELED } diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/ExternalSystemTaskType.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/ExternalSystemTaskType.java index 490e8a5dfe76..81ce2f65a230 100644 --- a/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/ExternalSystemTaskType.java +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/model/task/ExternalSystemTaskType.java @@ -1,7 +1,7 @@ package com.intellij.openapi.externalSystem.model.task; /** - * Enumerates interested types of tasks that may be enqueued to Gradle API. + * Enumerates interested types of tasks that may be enqueued to external systems API. * * @author Denis Zhdanov * @since 11/10/11 9:07 AM diff --git a/platform/external-system-api/src/com/intellij/openapi/externalSystem/task/ExternalSystemTaskManager.java b/platform/external-system-api/src/com/intellij/openapi/externalSystem/task/ExternalSystemTaskManager.java index 4629e383795c..df27cdb7a382 100644 --- a/platform/external-system-api/src/com/intellij/openapi/externalSystem/task/ExternalSystemTaskManager.java +++ b/platform/external-system-api/src/com/intellij/openapi/externalSystem/task/ExternalSystemTaskManager.java @@ -40,4 +40,8 @@ public interface ExternalSystemTaskManager<S extends ExternalSystemExecutionSett @Nullable String debuggerSetup, @NotNull ExternalSystemTaskNotificationListener listener) throws ExternalSystemException; + + void cancelTask(@NotNull ExternalSystemTaskId id, + @NotNull ExternalSystemTaskNotificationListener listener) + throws ExternalSystemException; } diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/AbstractExternalSystemFacadeImpl.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/AbstractExternalSystemFacadeImpl.java index 98deeacde3b5..5ff4b7228d96 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/AbstractExternalSystemFacadeImpl.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/AbstractExternalSystemFacadeImpl.java @@ -187,7 +187,16 @@ public abstract class AbstractExternalSystemFacadeImpl<S extends ExternalSystemE myProjectResolver.setNotificationListener(listener); myTaskManager.setNotificationListener(listener); } - + + @Override + public void cancelTask(@NotNull ExternalSystemTaskId id) throws RemoteException { + if(id.getType() == ExternalSystemTaskType.RESOLVE_PROJECT) { + myProjectResolver.cancelTask(id); + } else{ + myTaskManager.cancelTask(id); + } + } + private static class SwallowingNotificationListener implements ExternalSystemTaskNotificationListener { @NotNull private final RemoteExternalSystemProgressNotificationManager myManager; diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/RemoteExternalSystemFacade.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/RemoteExternalSystemFacade.java index 10c2e42d87c5..5a095f740069 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/RemoteExternalSystemFacade.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/RemoteExternalSystemFacade.java @@ -1,12 +1,12 @@ package com.intellij.openapi.externalSystem.service; -import com.intellij.openapi.externalSystem.service.internal.ExternalSystemTaskAware; +import com.intellij.openapi.externalSystem.model.settings.ExternalSystemExecutionSettings; import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId; import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType; -import com.intellij.openapi.externalSystem.model.settings.ExternalSystemExecutionSettings; -import com.intellij.openapi.externalSystem.service.remote.RemoteExternalSystemTaskManager; +import com.intellij.openapi.externalSystem.service.internal.ExternalSystemTaskAware; import com.intellij.openapi.externalSystem.service.remote.RemoteExternalSystemProgressNotificationManager; import com.intellij.openapi.externalSystem.service.remote.RemoteExternalSystemProjectResolver; +import com.intellij.openapi.externalSystem.service.remote.RemoteExternalSystemTaskManager; import org.jetbrains.annotations.NotNull; import java.rmi.Remote; @@ -56,6 +56,10 @@ public interface RemoteExternalSystemFacade<S extends ExternalSystemExecutionSet return false; } + @Override + public void cancelTask(@NotNull ExternalSystemTaskId id) throws RemoteException { + } + @NotNull @Override public Map<ExternalSystemTaskType, Set<ExternalSystemTaskId>> getTasksInProgress() throws RemoteException { diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/RemoteExternalSystemFacadeImpl.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/RemoteExternalSystemFacadeImpl.java index 16d181ea7bc3..625c83c2a0d9 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/RemoteExternalSystemFacadeImpl.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/RemoteExternalSystemFacadeImpl.java @@ -18,6 +18,8 @@ package com.intellij.openapi.externalSystem.service; import com.intellij.openapi.externalSystem.model.settings.ExternalSystemExecutionSettings; import com.intellij.openapi.externalSystem.service.project.ExternalSystemProjectResolver; import com.intellij.openapi.externalSystem.task.ExternalSystemTaskManager; +import com.intellij.openapi.externalSystem.util.ExternalSystemConstants; +import com.intellij.openapi.util.registry.Registry; import com.intellij.util.Alarm; import org.jetbrains.annotations.NotNull; @@ -82,6 +84,11 @@ public class RemoteExternalSystemFacadeImpl<S extends ExternalSystemExecutionSet )); } + // running the code indicates remote communication mode with external system + Registry.get( + System.getProperty(ExternalSystemConstants.EXTERNAL_SYSTEM_ID_KEY) + + ExternalSystemConstants.USE_IN_PROCESS_COMMUNICATION_REGISTRY_KEY_SUFFIX).setValue(false); + RemoteExternalSystemFacadeImpl facade = new RemoteExternalSystemFacadeImpl(resolverClass, buildManagerClass); facade.init(); start(facade); diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/AbstractExternalSystemTaskConfigurationType.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/AbstractExternalSystemTaskConfigurationType.java index 5f050bb0b276..2d8377bbf2e5 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/AbstractExternalSystemTaskConfigurationType.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/AbstractExternalSystemTaskConfigurationType.java @@ -129,7 +129,10 @@ public abstract class AbstractExternalSystemTaskConfigurationType implements Con Map<String/* project dir path */, String/* project file path */> rootProjectPaths = ContainerUtilRt.newHashMap(); for (ExternalProjectSettings projectSettings : s.getLinkedProjectsSettings()) { String path = projectSettings.getExternalProjectPath(); - rootProjectPaths.put(new File(path).getParentFile().getAbsolutePath(), path); + if(path == null) continue; + final File rootProjectPathFile = new File(path).getParentFile(); + if(rootProjectPathFile == null) continue; + rootProjectPaths.put(rootProjectPathFile.getAbsolutePath(), path); } String rootProjectPath = null; diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/ExternalSystemRunConfiguration.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/ExternalSystemRunConfiguration.java index be7193b37bfb..a9bb3df568a5 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/ExternalSystemRunConfiguration.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/execution/ExternalSystemRunConfiguration.java @@ -4,7 +4,10 @@ import com.intellij.execution.DefaultExecutionResult; import com.intellij.execution.ExecutionException; import com.intellij.execution.ExecutionResult; import com.intellij.execution.Executor; -import com.intellij.execution.configurations.*; +import com.intellij.execution.configurations.ConfigurationFactory; +import com.intellij.execution.configurations.LocatableConfigurationBase; +import com.intellij.execution.configurations.RunConfiguration; +import com.intellij.execution.configurations.RunProfileState; import com.intellij.execution.executors.DefaultDebugExecutor; import com.intellij.execution.filters.TextConsoleBuilderImpl; import com.intellij.execution.process.ProcessHandler; @@ -137,8 +140,6 @@ public class ExternalSystemRunConfiguration extends LocatableConfigurationBase { public ExecutionResult execute(Executor executor, @NotNull ProgramRunner runner) throws ExecutionException { ExternalSystemUtil.updateRecentTasks(new ExternalTaskExecutionInfo(mySettings.clone(), executor.getId()), myProject); ConsoleView console = new TextConsoleBuilderImpl(myProject).getConsole(); - final MyProcessHandler processHandler = new MyProcessHandler(); - console.attachToProcess(processHandler); final List<ExternalTaskPojo> tasks = ContainerUtilRt.newArrayList(); for (String taskName : mySettings.getTaskNames()) { tasks.add(new ExternalTaskPojo(taskName, mySettings.getExternalProjectPath(), null)); @@ -156,6 +157,10 @@ public class ExternalSystemRunConfiguration extends LocatableConfigurationBase { tasks, mySettings.getVmOptions(), debuggerSetup); + + final MyProcessHandler processHandler = new MyProcessHandler(task); + console.attachToProcess(processHandler); + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { @Override public void run() { @@ -190,14 +195,37 @@ public class ExternalSystemRunConfiguration extends LocatableConfigurationBase { return new DefaultExecutionResult(console, processHandler); } } - + private static class MyProcessHandler extends ProcessHandler { + private final ExternalSystemExecuteTaskTask myTask; + + public MyProcessHandler(ExternalSystemExecuteTaskTask task) { + myTask = task; + } + @Override protected void destroyProcessImpl() { } @Override protected void detachProcessImpl() { + myTask.cancel(new ExternalSystemTaskNotificationListenerAdapter() { + + private boolean myResetGreeting = true; + + @Override + public void onTaskOutput(@NotNull ExternalSystemTaskId id, @NotNull String text, boolean stdOut) { + if (myResetGreeting) { + notifyTextAvailable("\r", ProcessOutputTypes.SYSTEM); + myResetGreeting = false; + } + notifyTextAvailable(text, stdOut ? ProcessOutputTypes.STDOUT : ProcessOutputTypes.STDERR); + } + + @Override + public void onEnd(@NotNull ExternalSystemTaskId id) { + } + }); notifyProcessDetached(); } diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/internal/AbstractExternalSystemTask.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/internal/AbstractExternalSystemTask.java index e7367232bfce..562cf4d8e6bb 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/internal/AbstractExternalSystemTask.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/internal/AbstractExternalSystemTask.java @@ -143,6 +143,50 @@ public abstract class AbstractExternalSystemTask implements ExternalSystemTask { protected abstract void doExecute() throws Exception; + @Override + public void cancel(@NotNull final ProgressIndicator indicator, @NotNull ExternalSystemTaskNotificationListener... listeners) { + indicator.setIndeterminate(true); + ExternalSystemTaskNotificationListenerAdapter adapter = new ExternalSystemTaskNotificationListenerAdapter() { + @Override + public void onStatusChange(@NotNull ExternalSystemTaskNotificationEvent event) { + indicator.setText(wrapProgressText(event.getDescription())); + } + }; + final ExternalSystemTaskNotificationListener[] ls; + if (listeners.length > 0) { + ls = ArrayUtil.append(listeners, adapter); + } + else { + ls = new ExternalSystemTaskNotificationListener[] { adapter }; + } + + cancel(ls); + } + + @Override + public void cancel(@NotNull ExternalSystemTaskNotificationListener... listeners) { + ExternalSystemProgressNotificationManager progressManager = ServiceManager.getService(ExternalSystemProgressNotificationManager.class); + for (ExternalSystemTaskNotificationListener listener : listeners) { + progressManager.addNotificationListener(getId(), listener); + } + try { + doCancel(); + } + catch (Throwable e) { + setState(ExternalSystemTaskState.FAILED); + myError.set(e); + LOG.warn(e); + } + finally { + for (ExternalSystemTaskNotificationListener listener : listeners) { + progressManager.removeNotificationListener(listener); + } + } + } + + protected abstract void doCancel() throws Exception; + + @NotNull protected String wrapProgressText(@NotNull String text) { return ExternalSystemBundle.message("progress.update.text", getExternalSystemId(), text); diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/internal/ExternalSystemExecuteTaskTask.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/internal/ExternalSystemExecuteTaskTask.java index 50f882159eeb..8b5cb63beb04 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/internal/ExternalSystemExecuteTaskTask.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/internal/ExternalSystemExecuteTaskTask.java @@ -106,4 +106,18 @@ public class ExternalSystemExecuteTaskTask extends AbstractExternalSystemTask { setState(ExternalSystemTaskState.FINISHED); } } + + @Override + protected void doCancel() throws Exception { + final ExternalSystemFacadeManager manager = ServiceManager.getService(ExternalSystemFacadeManager.class); + RemoteExternalSystemFacade facade = manager.getFacade(getIdeProject(), getExternalProjectPath(), getExternalSystemId()); + RemoteExternalSystemTaskManager taskManager = facade.getTaskManager(); + setState(ExternalSystemTaskState.CANCELING); + try { + taskManager.cancelTask(getId()); + } + finally { + setState(ExternalSystemTaskState.CANCELED); + } + } } diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/internal/ExternalSystemResolveProjectTask.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/internal/ExternalSystemResolveProjectTask.java index 9f7f08da0bbb..fe0d5466dfa2 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/internal/ExternalSystemResolveProjectTask.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/internal/ExternalSystemResolveProjectTask.java @@ -62,6 +62,20 @@ public class ExternalSystemResolveProjectTask extends AbstractExternalSystemTask } } + protected void doCancel() throws Exception { + final ExternalSystemFacadeManager manager = ServiceManager.getService(ExternalSystemFacadeManager.class); + Project ideProject = getIdeProject(); + RemoteExternalSystemProjectResolver resolver = manager.getFacade(ideProject, myProjectPath, getExternalSystemId()).getResolver(); + + setState(ExternalSystemTaskState.CANCELING); + try { + resolver.cancelTask(getId()); + } + finally { + setState(ExternalSystemTaskState.CANCELED); + } + } + @Nullable public DataNode<ProjectData> getExternalProject() { return myExternalProject.get(); diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/internal/ExternalSystemTaskAware.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/internal/ExternalSystemTaskAware.java index 73947b4f82d1..7ecc0c6028f9 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/internal/ExternalSystemTaskAware.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/internal/ExternalSystemTaskAware.java @@ -27,6 +27,17 @@ public interface ExternalSystemTaskAware { boolean isTaskInProgress(@NotNull ExternalSystemTaskId id) throws RemoteException; /** + * Allows to cancel the target task by the current service. + * + * + * @param id target task's id + * @return <code>true</code> if a task was successfully canceled; + * <code>false</code> otherwise + * @throws RemoteException as required by RMI + */ + void cancelTask(@NotNull ExternalSystemTaskId id) throws RemoteException; + + /** * Allows to ask current service for all tasks being executed at the moment. * * @return ids of all tasks being executed at the moment grouped by type diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/ProjectStructureHelper.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/ProjectStructureHelper.java index 729d16d0d9fc..64d68884e0dc 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/ProjectStructureHelper.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/ProjectStructureHelper.java @@ -23,7 +23,7 @@ import org.jetbrains.annotations.Nullable; */ public class ProjectStructureHelper { - @NotNull private final PlatformFacade myFacade; + @NotNull private final PlatformFacade myFacade; @NotNull private final ExternalLibraryPathTypeMapper myLibraryPathTypeMapper; public ProjectStructureHelper(@NotNull PlatformFacade facade, @NotNull ExternalLibraryPathTypeMapper mapper) { @@ -56,10 +56,10 @@ public class ProjectStructureHelper { * <p/> * This methods serves as an utility which tries to find a library by it's given base name. * - * @param baseName base name of the target library - * @param ideProject target ide project - * @return target library for the given base name if there is one and only one library for it; - * <code>null</code> otherwise (if there are no libraries or more than one library for the given base name) + * @param baseName base name of the target library + * @param ideProject target ide project + * @return target library for the given base name if there is one and only one library for it; + * <code>null</code> otherwise (if there are no libraries or more than one library for the given base name) */ @Nullable public Library findIdeLibraryByBaseName(@NotNull String baseName, @NotNull Project ideProject) { @@ -93,8 +93,7 @@ public class ProjectStructureHelper { public Library findIdeLibrary(@NotNull String libraryName, @NotNull OrderRootType jarType, @NotNull String jarPath, - @NotNull Project ideProject) - { + @NotNull Project ideProject) { Library library = findIdeLibrary(libraryName, ideProject); if (library == null) { return null; @@ -110,8 +109,7 @@ public class ProjectStructureHelper { @Nullable public LibraryOrderEntry findIdeLibraryDependency(@NotNull final String moduleName, @NotNull final String libraryName, - @NotNull Project ideProject) - { + @NotNull Project ideProject) { final Module ideModule = findIdeModule(moduleName, ideProject); if (ideModule == null) { return null; @@ -137,8 +135,7 @@ public class ProjectStructureHelper { @Nullable public ModuleLibraryOrderEntryImpl findIdeModuleLocalLibraryDependency(@NotNull final String moduleName, @NotNull final String libraryName, - @NotNull Project ideProject) - { + @NotNull Project ideProject) { final Module ideModule = findIdeModule(moduleName, ideProject); if (ideModule == null) { return null; @@ -168,8 +165,7 @@ public class ProjectStructureHelper { @SuppressWarnings("MethodMayBeStatic") @Nullable public LibraryOrderEntry findIdeLibraryDependency(@NotNull final String libraryName, - @NotNull ModifiableRootModel model) - { + @NotNull ModifiableRootModel model) { for (OrderEntry entry : model.getOrderEntries()) { if (entry instanceof LibraryOrderEntry) { LibraryOrderEntry candidate = (LibraryOrderEntry)entry; @@ -181,46 +177,14 @@ public class ProjectStructureHelper { return null; } - @Nullable - public ModuleOrderEntry findIdeModuleDependency(@NotNull final ModuleDependencyData gradleDependency, @NotNull Project ideProject) { - return findIdeModuleDependency(gradleDependency.getOwnerModule().getName(), gradleDependency.getTarget().getName(), ideProject); - } - - @Nullable - public ModuleOrderEntry findIdeModuleDependency(@NotNull final String ownerModuleName, - @NotNull final String dependencyModuleName, - @NotNull Project ideProject) - { - final Module ideOwnerModule = findIdeModule(ownerModuleName, ideProject); - if (ideOwnerModule == null) { - return null; - } - - RootPolicy<ModuleOrderEntry> visitor = new RootPolicy<ModuleOrderEntry>() { - @Override - public ModuleOrderEntry visitModuleOrderEntry(ModuleOrderEntry ideDependency, ModuleOrderEntry value) { - if (dependencyModuleName.equals(ideDependency.getModuleName())) { - return ideDependency; - } - return value; - } - }; - for (OrderEntry orderEntry : myFacade.getOrderEntries(ideOwnerModule)) { - final ModuleOrderEntry result = orderEntry.accept(visitor, null); - if (result != null) { - return result; - } - } - return null; - } - @SuppressWarnings("MethodMayBeStatic") @Nullable public ModuleOrderEntry findIdeModuleDependency(@NotNull ModuleDependencyData dependency, @NotNull ModifiableRootModel model) { for (OrderEntry entry : model.getOrderEntries()) { if (entry instanceof ModuleOrderEntry) { ModuleOrderEntry candidate = (ModuleOrderEntry)entry; - if (dependency.getName().equals(candidate.getModuleName())) { + if (dependency.getName().equals(candidate.getModuleName()) && + dependency.getScope().equals(candidate.getScope())) { return candidate; } } diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/AbstractDependencyDataService.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/AbstractDependencyDataService.java index b21561c00068..3defa33fa403 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/AbstractDependencyDataService.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/AbstractDependencyDataService.java @@ -126,7 +126,15 @@ public abstract class AbstractDependencyDataService<E extends AbstractDependency // that's why we can't use target dependency object as is but need to get a reference to the current // entry object from the model instead. for (OrderEntry entry : moduleRootModel.getOrderEntries()) { - if (entry.getPresentableName().equals(dependency.getPresentableName())) { + if (entry instanceof ExportableOrderEntry) { + ExportableOrderEntry orderEntry = (ExportableOrderEntry)entry; + if (orderEntry.getPresentableName().equals(dependency.getPresentableName()) && + orderEntry.getScope().equals(dependency.getScope())) { + moduleRootModel.removeOrderEntry(entry); + break; + } + } + else if (entry.getPresentableName().equals(dependency.getPresentableName())) { moduleRootModel.removeOrderEntry(entry); break; } diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/ModuleDependencyDataService.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/ModuleDependencyDataService.java index 34130a2ed289..be88a64df782 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/ModuleDependencyDataService.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/project/manage/ModuleDependencyDataService.java @@ -28,10 +28,8 @@ import com.intellij.openapi.externalSystem.util.ExternalSystemConstants; import com.intellij.openapi.externalSystem.util.Order; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.Project; -import com.intellij.openapi.roots.ModifiableRootModel; -import com.intellij.openapi.roots.ModuleOrderEntry; -import com.intellij.openapi.roots.ModuleRootManager; -import com.intellij.openapi.roots.OrderEntry; +import com.intellij.openapi.roots.*; +import com.intellij.openapi.util.Pair; import com.intellij.util.BooleanFunction; import com.intellij.util.containers.ContainerUtilRt; import org.jetbrains.annotations.NotNull; @@ -94,11 +92,11 @@ public class ModuleDependencyDataService extends AbstractDependencyDataService<M @Override public void run() { ModuleRootManager moduleRootManager = ModuleRootManager.getInstance(module); - Map<String /* dependency module name */, ModuleOrderEntry> toRemove = ContainerUtilRt.newHashMap(); + Map<Pair<String /* dependency module name */, /* dependency module scope */DependencyScope> , ModuleOrderEntry> toRemove = ContainerUtilRt.newHashMap(); for (OrderEntry entry : moduleRootManager.getOrderEntries()) { if (entry instanceof ModuleOrderEntry) { ModuleOrderEntry e = (ModuleOrderEntry)entry; - toRemove.put(e.getModuleName(), e); + toRemove.put(Pair.create(e.getModuleName(), e.getScope()), e); } } @@ -106,7 +104,7 @@ public class ModuleDependencyDataService extends AbstractDependencyDataService<M try { for (DataNode<ModuleDependencyData> dependencyNode : toImport) { final ModuleDependencyData dependencyData = dependencyNode.getData(); - toRemove.remove(dependencyData.getName()); + toRemove.remove(Pair.create(dependencyData.getName(), dependencyData.getScope())); final String moduleName = dependencyData.getName(); Module ideDependencyModule = myProjectStructureHelper.findIdeModule(moduleName, module.getProject()); if (ideDependencyModule == null) { diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/RemoteExternalSystemProjectResolver.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/RemoteExternalSystemProjectResolver.java index 558fa5efed1c..2e5ce6469684 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/RemoteExternalSystemProjectResolver.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/RemoteExternalSystemProjectResolver.java @@ -20,9 +20,9 @@ import com.intellij.openapi.externalSystem.model.ExternalSystemException; import com.intellij.openapi.externalSystem.model.project.ProjectData; import com.intellij.openapi.externalSystem.model.settings.ExternalSystemExecutionSettings; import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId; +import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener; import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType; import com.intellij.openapi.externalSystem.service.RemoteExternalSystemService; -import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -64,6 +64,10 @@ public interface RemoteExternalSystemProjectResolver<S extends ExternalSystemExe return false; } + @Override + public void cancelTask(@NotNull ExternalSystemTaskId id) throws RemoteException { + } + @NotNull @Override public Map<ExternalSystemTaskType, Set<ExternalSystemTaskId>> getTasksInProgress() throws RemoteException { diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/RemoteExternalSystemProjectResolverImpl.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/RemoteExternalSystemProjectResolverImpl.java index ef892d295274..2bc337199d88 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/RemoteExternalSystemProjectResolverImpl.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/RemoteExternalSystemProjectResolverImpl.java @@ -42,4 +42,11 @@ public class RemoteExternalSystemProjectResolverImpl<S extends ExternalSystemExe } }); } + + @Nullable + @Override + public void cancelTask(@NotNull final ExternalSystemTaskId id) + throws ExternalSystemException, IllegalArgumentException, IllegalStateException { + // canceling of the project resolving does not support yet + } } diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/RemoteExternalSystemTaskManager.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/RemoteExternalSystemTaskManager.java index ff94b6aa6beb..6f9d4e3b596c 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/RemoteExternalSystemTaskManager.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/RemoteExternalSystemTaskManager.java @@ -51,6 +51,11 @@ public interface RemoteExternalSystemTaskManager<S extends ExternalSystemExecuti } @Override + public void cancelTask(@NotNull ExternalSystemTaskId id) throws RemoteException, ExternalSystemException + { + } + + @Override public void setSettings(@NotNull ExternalSystemExecutionSettings settings) throws RemoteException { } @@ -76,4 +81,6 @@ public interface RemoteExternalSystemTaskManager<S extends ExternalSystemExecuti @Nullable S settings, @Nullable String vmOptions, @Nullable String debuggerSetup) throws RemoteException, ExternalSystemException; + + void cancelTask(@NotNull ExternalSystemTaskId id) throws RemoteException, ExternalSystemException; } diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/RemoteExternalSystemTaskManagerImpl.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/RemoteExternalSystemTaskManagerImpl.java index dec99ec0973e..4e5a45ee915e 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/RemoteExternalSystemTaskManagerImpl.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/RemoteExternalSystemTaskManagerImpl.java @@ -15,10 +15,10 @@ */ package com.intellij.openapi.externalSystem.service.remote; -import com.intellij.openapi.externalSystem.task.ExternalSystemTaskManager; import com.intellij.openapi.externalSystem.model.ExternalSystemException; import com.intellij.openapi.externalSystem.model.settings.ExternalSystemExecutionSettings; import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId; +import com.intellij.openapi.externalSystem.task.ExternalSystemTaskManager; import com.intellij.util.Producer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -57,4 +57,10 @@ public class RemoteExternalSystemTaskManagerImpl<S extends ExternalSystemExecuti } }); } + + @Override + public void cancelTask(@NotNull final ExternalSystemTaskId id) throws RemoteException, ExternalSystemException + { + myDelegate.cancelTask(id, getNotificationListener()); + } } diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/wrapper/ExternalSystemFacadeWrapper.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/wrapper/ExternalSystemFacadeWrapper.java index 81ddcdca9405..231bf0697ae7 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/wrapper/ExternalSystemFacadeWrapper.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/wrapper/ExternalSystemFacadeWrapper.java @@ -1,12 +1,12 @@ package com.intellij.openapi.externalSystem.service.remote.wrapper; +import com.intellij.openapi.externalSystem.model.settings.ExternalSystemExecutionSettings; import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId; import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType; import com.intellij.openapi.externalSystem.service.RemoteExternalSystemFacade; -import com.intellij.openapi.externalSystem.model.settings.ExternalSystemExecutionSettings; -import com.intellij.openapi.externalSystem.service.remote.RemoteExternalSystemTaskManager; import com.intellij.openapi.externalSystem.service.remote.RemoteExternalSystemProgressNotificationManager; import com.intellij.openapi.externalSystem.service.remote.RemoteExternalSystemProjectResolver; +import com.intellij.openapi.externalSystem.service.remote.RemoteExternalSystemTaskManager; import org.jetbrains.annotations.NotNull; import java.rmi.RemoteException; @@ -72,4 +72,9 @@ public class ExternalSystemFacadeWrapper<S extends ExternalSystemExecutionSettin public Map<ExternalSystemTaskType, Set<ExternalSystemTaskId>> getTasksInProgress() throws RemoteException { return myDelegate.getTasksInProgress(); } + + @Override + public void cancelTask(@NotNull ExternalSystemTaskId id) throws RemoteException { + myDelegate.cancelTask(id); + } } diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/wrapper/ExternalSystemProjectResolverWrapper.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/wrapper/ExternalSystemProjectResolverWrapper.java index d9bb08cd9ecb..47a7a138ac9a 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/wrapper/ExternalSystemProjectResolverWrapper.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/wrapper/ExternalSystemProjectResolverWrapper.java @@ -3,11 +3,11 @@ package com.intellij.openapi.externalSystem.service.remote.wrapper; import com.intellij.openapi.externalSystem.model.DataNode; import com.intellij.openapi.externalSystem.model.ExternalSystemException; import com.intellij.openapi.externalSystem.model.project.ProjectData; -import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId; import com.intellij.openapi.externalSystem.model.settings.ExternalSystemExecutionSettings; +import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId; +import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener; import com.intellij.openapi.externalSystem.service.remote.RemoteExternalSystemProgressNotificationManager; import com.intellij.openapi.externalSystem.service.remote.RemoteExternalSystemProjectResolver; -import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListener; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -52,4 +52,16 @@ public class ExternalSystemProjectResolverWrapper<S extends ExternalSystemExecut myProgressManager.onEnd(id); } } + + @Override + public void cancelTask(@NotNull ExternalSystemTaskId id) + throws ExternalSystemException, IllegalArgumentException, IllegalStateException, RemoteException { + myProgressManager.onQueued(id); + try { + getDelegate().cancelTask(id); + } + finally { + myProgressManager.onEnd(id); + } + } } diff --git a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/wrapper/ExternalSystemTaskManagerWrapper.java b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/wrapper/ExternalSystemTaskManagerWrapper.java index 0ff6b38889dc..7cb04bd38d3b 100644 --- a/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/wrapper/ExternalSystemTaskManagerWrapper.java +++ b/platform/external-system-impl/src/com/intellij/openapi/externalSystem/service/remote/wrapper/ExternalSystemTaskManagerWrapper.java @@ -60,4 +60,17 @@ public class ExternalSystemTaskManagerWrapper<S extends ExternalSystemExecutionS myProgressManager.onEnd(id); } } + + @Override + public void cancelTask(@NotNull ExternalSystemTaskId id) throws RemoteException, ExternalSystemException + { + myProgressManager.onQueued(id); + try { + getDelegate().cancelTask(id); + } + finally { + myProgressManager.onEnd(id); + } + } } + diff --git a/platform/icons/src/actions/findWhite.png b/platform/icons/src/actions/findWhite.png Binary files differnew file mode 100644 index 000000000000..528960e8803d --- /dev/null +++ b/platform/icons/src/actions/findWhite.png diff --git a/platform/icons/src/actions/findWhite@2x.png b/platform/icons/src/actions/findWhite@2x.png Binary files differnew file mode 100644 index 000000000000..b1c125f24eff --- /dev/null +++ b/platform/icons/src/actions/findWhite@2x.png diff --git a/platform/icons/src/nodes/treeDownArrow.png b/platform/icons/src/nodes/treeDownArrow.png Binary files differnew file mode 100644 index 000000000000..f10b84def7e2 --- /dev/null +++ b/platform/icons/src/nodes/treeDownArrow.png diff --git a/platform/icons/src/nodes/treeDownArrow@2x.png b/platform/icons/src/nodes/treeDownArrow@2x.png Binary files differnew file mode 100644 index 000000000000..fa2ad0b87102 --- /dev/null +++ b/platform/icons/src/nodes/treeDownArrow@2x.png diff --git a/platform/icons/src/nodes/treeRightArrow.png b/platform/icons/src/nodes/treeRightArrow.png Binary files differnew file mode 100644 index 000000000000..1bff9016044d --- /dev/null +++ b/platform/icons/src/nodes/treeRightArrow.png diff --git a/platform/icons/src/nodes/treeRightArrow@2x.png b/platform/icons/src/nodes/treeRightArrow@2x.png Binary files differnew file mode 100644 index 000000000000..1b034a9c4444 --- /dev/null +++ b/platform/icons/src/nodes/treeRightArrow@2x.png diff --git a/platform/indexing-api/src/com/intellij/util/indexing/IdFilter.java b/platform/indexing-api/src/com/intellij/util/indexing/IdFilter.java index 799201064a6f..d402cbf8db72 100644 --- a/platform/indexing-api/src/com/intellij/util/indexing/IdFilter.java +++ b/platform/indexing-api/src/com/intellij/util/indexing/IdFilter.java @@ -38,9 +38,9 @@ public abstract class IdFilter { ContentIterator iterator = new ContentIterator() { @Override public boolean processFile(VirtualFile fileOrDir) { - idSet.set( - ((VirtualFileWithId)fileOrDir).getId() - ); + int id = ((VirtualFileWithId)fileOrDir).getId(); + if (id < 0) id = -id; // workaround for encountering invalid files, see EA-49915, EA-50599 + idSet.set(id); ProgressManager.checkCanceled(); return true; } diff --git a/platform/lang-api/src/com/intellij/codeInsight/TailType.java b/platform/lang-api/src/com/intellij/codeInsight/TailType.java index 18b3bdee5e9a..881726046811 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/TailType.java +++ b/platform/lang-api/src/com/intellij/codeInsight/TailType.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -26,7 +26,7 @@ import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.codeStyle.CommonCodeStyleSettings; -import com.intellij.psi.util.PsiUtilBase; +import com.intellij.psi.util.PsiUtilCore; import org.jetbrains.annotations.NotNull; /** @@ -99,7 +99,7 @@ public abstract class TailType { protected static CommonCodeStyleSettings getLocalCodeStyleSettings(Editor editor, int tailOffset) { final PsiFile psiFile = getFile(editor); - Language language = PsiUtilBase.getLanguageAtOffset(psiFile, tailOffset); + Language language = PsiUtilCore.getLanguageAtOffset(psiFile, tailOffset); final Project project = editor.getProject(); assert project != null; diff --git a/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionContributor.java b/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionContributor.java index f9e8f09831c7..56e474f74e6f 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionContributor.java +++ b/platform/lang-api/src/com/intellij/codeInsight/completion/CompletionContributor.java @@ -28,7 +28,7 @@ import com.intellij.openapi.util.Pair; import com.intellij.patterns.ElementPattern; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiReference; -import com.intellij.psi.util.PsiUtilBase; +import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.Consumer; import com.intellij.util.ProcessingContext; import com.intellij.util.containers.MultiMap; @@ -237,7 +237,7 @@ public abstract class CompletionContributor { return ApplicationManager.getApplication().runReadAction(new Computable<List<CompletionContributor>>() { @Override public List<CompletionContributor> compute() { - return forLanguage(PsiUtilBase.getLanguageAtOffset(parameters.getPosition().getContainingFile(), parameters.getOffset())); + return forLanguage(PsiUtilCore.getLanguageAtOffset(parameters.getPosition().getContainingFile(), parameters.getOffset())); } }); } diff --git a/platform/lang-api/src/com/intellij/codeInsight/completion/InsertionContext.java b/platform/lang-api/src/com/intellij/codeInsight/completion/InsertionContext.java index f75085af3ec2..cc394daf7645 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/completion/InsertionContext.java +++ b/platform/lang-api/src/com/intellij/codeInsight/completion/InsertionContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -24,7 +24,7 @@ import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.codeStyle.CommonCodeStyleSettings; -import com.intellij.psi.util.PsiUtilBase; +import com.intellij.psi.util.PsiUtilCore; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -138,7 +138,7 @@ public class InsertionContext { } public CommonCodeStyleSettings getCodeStyleSettings() { - Language lang = PsiUtilBase.getLanguageAtOffset(getFile(), getTailOffset()); + Language lang = PsiUtilCore.getLanguageAtOffset(getFile(), getTailOffset()); return CodeStyleSettingsManager.getSettings(getProject()).getCommonSettings(lang); } } diff --git a/platform/lang-api/src/com/intellij/codeInsight/completion/SkipAutopopupInStrings.java b/platform/lang-api/src/com/intellij/codeInsight/completion/SkipAutopopupInStrings.java index 8f3714dbda93..437cef2f02f4 100644 --- a/platform/lang-api/src/com/intellij/codeInsight/completion/SkipAutopopupInStrings.java +++ b/platform/lang-api/src/com/intellij/codeInsight/completion/SkipAutopopupInStrings.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2011 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -22,7 +22,7 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiErrorElement; import com.intellij.psi.PsiFile; import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.psi.util.PsiUtilBase; +import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.ThreeState; import org.jetbrains.annotations.NotNull; @@ -47,7 +47,7 @@ public class SkipAutopopupInStrings extends CompletionConfidence { } public static boolean isInStringLiteral(PsiElement element) { - ParserDefinition definition = LanguageParserDefinitions.INSTANCE.forLanguage(PsiUtilBase.findLanguageFromElement(element)); + ParserDefinition definition = LanguageParserDefinitions.INSTANCE.forLanguage(PsiUtilCore.findLanguageFromElement(element)); if (definition == null) { return false; } diff --git a/platform/lang-api/src/com/intellij/formatting/FormattingDocumentModel.java b/platform/lang-api/src/com/intellij/formatting/FormattingDocumentModel.java index 8e75e780760b..77397261c4ca 100644 --- a/platform/lang-api/src/com/intellij/formatting/FormattingDocumentModel.java +++ b/platform/lang-api/src/com/intellij/formatting/FormattingDocumentModel.java @@ -54,6 +54,7 @@ public interface FormattingDocumentModel { */ int getTextLength(); + @NotNull Document getDocument(); /** diff --git a/platform/lang-api/src/com/intellij/framework/FrameworkGroupVersion.java b/platform/lang-api/src/com/intellij/framework/FrameworkGroupVersion.java deleted file mode 100644 index 6ee7bd7e4529..000000000000 --- a/platform/lang-api/src/com/intellij/framework/FrameworkGroupVersion.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.intellij.framework; - -/** - * @author nik - */ -public interface FrameworkGroupVersion extends FrameworkVersion { -} diff --git a/platform/lang-api/src/com/intellij/framework/FrameworkVersion.java b/platform/lang-api/src/com/intellij/framework/FrameworkVersion.java index 7f6a45f468d1..e5c37e62dd76 100644 --- a/platform/lang-api/src/com/intellij/framework/FrameworkVersion.java +++ b/platform/lang-api/src/com/intellij/framework/FrameworkVersion.java @@ -20,13 +20,10 @@ import org.jetbrains.annotations.NotNull; /** * @author nik */ -public interface FrameworkVersion { +public interface FrameworkVersion extends PresentableVersion { @NotNull String getId(); @NotNull - String getPresentableName(); - - @NotNull FrameworkAvailabilityCondition getAvailabilityCondition(); } diff --git a/platform/lang-api/src/com/intellij/framework/PresentableVersion.java b/platform/lang-api/src/com/intellij/framework/PresentableVersion.java new file mode 100644 index 000000000000..e9e7c854a85e --- /dev/null +++ b/platform/lang-api/src/com/intellij/framework/PresentableVersion.java @@ -0,0 +1,32 @@ +/* + * Copyright 2000-2013 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.framework; + +import org.jetbrains.annotations.NotNull; + +/** + * @author Dmitry Avdeev + * Date: 09.10.13 + */ +public interface PresentableVersion { + + /** Full version name as presented in combos, like JavaEE 6 */ + @NotNull + String getPresentableName(); + + /** Just version number, like 2.2.1 */ + String getVersionNumber(); +} diff --git a/platform/lang-api/src/com/intellij/framework/library/FrameworkLibraryVersion.java b/platform/lang-api/src/com/intellij/framework/library/FrameworkLibraryVersion.java index 75c419ef8232..2ccc6ef99748 100644 --- a/platform/lang-api/src/com/intellij/framework/library/FrameworkLibraryVersion.java +++ b/platform/lang-api/src/com/intellij/framework/library/FrameworkLibraryVersion.java @@ -15,6 +15,7 @@ */ package com.intellij.framework.library; +import com.intellij.framework.PresentableVersion; import com.intellij.util.download.DownloadableFileSetDescription; import org.jetbrains.annotations.NotNull; @@ -23,7 +24,7 @@ import java.util.List; /** * @author nik */ -public interface FrameworkLibraryVersion extends DownloadableFileSetDescription { +public interface FrameworkLibraryVersion extends DownloadableFileSetDescription, PresentableVersion { @NotNull String getDefaultLibraryName(); diff --git a/platform/lang-api/src/com/intellij/ide/util/frameworkSupport/FrameworkRole.java b/platform/lang-api/src/com/intellij/ide/util/frameworkSupport/FrameworkRole.java new file mode 100644 index 000000000000..2331b27c9cae --- /dev/null +++ b/platform/lang-api/src/com/intellij/ide/util/frameworkSupport/FrameworkRole.java @@ -0,0 +1,49 @@ +/* + * Copyright 2000-2013 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.ide.util.frameworkSupport; + +import org.jetbrains.annotations.NotNull; + +/** + * @author Dmitry Avdeev + * Date: 04.10.13 + */ +public class FrameworkRole { + + public static FrameworkRole[] UNKNOWN = new FrameworkRole[0]; + + /** Groovy etc. */ +// public static FrameworkRole JVM_LANGUAGES = new FrameworkRole(); + + /** servlet-based frameworks like Struts, Tapestry etc. */ + public static FrameworkRole JEE_FRAMEWORKS = new FrameworkRole("javaee"); + + private final String myId; + + public FrameworkRole(@NotNull String id) { + myId = id; + } + + @Override + public boolean equals(Object obj) { + return obj instanceof FrameworkRole && myId.equals(((FrameworkRole)obj).myId); + } + + @Override + public String toString() { + return myId; + } +} diff --git a/platform/lang-api/src/com/intellij/ide/util/frameworkSupport/FrameworkSupportProvider.java b/platform/lang-api/src/com/intellij/ide/util/frameworkSupport/FrameworkSupportProvider.java index 770a23cd81ac..59de4af358c9 100644 --- a/platform/lang-api/src/com/intellij/ide/util/frameworkSupport/FrameworkSupportProvider.java +++ b/platform/lang-api/src/com/intellij/ide/util/frameworkSupport/FrameworkSupportProvider.java @@ -78,6 +78,10 @@ public abstract class FrameworkSupportProvider { return getGroupId() == null ? ArrayUtil.EMPTY_STRING_ARRAY : new String[] { getGroupId() }; } + public FrameworkRole[] getRoles() { + return getGroupId() == null ? FrameworkRole.UNKNOWN : new FrameworkRole[] { new FrameworkRole(getGroupId()) }; + } + @Nullable public Icon getIcon() { return null; diff --git a/platform/lang-api/src/com/intellij/ide/util/projectWizard/ModuleBuilder.java b/platform/lang-api/src/com/intellij/ide/util/projectWizard/ModuleBuilder.java index ee33ac83ef48..349d3588c435 100644 --- a/platform/lang-api/src/com/intellij/ide/util/projectWizard/ModuleBuilder.java +++ b/platform/lang-api/src/com/intellij/ide/util/projectWizard/ModuleBuilder.java @@ -17,6 +17,7 @@ package com.intellij.ide.util.projectWizard; import com.intellij.ide.IdeBundle; import com.intellij.ide.highlighter.ModuleFileType; +import com.intellij.ide.util.frameworkSupport.FrameworkRole; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; @@ -381,6 +382,11 @@ public abstract class ModuleBuilder extends AbstractModuleBuilder { myAvailableFrameworks = availableFrameworks; } + @NotNull + public FrameworkRole getDefaultAcceptableRole() { + return getModuleType().getDefaultAcceptableRole(); + } + public static abstract class ModuleConfigurationUpdater { public abstract void update(@NotNull Module module, @NotNull ModifiableRootModel rootModel); diff --git a/platform/lang-api/src/com/intellij/openapi/module/ModuleType.java b/platform/lang-api/src/com/intellij/openapi/module/ModuleType.java index b436bfae7729..3e098021b82e 100644 --- a/platform/lang-api/src/com/intellij/openapi/module/ModuleType.java +++ b/platform/lang-api/src/com/intellij/openapi/module/ModuleType.java @@ -15,6 +15,7 @@ */ package com.intellij.openapi.module; +import com.intellij.ide.util.frameworkSupport.FrameworkRole; import com.intellij.ide.util.projectWizard.ModuleBuilder; import com.intellij.ide.util.projectWizard.ModuleWizardStep; import com.intellij.ide.util.projectWizard.SettingsStep; @@ -33,9 +34,11 @@ public abstract class ModuleType<T extends ModuleBuilder> { @NotNull private final String myId; + private final FrameworkRole myFrameworkRole; protected ModuleType(@NotNull @NonNls String id) { myId = id; + myFrameworkRole = new FrameworkRole(id); } @NotNull @@ -107,4 +110,9 @@ public abstract class ModuleType<T extends ModuleBuilder> { ModuleTypeManager instance = ModuleTypeManager.getInstance(); return instance == null && ApplicationManager.getApplication().isUnitTestMode() ? EMPTY : instance.findByID(module.getOptionValue(Module.ELEMENT_TYPE)); } + + @NotNull + public FrameworkRole getDefaultAcceptableRole() { + return myFrameworkRole; + } } diff --git a/platform/lang-api/src/com/intellij/psi/codeStyle/CommonCodeStyleSettings.java b/platform/lang-api/src/com/intellij/psi/codeStyle/CommonCodeStyleSettings.java index b9fcf04069a6..2ba2f35e1b28 100644 --- a/platform/lang-api/src/com/intellij/psi/codeStyle/CommonCodeStyleSettings.java +++ b/platform/lang-api/src/com/intellij/psi/codeStyle/CommonCodeStyleSettings.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -50,7 +50,8 @@ public class CommonCodeStyleSettings { private ArrangementSettings myArrangementSettings; private CodeStyleSettings myRootSettings; private IndentOptions myIndentOptions; - private FileType myFileType; + private final FileType myFileType; + private boolean myForceArrangeMenuAvailable; @NonNls private static final String INDENT_OPTIONS_TAG = "indentOptions"; @@ -60,10 +61,7 @@ public class CommonCodeStyleSettings { } public CommonCodeStyleSettings(Language language) { - myLanguage = language; - if (language != null) { - myFileType = language.getAssociatedFileType(); - } + this(language, language == null ? null : language.getAssociatedFileType()); } void setRootSettings(@NotNull CodeStyleSettings rootSettings) { @@ -131,11 +129,20 @@ public class CommonCodeStyleSettings { myArrangementSettings = settings; } + public void setForceArrangeMenuAvailable(boolean value) { + myForceArrangeMenuAvailable = value; + } + + public boolean isForceArrangeMenuAvailable() { + return myForceArrangeMenuAvailable; + } + @SuppressWarnings("unchecked") public CommonCodeStyleSettings clone(@NotNull CodeStyleSettings rootSettings) { CommonCodeStyleSettings commonSettings = new CommonCodeStyleSettings(myLanguage, getFileType()); copyPublicFields(this, commonSettings); commonSettings.setRootSettings(rootSettings); + commonSettings.myForceArrangeMenuAvailable = this.myForceArrangeMenuAvailable; if (myIndentOptions != null) { IndentOptions targetIndentOptions = commonSettings.initIndentOptions(); targetIndentOptions.copyFrom(myIndentOptions); @@ -225,6 +232,7 @@ public class CommonCodeStyleSettings { Set<String> supportedFields = getSupportedFields(); if (supportedFields != null) { supportedFields.add("PARENT_SETTINGS_INSTALLED"); + supportedFields.add("FORCE_REARRANGE_MODE"); } DefaultJDOMExternalizer.writeExternal(this, element, new SupportedFieldsDiffFilter(this, supportedFields, defaultSettings)); if (myIndentOptions != null) { @@ -916,6 +924,14 @@ public class CommonCodeStyleSettings { // public boolean PARENT_SETTINGS_INSTALLED = false; + //-------------------------Force rearrange settings--------------------------------------- + public static int REARRANGE_ACCORDIND_TO_DIALOG = 0; + public static int REARRANGE_ALWAYS = 1; + public static int REARRANGE_NEVER = 2; + + public int FORCE_REARRANGE_MODE = REARRANGE_ACCORDIND_TO_DIALOG; + + //-------------------------Indent options------------------------------------------------- public static class IndentOptions implements JDOMExternalizable, Cloneable { public int INDENT_SIZE = 4; diff --git a/platform/lang-api/src/com/intellij/psi/util/PsiUtilBase.java b/platform/lang-api/src/com/intellij/psi/util/PsiUtilBase.java index f5b10ccf1b20..04c4b63b5084 100644 --- a/platform/lang-api/src/com/intellij/psi/util/PsiUtilBase.java +++ b/platform/lang-api/src/com/intellij/psi/util/PsiUtilBase.java @@ -73,31 +73,6 @@ public class PsiUtilBase extends PsiUtilCore { throw new RuntimeException("Cannot find root for: "+root); } - @NotNull - public static Language getLanguageAtOffset (@NotNull PsiFile file, int offset) { - final PsiElement elt = file.findElementAt(offset); - if (elt == null) return file.getLanguage(); - if (elt instanceof PsiWhiteSpace) { - final int decremented = elt.getTextRange().getStartOffset() - 1; - if (decremented >= 0) { - return getLanguageAtOffset(file, decremented); - } - } - return findLanguageFromElement(elt); - } - - @NotNull - public static Language findLanguageFromElement(final PsiElement elt) { - if (elt.getFirstChild() == null) { //is leaf - final PsiElement parent = elt.getParent(); - if (parent != null) { - return parent.getLanguage(); - } - } - - return elt.getLanguage(); - } - public static boolean isUnderPsiRoot(PsiFile root, PsiElement element) { PsiFile containingFile = element.getContainingFile(); if (containingFile == root) return true; diff --git a/platform/lang-impl/src/com/intellij/application/options/GeneralCodeStylePanel.form b/platform/lang-impl/src/com/intellij/application/options/GeneralCodeStylePanel.form index 72d2e4e399f7..38d72c46c3cb 100644 --- a/platform/lang-impl/src/com/intellij/application/options/GeneralCodeStylePanel.form +++ b/platform/lang-impl/src/com/intellij/application/options/GeneralCodeStylePanel.form @@ -71,7 +71,7 @@ <border type="none" title="Default Indent Options"/> <children/> </grid> - <grid id="f6e61" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <grid id="f6e61" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> <margin top="0" left="0" bottom="0" right="0"/> <constraints> <grid row="4" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/> @@ -82,54 +82,78 @@ </clientProperties> <border type="none" title-resource-bundle="messages/ApplicationBundle" title-key="settings.code.style.general.formatter.control"/> <children> - <component id="1afd0" class="com.intellij.ui.components.JBLabel"> + <component id="3b152" class="javax.swing.JCheckBox" binding="myEnableFormatterTags"> <constraints> - <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + <grid row="0" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> </constraints> <properties> - <text resource-bundle="messages/ApplicationBundle" key="settings.code.style.general.formatter.off.tag"/> + <text resource-bundle="messages/ApplicationBundle" key="settings.code.style.general.enable.formatter.tags"/> </properties> </component> - <component id="a4d" class="javax.swing.JTextField" binding="myFormatterOffTagField"> + <grid id="3e0c8" binding="myMarkersPanel" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <margin top="0" left="0" bottom="0" right="0"/> <constraints> - <grid row="2" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false"> - <preferred-size width="150" height="-1"/> - </grid> + <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/> </constraints> <properties/> - </component> - <component id="66f0a" class="javax.swing.JTextField" binding="myFormatterOnTagField"> - <constraints> - <grid row="3" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false"> - <preferred-size width="150" height="-1"/> + <border type="none" title="Markers:"/> + <children> + <component id="1afd0" class="com.intellij.ui.components.JBLabel" binding="myFormatterOffLabel"> + <constraints> + <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <labelFor value="a4d"/> + <text resource-bundle="messages/ApplicationBundle" key="settings.code.style.general.formatter.off.tag"/> + </properties> + </component> + <component id="96c9" class="com.intellij.ui.components.JBLabel" binding="myFormatterOnLabel"> + <constraints> + <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <labelFor value="66f0a"/> + <text resource-bundle="messages/ApplicationBundle" key="settings.code.style.general.formatter.on.tag"/> + </properties> + </component> + <component id="66f0a" class="javax.swing.JTextField" binding="myFormatterOnTagField"> + <constraints> + <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false"> + <preferred-size width="150" height="-1"/> + </grid> + </constraints> + <properties/> + </component> + <component id="a4d" class="javax.swing.JTextField" binding="myFormatterOffTagField"> + <constraints> + <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false"> + <preferred-size width="150" height="-1"/> + </grid> + </constraints> + <properties/> + </component> + <grid id="8023a" binding="myMarkerOptionsPanel" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <margin top="0" left="0" bottom="0" right="0"/> + <constraints> + <grid row="2" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/> + </constraints> + <properties/> + <border type="empty"> + <font/> + </border> + <children> + <component id="f7481" class="javax.swing.JCheckBox" binding="myAcceptRegularExpressionsCheckBox"> + <constraints> + <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="messages/ApplicationBundle" key="settings.code.style.general.formatter.marker.regexp"/> + </properties> + </component> + </children> </grid> - </constraints> - <properties/> - </component> - <component id="96c9" class="com.intellij.ui.components.JBLabel"> - <constraints> - <grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/> - </constraints> - <properties> - <text resource-bundle="messages/ApplicationBundle" key="settings.code.style.general.formatter.on.tag"/> - </properties> - </component> - <component id="f7481" class="javax.swing.JCheckBox" binding="myAcceptRegularExpressionsCheckBox"> - <constraints> - <grid row="1" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> - </constraints> - <properties> - <text resource-bundle="messages/ApplicationBundle" key="settings.code.style.general.formatter.marker.regexp"/> - </properties> - </component> - <component id="3b152" class="javax.swing.JCheckBox" binding="myEnableFormatterTags"> - <constraints> - <grid row="0" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> - </constraints> - <properties> - <text resource-bundle="messages/ApplicationBundle" key="settings.code.style.general.enable.formatter.tags"/> - </properties> - </component> + </children> + </grid> </children> </grid> </children> diff --git a/platform/lang-impl/src/com/intellij/application/options/GeneralCodeStylePanel.java b/platform/lang-impl/src/com/intellij/application/options/GeneralCodeStylePanel.java index c434c670823d..844b1ccbc1ba 100644 --- a/platform/lang-impl/src/com/intellij/application/options/GeneralCodeStylePanel.java +++ b/platform/lang-impl/src/com/intellij/application/options/GeneralCodeStylePanel.java @@ -39,7 +39,9 @@ import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.DisplayPriority; import com.intellij.psi.codeStyle.FileTypeIndentOptionsProvider; import com.intellij.psi.codeStyle.LanguageCodeStyleSettingsProvider; +import com.intellij.ui.IdeBorderFactory; import com.intellij.ui.awt.RelativePoint; +import com.intellij.ui.components.JBLabel; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -73,6 +75,10 @@ public class GeneralCodeStylePanel extends CodeStyleAbstractPanel { private JTextField myFormatterOnTagField; private JTextField myFormatterOffTagField; private JCheckBox myAcceptRegularExpressionsCheckBox; + private JPanel myMarkersPanel; + private JBLabel myFormatterOffLabel; + private JBLabel myFormatterOnLabel; + private JPanel myMarkerOptionsPanel; private final SmartIndentOptionsEditor myIndentOptionsEditor; @@ -112,11 +118,14 @@ public class GeneralCodeStylePanel extends CodeStyleAbstractPanel { @Override public void actionPerformed(ActionEvent e) { boolean tagsEnabled = myEnableFormatterTags.isSelected(); - myAcceptRegularExpressionsCheckBox.setEnabled(tagsEnabled); - myFormatterOnTagField.setEnabled(tagsEnabled); - myFormatterOffTagField.setEnabled(tagsEnabled); + setFormatterTagControlsEnabled(tagsEnabled); } }); + + myMarkersPanel.setBorder(IdeBorderFactory.createTitledBorder( + ApplicationBundle.message("settings.code.style.general.formatter.marker.title"), true)); + myMarkerOptionsPanel.setBorder( + IdeBorderFactory.createTitledBorder(ApplicationBundle.message("settings.code.style.general.formatter.marker.options.title"), true)); } @Nullable @@ -258,14 +267,22 @@ public class GeneralCodeStylePanel extends CodeStyleAbstractPanel { myIndentOptionsEditor.setEnabled(true); myAcceptRegularExpressionsCheckBox.setSelected(settings.FORMATTER_TAGS_ACCEPT_REGEXP); - myAcceptRegularExpressionsCheckBox.setEnabled(settings.FORMATTER_TAGS_ENABLED); myEnableFormatterTags.setSelected(settings.FORMATTER_TAGS_ENABLED); myFormatterOnTagField.setText(settings.FORMATTER_ON_TAG); - myFormatterOnTagField.setEnabled(settings.FORMATTER_TAGS_ENABLED); - myFormatterOffTagField.setText(settings.FORMATTER_OFF_TAG); - myFormatterOffTagField.setEnabled(settings.FORMATTER_TAGS_ENABLED); + + setFormatterTagControlsEnabled(settings.FORMATTER_TAGS_ENABLED); + } + + private void setFormatterTagControlsEnabled(boolean isEnabled) { + myFormatterOffTagField.setEnabled(isEnabled); + myFormatterOnTagField.setEnabled(isEnabled); + myMarkersPanel.setEnabled(isEnabled); + myAcceptRegularExpressionsCheckBox.setEnabled(isEnabled); + myFormatterOffLabel.setEnabled(isEnabled); + myFormatterOnLabel.setEnabled(isEnabled); + myMarkerOptionsPanel.setEnabled(isEnabled); } @Override diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementSettingsPanel.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementSettingsPanel.java index 969496745694..a6231d2f26fe 100644 --- a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementSettingsPanel.java +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/ArrangementSettingsPanel.java @@ -17,6 +17,7 @@ package com.intellij.application.options.codeStyle.arrangement; import com.intellij.application.options.CodeStyleAbstractPanel; import com.intellij.application.options.codeStyle.arrangement.action.RemoveArrangementRuleAction; +import com.intellij.application.options.codeStyle.arrangement.additional.ForceArrangementPanel; import com.intellij.application.options.codeStyle.arrangement.color.ArrangementColorsProvider; import com.intellij.application.options.codeStyle.arrangement.color.ArrangementColorsProviderImpl; import com.intellij.application.options.codeStyle.arrangement.group.ArrangementGroupingRulesPanel; @@ -45,6 +46,7 @@ import java.awt.*; import java.util.ArrayList; import java.util.List; + /** * @author Denis Zhdanov * @since 10/30/12 5:17 PM @@ -57,6 +59,7 @@ public abstract class ArrangementSettingsPanel extends CodeStyleAbstractPanel { @NotNull private final ArrangementStandardSettingsAware mySettingsAware; @NotNull private final ArrangementGroupingRulesPanel myGroupingRulesPanel; @NotNull private final ArrangementMatchingRulesPanel myMatchingRulesPanel; + @Nullable private final ForceArrangementPanel myForceArrangementPanel; public ArrangementSettingsPanel(@NotNull CodeStyleSettings settings, @NotNull Language language) { super(settings); @@ -80,7 +83,18 @@ public abstract class ArrangementSettingsPanel extends CodeStyleAbstractPanel { myMatchingRulesPanel = new ArrangementMatchingRulesPanel(settingsManager, colorsProvider); myContent.add(myGroupingRulesPanel, new GridBag().coverLine().fillCellHorizontally().weightx(1)); - myContent.add(myMatchingRulesPanel, new GridBag().fillCell().weightx(1).weighty(1)); + myContent.add(myMatchingRulesPanel, new GridBag().fillCell().weightx(1).weighty(1).coverLine()); + + + + if (settings.getCommonSettings(myLanguage).isForceArrangeMenuAvailable()) { + myForceArrangementPanel = new ForceArrangementPanel(); + myForceArrangementPanel.setSelectedMode(settings.getCommonSettings(language).FORCE_REARRANGE_MODE); + myContent.add(myForceArrangementPanel.getPanel(), new GridBag().anchor(GridBagConstraints.WEST).coverLine().fillCellHorizontally()); + } + else { + myForceArrangementPanel = null; + } final List<CompositeArrangementSettingsToken> groupingTokens = settingsManager.getSupportedGroupingTokens(); myGroupingRulesPanel.setVisible(groupingTokens != null && !groupingTokens.isEmpty()); @@ -117,12 +131,16 @@ public abstract class ArrangementSettingsPanel extends CodeStyleAbstractPanel { public void apply(CodeStyleSettings settings) { CommonCodeStyleSettings commonSettings = settings.getCommonSettings(myLanguage); commonSettings.setArrangementSettings(new StdRulePriorityAwareSettings(myGroupingRulesPanel.getRules(), myMatchingRulesPanel.getRules())); + if (myForceArrangementPanel != null) { + commonSettings.FORCE_REARRANGE_MODE = myForceArrangementPanel.getRearrangeMode(); + } } @Override public boolean isModified(CodeStyleSettings settings) { StdArrangementSettings s = new StdRulePriorityAwareSettings(myGroupingRulesPanel.getRules(), myMatchingRulesPanel.getRules()); - return !Comparing.equal(getSettings(settings), s); + return !Comparing.equal(getSettings(settings), s) + || myForceArrangementPanel != null && settings.getCommonSettings(myLanguage).FORCE_REARRANGE_MODE != myForceArrangementPanel.getRearrangeMode(); } @Override @@ -138,6 +156,9 @@ public abstract class ArrangementSettingsPanel extends CodeStyleAbstractPanel { myGroupingRulesPanel.setRules(ContainerUtilRt.newArrayList(groupings)); } myMatchingRulesPanel.setRules(copy(s.getRules())); + if (myForceArrangementPanel != null) { + myForceArrangementPanel.setSelectedMode(settings.getCommonSettings(myLanguage).FORCE_REARRANGE_MODE); + } } } diff --git a/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/additional/ForceArrangementPanel.java b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/additional/ForceArrangementPanel.java new file mode 100644 index 000000000000..9524bb28417f --- /dev/null +++ b/platform/lang-impl/src/com/intellij/application/options/codeStyle/arrangement/additional/ForceArrangementPanel.java @@ -0,0 +1,103 @@ +/* + * Copyright 2000-2013 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.application.options.codeStyle.arrangement.additional; + +import com.intellij.openapi.application.ApplicationBundle; +import com.intellij.psi.codeStyle.CommonCodeStyleSettings; +import com.intellij.ui.EnumComboBoxModel; +import com.intellij.ui.OptionGroup; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.awt.*; + +public class ForceArrangementPanel { + + @NotNull private final JComboBox myForceRearrangeComboBox; + @NotNull private final JPanel myPanel; + + public ForceArrangementPanel() { + myForceRearrangeComboBox = new JComboBox(); + myForceRearrangeComboBox.setModel(new EnumComboBoxModel<SelectedMode>(SelectedMode.class)); + myForceRearrangeComboBox.setMaximumSize(myForceRearrangeComboBox.getPreferredSize()); + myPanel = createPanel(); + } + + public int getRearrangeMode() { + return getSelectedMode().rearrangeMode; + } + + public void setSelectedMode(@NotNull SelectedMode mode) { + myForceRearrangeComboBox.setSelectedItem(mode); + } + + public void setSelectedMode(int mode) { + SelectedMode toSetUp = SelectedMode.getByMode(mode); + assert(toSetUp != null); + setSelectedMode(toSetUp); + } + + @NotNull + public JPanel getPanel() { + return myPanel; + } + + @NotNull + private JPanel createPanel() { + OptionGroup group = new OptionGroup(ApplicationBundle.message("arrangement.settings.additional.title")); + JPanel textWithComboPanel = new JPanel(); + textWithComboPanel.setLayout(new BoxLayout(textWithComboPanel, BoxLayout.LINE_AXIS)); + textWithComboPanel.add(new JLabel(ApplicationBundle.message("arrangement.settings.additional.force.combobox.name"))); + textWithComboPanel.add(Box.createRigidArea(new Dimension(5, 0))); + textWithComboPanel.add(myForceRearrangeComboBox); + group.add(textWithComboPanel); + return group.createPanel(); + } + + @NotNull + private SelectedMode getSelectedMode() { + return (SelectedMode)myForceRearrangeComboBox.getSelectedItem(); + } + + private enum SelectedMode { + FROM_DIALOG(ApplicationBundle.message("arrangement.settings.additional.force.rearrange.according.to.dialog"), CommonCodeStyleSettings.REARRANGE_ACCORDIND_TO_DIALOG), + ALWAYS(ApplicationBundle.message("arrangement.settings.additional.force.rearrange.always"), CommonCodeStyleSettings.REARRANGE_ALWAYS), + NEVER(ApplicationBundle.message("arrangement.settings.additional.force.rearrange.never"), CommonCodeStyleSettings.REARRANGE_NEVER); + + public final int rearrangeMode; + @NotNull private final String myName; + + SelectedMode(@NotNull String name, int mode) { + myName = name; + rearrangeMode = mode; + } + + @Nullable + private static SelectedMode getByMode(int mode) { + for (SelectedMode currentMode: values()) { + if (currentMode.rearrangeMode == mode) return currentMode; + } + return null; + } + + @Override + @NotNull + public String toString() { + return myName; + } + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeDialog.java b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeDialog.java index 3914ce6e7e31..45862ed4e7ba 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeDialog.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/actions/LayoutCodeDialog.java @@ -26,6 +26,8 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiFile; +import com.intellij.psi.codeStyle.CodeStyleSettingsManager; +import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.intellij.psi.codeStyle.arrangement.Rearranger; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -38,9 +40,10 @@ import java.awt.event.ItemEvent; import java.awt.event.ItemListener; public class LayoutCodeDialog extends DialogWrapper { - private final PsiFile myFile; + @NotNull private final Project myProject; + @Nullable private final PsiFile myFile; @Nullable private final PsiDirectory myDirectory; - private final Boolean myTextSelected; + private final Boolean myTextSelected; private JRadioButton myRbFile; private JRadioButton myRbSelectedText; @@ -52,6 +55,10 @@ public class LayoutCodeDialog extends DialogWrapper { private JCheckBox myDoNotAskMeCheckBox; private final String myHelpId; + @NotNull private String myRearrangeEntriesKeyForLanguage; + @Nullable private CommonCodeStyleSettings myCommonSettings; + private boolean myForceArrangeModeEnabled; + public LayoutCodeDialog(@NotNull Project project, @NotNull String title, @@ -61,9 +68,16 @@ public class LayoutCodeDialog extends DialogWrapper { final String helpId) { super(project, true); myFile = file; + myProject = project; myDirectory = directory; myTextSelected = isTextSelected; + if (myFile != null) myCommonSettings = CodeStyleSettingsManager.getSettings(myProject).getCommonSettings(myFile.getLanguage()); + myForceArrangeModeEnabled = myCommonSettings != null + && myCommonSettings.isForceArrangeMenuAvailable() + && myCommonSettings.FORCE_REARRANGE_MODE != CommonCodeStyleSettings.REARRANGE_ACCORDIND_TO_DIALOG; + + myRearrangeEntriesKeyForLanguage = LayoutCodeConstants.REARRANGE_ENTRIES_KEY + (myFile == null ? "" : myFile.getLanguage().getDisplayName()); setOKButtonText(CodeInsightBundle.message("reformat.code.accept.button.text")); setTitle(title); init(); @@ -89,7 +103,9 @@ public class LayoutCodeDialog extends DialogWrapper { myCbIncludeSubdirs.setSelected(true); //Loading previous state myCbOptimizeImports.setSelected(PropertiesComponent.getInstance().getBoolean(LayoutCodeConstants.OPTIMIZE_IMPORTS_KEY, false)); - myCbArrangeEntries.setSelected(PropertiesComponent.getInstance().getBoolean(LayoutCodeConstants.REARRANGE_ENTRIES_KEY, false)); + myCbArrangeEntries.setSelected(myForceArrangeModeEnabled ? myCommonSettings.FORCE_REARRANGE_MODE == CommonCodeStyleSettings.REARRANGE_ALWAYS + : PropertiesComponent.getInstance(myProject).getBoolean(myRearrangeEntriesKeyForLanguage, false)); + myCbOnlyVcsChangedRegions.setSelected(PropertiesComponent.getInstance().getBoolean(LayoutCodeConstants.PROCESS_CHANGED_TEXT_KEY, false)); ItemListener listener = new ItemListener() { @@ -114,6 +130,7 @@ public class LayoutCodeDialog extends DialogWrapper { ); myCbArrangeEntries.setEnabled(myFile != null && Rearranger.EXTENSION.forLanguage(myFile.getLanguage()) != null + && !myForceArrangeModeEnabled ); myCbOnlyVcsChangedRegions.setEnabled(canTargetVcsRegions()); @@ -257,7 +274,7 @@ public class LayoutCodeDialog extends DialogWrapper { super.doOKAction(); //Saving checkboxes state PropertiesComponent.getInstance().setValue(LayoutCodeConstants.OPTIMIZE_IMPORTS_KEY, Boolean.toString(myCbOptimizeImports.isSelected())); - PropertiesComponent.getInstance().setValue(LayoutCodeConstants.REARRANGE_ENTRIES_KEY, Boolean.toString(myCbArrangeEntries.isSelected())); + PropertiesComponent.getInstance(myProject).setValue(myRearrangeEntriesKeyForLanguage, Boolean.toString(myCbArrangeEntries.isSelected())); PropertiesComponent.getInstance().setValue(LayoutCodeConstants.PROCESS_CHANGED_TEXT_KEY, Boolean.toString(myCbOnlyVcsChangedRegions.isSelected())); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java index 8f87b887fb8f..c5c5d6fe1737 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CodeCompletionHandlerBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -43,9 +43,11 @@ import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.IndexNotReadyException; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.*; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; @@ -56,6 +58,7 @@ import com.intellij.psi.impl.source.PostprocessReformattingAspect; import com.intellij.psi.impl.source.PsiFileImpl; import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; import com.intellij.psi.util.PsiUtilBase; +import com.intellij.psi.util.PsiUtilCore; import com.intellij.reference.SoftReference; import com.intellij.util.ThreeState; import com.intellij.util.concurrency.Semaphore; @@ -231,7 +234,7 @@ public class CodeCompletionHandlerBase { } if (elementAt == null) return true; - Language language = PsiUtilBase.findLanguageFromElement(elementAt); + Language language = PsiUtilCore.findLanguageFromElement(elementAt); for (CompletionConfidence confidence : CompletionConfidenceEP.forLanguage(language)) { final ThreeState result = confidence.shouldSkipAutopopup(elementAt, psiFile, offset); @@ -759,7 +762,7 @@ public class CodeCompletionHandlerBase { private static PsiFile createFileCopy(PsiFile file) { final VirtualFile virtualFile = file.getVirtualFile(); - if (file.isPhysical() && virtualFile != null && virtualFile.getFileSystem() == LocalFileSystem.getInstance() + if (file.isPhysical() && virtualFile != null && virtualFile.isInLocalFileSystem() // must not cache injected file copy, since it does not reflect changes in host document && !InjectedLanguageManager.getInstance(file.getProject()).isInjectedFragment(file)) { final SoftReference<Pair<PsiFile, Document>> reference = file.getUserData(FILE_COPY_KEY); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java index 91d89b2217a5..899a6017f6c7 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/CompletionProgressIndicator.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -56,7 +56,7 @@ import com.intellij.patterns.ElementPattern; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiReference; import com.intellij.psi.ReferenceRange; -import com.intellij.psi.util.PsiUtilBase; +import com.intellij.psi.util.PsiUtilCore; import com.intellij.ui.LightweightHint; import com.intellij.util.Alarm; import com.intellij.util.ObjectUtils; @@ -716,7 +716,7 @@ public class CompletionProgressIndicator extends ProgressIndicatorBase implement return false; } - final Language language = PsiUtilBase.getLanguageAtOffset(parameters.getPosition().getContainingFile(), parameters.getOffset()); + final Language language = PsiUtilCore.getLanguageAtOffset(parameters.getPosition().getContainingFile(), parameters.getOffset()); for (CompletionConfidence confidence : CompletionConfidenceEP.forLanguage(language)) { final ThreeState result = confidence.shouldFocusLookup(parameters); if (result != ThreeState.UNSURE) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CamelHumpMatcher.java b/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CamelHumpMatcher.java index ea045464221e..ee3a195ae8fa 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CamelHumpMatcher.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/completion/impl/CamelHumpMatcher.java @@ -143,7 +143,7 @@ public class CamelHumpMatcher extends PrefixMatcher { int matchStart = ranges.get(0).getStartOffset(); int underscoreEnd = skipUnderscores(string); if (matchStart > 0 && matchStart <= underscoreEnd) { - return myMatcher.matchingDegree(string.substring(matchStart)) - 1; + return myCaseInsensitiveMatcher.matchingDegree(string.substring(matchStart)) - 1; } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonEditorPopup.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonEditorPopup.java index d788f8537548..2566e2279aff 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonEditorPopup.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/DaemonEditorPopup.java @@ -21,8 +21,11 @@ package com.intellij.codeInsight.daemon.impl; import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings; +import com.intellij.ide.IdeBundle; +import com.intellij.ide.ui.UISettings; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.EditorBundle; +import com.intellij.openapi.ui.JBCheckboxMenuItem; import com.intellij.openapi.ui.JBMenuItem; import com.intellij.openapi.ui.JBPopupMenu; import com.intellij.psi.PsiFile; @@ -86,6 +89,18 @@ public class DaemonEditorPopup extends PopupHandler { component.showComponent(new RelativePoint(comp, new Point(point.x - dimension.width, point.y))); } }); + + final JBCheckboxMenuItem previewCheckbox = new JBCheckboxMenuItem(IdeBundle.message("checkbox.show.editor.preview.popup"), UISettings.getInstance().SHOW_EDITOR_TOOLTIP); + popupMenu.addSeparator(); + popupMenu.add(previewCheckbox); + previewCheckbox.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + UISettings.getInstance().SHOW_EDITOR_TOOLTIP = previewCheckbox.isSelected(); + UISettings.getInstance().fireUISettingsChanged(); + } + }); + PsiFile file = myPsiFile; if (file != null && DaemonCodeAnalyzer.getInstance(myPsiFile.getProject()).isHighlightingAvailable(file)) { popupMenu.show(comp, x, y); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/LocalInspectionsPass.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/LocalInspectionsPass.java index 5de93879636a..aec61188112f 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/LocalInspectionsPass.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/LocalInspectionsPass.java @@ -703,6 +703,10 @@ public class LocalInspectionsPass extends ProgressableTextEditorHighlightingPass if (toolWrapper instanceof LocalInspectionToolWrapper) { wrapper = (LocalInspectionToolWrapper)toolWrapper; } + else if (toolWrapper instanceof GlobalInspectionToolWrapper) { + final GlobalInspectionToolWrapper globalInspectionToolWrapper = (GlobalInspectionToolWrapper)toolWrapper; + wrapper = globalInspectionToolWrapper.getSharedLocalInspectionToolWrapper(); + } if (wrapper == null) continue; if (myIgnoreSuppressed) { if (wrapper.isApplicable(language) && SuppressionUtil.inspectionResultSuppressed(myFile, wrapper.getTool())) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/PassExecutorService.java b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/PassExecutorService.java index f9488aad2794..22e3b55f58a0 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/PassExecutorService.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/PassExecutorService.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 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,7 +19,6 @@ package com.intellij.codeInsight.daemon.impl; import com.intellij.codeHighlighting.HighlightingPass; import com.intellij.codeHighlighting.TextEditorHighlightingPass; import com.intellij.concurrency.Job; -import com.intellij.concurrency.JobImpl; import com.intellij.concurrency.JobLauncher; import com.intellij.injected.editor.EditorWindow; import com.intellij.openapi.Disposable; @@ -47,7 +46,6 @@ import com.intellij.util.containers.ContainerUtil; import gnu.trove.THashMap; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.TestOnly; import java.util.*; import java.util.concurrent.*; @@ -79,17 +77,17 @@ public abstract class PassExecutorService implements Disposable { submittedPass.cancel(); } if (waitForTermination) { - for (Job<Void> job : mySubmittedPasses.values()) { - try { - if (job instanceof JobImpl) { - JobImpl ji = (JobImpl)job; - if (!job.isDone()) ji.waitForTermination(); - } - } - catch (Throwable throwable) { - LOG.error(throwable); + try { + while (!waitFor(50)) { + int i = 0; } } + catch (ProcessCanceledException ignored) { + + } + catch (Throwable throwable) { + LOG.error(throwable); + } } mySubmittedPasses.clear(); } @@ -360,7 +358,9 @@ public abstract class PassExecutorService implements Disposable { catch (ProcessCanceledException e) { log(myUpdateProgress, myPass, "Canceled "); - myUpdateProgress.cancel(e); //in case when some smart asses throw PCE just for fun + if (!myUpdateProgress.isCanceled()) { + myUpdateProgress.cancel(e); //in case when some smart asses throw PCE just for fun + } } catch (RuntimeException e) { myUpdateProgress.cancel(e); @@ -518,20 +518,23 @@ public abstract class PassExecutorService implements Disposable { return indicator.getUserData(THROWABLE_KEY); } - @TestOnly - public void waitFor(int millis) throws Exception { + // return true if terminated + public boolean waitFor(int millis) throws Throwable { ApplicationManager.getApplication().assertIsDispatchThread(); try { for (Job<Void> job : mySubmittedPasses.values()) { - if (!job.isDone()) { - for (FutureTask task : ((JobImpl)job).getTasks()) { - task.get(millis, TimeUnit.MILLISECONDS); - } - } + job.waitForCompletion(millis); } + return true; } catch (TimeoutException ignored) { - + return false; + } + catch (InterruptedException e) { + return true; + } + catch (ExecutionException e) { + throw e.getCause(); } } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/AutoHardWrapHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/AutoHardWrapHandler.java index 341c1447409e..4c24ff5ce156 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/AutoHardWrapHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/AutoHardWrapHandler.java @@ -108,8 +108,9 @@ public class AutoHardWrapHandler { int line = document.getLineNumber(caretOffset); int startOffset = document.getLineStartOffset(line); int endOffset = document.getLineEndOffset(line); - final String endOfString = document.getText().substring(caretOffset, endOffset); - final boolean endsWithSpaces = StringUtil.isEmptyOrSpaces(endOfString); + + final CharSequence endOfString = document.getCharsSequence().subSequence(caretOffset, endOffset); + final boolean endsWithSpaces = StringUtil.isEmptyOrSpaces(String.valueOf(endOfString)); // Check if right margin is exceeded. int margin = editor.getSettings().getRightMargin(project); if (margin <= 0) { @@ -185,14 +186,16 @@ public class AutoHardWrapHandler { caretOffsetDiff[0] += event.getNewLength() - event.getOldLength(); } - if (event.getNewLength() <= event.getOldLength() && endsWithSpaces) { - // There is a possible case that document fragment is removed because of auto-formatting. - // We don't want to process such events in case of current string ends with spaces. + if (autoFormatted(event)) { return; } wrapIntroducedSymbolsNumber[0] += event.getNewLength() - event.getOldLength(); } + private boolean autoFormatted(DocumentEvent event) { + return event.getNewLength() <= event.getOldLength() && endsWithSpaces; + } + @Override public void documentChanged(DocumentEvent event) { } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/CodeDocumentationUtil.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/CodeDocumentationUtil.java index 85a84021e5a0..bcb6196c9434 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/CodeDocumentationUtil.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/CodeDocumentationUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -23,7 +23,7 @@ import com.intellij.openapi.editor.Document; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; -import com.intellij.psi.util.PsiUtilBase; +import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.text.CharArrayUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -96,7 +96,7 @@ public class CodeDocumentationUtil { */ @NotNull public static CommentContext tryParseCommentContext(@NotNull PsiFile file, @NotNull CharSequence chars, int offset, int lineStartOffset) { - Commenter langCommenter = LanguageCommenters.INSTANCE.forLanguage(PsiUtilBase.getLanguageAtOffset(file, offset)); + Commenter langCommenter = LanguageCommenters.INSTANCE.forLanguage(PsiUtilCore.getLanguageAtOffset(file, offset)); final boolean isInsideCommentLikeCode = langCommenter instanceof CodeDocumentationAwareCommenter; if (!isInsideCommentLikeCode) { return new CommentContext(); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/FixDocCommentAction.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/FixDocCommentAction.java index c12a0dde63e9..58a0c772da8a 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/FixDocCommentAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/FixDocCommentAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -33,7 +33,7 @@ import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.psi.codeStyle.CodeStyleManager; -import com.intellij.psi.util.PsiUtilBase; +import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.text.CharArrayUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -79,7 +79,7 @@ public class FixDocCommentAction extends EditorAction { return; } - Language language = PsiUtilBase.getLanguageAtOffset(file, offset); + Language language = PsiUtilCore.getLanguageAtOffset(file, offset); final CodeDocumentationProvider docProvider; final DocumentationProvider langDocumentationProvider = LanguageDocumentation.INSTANCE.forLanguage(language); if (langDocumentationProvider instanceof CompositeDocumentationProvider) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/SelectWordUtil.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/SelectWordUtil.java index 642069f0d918..c6e5851857ba 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/SelectWordUtil.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/SelectWordUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -39,7 +39,6 @@ import java.util.List; * @author Mike */ public class SelectWordUtil { - private static ExtendWordSelectionHandler[] SELECTIONERS = new ExtendWordSelectionHandler[]{ }; @@ -66,38 +65,53 @@ public class SelectWordUtil { return SELECTIONERS; } + private static final CharCondition JAVA_IDENTIFIER_PART_CONDITION = new CharCondition() { + @Override + public boolean value(char ch) { + return Character.isJavaIdentifierPart(ch); + } + }; + public static void addWordSelection(boolean camel, CharSequence editorText, int cursorOffset, @NotNull List<TextRange> ranges) { - TextRange camelRange = camel ? getCamelSelectionRange(editorText, cursorOffset) : null; + addWordSelection(camel, editorText, cursorOffset, ranges, JAVA_IDENTIFIER_PART_CONDITION); + } + + public static void addWordSelection(boolean camel, + CharSequence editorText, + int cursorOffset, + @NotNull List<TextRange> ranges, + CharCondition isWordPartCondition) { + TextRange camelRange = camel ? getCamelSelectionRange(editorText, cursorOffset, isWordPartCondition) : null; if (camelRange != null) { ranges.add(camelRange); } - TextRange range = getWordSelectionRange(editorText, cursorOffset); + TextRange range = getWordSelectionRange(editorText, cursorOffset, isWordPartCondition); if (range != null && !range.equals(camelRange)) { ranges.add(range); } } @Nullable - private static TextRange getCamelSelectionRange(CharSequence editorText, int cursorOffset) { + private static TextRange getCamelSelectionRange(CharSequence editorText, int cursorOffset, CharCondition isWordPartCondition) { if (cursorOffset < 0 || cursorOffset >= editorText.length()) { return null; } - if (cursorOffset > 0 && !Character.isJavaIdentifierPart(editorText.charAt(cursorOffset)) && - Character.isJavaIdentifierPart(editorText.charAt(cursorOffset - 1))) { + if (cursorOffset > 0 && !isWordPartCondition.value(editorText.charAt(cursorOffset)) && + isWordPartCondition.value(editorText.charAt(cursorOffset - 1))) { cursorOffset--; } - if (Character.isJavaIdentifierPart(editorText.charAt(cursorOffset))) { + if (isWordPartCondition.value(editorText.charAt(cursorOffset))) { int start = cursorOffset; int end = cursorOffset + 1; final int textLen = editorText.length(); - while (start > 0 && Character.isJavaIdentifierPart(editorText.charAt(start - 1)) && !EditorActionUtil.isHumpBound(editorText, start, true)) { + while (start > 0 && isWordPartCondition.value(editorText.charAt(start - 1)) && !EditorActionUtil.isHumpBound(editorText, start, true)) { start--; } - while (end < textLen && Character.isJavaIdentifierPart(editorText.charAt(end)) && !EditorActionUtil.isHumpBound(editorText, end, false)) { + while (end < textLen && isWordPartCondition.value(editorText.charAt(end)) && !EditorActionUtil.isHumpBound(editorText, end, false)) { end++; } @@ -110,24 +124,24 @@ public class SelectWordUtil { } @Nullable - public static TextRange getWordSelectionRange(@NotNull CharSequence editorText, int cursorOffset) { + public static TextRange getWordSelectionRange(@NotNull CharSequence editorText, int cursorOffset, @NotNull CharCondition isWordPartCondition) { int length = editorText.length(); if (length == 0) return null; if (cursorOffset == length || - cursorOffset > 0 && !Character.isJavaIdentifierPart(editorText.charAt(cursorOffset)) && - Character.isJavaIdentifierPart(editorText.charAt(cursorOffset - 1))) { + cursorOffset > 0 && !isWordPartCondition.value(editorText.charAt(cursorOffset)) && + isWordPartCondition.value(editorText.charAt(cursorOffset - 1))) { cursorOffset--; } - if (Character.isJavaIdentifierPart(editorText.charAt(cursorOffset))) { + if (isWordPartCondition.value(editorText.charAt(cursorOffset))) { int start = cursorOffset; int end = cursorOffset; - while (start > 0 && Character.isJavaIdentifierPart(editorText.charAt(start - 1))) { + while (start > 0 && isWordPartCondition.value(editorText.charAt(start - 1))) { start--; } - while (end < length && Character.isJavaIdentifierPart(editorText.charAt(end))) { + while (end < length && isWordPartCondition.value(editorText.charAt(end))) { end++; } @@ -229,7 +243,7 @@ public class SelectWordUtil { result.add(new TextRange(lexer.getTokenStart(), lexer.getTokenEnd())); } else { - TextRange word = getWordSelectionRange(editorText, cursorOffset); + TextRange word = getWordSelectionRange(editorText, cursorOffset, JAVA_IDENTIFIER_PART_CONDITION); if (word != null) { result.add(new TextRange(Math.max(word.getStartOffset(), lexer.getTokenStart()), Math.min(word.getEndOffset(), lexer.getTokenEnd()))); @@ -240,4 +254,6 @@ public class SelectWordUtil { lexer.advance(); } } + + public interface CharCondition { boolean value(char ch); } } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByLineCommentHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByLineCommentHandler.java index ef79a906b9f4..b6c0f77f1a9f 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByLineCommentHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/generation/CommentByLineCommentHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -45,6 +45,7 @@ import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.codeStyle.CodeStyleSettingsManager; import com.intellij.psi.codeStyle.Indent; import com.intellij.psi.util.PsiUtilBase; +import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.DocumentUtil; import com.intellij.util.containers.IntArrayList; import com.intellij.util.text.CharArrayUtil; @@ -385,8 +386,8 @@ public class CommentByLineCommentHandler implements CodeInsightActionHandler { final CharSequence charSequence = myDocument.getCharsSequence(); lineStartOffset = CharArrayUtil.shiftForward(charSequence, lineStartOffset, " \t"); lineEndOffset = CharArrayUtil.shiftBackward(charSequence, lineEndOffset < 0 ? 0 : lineEndOffset, " \t"); - final Language lineStartLanguage = PsiUtilBase.getLanguageAtOffset(myFile, lineStartOffset); - final Language lineEndLanguage = PsiUtilBase.getLanguageAtOffset(myFile, lineEndOffset); + final Language lineStartLanguage = PsiUtilCore.getLanguageAtOffset(myFile, lineStartOffset); + final Language lineEndLanguage = PsiUtilCore.getLanguageAtOffset(myFile, lineEndOffset); return CommentByBlockCommentHandler.getCommenter(myFile, myEditor, lineStartLanguage, lineEndLanguage); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/generation/DelegateMethodsHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/generation/DelegateMethodsHandler.java index 3f00e7919357..e21167186cd4 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/generation/DelegateMethodsHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/generation/DelegateMethodsHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -25,7 +25,7 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiFile; -import com.intellij.psi.util.PsiUtilBase; +import com.intellij.psi.util.PsiUtilCore; import org.jetbrains.annotations.NotNull; public class DelegateMethodsHandler implements CodeInsightActionHandler{ @@ -36,7 +36,7 @@ public class DelegateMethodsHandler implements CodeInsightActionHandler{ return; } - Language language = PsiUtilBase.getLanguageAtOffset(file, editor.getCaretModel().getOffset()); + Language language = PsiUtilCore.getLanguageAtOffset(file, editor.getCaretModel().getOffset()); final LanguageCodeInsightActionHandler codeInsightActionHandler = CodeInsightActions.DELEGATE_METHODS.forLanguage(language); if (codeInsightActionHandler != null) { codeInsightActionHandler.invoke(project, editor, file); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/generation/ImplementMethodsHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/generation/ImplementMethodsHandler.java index 25f0976cd949..3516cf3de745 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/generation/ImplementMethodsHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/generation/ImplementMethodsHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -25,7 +25,7 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiFile; -import com.intellij.psi.util.PsiUtilBase; +import com.intellij.psi.util.PsiUtilCore; import org.jetbrains.annotations.NotNull; public class ImplementMethodsHandler implements CodeInsightActionHandler{ @@ -36,7 +36,7 @@ public class ImplementMethodsHandler implements CodeInsightActionHandler{ return; } - Language language = PsiUtilBase.getLanguageAtOffset(file, editor.getCaretModel().getOffset()); + Language language = PsiUtilCore.getLanguageAtOffset(file, editor.getCaretModel().getOffset()); final LanguageCodeInsightActionHandler codeInsightActionHandler = CodeInsightActions.IMPLEMENT_METHOD.forLanguage(language); if (codeInsightActionHandler != null) { codeInsightActionHandler.invoke(project, editor, file); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/generation/OverrideMethodsHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/generation/OverrideMethodsHandler.java index ef1b513bed4f..18bb33d80691 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/generation/OverrideMethodsHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/generation/OverrideMethodsHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -25,7 +25,7 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiFile; -import com.intellij.psi.util.PsiUtilBase; +import com.intellij.psi.util.PsiUtilCore; import org.jetbrains.annotations.NotNull; public class OverrideMethodsHandler implements CodeInsightActionHandler{ @@ -36,7 +36,7 @@ public class OverrideMethodsHandler implements CodeInsightActionHandler{ return; } - Language language = PsiUtilBase.getLanguageAtOffset(file, editor.getCaretModel().getOffset()); + Language language = PsiUtilCore.getLanguageAtOffset(file, editor.getCaretModel().getOffset()); final LanguageCodeInsightActionHandler codeInsightActionHandler = CodeInsightActions.OVERRIDE_METHOD.forLanguage(language); if (codeInsightActionHandler != null) { codeInsightActionHandler.invoke(project, editor, file); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/DelegateMethodsAction.java b/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/DelegateMethodsAction.java index bc5d831dcd47..73bde9118d8f 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/DelegateMethodsAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/DelegateMethodsAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -26,7 +26,7 @@ import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiFile; -import com.intellij.psi.util.PsiUtilBase; +import com.intellij.psi.util.PsiUtilCore; import org.jetbrains.annotations.NotNull; public class DelegateMethodsAction extends BaseCodeInsightAction { @@ -39,7 +39,7 @@ public class DelegateMethodsAction extends BaseCodeInsightAction { @Override protected boolean isValidForFile(@NotNull Project project, @NotNull Editor editor, @NotNull final PsiFile file) { - Language language = PsiUtilBase.getLanguageAtOffset(file, editor.getCaretModel().getOffset()); + Language language = PsiUtilCore.getLanguageAtOffset(file, editor.getCaretModel().getOffset()); final LanguageCodeInsightActionHandler codeInsightActionHandler = CodeInsightActions.DELEGATE_METHODS.forLanguage(language); if (codeInsightActionHandler != null) { return codeInsightActionHandler.isValidFor(editor, file); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/ImplementMethodsAction.java b/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/ImplementMethodsAction.java index ed97b98f7868..27c2a4645a16 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/ImplementMethodsAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/ImplementMethodsAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -26,7 +26,7 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.psi.PsiFile; -import com.intellij.psi.util.PsiUtilBase; +import com.intellij.psi.util.PsiUtilCore; import org.jetbrains.annotations.NotNull; public class ImplementMethodsAction extends BaseCodeInsightAction { @@ -38,7 +38,7 @@ public class ImplementMethodsAction extends BaseCodeInsightAction { @Override protected boolean isValidForFile(@NotNull Project project, @NotNull Editor editor, @NotNull final PsiFile file) { - final Language language = PsiUtilBase.getLanguageAtOffset(file, editor.getCaretModel().getOffset()); + final Language language = PsiUtilCore.getLanguageAtOffset(file, editor.getCaretModel().getOffset()); final LanguageCodeInsightActionHandler codeInsightActionHandler = CodeInsightActions.IMPLEMENT_METHOD.forLanguage(language); return codeInsightActionHandler != null && codeInsightActionHandler.isValidFor(editor, file); } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/OverrideMethodsAction.java b/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/OverrideMethodsAction.java index 9827f1272294..134bba4eb3ee 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/OverrideMethodsAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/generation/actions/OverrideMethodsAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2011 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -26,7 +26,7 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.psi.PsiFile; -import com.intellij.psi.util.PsiUtilBase; +import com.intellij.psi.util.PsiUtilCore; import org.jetbrains.annotations.NotNull; public class OverrideMethodsAction extends BaseCodeInsightAction { @@ -39,7 +39,7 @@ public class OverrideMethodsAction extends BaseCodeInsightAction { @Override protected boolean isValidForFile(@NotNull Project project, @NotNull Editor editor, @NotNull final PsiFile file) { - Language language = PsiUtilBase.getLanguageAtOffset(file, editor.getCaretModel().getOffset()); + Language language = PsiUtilCore.getLanguageAtOffset(file, editor.getCaretModel().getOffset()); final LanguageCodeInsightActionHandler codeInsightActionHandler = CodeInsightActions.OVERRIDE_METHOD.forLanguage(language); if (codeInsightActionHandler != null) { return codeInsightActionHandler.isValidFor(editor, file); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/highlighting/BraceHighlightingHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/highlighting/BraceHighlightingHandler.java index 651ffc8ee015..85f5c2233479 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/highlighting/BraceHighlightingHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/highlighting/BraceHighlightingHandler.java @@ -55,6 +55,7 @@ import com.intellij.psi.*; import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiUtilBase; +import com.intellij.psi.util.PsiUtilCore; import com.intellij.ui.ColorUtil; import com.intellij.util.Alarm; import com.intellij.util.Processor; @@ -493,7 +494,8 @@ public class BraceHighlightingHandler { int start = lbraceStart; if (!(myPsiFile instanceof PsiPlainTextFile) && myPsiFile.isValid()) { PsiDocumentManager.getInstance(myProject).commitAllDocuments(); - start = BraceMatchingUtil.getBraceMatcher(getFileTypeByOffset(lbraceStart), PsiUtilBase.getLanguageAtOffset(myPsiFile, lbraceStart)).getCodeConstructStart(myPsiFile, lbraceStart); + start = BraceMatchingUtil.getBraceMatcher(getFileTypeByOffset(lbraceStart), PsiUtilCore + .getLanguageAtOffset(myPsiFile, lbraceStart)).getCodeConstructStart(myPsiFile, lbraceStart); } TextRange range = new TextRange(start, lbraceEnd); int line1 = myDocument.getLineNumber(range.getStartOffset()); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hint/ParameterInfoController.java b/platform/lang-impl/src/com/intellij/codeInsight/hint/ParameterInfoController.java index 966c935b0698..a4d94da42c4c 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hint/ParameterInfoController.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/hint/ParameterInfoController.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -34,7 +34,7 @@ import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; -import com.intellij.psi.util.PsiUtilBase; +import com.intellij.psi.util.PsiUtilCore; import com.intellij.ui.LightweightHint; import com.intellij.util.Alarm; import com.intellij.util.ArrayUtil; @@ -317,7 +317,7 @@ public class ParameterInfoController implements Disposable { @Nullable public static <E extends PsiElement> E findArgumentList(PsiFile file, int offset, int lbraceOffset){ if (file == null) return null; - ParameterInfoHandler[] handlers = ShowParameterInfoHandler.getHandlers(file.getProject(), PsiUtilBase.getLanguageAtOffset(file, offset), file.getViewProvider().getBaseLanguage()); + ParameterInfoHandler[] handlers = ShowParameterInfoHandler.getHandlers(file.getProject(), PsiUtilCore.getLanguageAtOffset(file, offset), file.getViewProvider().getBaseLanguage()); if (handlers != null) { for(ParameterInfoHandler handler:handlers) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/hint/actions/ShowParameterInfoAction.java b/platform/lang-impl/src/com/intellij/codeInsight/hint/actions/ShowParameterInfoAction.java index 439e6dc7b8c5..437603ff5bdb 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/hint/actions/ShowParameterInfoAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/hint/actions/ShowParameterInfoAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -24,7 +24,7 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiFile; -import com.intellij.psi.util.PsiUtilBase; +import com.intellij.psi.util.PsiUtilCore; import org.jetbrains.annotations.NotNull; public class ShowParameterInfoAction extends BaseCodeInsightAction implements DumbAware { @@ -40,7 +40,7 @@ public class ShowParameterInfoAction extends BaseCodeInsightAction implements Du @Override protected boolean isValidForFile(@NotNull Project project, @NotNull Editor editor, @NotNull final PsiFile file) { - final Language language = PsiUtilBase.getLanguageAtOffset(file, editor.getCaretModel().getOffset()); + final Language language = PsiUtilCore.getLanguageAtOffset(file, editor.getCaretModel().getOffset()); return ShowParameterInfoHandler.getHandlers(project, language, file.getViewProvider().getBaseLanguage()) != null; } diff --git a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/QuickEditHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/QuickEditHandler.java index 8af41a35df81..81312281355b 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/QuickEditHandler.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/intention/impl/QuickEditHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -147,6 +147,7 @@ public class QuickEditHandler extends DocumentAdapter implements Disposable { public void update(AnActionEvent e) { Editor editor = CommonDataKeys.EDITOR.getData(e.getDataContext()); e.getPresentation().setEnabled( + !myAction.isShowInBalloon() && editor != null && LookupManager.getActiveLookup(editor) == null && TemplateManager.getInstance(myProject).getActiveTemplate(editor) == null && (editorEscape == null || !editorEscape.isEnabled(editor, e.getDataContext()))); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/navigation/actions/GotoSuperAction.java b/platform/lang-impl/src/com/intellij/codeInsight/navigation/actions/GotoSuperAction.java index ea26953e25ba..fc0e0cd73afc 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/navigation/actions/GotoSuperAction.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/navigation/actions/GotoSuperAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2011 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -26,7 +26,7 @@ import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; -import com.intellij.psi.util.PsiUtilBase; +import com.intellij.psi.util.PsiUtilCore; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -45,7 +45,7 @@ public class GotoSuperAction extends BaseCodeInsightAction implements CodeInsigh PsiDocumentManager.getInstance(project).commitAllDocuments(); int offset = editor.getCaretModel().getOffset(); - final Language language = PsiUtilBase.getLanguageAtOffset(file, offset); + final Language language = PsiUtilCore.getLanguageAtOffset(file, offset); final CodeInsightActionHandler codeInsightActionHandler = CodeInsightActions.GOTO_SUPER.forLanguage(language); if (codeInsightActionHandler != null) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/LiveTemplateBuilder.java b/platform/lang-impl/src/com/intellij/codeInsight/template/LiveTemplateBuilder.java index b2756647a745..592b6915cd36 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/LiveTemplateBuilder.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/LiveTemplateBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -50,10 +50,6 @@ public class LiveTemplateBuilder { return name.startsWith(END_PREFIX); } - public void insertVariableSegment(int offset, String name) { - myVariableOccurences.add(new VarOccurence(name, offset)); - } - private static class VarOccurence { String myName; int myOffset; @@ -262,7 +258,10 @@ public class LiveTemplateBuilder { for (int i = 0; i < template.getSegmentsCount(); i++) { String segmentName = template.getSegmentName(i); int localOffset = template.getSegmentOffset(i); - if (!TemplateImpl.INTERNAL_VARS_SET.contains(segmentName)) { + if (TemplateImpl.END.equals(segmentName)) { + end = offset + localOffset; + } + else { if (predefinedVarValues != null && predefinedVarValues.containsKey(segmentName)) { String value = predefinedVarValues.get(segmentName); insertText(offset + localOffset, value, false); @@ -274,9 +273,6 @@ public class LiveTemplateBuilder { } myVariableOccurences.add(new VarOccurence(segmentName, offset + localOffset)); } - else if (TemplateImpl.END.equals(segmentName)) { - end = offset + localOffset; - } } int endOffset = end >= 0 ? end : offset + text.length(); if (endOffset > 0 && diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/LiveTemplateLookupElement.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/LiveTemplateLookupElement.java index 00e6c44d728d..e22a4114beed 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/LiveTemplateLookupElement.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/LiveTemplateLookupElement.java @@ -65,7 +65,10 @@ public class LiveTemplateLookupElement extends LookupElement { } presentation.setTypeText(" [" + KeyEvent.getKeyText(shortcutChar) + "] "); } - presentation.setTailText(" (" + myTemplate.getDescription() + ")", true); + String description = myTemplate.getDescription(); + if (description != null) { + presentation.setTailText(" (" + description + ")", true); + } } else { presentation.setTypeText(myTemplate.getDescription()); diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateManagerImpl.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateManagerImpl.java index f1a94deefaab..0c1e6045fee6 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateManagerImpl.java @@ -36,6 +36,7 @@ import com.intellij.openapi.util.Key; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.psi.util.PsiUtilBase; +import com.intellij.psi.util.PsiUtilCore; import com.intellij.util.PairProcessor; import com.intellij.util.containers.HashMap; import org.jetbrains.annotations.NotNull; @@ -570,7 +571,7 @@ public class TemplateManagerImpl extends TemplateManager implements ProjectCompo // if we have, for example, a Ruby fragment in RHTML selected with its exact bounds, the file language and the base // language will be ERb, so we won't match HTML templates for it. but they're actually valid - Language languageAtOffset = PsiUtilBase.getLanguageAtOffset(file, offset); + Language languageAtOffset = PsiUtilCore.getLanguageAtOffset(file, offset); if (languageAtOffset != file.getLanguage() && languageAtOffset != baseLanguage) { PsiFile basePsi = file.getViewProvider().getPsi(languageAtOffset); if (basePsi != null) { diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java index 38bd35f6d96a..3f31d5c2d008 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java @@ -51,7 +51,6 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.psi.impl.source.codeStyle.CodeStyleManagerImpl; -import com.intellij.psi.util.PsiUtilBase; import com.intellij.psi.util.PsiUtilCore; import com.intellij.refactoring.rename.inplace.InplaceRefactoring; import com.intellij.util.IncorrectOperationException; @@ -339,11 +338,11 @@ public class TemplateState implements Disposable { mySegments.addSegment(segmentOffset, segmentOffset); } - LOG.assertTrue(myTemplateRange.isValid()); + LOG.assertTrue(myTemplateRange.isValid(), myTemplateRange.toString()); calcResults(false); - LOG.assertTrue(myTemplateRange.isValid()); + LOG.assertTrue(myTemplateRange.isValid(), myTemplateRange.toString()); calcResults(false); //Fixed SCR #[vk500] : all variables should be recalced twice on start. - LOG.assertTrue(myTemplateRange.isValid()); + LOG.assertTrue(myTemplateRange.isValid(), myTemplateRange.toString()); doReformat(null); int nextVariableNumber = getNextVariableNumber(-1); diff --git a/platform/lang-impl/src/com/intellij/codeInspection/LossyEncodingInspection.java b/platform/lang-impl/src/com/intellij/codeInspection/LossyEncodingInspection.java index 72336b57c591..7ec8be737e37 100644 --- a/platform/lang-impl/src/com/intellij/codeInspection/LossyEncodingInspection.java +++ b/platform/lang-impl/src/com/intellij/codeInspection/LossyEncodingInspection.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -22,7 +22,6 @@ */ package com.intellij.codeInspection; -import com.intellij.codeStyle.CodeStyleFacade; import com.intellij.ide.DataManager; import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.lang.properties.charset.Native2AsciiCharset; @@ -41,11 +40,9 @@ import com.intellij.openapi.ui.popup.ListPopup; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.encoding.ChangeFileEncodingAction; import com.intellij.openapi.vfs.encoding.EncodingUtil; -import com.intellij.openapi.vfs.ex.temp.TempFileSystem; import com.intellij.psi.PsiFile; import com.intellij.psi.util.PsiUtilBase; import com.intellij.util.ArrayUtil; @@ -99,9 +96,7 @@ public class LossyEncodingInspection extends LocalInspectionTool { if (file.getViewProvider().getBaseLanguage() != file.getLanguage()) return null; VirtualFile virtualFile = file.getVirtualFile(); if (virtualFile == null) return null; - if (virtualFile.getFileSystem() != LocalFileSystem.getInstance() - // tests - && virtualFile.getFileSystem() != TempFileSystem.getInstance()) return null; + if (!virtualFile.isInLocalFileSystem()) return null; String text = file.getText(); Charset charset = LoadTextUtil.extractCharsetFromFileContent(file.getProject(), virtualFile, text); @@ -157,7 +152,7 @@ public class LossyEncodingInspection extends LocalInspectionTool { if (separator == null) { separator = documentManager.isDocumentUnsaved(document) ? FileDocumentManagerImpl.getLineSeparator(document, virtualFile) : - CodeStyleFacade.getInstance(project).getLineSeparator(); + FileDocumentManager.getInstance().getLineSeparator(null, project); } String toSave = StringUtil.convertLineSeparators(text, separator); byte[] bom = virtualFile.getBOM(); diff --git a/platform/lang-impl/src/com/intellij/execution/console/ConsoleHistoryController.java b/platform/lang-impl/src/com/intellij/execution/console/ConsoleHistoryController.java index d7d1ab894833..ac25e69af98b 100644 --- a/platform/lang-impl/src/com/intellij/execution/console/ConsoleHistoryController.java +++ b/platform/lang-impl/src/com/intellij/execution/console/ConsoleHistoryController.java @@ -19,9 +19,7 @@ import com.intellij.codeInsight.lookup.LookupManager; import com.intellij.execution.process.ConsoleHistoryModel; import com.intellij.lang.Language; import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.AnAction; -import com.intellij.openapi.actionSystem.AnActionEvent; -import com.intellij.openapi.actionSystem.EmptyAction; +import com.intellij.openapi.actionSystem.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.PathManager; import com.intellij.openapi.command.WriteCommandAction; @@ -127,8 +125,16 @@ public class ConsoleHistoryController { EmptyAction.setupAction(myHistoryPrev, "Console.History.Previous", null); EmptyAction.setupAction(myBrowseHistory, "Console.History.Browse", null); if (!myMultiline) { - myHistoryNext.registerCustomShortcutSet(KeyEvent.VK_UP, 0, null); - myHistoryPrev.registerCustomShortcutSet(KeyEvent.VK_DOWN, 0, null); + AnAction up = ActionManager.getInstance().getActionOrStub(IdeActions.ACTION_EDITOR_MOVE_CARET_UP); + AnAction down = ActionManager.getInstance().getActionOrStub(IdeActions.ACTION_EDITOR_MOVE_CARET_DOWN); + if (up != null && down != null) { + myHistoryNext.registerCustomShortcutSet(up.getShortcutSet(), null); + myHistoryPrev.registerCustomShortcutSet(down.getShortcutSet(), null); + } + else { + myHistoryNext.registerCustomShortcutSet(KeyEvent.VK_UP, 0, null); + myHistoryPrev.registerCustomShortcutSet(KeyEvent.VK_DOWN, 0, null); + } } myHistoryNext.registerCustomShortcutSet(myHistoryNext.getShortcutSet(), myConsole.getCurrentEditor().getComponent()); myHistoryPrev.registerCustomShortcutSet(myHistoryPrev.getShortcutSet(), myConsole.getCurrentEditor().getComponent()); diff --git a/platform/lang-impl/src/com/intellij/execution/console/LanguageConsoleImpl.java b/platform/lang-impl/src/com/intellij/execution/console/LanguageConsoleImpl.java index 8acd2da516ef..e39640d74694 100644 --- a/platform/lang-impl/src/com/intellij/execution/console/LanguageConsoleImpl.java +++ b/platform/lang-impl/src/com/intellij/execution/console/LanguageConsoleImpl.java @@ -557,7 +557,8 @@ public class LanguageConsoleImpl implements Disposable, TypeSafeDataProvider { } if (preserveMarkup) { duplicateHighlighters(markupModel, DocumentMarkupModel.forDocument(consoleEditor.getDocument(), myProject, true), offset, textRange); - duplicateHighlighters(markupModel, consoleEditor.getMarkupModel(), offset, textRange); + // don't copy editor markup model, i.e. brace matcher, spell checker, etc. + // duplicateHighlighters(markupModel, consoleEditor.getMarkupModel(), offset, textRange); } if (!text.endsWith("\n")) { appendToHistoryDocument(history, "\n"); diff --git a/platform/lang-impl/src/com/intellij/framework/library/impl/FrameworkLibraryVersionImpl.java b/platform/lang-impl/src/com/intellij/framework/library/impl/FrameworkLibraryVersionImpl.java index 9a265a198544..5e8a897ade67 100644 --- a/platform/lang-impl/src/com/intellij/framework/library/impl/FrameworkLibraryVersionImpl.java +++ b/platform/lang-impl/src/com/intellij/framework/library/impl/FrameworkLibraryVersionImpl.java @@ -55,4 +55,15 @@ public class FrameworkLibraryVersionImpl extends DownloadableFileSetDescriptionI String libName = StringUtil.isEmptyOrSpaces(myLibraryName) ? myLibraryCategory : myLibraryName; return myVersionString.length() > 0 ? libName + "-" + myVersionString : myLibraryCategory; } + + @NotNull + @Override + public String getPresentableName() { + return getDefaultLibraryName(); + } + + @Override + public String getVersionNumber() { + return getVersionString(); + } } diff --git a/platform/lang-impl/src/com/intellij/ide/actions/SearchEverywhereAction.java b/platform/lang-impl/src/com/intellij/ide/actions/SearchEverywhereAction.java index 91adb014bc16..da6216f32262 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/SearchEverywhereAction.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/SearchEverywhereAction.java @@ -19,6 +19,7 @@ import com.intellij.codeInsight.navigation.NavigationUtil; import com.intellij.icons.AllIcons; import com.intellij.ide.DataManager; import com.intellij.ide.IdeEventQueue; +import com.intellij.ide.IdeTooltipManager; import com.intellij.ide.SearchTopHitProvider; import com.intellij.ide.ui.LafManager; import com.intellij.ide.ui.LafManagerListener; @@ -38,6 +39,7 @@ import com.intellij.openapi.editor.actions.TextComponentEditorAction; import com.intellij.openapi.fileEditor.OpenFileDescriptor; import com.intellij.openapi.fileEditor.impl.EditorHistoryManager; import com.intellij.openapi.keymap.KeymapUtil; +import com.intellij.openapi.keymap.MacKeymapUtil; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.SearchableConfigurable; import com.intellij.openapi.options.ex.IdeConfigurablesGroup; @@ -48,12 +50,11 @@ import com.intellij.openapi.progress.util.ProgressIndicatorBase; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.DumbServiceImpl; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.popup.Balloon; import com.intellij.openapi.ui.popup.ComponentPopupBuilder; import com.intellij.openapi.ui.popup.JBPopup; import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.ui.popup.MouseChecker; import com.intellij.openapi.util.*; -import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFilePathWrapper; @@ -73,7 +74,6 @@ import com.intellij.ui.components.JBList; import com.intellij.ui.components.JBScrollPane; import com.intellij.ui.components.OnOffButton; import com.intellij.ui.popup.AbstractPopup; -import com.intellij.ui.popup.PopupPositionManager; import com.intellij.util.Alarm; import com.intellij.util.ArrayUtil; import com.intellij.util.Consumer; @@ -92,8 +92,7 @@ import java.lang.reflect.Field; import java.util.*; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; - -import static com.intellij.ui.popup.PopupPositionManager.Position.*; +import java.util.concurrent.atomic.AtomicLong; /** * @author Konstantin Bulenkov @@ -122,23 +121,14 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA private Alarm myAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, ApplicationManager.getApplication()); private Alarm myUpdateAlarm = new Alarm(ApplicationManager.getApplication()); - private JBList myList = new JBList(myListModel) { - { - setOpaque(false); - } - @Override - protected void paintComponent(Graphics g) { - //g.setColor(getTitlePanelBackground()); - //g.fillRect(0, 0, myLeftWidth - 1, getHeight()); - //g.setColor(getSeparatorColor()); - //g.drawLine(myLeftWidth-1, 0, myLeftWidth-1, getHeight()); - super.paintComponent(g); - } - }; + private JBList myList = new JBList(myListModel); private AnActionEvent myActionEvent; private Component myContextComponent; private CalcThread myCalcThread; - private static AtomicBoolean ourShiftCanBeUsed = new AtomicBoolean(false); + private static AtomicBoolean ourPressed = new AtomicBoolean(false); + private static AtomicBoolean ourReleased = new AtomicBoolean(false); + private static AtomicBoolean ourOtherKeyWasPressed = new AtomicBoolean(false); + private static AtomicLong ourLastTimePressed = new AtomicLong(0); private ArrayList<VirtualFile> myAlreadyAddedFiles = new ArrayList<VirtualFile>(); static { @@ -146,50 +136,125 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA @Override public boolean dispatch(AWTEvent event) { if (event instanceof KeyEvent) { - ourShiftCanBeUsed.set((((KeyEvent)event).getKeyCode() != KeyEvent.VK_SHIFT) || event.getID() != KeyEvent.KEY_PRESSED); + final KeyEvent keyEvent = (KeyEvent)event; + final int keyCode = keyEvent.getKeyCode(); + + if (keyCode == KeyEvent.VK_SHIFT) { + if (ourOtherKeyWasPressed.get() && System.currentTimeMillis() - ourLastTimePressed.get() < 300) { + ourPressed.set(false); + ourReleased.set(false); + return false; + } + ourOtherKeyWasPressed.set(false); + if (System.currentTimeMillis() - ourLastTimePressed.get() > 400) { + ourPressed.set(false); + ourReleased.set(false); + } + if (event.getID() == KeyEvent.KEY_PRESSED) { + if (!ourPressed.get()) { + ourPressed.set(true); + ourLastTimePressed.set(System.currentTimeMillis()); + } else { + if (ourPressed.get() && ourReleased.get()) { + ourPressed.set(false); + ourReleased.set(false); + ourLastTimePressed.set(System.currentTimeMillis()); + final ActionManager actionManager = ActionManager.getInstance(); + final AnAction action = actionManager.getAction("SearchEverywhere"); + + final AnActionEvent anActionEvent = new AnActionEvent(keyEvent, + DataManager.getInstance().getDataContext(IdeFocusManager.findInstance().getFocusOwner()), + ActionPlaces.UNKNOWN, + action.getTemplatePresentation(), + actionManager, + 0); + action.actionPerformed(anActionEvent); + } + } + } else if (event.getID() == KeyEvent.KEY_RELEASED) { + if (ourPressed.get()) { + ourReleased.set(true); + } + } + return false; + } else { + ourLastTimePressed.set(System.currentTimeMillis()); + ourOtherKeyWasPressed.set(true); + } + ourPressed.set(false); + ourReleased.set(false); } return false; } }, null); } - private Balloon myBalloon; + private JBPopup myBalloon; private JLabel mySearchLabel; private int myPopupActualWidth; + private Component myFocusOwner; public SearchEverywhereAction() { - myContentPanel = new JPanel(new BorderLayout()); + myContentPanel = new JPanel(new BorderLayout()) { + @Override + protected void paintComponent(Graphics g) { + if (myBalloon != null && !myBalloon.isDisposed() && myActionEvent != null && myActionEvent.getInputEvent() == null) { + ((Graphics2D)g).setPaint(new GradientPaint(0,0, new JBColor(new Color(101, 136, 242), new Color(16, 91, 180)), 0, getHeight(), + new JBColor(new Color(44, 96, 238), new Color(16, 80, 147)))); + g.fillRect(0,0,getWidth(), getHeight()); + } else { + super.paintComponent(g); + } + } + }; myContentPanel.setOpaque(false); + myList.setOpaque(false); mySearchLabel = new JBLabel(AllIcons.Actions.FindPlain) { { enableEvents(AWTEvent.MOUSE_EVENT_MASK); enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK); - setToolTipText("<html><body>Search Everywhere<br/>Press <b>" - + - KeymapUtil.getShortcutText(CustomShortcutSet.fromString("shift SPACE").getShortcuts()[0]) + - "</b> to access<br/> - Classes<br/> - Files<br/> - Tool Windows<br/> - Actions<br/> - Settings</body></html>"); } }; + myContentPanel.add(mySearchLabel, BorderLayout.CENTER); + initTooltip(); mySearchLabel.addMouseListener(new MouseAdapter() { @Override public void mousePressed(MouseEvent e) { - mySearchLabel.setIcon(AllIcons.Actions.FindPlain); - myContentPanel.remove(mySearchLabel); - field.getTextEditor().setColumns(SEARCH_FIELD_COLUMNS); - myContentPanel.add(field, BorderLayout.CENTER); - myContentPanel.revalidate(); - myContentPanel.repaint(); - IdeFocusManager.findInstanceByComponent(field.getTextEditor()).requestFocus(field.getTextEditor(), true); + if (myBalloon != null) { + myBalloon.cancel(); + } + myFocusOwner = IdeFocusManager.findInstance().getFocusOwner(); + mySearchLabel.setToolTipText(null); + IdeTooltipManager.getInstance().hideCurrentNow(false); + mySearchLabel.setIcon(AllIcons.Actions.FindWhite); + actionPerformed(null); +// mySearchLabel.setIcon(AllIcons.Actions.FindPlain); +// myContentPanel.remove(mySearchLabel); +// field.getTextEditor().setColumns(SEARCH_FIELD_COLUMNS); +// myContentPanel.add(field, BorderLayout.CENTER); +// field.setOpaque(false); +// myContentPanel.setOpaque(false); +// JComponent parent = UIUtil.getParentOfType(Box.class, myContentPanel); +// if (parent == null) { +// parent = (JComponent)myContentPanel.getParent().getParent(); +// } +// parent.revalidate(); +// parent.repaint(); +// IdeFocusManager.findInstanceByComponent(field.getTextEditor()).requestFocus(field.getTextEditor(), true); } @Override public void mouseEntered(MouseEvent e) { - mySearchLabel.setIcon(AllIcons.Actions.Find); + if (myBalloon == null || myBalloon.isDisposed()) { + mySearchLabel.setIcon(AllIcons.Actions.Find); + } } @Override public void mouseExited(MouseEvent e) { - mySearchLabel.setIcon(AllIcons.Actions.FindPlain); + if (myBalloon == null || myBalloon.isDisposed()) { + mySearchLabel.setIcon(AllIcons.Actions.FindPlain); + } } }); @@ -221,16 +286,13 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA } - @Override - public void update(AnActionEvent e) { - final InputEvent event = e.getInputEvent(); - if (event instanceof KeyEvent && event.isShiftDown() && ((KeyEvent)event).getKeyCode() == KeyEvent.VK_SPACE) { - e.getPresentation().setEnabledAndVisible(!ourShiftCanBeUsed.get() && Registry.is("search.everywhere.enabled")); - } else { - e.getPresentation().setEnabled(true); - } - } + private void initTooltip() { + mySearchLabel.setToolTipText("<html><body>Search Everywhere<br/>Press <b>" + + "Double " + + (SystemInfo.isMac ? MacKeymapUtil.SHIFT : "Shift") + + "</b> to access<br/> - Classes<br/> - Files<br/> - Tool Windows<br/> - Actions<br/> - Settings</body></html>"); + } private void createSearchField() { field = new MySearchTextField(); @@ -239,6 +301,8 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA private void initSearchField(final SearchTextField search) { final JTextField editor = search.getTextEditor(); +// editor.setOpaque(false); + editor.putClientProperty("JTextField.Search.noFocusRing", Boolean.TRUE); onFocusLost(editor); editor.getDocument().addDocumentListener(new DocumentAdapter() { @Override @@ -312,18 +376,22 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA SwingUtilities.invokeLater(new Runnable() { @Override public void run() { - editor.setColumns(SystemInfo.isMac ? 5 : 8); - field.getTextEditor().setForeground(UIUtil.getLabelDisabledForeground()); - field.setText(" " + KeymapUtil.getFirstKeyboardShortcutText(SearchEverywhereAction.this)); +// editor.setColumns(SystemInfo.isMac ? 5 : 8); +// field.getTextEditor().setForeground(UIUtil.getLabelDisabledForeground()); +// field.setText(" " + KeymapUtil.getFirstKeyboardShortcutText(SearchEverywhereAction.this)); if (myCalcThread != null) { myCalcThread.cancel(); myCalcThread = null; } myAlarm.cancelAllRequests(); clearModel(); - myContentPanel.remove(field); - mySearchLabel.setIcon(AllIcons.Actions.FindPlain); - myContentPanel.add(mySearchLabel); + if (myBalloon != null && !myBalloon.isDisposed() && myPopup != null && !myPopup.isDisposed()) { + myBalloon.cancel(); + myPopup.cancel(); + } +// myContentPanel.remove(field); +// mySearchLabel.setIcon(AllIcons.Actions.FindPlain); +// myContentPanel.add(mySearchLabel, BorderLayout.CENTER); //noinspection SSBasedInspection SwingUtilities.invokeLater(new Runnable() { @@ -431,43 +499,68 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA @Override public void actionPerformed(AnActionEvent e) { + if (e == null && myFocusOwner != null) { + e = new AnActionEvent(null, DataManager.getInstance().getDataContext(myFocusComponent), ActionPlaces.UNKNOWN, getTemplatePresentation(), ActionManager.getInstance(), 0); + } + if (e == null) return; myContextComponent = PlatformDataKeys.CONTEXT_COMPONENT.getData(e.getDataContext()); myActionEvent = e; myPopupField = new MySearchTextField(); - myPopupField.setOpaque(true); + myPopupField.setOpaque(false); initSearchField(myPopupField); myPopupField.getTextEditor().setColumns(SEARCH_FIELD_COLUMNS); - final JPanel panel = new JPanel(new BorderLayout()); + final JPanel panel = new JPanel(new BorderLayout()) { + @Override + protected void paintComponent(Graphics g) { + ((Graphics2D)g).setPaint(new GradientPaint(0,0, new JBColor(new Color(0x6688f2), new Color(59, 81, 162)), 0, getHeight(), + new JBColor(new Color(0x2d60ee), new Color(53, 67, 134)))); + g.fillRect(0,0, getWidth(), getHeight()); + } + }; final JLabel title = new JLabel(" Search Everywhere:"); + title.setForeground(new JBColor(Gray._255, Gray._160)); if (SystemInfo.isMac) { title.setFont(title.getFont().deriveFont(Font.BOLD, title.getFont().getSize() - 1f)); } else { title.setFont(title.getFont().deriveFont(Font.BOLD)); } - panel.add(title, BorderLayout.NORTH); + panel.add(title, BorderLayout.WEST); panel.add(myPopupField, BorderLayout.CENTER); - panel.setBorder(IdeBorderFactory.createEmptyBorder(0, 5, 2, 5)); - myBalloon = JBPopupFactory.getInstance().createBalloonBuilder(panel) - .setShowCallout(false) - .setHideOnKeyOutside(false) - .setHideOnAction(false) - .setAnimationCycle(0) - .setDialogMode(false) - .setBorderColor(new JBColor(Gray._130, Gray._77)) - .setFillColor(new JBColor(Gray._242, new Color(60, 63, 65))) - .createBalloon(); + panel.setBorder(IdeBorderFactory.createEmptyBorder(3, 5, 4, 5)); +// final BalloonPopupBuilderImpl builder = (BalloonPopupBuilderImpl)JBPopupFactory.getInstance().createBalloonBuilder(panel); +// myBalloon = builder +// .setCustomPointlessBorder(new EmptyBorder(0,0,0,0)) +// .setShowCallout(false) +// .setHideOnKeyOutside(false) +// .setHideOnAction(false) +// .setAnimationCycle(0) +// .setBorderColor(new JBColor(Gray._130, Gray._77)) +// .setFillColor(new JBColor(new Color(0x2d60ee), new Color(60, 63, 65))) +// .createBalloon(); + final ComponentPopupBuilder builder = JBPopupFactory.getInstance().createComponentPopupBuilder(panel, myPopupField.getTextEditor()); + myBalloon = builder + .setCancelOnClickOutside(true) + .setModalContext(false) + .createPopup(); + final Window window = WindowManager.getInstance().suggestParentWindow(e.getProject()); Component parent = UIUtil.findUltimateParent(window); + final RelativePoint showPoint; - if (parent != null) { - showPoint = new RelativePoint(parent, new Point((parent.getSize().width - panel.getPreferredSize().width)/ 2, parent.getHeight()/3)); + if (e.getInputEvent() == null) { + final Component button = mySearchLabel.getParent(); + assert button != null; + showPoint = new RelativePoint(button, new Point(button.getWidth() - panel.getPreferredSize().width, button.getHeight())); } else { - showPoint = JBPopupFactory.getInstance().guessBestPopupLocation(e.getDataContext()); + if (parent != null) { + showPoint = new RelativePoint(parent, new Point((parent.getSize().width - panel.getPreferredSize().width)/ 2, parent.getHeight()/3)); + } else { + showPoint = JBPopupFactory.getInstance().guessBestPopupLocation(e.getDataContext()); + } } - myBalloon.show(showPoint, Balloon.Position.below); - + myBalloon.show(showPoint); IdeFocusManager focusManager = IdeFocusManager.getInstance(e.getProject()); focusManager.requestFocus(myPopupField.getTextEditor(), true); } @@ -1067,14 +1160,29 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA myPopup = builder .setRequestFocus(false) .setCancelKeyEnabled(false) + .setCancelCallback(new Computable<Boolean>() { + @Override + public Boolean compute() { + return myBalloon == null || myBalloon.isDisposed() || !getField().getTextEditor().hasFocus(); + } + }) + .setCancelOnMouseOutCallback(new MouseChecker() { + @Override + public boolean check(MouseEvent event) { + final Component c = event.getComponent(); + return myContentPanel != c && getField().getTextEditor() != c; + } + }) .createPopup(); Disposer.register(myPopup, new Disposable() { @Override public void dispose() { callback.setDone(); if (myBalloon!= null) { - myBalloon.hide(); + myBalloon.cancel(); myBalloon = null; + initTooltip(); + mySearchLabel.setIcon(AllIcons.Actions.FindPlain); } myFileModel = null; myClassModel = null; @@ -1085,11 +1193,8 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA myConfigurables.clear(); } }); - if (getField() == field) { - myPopup.showUnderneathOf(field); - } else { - myPopup.show(new RelativePoint(getField(), new Point(0, getField().getHeight() + 4))); - } + myPopup.show(new RelativePoint(getField().getParent(), new Point(0, getField().getParent().getHeight()))); + ActionManager.getInstance().addAnActionListener(new AnActionListener.Adapter() { @Override public void beforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) { @@ -1177,11 +1282,12 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA if (myPopup == null || !myPopup.isVisible()) { return; } + final Container parent = getField().getParent(); final Dimension size = myList.getPreferredSize(); - if (size.width < myPopupActualWidth) { - size.width = myPopupActualWidth; + if (size.width < parent.getWidth()) { + size.width = parent.getWidth(); } - Dimension sz = new Dimension(Math.max(getField().getWidth(), size.width), size.height); + Dimension sz = new Dimension(size.width, size.height); if (sz.width > 800 || sz.height > 800) { final int extra = new JBScrollPane().getVerticalScrollBar().getWidth(); sz = new Dimension(Math.min(800, Math.max(getField().getWidth(), size.width + extra)), Math.min(800, size.height + extra)); @@ -1194,11 +1300,11 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA sz.width++; } myPopup.setSize(sz); - if (getField() == field) { - final Point p = getField().getLocationOnScreen(); - p.y += getField().getHeight(); - if (getField().getWidth() < sz.width) { - p.x -= sz.width - getField().getWidth(); + if (myActionEvent != null && myActionEvent.getInputEvent() == null) { + final Point p = parent.getLocationOnScreen(); + p.y += parent.getHeight(); + if (parent.getWidth() < sz.width) { + p.x -= sz.width - parent.getWidth(); } myPopup.setLocation(p); } else { @@ -1207,7 +1313,7 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA } private void adjustPopup() { - new PopupPositionManager.PositionAdjuster(getField().getTextEditor().getParent()).adjust(myPopup, BOTTOM, RIGHT, LEFT, TOP); +// new PopupPositionManager.PositionAdjuster(getField().getParent()).adjust(myPopup, BOTTOM, RIGHT, LEFT, TOP); } private static boolean isToolWindowAction(Object o) { @@ -1330,7 +1436,7 @@ public class SearchEverywhereAction extends AnAction implements CustomComponentA titleLabel.setForeground(UIUtil.getLabelDisabledForeground()); final Color bg = UIUtil.getListBackground(); SeparatorComponent separatorComponent = - new SeparatorComponent(titleLabel.getPreferredSize().height / 2, new JBColor(bg.darker(), Gray._80), null); + new SeparatorComponent(titleLabel.getPreferredSize().height / 2, new JBColor(Gray._240, Gray._80), null); JPanel result = new JPanel(new BorderLayout(5, 10)); result.add(titleLabel, BorderLayout.WEST); diff --git a/platform/lang-impl/src/com/intellij/ide/actions/ViewStructureAction.java b/platform/lang-impl/src/com/intellij/ide/actions/ViewStructureAction.java index 1a8218feee88..81355ac06f14 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/ViewStructureAction.java +++ b/platform/lang-impl/src/com/intellij/ide/actions/ViewStructureAction.java @@ -23,7 +23,10 @@ import com.intellij.ide.structureView.StructureViewModel; import com.intellij.ide.util.FileStructureDialog; import com.intellij.ide.util.FileStructurePopup; import com.intellij.openapi.Disposable; -import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.CommonDataKeys; +import com.intellij.openapi.actionSystem.PlatformDataKeys; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.project.Project; @@ -69,7 +72,7 @@ public class ViewStructureAction extends AnAction { Navigatable navigatable = e.getData(CommonDataKeys.NAVIGATABLE); if (Registry.is("file.structure.tree.mode")) { - FileStructurePopup popup = createPopup(editor, project, navigatable, fileEditor); + FileStructurePopup popup = createPopup(project, fileEditor); if (popup == null) return; popup.setTitle(title); @@ -93,35 +96,34 @@ public class ViewStructureAction extends AnAction { } @Nullable - public static FileStructurePopup createPopup(@Nullable Editor editor, @NotNull Project project, @Nullable Navigatable navigatable, @NotNull FileEditor fileEditor) { - final StructureViewBuilder structureViewBuilder = fileEditor.getStructureViewBuilder(); + public static FileStructurePopup createPopup(@NotNull Project project, @NotNull FileEditor fileEditor) { + StructureViewBuilder structureViewBuilder = fileEditor.getStructureViewBuilder(); if (structureViewBuilder == null) return null; StructureView structureView = structureViewBuilder.createStructureView(fileEditor, project); - final StructureViewModel model = structureView.getTreeModel(); + StructureViewModel model = structureView.getTreeModel(); if (model instanceof PlaceHolder) { //noinspection unchecked ((PlaceHolder)model).setPlace(PLACE); } - return createStructureViewPopup(model, editor, project, navigatable, structureView); + return createStructureViewPopup(project, fileEditor, structureView); } public static boolean isInStructureViewPopup(@NotNull PlaceHolder<String> model) { return PLACE.equals(model.getPlace()); } - public static FileStructureDialog createStructureViewBasedDialog(final StructureViewModel structureViewModel, - final Editor editor, - final Project project, - final Navigatable navigatable, - final @NotNull Disposable alternativeDisposable) { + private static FileStructureDialog createStructureViewBasedDialog(StructureViewModel structureViewModel, + Editor editor, + Project project, + Navigatable navigatable, + @NotNull Disposable alternativeDisposable) { return new FileStructureDialog(structureViewModel, editor, project, navigatable, alternativeDisposable, true); } - public static FileStructurePopup createStructureViewPopup(final StructureViewModel structureViewModel, - final Editor editor, - final Project project, - final Navigatable navigatable, - final @NotNull Disposable alternativeDisposable) { - return new FileStructurePopup(structureViewModel, editor, project, alternativeDisposable, true); + + private static FileStructurePopup createStructureViewPopup(Project project, + FileEditor fileEditor, + StructureView structureView) { + return new FileStructurePopup(project, fileEditor, structureView, true); } @Override diff --git a/platform/lang-impl/src/com/intellij/ide/fileTemplates/FileTemplateUtil.java b/platform/lang-impl/src/com/intellij/ide/fileTemplates/FileTemplateUtil.java index 4db5b6d88ac5..a5826879de90 100644 --- a/platform/lang-impl/src/com/intellij/ide/fileTemplates/FileTemplateUtil.java +++ b/platform/lang-impl/src/com/intellij/ide/fileTemplates/FileTemplateUtil.java @@ -349,7 +349,7 @@ public class FileTemplateUtil{ } } - //Set escaped references to dummy values to remove leading "\" (if not already explicitely set) + //Set escaped references to dummy values to remove leading "\" (if not already explicitly set) String[] dummyRefs = calculateAttributes(template.getText(), propsMap, true); for (String dummyRef : dummyRefs) { propsMap.put(dummyRef, ""); @@ -384,7 +384,7 @@ public class FileTemplateUtil{ } }); } - }, template.isTemplateOfType(StdFileTypes.JAVA) + }, template.isTemplateOfType(StdFileTypes.JAVA) && !"package-info".equals(template.getName()) ? IdeBundle.message("command.create.class.from.template") : IdeBundle.message("command.create.file.from.template"), null); if(commandException[0] != null){ diff --git a/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/FileTemplateConfigurable.java b/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/FileTemplateConfigurable.java index 7963b7386b93..944cbc28a5f7 100644 --- a/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/FileTemplateConfigurable.java +++ b/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/FileTemplateConfigurable.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -173,7 +173,7 @@ public class FileTemplateConfigurable implements Configurable, Configurable.NoSc myMainPanel = new JPanel(new GridBagLayout()); myNameField = new JTextField(); myExtensionField = new JTextField(); - mySplitter = new Splitter(true, 0.66f); + mySplitter = new Splitter(true, 0.4f); myTemplateEditor = createEditor(); diff --git a/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/FileTemplateManagerImpl.java b/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/FileTemplateManagerImpl.java index 3c8716435c48..835299105ca8 100644 --- a/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/FileTemplateManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/FileTemplateManagerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -68,6 +68,8 @@ public class FileTemplateManagerImpl extends FileTemplateManager implements JDOM private final FTManager myCodeTemplatesManager; private final FTManager myJ2eeTemplatesManager; private final FTManager[] myAllManagers; + private final URL myDefaultTemplateDescription; + private final URL myDefaultIncludeDescription; public static FileTemplateManagerImpl getInstanceImpl() { return (FileTemplateManagerImpl)ServiceManager.getService(FileTemplateManager.class); @@ -84,6 +86,8 @@ public class FileTemplateManagerImpl extends FileTemplateManager implements JDOM myCodeTemplatesManager = myTemplateSettings.getCodeTemplatesManager(); myJ2eeTemplatesManager = myTemplateSettings.getJ2eeTemplatesManager(); myAllManagers = myTemplateSettings.getAllManagers(); + myDefaultTemplateDescription = myTemplateSettings.getDefaultTemplateDescription(); + myDefaultIncludeDescription = myTemplateSettings.getDefaultIncludeDescription(); if (ApplicationManager.getApplication().isUnitTestMode()) { for (String tname : Arrays.asList("Class", "AnnotationType", "Enum", "Interface")) { @@ -473,11 +477,11 @@ public class FileTemplateManagerImpl extends FileTemplateManager implements JDOM } public URL getDefaultTemplateDescription() { - return null; // todo + return myDefaultTemplateDescription; } public URL getDefaultIncludeDescription() { - return null; // todo + return myDefaultIncludeDescription; } private Date myTestDate; diff --git a/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/FileTemplateTab.java b/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/FileTemplateTab.java index 67492fd345fc..e820ac7ac89c 100644 --- a/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/FileTemplateTab.java +++ b/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/FileTemplateTab.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -17,6 +17,7 @@ package com.intellij.ide.fileTemplates.impl; import com.intellij.ide.fileTemplates.FileTemplate; +import com.intellij.ui.JBColor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -31,7 +32,7 @@ import java.util.List; abstract class FileTemplateTab { protected final List<FileTemplateBase> myTemplates = new ArrayList<FileTemplateBase>(); private final String myTitle; - protected static final Color MODIFIED_FOREGROUND = new Color(0, 0, 210); + protected static final Color MODIFIED_FOREGROUND = JBColor.BLUE; protected FileTemplateTab(String title) { myTitle = title; diff --git a/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/FileTemplatesLoader.java b/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/FileTemplatesLoader.java index 59c1ae95f391..2f9d057fee9c 100644 --- a/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/FileTemplatesLoader.java +++ b/platform/lang-impl/src/com/intellij/ide/fileTemplates/impl/FileTemplatesLoader.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -44,7 +44,7 @@ public class FileTemplatesLoader { private static final String TEMPLATES_DIR = "fileTemplates"; private static final String DEFAULT_TEMPLATES_ROOT = TEMPLATES_DIR; public static final String DESCRIPTION_FILE_EXTENSION = "html"; - static final String DESCRIPTION_EXTENSION_SUFFIX = "." + DESCRIPTION_FILE_EXTENSION; + private static final String DESCRIPTION_EXTENSION_SUFFIX = "." + DESCRIPTION_FILE_EXTENSION; //static final String DESCRIPTION_FILE_NAME = "default." + DESCRIPTION_FILE_EXTENSION; private final FTManager myDefaultTemplatesManager; @@ -63,6 +63,9 @@ public class FileTemplatesLoader { private static final String ROOT_DIR = "."; private final FileTypeManagerEx myTypeManager; + private URL myDefaultTemplateDescription; + private URL myDefaultIncludeDescription; + public FileTemplatesLoader(@NotNull FileTypeManagerEx typeManager) { myTypeManager = typeManager; myDefaultTemplatesManager = new FTManager(FileTemplateManager.DEFAULT_TEMPLATES_CATEGORY, ROOT_DIR); @@ -116,6 +119,14 @@ public class FileTemplatesLoader { return myJ2eeTemplatesManager; } + public URL getDefaultTemplateDescription() { + return myDefaultTemplateDescription; + } + + public URL getDefaultIncludeDescription() { + return myDefaultIncludeDescription; + } + private void loadDefaultTemplates() { final Set<URL> processedUrls = new HashSet<URL>(); for (PluginDescriptor plugin : PluginManager.getPlugins()) { @@ -151,7 +162,13 @@ public class FileTemplatesLoader { } final Set<String> descriptionPaths = new HashSet<String>(); for (String path : children) { - if (path.endsWith(DESCRIPTION_EXTENSION_SUFFIX)) { + if (path.equals("default.html")) { + myDefaultTemplateDescription = UrlClassLoader.internProtocol(new URL(root.toExternalForm() + "/" + path)); + } + else if (path.equals("includes/default.html")) { + myDefaultIncludeDescription = UrlClassLoader.internProtocol(new URL(root.toExternalForm() + "/" + path)); + } + else if (path.endsWith(DESCRIPTION_EXTENSION_SUFFIX)) { descriptionPaths.add(path); } } @@ -176,7 +193,7 @@ public class FileTemplatesLoader { private void loadCustomizedContent(FTManager manager) { final File configRoot = manager.getConfigRoot(false); - File[] configFiles = configRoot.listFiles(); + final File[] configFiles = configRoot.listFiles(); if (configFiles == null) { return; } diff --git a/platform/lang-impl/src/com/intellij/ide/projectView/impl/AbstractProjectViewPSIPane.java b/platform/lang-impl/src/com/intellij/ide/projectView/impl/AbstractProjectViewPSIPane.java index 90dfe7e5a81f..34c23d723574 100644 --- a/platform/lang-impl/src/com/intellij/ide/projectView/impl/AbstractProjectViewPSIPane.java +++ b/platform/lang-impl/src/com/intellij/ide/projectView/impl/AbstractProjectViewPSIPane.java @@ -85,7 +85,7 @@ public abstract class AbstractProjectViewPSIPane extends AbstractProjectViewPane @Override public void uiSettingsChanged(UISettings source) { - myTree.setRowHeight(-1); +// myTree.setRowHeight(-1); myTree.setFont(UIUtil.getTreeFont()); myTree.invalidate(); myTree.repaint(); diff --git a/platform/lang-impl/src/com/intellij/ide/structureView/newStructureView/StructureViewComponent.java b/platform/lang-impl/src/com/intellij/ide/structureView/newStructureView/StructureViewComponent.java index 27a4f6bf085c..0c6d351e3f11 100644 --- a/platform/lang-impl/src/com/intellij/ide/structureView/newStructureView/StructureViewComponent.java +++ b/platform/lang-impl/src/com/intellij/ide/structureView/newStructureView/StructureViewComponent.java @@ -26,10 +26,7 @@ import com.intellij.ide.structureView.impl.StructureViewState; import com.intellij.ide.structureView.impl.common.PsiTreeElementBase; import com.intellij.ide.ui.customization.CustomizationUtil; import com.intellij.ide.util.FileStructurePopup; -import com.intellij.ide.util.treeView.AbstractTreeNode; -import com.intellij.ide.util.treeView.AbstractTreeStructure; -import com.intellij.ide.util.treeView.NodeDescriptorProvidingKey; -import com.intellij.ide.util.treeView.NodeRenderer; +import com.intellij.ide.util.treeView.*; import com.intellij.ide.util.treeView.smartTree.*; import com.intellij.ide.util.treeView.smartTree.TreeModel; import com.intellij.openapi.Disposable; @@ -51,14 +48,16 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.impl.source.tree.CompositeElement; import com.intellij.psi.util.PsiUtilCore; import com.intellij.ui.*; -import com.intellij.ui.treeStructure.Tree; import com.intellij.ui.treeStructure.actions.CollapseAllAction; import com.intellij.ui.treeStructure.actions.ExpandAllAction; +import com.intellij.ui.treeStructure.filtered.FilteringTreeStructure; import com.intellij.util.Alarm; import com.intellij.util.ArrayUtil; import com.intellij.util.EditSourceOnDoubleClickHandler; import com.intellij.util.OpenSourceUtil; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.Convertor; +import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.tree.TreeUtil; import gnu.trove.THashSet; import org.jetbrains.annotations.NonNls; @@ -79,7 +78,7 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre private static final Logger LOG = Logger.getInstance("#com.intellij.ide.structureView.newStructureView.StructureViewComponent"); @NonNls private static final String ourHelpID = "viewingStructure.fileStructureView"; - private StructureTreeBuilder myAbstractTreeBuilder; + private AbstractTreeBuilder myAbstractTreeBuilder; private FileEditor myFileEditor; private final TreeModelWrapper myTreeModelWrapper; @@ -97,7 +96,6 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre private final Project myProject; private final StructureViewModel myTreeModel; private static int ourSettingsModificationCount; - private Tree myTree; public StructureViewComponent(FileEditor editor, StructureViewModel structureViewModel, Project project) { this(editor, structureViewModel, project, true); @@ -139,12 +137,12 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre }; final DefaultTreeModel model = new DefaultTreeModel(new DefaultMutableTreeNode(treeStructure.getRootElement())); - myTree = new JBTreeWithHintProvider(model); - myTree.setRootVisible(showRootNode); - myTree.setShowsRootHandles(true); + JTree tree = new JBTreeWithHintProvider(model); + tree.setRootVisible(showRootNode); + tree.setShowsRootHandles(true); - myAbstractTreeBuilder = new StructureTreeBuilder(project, myTree, - (DefaultTreeModel)myTree.getModel(),treeStructure,myTreeModelWrapper) { + myAbstractTreeBuilder = new StructureTreeBuilder(project, tree, + (DefaultTreeModel)tree.getModel(),treeStructure,myTreeModelWrapper) { @Override protected boolean validateNode(Object child) { return isValid(child); @@ -244,41 +242,37 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre private static Object[] convertPathsToValues(TreePath[] selectionPaths) { - if (selectionPaths != null) { - List<Object> result = new ArrayList<Object>(); - - for (TreePath selectionPath : selectionPaths) { - final Object userObject = ((DefaultMutableTreeNode)selectionPath.getLastPathComponent()).getUserObject(); - if (userObject instanceof AbstractTreeNode) { - Object value = ((AbstractTreeNode)userObject).getValue(); - if (value instanceof StructureViewTreeElement) { - value = ((StructureViewTreeElement)value).getValue(); - } - result.add(value); - } - } - return ArrayUtil.toObjectArray(result); - } - else { - return null; + if (selectionPaths == null) return null; + List<Object> result = new ArrayList<Object>(); + for (TreePath selectionPath : selectionPaths) { + ContainerUtil.addIfNotNull(result, getNodeTreeValue((DefaultMutableTreeNode)selectionPath.getLastPathComponent())); } + return ArrayUtil.toObjectArray(result); } @Nullable private static Object[] convertPathsToTreeElements(TreePath[] selectionPaths) { - if (selectionPaths != null) { - Object[] result = new Object[selectionPaths.length]; - - for (int i = 0; i < selectionPaths.length; i++) { - Object userObject = ((DefaultMutableTreeNode)selectionPaths[i].getLastPathComponent()).getUserObject(); - if (!(userObject instanceof AbstractTreeNode)) return null; - result[i] = ((AbstractTreeNode)userObject).getValue(); - } - return result; + if (selectionPaths == null) return null; + List<Object> result = new ArrayList<Object>(); + for (TreePath selectionPath : selectionPaths) { + ContainerUtil.addIfNotNull(result, getNodeValue((DefaultMutableTreeNode)selectionPath.getLastPathComponent())); } - else { - return null; + return ArrayUtil.toObjectArray(result); + } + + @Nullable + private static Object getNodeValue(DefaultMutableTreeNode mutableTreeNode) { + Object userObject = mutableTreeNode.getUserObject(); + if (userObject instanceof FilteringTreeStructure.FilteringNode) { + userObject = ((FilteringTreeStructure.FilteringNode)userObject).getDelegate(); } + return userObject instanceof AbstractTreeNode ? ((AbstractTreeNode)userObject).getValue() : null; + } + + @Nullable + private static Object getNodeTreeValue(DefaultMutableTreeNode mutableTreeNode) { + Object value = getNodeValue(mutableTreeNode); + return value instanceof StructureViewTreeElement ? ((StructureViewTreeElement)value).getValue() : null; } private void addTreeMouseListeners() { @@ -596,28 +590,25 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre new Runnable() { @Override public void run() { - if (myAbstractTreeBuilder == null) { - return; - } - try { - selectViewableElement(); - } - catch (IndexNotReadyException ignore) { - } + if (myAbstractTreeBuilder == null) return; + if (UIUtil.isFocusAncestor(StructureViewComponent.this)) return; + scrollToSelectedElementInner(); } - }, 1000 - ); + }, 1000); } - private void selectViewableElement() { - PsiDocumentManager.getInstance(myProject).commitAllDocuments(); - final Object currentEditorElement = myTreeModel.getCurrentEditorElement(); - if (currentEditorElement != null) { - select(currentEditorElement, false); + private void scrollToSelectedElementInner() { + try { + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); + final Object currentEditorElement = myTreeModel.getCurrentEditorElement(); + if (currentEditorElement != null) { + select(currentEditorElement, false); + } + } + catch (IndexNotReadyException ignore) { } } - @Override public void dispose() { LOG.assertTrue(EventQueue.isDispatchThread(), Thread.currentThread().getName()); @@ -634,10 +625,8 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre @Override public void centerSelectedRow() { TreePath path = getTree().getSelectionPath(); - if (path == null) - { - return; - } + if (path == null) return; + myAutoScrollToSourceHandler.setShouldAutoScroll(false); TreeUtil.showRowCentered(getTree(), getTree().getRowForPath(path), false); myAutoScrollToSourceHandler.setShouldAutoScroll(true); @@ -667,10 +656,18 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre return myAbstractTreeBuilder.getTreeStructure(); } - public Tree getTree() { - return myTree; + public JTree getTree() { + return myAbstractTreeBuilder.getTree(); } + public AbstractTreeBuilder getTreeBuilder() { + return myAbstractTreeBuilder; + } + + //public void setTreeBuilder(AbstractTreeBuilder treeBuilder) { + // myAbstractTreeBuilder = treeBuilder; + //} + private final class MyAutoScrollToSourceHandler extends AutoScrollToSourceHandler { private boolean myShouldAutoScroll = true; @@ -742,7 +739,7 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre getSettings().AUTOSCROLL_FROM_SOURCE = state; final FileEditor[] selectedEditors = FileEditorManager.getInstance(myProject).getSelectedEditors(); if (selectedEditors.length > 0 && state) { - scrollToSelectedElement(); + scrollToSelectedElementInner(); } } } @@ -753,10 +750,7 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre TreePath path = getSelectedUniquePath(); if (path == null) return null; DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent(); - Object userObject = node.getUserObject(); - if (!(userObject instanceof AbstractTreeNode)) return null; - AbstractTreeNode descriptor = (AbstractTreeNode)userObject; - Object element = descriptor.getValue(); + Object element = getNodeValue(node); if (element instanceof StructureViewTreeElement) { element = ((StructureViewTreeElement)element).getValue(); } @@ -831,7 +825,7 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre public void doUpdate() { assert ApplicationManager.getApplication().isUnitTestMode(); - myAbstractTreeBuilder.addRootToUpdate(); + myAbstractTreeBuilder.queueUpdate(true); } //todo [kirillk] dirty hack for discovering invalid psi elements, to delegate it to a proper place after 8.1 @@ -994,7 +988,7 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre @Override public Dimension getCurrentSize() { - return myTree.getSize(); + return getTree().getSize(); } @Override @@ -1012,11 +1006,12 @@ public class StructureViewComponent extends SimpleToolWindowPanel implements Tre } private void _setRefSize(Dimension size) { - myTree.setPreferredSize(size); - myTree.setMinimumSize(size); - myTree.setMaximumSize(size); + JTree tree = getTree(); + tree.setPreferredSize(size); + tree.setMinimumSize(size); + tree.setMaximumSize(size); - myTree.revalidate(); - myTree.repaint(); + tree.revalidate(); + tree.repaint(); } } diff --git a/platform/lang-impl/src/com/intellij/ide/util/FileStructurePopup.java b/platform/lang-impl/src/com/intellij/ide/util/FileStructurePopup.java index ef31e14c0550..d3186cf4d17e 100644 --- a/platform/lang-impl/src/com/intellij/ide/util/FileStructurePopup.java +++ b/platform/lang-impl/src/com/intellij/ide/util/FileStructurePopup.java @@ -20,8 +20,8 @@ import com.intellij.ide.DataManager; import com.intellij.ide.DefaultTreeExpander; import com.intellij.ide.IdeBundle; import com.intellij.ide.TreeExpander; +import com.intellij.ide.structureView.ModelListener; import com.intellij.ide.structureView.StructureView; -import com.intellij.ide.structureView.StructureViewBuilder; import com.intellij.ide.structureView.StructureViewModel; import com.intellij.ide.structureView.StructureViewTreeElement; import com.intellij.ide.structureView.impl.StructureViewComposite; @@ -40,9 +40,8 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.CommandProcessor; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; -import com.intellij.openapi.editor.impl.EditorImpl; import com.intellij.openapi.fileEditor.FileEditor; -import com.intellij.openapi.fileEditor.FileEditorManager; +import com.intellij.openapi.fileEditor.TextEditor; import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory; import com.intellij.openapi.keymap.KeymapUtil; import com.intellij.openapi.project.Project; @@ -51,12 +50,10 @@ import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.util.*; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; -import com.intellij.psi.PsiManager; import com.intellij.ui.*; import com.intellij.ui.popup.AbstractPopup; import com.intellij.ui.popup.PopupUpdateProcessor; @@ -96,7 +93,6 @@ import java.util.List; */ public class FileStructurePopup implements Disposable { private static final Logger LOG = Logger.getInstance("#com.intellij.ide.util.FileStructurePopup"); - private final Editor myEditor; private final Project myProject; private final StructureViewModel myTreeModel; private final StructureViewModel myBaseTreeModel; @@ -121,53 +117,49 @@ public class FileStructurePopup implements Disposable { private boolean myInitialNodeIsLeaf; private final List<Pair<String, JCheckBox>> myTriggeredCheckboxes = new ArrayList<Pair<String, JCheckBox>>(); private final TreeExpander myTreeExpander; - private StructureView myStructureView; + @NotNull private final FileEditor myFileEditor; + private final StructureView myStructureViewDelegate; - public FileStructurePopup(StructureViewModel structureViewModel, - @Nullable Editor editor, - Project project, - @NotNull final Disposable auxDisposable, + public FileStructurePopup(@NotNull Project project, + @NotNull FileEditor fileEditor, + @NotNull StructureView structureView, final boolean applySortAndFilter) { myProject = project; - myEditor = editor; + myFileEditor = fileEditor; + myStructureViewDelegate = structureView; //Stop code analyzer to speedup EDT DaemonCodeAnalyzer.getInstance(myProject).disableUpdateByTimer(this); IdeFocusManager.getInstance(myProject).typeAheadUntil(myTreeHasBuilt); + Disposer.register(this, myStructureViewDelegate); //long l = System.currentTimeMillis(); - if (editor instanceof EditorImpl) { - VirtualFile file = ((EditorImpl)editor).getVirtualFile(); - FileEditor fileEditor = FileEditorManager.getInstance(myProject).getSelectedEditor(file); - if (fileEditor != null) { - StructureViewBuilder builder = fileEditor.getStructureViewBuilder(); - myPsiFile = PsiManager.getInstance(project).findFile(file); - if (builder != null && myPsiFile != null) { - myStructureView = builder.createStructureView(fileEditor, project); - Disposer.register(this, myStructureView); - } - } + if (myFileEditor instanceof TextEditor) { + Editor e = ((TextEditor)myFileEditor).getEditor(); + myPsiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(e.getDocument()); } + //System.out.println(System.currentTimeMillis() - l); - if (myStructureView instanceof StructureViewComposite) { - StructureViewComposite.StructureViewDescriptor[] views = ((StructureViewComposite)myStructureView).getStructureViews(); + if (myStructureViewDelegate instanceof StructureViewComposite) { + StructureViewComposite.StructureViewDescriptor[] views = ((StructureViewComposite)myStructureViewDelegate).getStructureViews(); myBaseTreeModel = new StructureViewCompositeModel(myPsiFile, views); Disposer.register(this, (Disposable)myBaseTreeModel); - } else { - myBaseTreeModel = structureViewModel; } - Disposer.register(this, auxDisposable); + else { + myBaseTreeModel = myStructureViewDelegate.getTreeModel(); + } + if (applySortAndFilter) { myTreeActionsOwner = new TreeStructureActionsOwner(myBaseTreeModel); myTreeModel = new TreeModelWrapper(myBaseTreeModel, myTreeActionsOwner); } else { myTreeActionsOwner = null; - myTreeModel = structureViewModel; + myTreeModel = myStructureViewDelegate.getTreeModel(); } - myTreeStructure = new SmartTreeStructure(project, myTreeModel){ + myTreeStructure = new SmartTreeStructure(project, myTreeModel) { @Override public void rebuildTree() { if (ApplicationManager.getApplication().isUnitTestMode() || !myPopup.isDisposed()) { @@ -200,9 +192,10 @@ public class FileStructurePopup implements Disposable { @NotNull SimpleTextAttributes attributes, boolean isMainText, boolean selected) { - if (!isMainText ) { + if (!isMainText) { super.doAppend(fragment, attributes, isMainText, selected); - } else { + } + else { SpeedSearchUtil.appendFragmentsForSpeedSearch(myTree, fragment, attributes, selected, this); } } @@ -247,10 +240,23 @@ public class FileStructurePopup implements Disposable { }; myTreeExpander = new DefaultTreeExpander(myTree); + final ModelListener modelListener = new ModelListener() { + @Override + public void onModelChanged() { + myAbstractTreeBuilder.queueUpdate(); + } + }; + myTreeModel.addModelListener(modelListener); + Disposer.register(this, new Disposable() { + @Override + public void dispose() { + myTreeModel.removeModelListener(modelListener); + } + }); //myAbstractTreeBuilder.getUi().setPassthroughMode(true); myAbstractTreeBuilder.getUi().getUpdater().setDelay(1); - myInitialPsiElement = getCurrentElement(getPsiFile(myProject)); + myInitialPsiElement = getCurrentElement(myPsiFile); //myAbstractTreeBuilder.setCanYieldUpdate(true); Disposer.register(this, myAbstractTreeBuilder); TreeUtil.installActions(myTree); @@ -304,9 +310,10 @@ public class FileStructurePopup implements Disposable { }); myTree.getEmptyText().setText("Loading..."); final Point location = DimensionService.getInstance().getLocation(getDimensionServiceKey(), myProject); - if (location != null && myEditor != null) { - myPopup.showInScreenCoordinates(myEditor.getContentComponent(), location); - } else { + if (location != null) { + myPopup.showInScreenCoordinates(myFileEditor.getComponent(), location); + } + else { myPopup.showCenteredInCurrentWindow(myProject); } @@ -436,7 +443,7 @@ public class FileStructurePopup implements Disposable { Set<PsiElement> parents = getAllParents(element); FilteringTreeStructure.FilteringNode node = (FilteringTreeStructure.FilteringNode)myAbstractTreeBuilder.getRootElement(); - if (element != null && node != null && myStructureView instanceof StructureViewComposite) { + if (element != null && node != null && myStructureViewDelegate instanceof StructureViewComposite) { parents.remove(element.getContainingFile()); final List<FilteringTreeStructure.FilteringNode> fileNodes = node.children(); @@ -446,7 +453,8 @@ public class FileStructurePopup implements Disposable { return found; } } - } else { + } + else { final FilteringTreeStructure.FilteringNode found = findNode(parents, node); if (found == null) { TreeUtil.ensureSelection(myTree); @@ -506,11 +514,6 @@ public class FileStructurePopup implements Disposable { return null; } - @Nullable - protected PsiFile getPsiFile(final Project project) { - return myEditor == null ? null : PsiDocumentManager.getInstance(project).getPsiFile(myEditor.getDocument()); - } - @Override public void dispose() { @@ -523,8 +526,6 @@ public class FileStructurePopup implements Disposable { @Nullable public PsiElement getCurrentElement(@Nullable final PsiFile psiFile) { - if (psiFile == null) return null; - PsiDocumentManager.getInstance(myProject).commitAllDocuments(); Object elementAtCursor = myTreeModel.getCurrentEditorElement(); @@ -532,8 +533,8 @@ public class FileStructurePopup implements Disposable { return (PsiElement)elementAtCursor; } - if (myEditor != null) { - return psiFile.getViewProvider().findElementAt(myEditor.getCaretModel().getOffset()); + if (psiFile != null && myFileEditor instanceof TextEditor) { + return psiFile.getViewProvider().findElementAt(((TextEditor)myFileEditor).getEditor().getCaretModel().getOffset()); } return null; @@ -543,7 +544,7 @@ public class FileStructurePopup implements Disposable { List<FileStructureFilter> fileStructureFilters = new ArrayList<FileStructureFilter>(); List<FileStructureNodeProvider> fileStructureNodeProviders = new ArrayList<FileStructureNodeProvider>(); if (myTreeActionsOwner != null) { - for(Filter filter: myBaseTreeModel.getFilters()) { + for (Filter filter : myBaseTreeModel.getFilters()) { if (filter instanceof FileStructureFilter) { final FileStructureFilter fsFilter = (FileStructureFilter)filter; myTreeActionsOwner.setActionIncluded(fsFilter, true); @@ -580,7 +581,8 @@ public class FileStructurePopup implements Disposable { public void actionPerformed(AnActionEvent e) { if (mySpeedSearch != null && mySpeedSearch.isPopupActive()) { mySpeedSearch.hidePopup(); - } else { + } + else { myPopup.cancel(); } } @@ -596,7 +598,7 @@ public class FileStructurePopup implements Disposable { } }.installOn(myTree); - for(FileStructureFilter filter: fileStructureFilters) { + for (FileStructureFilter filter : fileStructureFilters) { addCheckbox(comboPanel, filter); } @@ -612,14 +614,27 @@ public class FileStructurePopup implements Disposable { DataManager.registerDataProvider(panel, new DataProvider() { @Override public Object getData(@NonNls String dataId) { - if (PlatformDataKeys.PROJECT.is(dataId)) { + if (CommonDataKeys.PROJECT.is(dataId)) { return myProject; } + if (PlatformDataKeys.FILE_EDITOR.is(dataId)) { + return myFileEditor; + } if (CommonDataKeys.PSI_ELEMENT.is(dataId)) { - final Object node = ContainerUtil.getFirstItem(myAbstractTreeBuilder.getSelectedElements()); + Object node = ContainerUtil.getFirstItem(myAbstractTreeBuilder.getSelectedElements()); if (!(node instanceof FilteringTreeStructure.FilteringNode)) return null; return getPsi((FilteringTreeStructure.FilteringNode)node); } + if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) { + Set<Object> nodes = myAbstractTreeBuilder.getSelectedElements(); + if (nodes.isEmpty()) return PsiElement.EMPTY_ARRAY; + ArrayList<PsiElement> result = new ArrayList<PsiElement>(); + for (Object o : nodes) { + if (!(o instanceof FilteringTreeStructure.FilteringNode)) continue; + ContainerUtil.addIfNotNull(result, getPsi((FilteringTreeStructure.FilteringNode)o)); + } + return ContainerUtil.toArray(result, PsiElement.ARRAY_FACTORY); + } if (LangDataKeys.POSITION_ADJUSTER_POPUP.is(dataId)) { return myPopup; } @@ -722,8 +737,7 @@ public class FileStructurePopup implements Disposable { if (text == null) return; Shortcut[] shortcuts = action instanceof FileStructureFilter ? - ((FileStructureFilter)action).getShortcut() : ((FileStructureNodeProvider)action).getShortcut(); - + ((FileStructureFilter)action).getShortcut() : ((FileStructureNodeProvider)action).getShortcut(); final JCheckBox chkFilter = new JCheckBox(); @@ -770,7 +784,8 @@ public class FileStructurePopup implements Disposable { }; if (ApplicationManager.getApplication().isUnitTestMode()) { runnable.run(); - } else { + } + else { ApplicationManager.getApplication().invokeLater(runnable); } } @@ -911,10 +926,10 @@ public class FileStructurePopup implements Disposable { myVisibleParents.add(o); } return true; - } else { + } + else { return false; } - } return true; } @@ -926,7 +941,6 @@ public class FileStructurePopup implements Disposable { } return mySpeedSearch.matchingFragments(text) != null; } - } @Nullable @@ -934,7 +948,7 @@ public class FileStructurePopup implements Disposable { if (ApplicationManager.getApplication().isUnitTestMode()) return myTestSearchFilter; return mySpeedSearch != null && !StringUtil.isEmpty(mySpeedSearch.getEnteredPrefix()) - ? mySpeedSearch.getEnteredPrefix() : null; + ? mySpeedSearch.getEnteredPrefix() : null; } public class MyTreeSpeedSearch extends TreeSpeedSearch { @@ -997,7 +1011,8 @@ public class FileStructurePopup implements Disposable { max = size; cur.clear(); cur.add(p); - } else if (size == max) { + } + else if (size == max) { cur.add(p); } } @@ -1013,7 +1028,6 @@ public class FileStructurePopup implements Disposable { }); return cur.isEmpty() ? null : cur.get(0).node; } - } class FileStructureTree extends JBTreeWithHintProvider implements AlwaysExpandedTree { @@ -1038,7 +1052,8 @@ public class FileStructurePopup implements Disposable { newValueIsSet = false; } fast = newValueIsSet; - } else { + } + else { fast = false; } diff --git a/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorPsiDataProvider.java b/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorPsiDataProvider.java index be2b52ec1706..1f911936637a 100644 --- a/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorPsiDataProvider.java +++ b/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorPsiDataProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.psi.*; import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; -import com.intellij.psi.util.PsiUtilBase; +import com.intellij.psi.util.PsiUtilCore; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -132,7 +132,7 @@ public class TextEditorPsiDataProvider implements EditorDataProvider { return getLanguageAtOffset(psiFile, mostProbablyCorrectLanguageOffset, selectionModel.getSelectionEnd()); } - return PsiUtilBase.getLanguageAtOffset(psiFile, mostProbablyCorrectLanguageOffset); + return PsiUtilCore.getLanguageAtOffset(psiFile, mostProbablyCorrectLanguageOffset); } private static Language getLanguageAtOffset(PsiFile psiFile, int mostProbablyCorrectLanguageOffset, int end) { @@ -144,7 +144,7 @@ public class TextEditorPsiDataProvider implements EditorDataProvider { return getLanguageAtOffset(psiFile, incremented, end); } } - return PsiUtilBase.findLanguageFromElement(elt); + return PsiUtilCore.findLanguageFromElement(elt); } @Nullable diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/CommonContentEntriesEditor.java b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/CommonContentEntriesEditor.java index 669d08e6796a..b7583d05ffdb 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/CommonContentEntriesEditor.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/CommonContentEntriesEditor.java @@ -163,6 +163,7 @@ public class CommonContentEntriesEditor extends ModuleElementsEditor { entriesPanel.add(new ToolbarPanel(myScrollPane, group), BorderLayout.CENTER); final Splitter splitter = new Splitter(false); + splitter.setProportion(0.4f); splitter.setHonorComponentsMinimumSize(true); mainPanel.add(splitter, BorderLayout.CENTER); diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/JavaTestSourceRootEditHandler.java b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/JavaTestSourceRootEditHandler.java index f62f3a4ae202..2cdcbc42de55 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/JavaTestSourceRootEditHandler.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/ui/configuration/JavaTestSourceRootEditHandler.java @@ -18,6 +18,7 @@ package com.intellij.openapi.roots.ui.configuration; import com.intellij.icons.AllIcons; import com.intellij.openapi.actionSystem.CustomShortcutSet; import com.intellij.openapi.project.ProjectBundle; +import com.intellij.ui.JBColor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.model.java.JavaSourceRootType; @@ -31,7 +32,7 @@ import java.awt.event.KeyEvent; * @author nik */ public class JavaTestSourceRootEditHandler extends JavaSourceRootEditHandlerBase { - private static final Color TESTS_COLOR = new Color(0x008C2E); + private static final Color TESTS_COLOR = new JBColor(new Color(0x008C2E), new Color(73, 140, 101)); public JavaTestSourceRootEditHandler() { super(JavaSourceRootType.TEST_SOURCE); diff --git a/platform/lang-impl/src/com/intellij/psi/formatter/DocumentBasedFormattingModel.java b/platform/lang-impl/src/com/intellij/psi/formatter/DocumentBasedFormattingModel.java index 73acc4f69780..403324cf3171 100644 --- a/platform/lang-impl/src/com/intellij/psi/formatter/DocumentBasedFormattingModel.java +++ b/platform/lang-impl/src/com/intellij/psi/formatter/DocumentBasedFormattingModel.java @@ -39,14 +39,14 @@ import org.jetbrains.annotations.Nullable; public class DocumentBasedFormattingModel implements FormattingModel { private final Block myRootBlock; private final FormattingDocumentModel myDocumentModel; - private final Document myDocument; + @NotNull private final Document myDocument; private final Project myProject; private final CodeStyleSettings mySettings; private final FileType myFileType; private final PsiFile myFile; public DocumentBasedFormattingModel(final Block rootBlock, - final Document document, + @NotNull final Document document, final Project project, final CodeStyleSettings settings, final FileType fileType, @@ -221,6 +221,7 @@ public class DocumentBasedFormattingModel implements FormattingModel { return mySettings.getIndentOptions(myFileType); } + @NotNull public Document getDocument() { return myDocument; } diff --git a/platform/lang-impl/src/com/intellij/psi/formatter/FormattingDocumentModelImpl.java b/platform/lang-impl/src/com/intellij/psi/formatter/FormattingDocumentModelImpl.java index 2bab927fbba9..77507a6c6abd 100644 --- a/platform/lang-impl/src/com/intellij/psi/formatter/FormattingDocumentModelImpl.java +++ b/platform/lang-impl/src/com/intellij/psi/formatter/FormattingDocumentModelImpl.java @@ -38,13 +38,13 @@ public class FormattingDocumentModelImpl implements FormattingDocumentModel { private final WhiteSpaceFormattingStrategy myWhiteSpaceStrategy; //private final CharBuffer myBuffer = CharBuffer.allocate(1); - private final Document myDocument; + @NotNull private final Document myDocument; private final PsiFile myFile; private static final Logger LOG = Logger.getInstance("#com.intellij.psi.formatter.FormattingDocumentModelImpl"); private final CodeStyleSettings mySettings; - public FormattingDocumentModelImpl(final Document document, PsiFile file) { + public FormattingDocumentModelImpl(@NotNull final Document document, PsiFile file) { myDocument = document; myFile = file; if (file != null) { @@ -117,6 +117,7 @@ public class FormattingDocumentModelImpl implements FormattingDocumentModel { return myDocument.getTextLength(); } + @NotNull @Override public Document getDocument() { return myDocument; diff --git a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeFormatterFacade.java b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeFormatterFacade.java index 8ebc3aadbe07..a8bdbae7ea2f 100644 --- a/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeFormatterFacade.java +++ b/platform/lang-impl/src/com/intellij/psi/impl/source/codeStyle/CodeFormatterFacade.java @@ -175,6 +175,7 @@ public class CodeFormatterFacade { if (builder != null) { if (file.getTextLength() > 0) { + LOG.assertTrue(document != null); try { final PsiElement startElement = file.findElementAt(textRanges.get(0).getTextRange().getStartOffset()); final PsiElement endElement = file.findElementAt(textRanges.get(textRanges.size() - 1).getTextRange().getEndOffset() - 1); diff --git a/platform/lang-impl/src/com/intellij/refactoring/extractMethod/SimpleDuplicatesFinder.java b/platform/lang-impl/src/com/intellij/refactoring/extractMethod/SimpleDuplicatesFinder.java index 6c067e5ee0a6..7197eb646f17 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/extractMethod/SimpleDuplicatesFinder.java +++ b/platform/lang-impl/src/com/intellij/refactoring/extractMethod/SimpleDuplicatesFinder.java @@ -127,7 +127,10 @@ public class SimpleDuplicatesFinder { if (pattern == null || candidate == null) return pattern == candidate; final PsiElement[] children1 = PsiEquivalenceUtil.getFilteredChildren(pattern, null, true); final PsiElement[] children2 = PsiEquivalenceUtil.getFilteredChildren(candidate, null, true); - if (pattern.getUserData(PARAMETER) != null && pattern.getParent().getClass() == candidate.getParent().getClass()) { + final PsiElement patternParent = pattern.getParent(); + final PsiElement candidateParent = candidate.getParent(); + if (patternParent == null || candidateParent == null) return false; + if (pattern.getUserData(PARAMETER) != null && patternParent.getClass() == candidateParent.getClass()) { match.changeParameter(pattern.getText(), candidate.getText()); return true; } @@ -140,7 +143,7 @@ public class SimpleDuplicatesFinder { } if (children1.length == 0) { - if (pattern.getUserData(PARAMETER) != null && pattern.getParent().getClass() == candidate.getParent().getClass()) { + if (pattern.getUserData(PARAMETER) != null && patternParent.getClass() == candidateParent.getClass()) { match.changeParameter(pattern.getText(), candidate.getText()); return true; } diff --git a/platform/lang-impl/src/com/intellij/refactoring/move/moveFilesOrDirectories/MoveFilesOrDirectoriesHandler.java b/platform/lang-impl/src/com/intellij/refactoring/move/moveFilesOrDirectories/MoveFilesOrDirectoriesHandler.java index a7812f880484..66505d903dbf 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/move/moveFilesOrDirectories/MoveFilesOrDirectoriesHandler.java +++ b/platform/lang-impl/src/com/intellij/refactoring/move/moveFilesOrDirectories/MoveFilesOrDirectoriesHandler.java @@ -75,7 +75,7 @@ public class MoveFilesOrDirectoriesHandler extends MoveHandlerDelegate { @Override public void doMove(final Project project, final PsiElement[] elements, final PsiElement targetContainer, @Nullable final MoveCallback callback) { if (!LOG.assertTrue(targetContainer == null || targetContainer instanceof PsiDirectory || targetContainer instanceof PsiDirectoryContainer, - "container: " + targetContainer + "; elements: " + Arrays.toString(elements))) { + "container: " + targetContainer + "; elements: " + Arrays.toString(elements) + "; working handler: " + toString())) { return; } MoveFilesOrDirectoriesUtil.doMove(project, adjustForMove(project, elements, targetContainer), new PsiElement[] {targetContainer}, callback); diff --git a/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/InplaceRefactoring.java b/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/InplaceRefactoring.java index 1ff57760a628..33f23494b341 100644 --- a/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/InplaceRefactoring.java +++ b/platform/lang-impl/src/com/intellij/refactoring/rename/inplace/InplaceRefactoring.java @@ -61,6 +61,7 @@ import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.util.*; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; +import com.intellij.psi.impl.source.resolve.reference.impl.PsiMultiReference; import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; import com.intellij.psi.search.LocalSearchScope; import com.intellij.psi.search.ProjectScope; @@ -530,6 +531,18 @@ public abstract class InplaceRefactoring { final PsiReference reference = (myEditorFile != null ? myEditorFile : myElementToRename.getContainingFile()) .findReferenceAt(myEditor.getCaretModel().getOffset()); + if (reference instanceof PsiMultiReference) { + final PsiReference[] references = ((PsiMultiReference)reference).getReferences(); + for (PsiReference ref : references) { + addReferenceIfNeeded(refs, ref); + } + } + else { + addReferenceIfNeeded(refs, reference); + } + } + + private void addReferenceIfNeeded(@NotNull final Collection<PsiReference> refs, @Nullable final PsiReference reference) { if (reference != null && reference.isReferenceTo(myElementToRename) && !refs.contains(reference)) { refs.add(reference); } diff --git a/platform/lang-impl/src/com/intellij/ui/tabs/FileColorConfigurationEditDialog.java b/platform/lang-impl/src/com/intellij/ui/tabs/FileColorConfigurationEditDialog.java index c099253fb247..24d886cfb548 100644 --- a/platform/lang-impl/src/com/intellij/ui/tabs/FileColorConfigurationEditDialog.java +++ b/platform/lang-impl/src/com/intellij/ui/tabs/FileColorConfigurationEditDialog.java @@ -54,7 +54,7 @@ public class FileColorConfigurationEditDialog extends DialogWrapper { public FileColorConfigurationEditDialog(@NotNull final FileColorManager manager, @Nullable final FileColorConfiguration configuration) { super(true); - setTitle(configuration == null ? "Add color label" : "Edit color label"); + setTitle(configuration == null ? "Add Color Label" : "Edit Color Label"); setResizable(false); myManager = manager; diff --git a/platform/lang-impl/src/com/intellij/util/ui/classpath/SimpleClasspathElementFactory.java b/platform/lang-impl/src/com/intellij/util/ui/classpath/SimpleClasspathElementFactory.java index 9f45e024501a..28e4fa85011e 100644 --- a/platform/lang-impl/src/com/intellij/util/ui/classpath/SimpleClasspathElementFactory.java +++ b/platform/lang-impl/src/com/intellij/util/ui/classpath/SimpleClasspathElementFactory.java @@ -7,11 +7,13 @@ import com.intellij.openapi.roots.libraries.LibraryTable; import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; import org.jdom.Element; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; @@ -68,4 +70,20 @@ public class SimpleClasspathElementFactory { } return list; } + + public static List<VirtualFile> convertToFiles(Collection<SimpleClasspathElement> cpeList) + { + VirtualFileManager fileManager = VirtualFileManager.getInstance(); + List<VirtualFile> files = new ArrayList<VirtualFile>(); + for (SimpleClasspathElement cpe : cpeList) { + for (String fileUrl : cpe.getClassesRootUrls()) { + VirtualFile file = fileManager.findFileByUrl(fileUrl); + if (file != null) { + files.add(file); + } + } + } + return files; + } + } diff --git a/platform/platform-api/src/com/intellij/openapi/application/ApplicationInfo.java b/platform/platform-api/src/com/intellij/openapi/application/ApplicationInfo.java index 6fb51704318c..4661dbda9e72 100644 --- a/platform/platform-api/src/com/intellij/openapi/application/ApplicationInfo.java +++ b/platform/platform-api/src/com/intellij/openapi/application/ApplicationInfo.java @@ -32,6 +32,7 @@ public abstract class ApplicationInfo { } public abstract BuildNumber getBuild(); + public abstract String getApiVersion(); public abstract String getMajorVersion(); public abstract String getMinorVersion(); diff --git a/platform/platform-api/src/com/intellij/openapi/ui/MessageType.java b/platform/platform-api/src/com/intellij/openapi/ui/MessageType.java index f3343a27f605..a9d4ca3f20d7 100644 --- a/platform/platform-api/src/com/intellij/openapi/ui/MessageType.java +++ b/platform/platform-api/src/com/intellij/openapi/ui/MessageType.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -25,11 +25,11 @@ import java.awt.*; public class MessageType { public static final MessageType ERROR = new MessageType(UIUtil.getBalloonErrorIcon(), - new JBColor(new Color(255, 204, 204, 230), new Color(0x70383a))); + new JBColor(new Color(255, 204, 204, 230), new Color(112, 71, 69))); public static final MessageType INFO = new MessageType(UIUtil.getBalloonInformationIcon(), - new JBColor(new Color(186, 238, 186, 230), new Color(0x356936))); + new JBColor(new Color(186, 238, 186, 230), new Color(73, 117, 73))); public static final MessageType WARNING = new MessageType(UIUtil.getBalloonWarningIcon(), - new JBColor(new Color(249, 247, 142, 230), new Color(0x5C5C42))); + new JBColor(new Color(249, 247, 142, 230), new Color(128, 125, 75))); private final Icon myDefaultIcon; private final Color myPopupBackground; diff --git a/platform/platform-api/src/com/intellij/openapi/vfs/VfsUtil.java b/platform/platform-api/src/com/intellij/openapi/vfs/VfsUtil.java index 7bc02081df30..d9f435594bbb 100644 --- a/platform/platform-api/src/com/intellij/openapi/vfs/VfsUtil.java +++ b/platform/platform-api/src/com/intellij/openapi/vfs/VfsUtil.java @@ -567,28 +567,6 @@ public class VfsUtil extends VfsUtilCore { }); } - public static void processFilesRecursively(@NotNull VirtualFile root, @NotNull Processor<VirtualFile> processor, - @NotNull Convertor<VirtualFile, Boolean> directoryFilter) { - if (!processor.process(root)) return; - - if (root.isDirectory() && directoryFilter.convert(root)) { - final LinkedList<VirtualFile[]> queue = new LinkedList<VirtualFile[]>(); - - queue.add(root.getChildren()); - - do { - final VirtualFile[] files = queue.removeFirst(); - - for (VirtualFile file : files) { - if (!processor.process(file)) return; - if (file.isDirectory() && directoryFilter.convert(file)) { - queue.add(file.getChildren()); - } - } - } while (!queue.isEmpty()); - } - } - @Nullable public static <T> T processInputStream(@NotNull final VirtualFile file, @NotNull Function<InputStream, T> function) { InputStream stream = null; @@ -598,7 +576,8 @@ public class VfsUtil extends VfsUtilCore { } catch (IOException e) { LOG.error(e); - } finally { + } + finally { try { if (stream != null) { stream.close(); diff --git a/platform/platform-api/src/com/intellij/ui/CheckedTreeNode.java b/platform/platform-api/src/com/intellij/ui/CheckedTreeNode.java index 69b4aaa7e14e..a7cfb7162fa9 100644 --- a/platform/platform-api/src/com/intellij/ui/CheckedTreeNode.java +++ b/platform/platform-api/src/com/intellij/ui/CheckedTreeNode.java @@ -26,6 +26,10 @@ import javax.swing.tree.DefaultMutableTreeNode; public class CheckedTreeNode extends DefaultMutableTreeNode { protected boolean isChecked = true; private boolean isEnabled = true; + + public CheckedTreeNode() { + } + public CheckedTreeNode(Object userObject) { super(userObject); } diff --git a/platform/platform-api/src/com/intellij/ui/SearchTextField.java b/platform/platform-api/src/com/intellij/ui/SearchTextField.java index ad153fe7a479..8603393f1e9b 100644 --- a/platform/platform-api/src/com/intellij/ui/SearchTextField.java +++ b/platform/platform-api/src/com/intellij/ui/SearchTextField.java @@ -110,7 +110,7 @@ public class SearchTextField extends JPanel { } }); - if (hasNativeLeopardSearchControl() || UIUtil.isUnderDarcula()) { + if (hasNativeLeopardSearchControl() || UIUtil.isUnderDarcula() || UIUtil.isUnderIntelliJLaF()) { myTextField.putClientProperty("JTextField.variant", "search"); } if (hasNativeLeopardSearchControl()) { @@ -204,7 +204,7 @@ public class SearchTextField extends JPanel { } private static boolean hasNativeLeopardSearchControl() { - return (SystemInfo.isMacOSLeopard && UIUtil.isUnderAquaLookAndFeel()) || UIUtil.isUnderDarcula(); + return (SystemInfo.isMacOSLeopard && UIUtil.isUnderAquaLookAndFeel()) || UIUtil.isUnderDarcula() || UIUtil.isUnderIntelliJLaF(); } private static boolean hasIconsOutsideOfTextField() { diff --git a/platform/platform-api/src/com/intellij/ui/components/OnOffButton.java b/platform/platform-api/src/com/intellij/ui/components/OnOffButton.java index c09a66991aad..7ee0ab9207eb 100644 --- a/platform/platform-api/src/com/intellij/ui/components/OnOffButton.java +++ b/platform/platform-api/src/com/intellij/ui/components/OnOffButton.java @@ -16,6 +16,7 @@ package com.intellij.ui.components; import com.intellij.ui.Gray; +import com.intellij.ui.JBColor; import com.intellij.util.ui.GraphicsUtil; import com.intellij.util.ui.UIUtil; @@ -78,7 +79,7 @@ public class OnOffButton extends JToggleButton { int w = fm.stringWidth(text); int h = fm.getHeight(); h += 2*4; - w += 3 * h / 2 + 4; + w += 3 * h / 2; return new Dimension(w, h); } @Override @@ -94,11 +95,11 @@ public class OnOffButton extends JToggleButton { GraphicsUtil.setupAAPainting(g); g.translate(1,1); if (button.isSelected()) { - g.setColor(new Color(13, 41, 62)); + g.setColor(new JBColor(new Color(57, 113, 238), new Color(13, 41, 62))); g.fillRoundRect(0, 0, w, h, h, h); g.setColor(UIUtil.getBorderColor()); g.drawRoundRect(0, 0, w, h, h, h); - g.setColor(Gray._128); + g.setColor(new JBColor(Gray._220, Gray._128)); g.fillOval(w - h + 1, 1, h - 1, h - 1); g.setColor(UIUtil.getListForeground(true)); g.drawString(button.getOnText(), h/2, h - 4); diff --git a/platform/platform-api/src/com/intellij/util/MixinEP.java b/platform/platform-api/src/com/intellij/util/MixinEP.java index 6a8032641d3c..406ec2865807 100644 --- a/platform/platform-api/src/com/intellij/util/MixinEP.java +++ b/platform/platform-api/src/com/intellij/util/MixinEP.java @@ -15,6 +15,7 @@ */ package com.intellij.util; +import com.intellij.diagnostic.PluginException; import com.intellij.openapi.extensions.AbstractExtensionPointBean; import com.intellij.openapi.util.LazyInstance; import com.intellij.openapi.util.NotNullLazyValue; @@ -36,6 +37,13 @@ public class MixinEP<T> extends AbstractExtensionPointBean { private final NotNullLazyValue<Class> myKey = new NotNullLazyValue<Class>() { @NotNull protected Class compute() { + if (key == null) { + String error = "No key specified for mixin with implementation class " + implementationClass; + if (myPluginDescriptor != null) { + throw new PluginException(error, myPluginDescriptor.getPluginId()); + } + throw new IllegalArgumentException(error); + } try { return findClass(key); } diff --git a/platform/platform-impl/src/com/intellij/concurrency/ApplierCompleter.java b/platform/platform-impl/src/com/intellij/concurrency/ApplierCompleter.java new file mode 100644 index 000000000000..1644e54df8b1 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/concurrency/ApplierCompleter.java @@ -0,0 +1,231 @@ +/* + * Copyright 2000-2013 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.concurrency; + +import com.intellij.openapi.application.ex.ApplicationManagerEx; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.util.Processor; +import jsr166e.CountedCompleter; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * Executes processor on array elements in range from lo (inclusive) to hi (exclusive). + * To do this it starts executing processor on first array items and, if it takes too much time, splits the work and forks the right half. + * The series of splits lead to linked list of forked sub tasks, each of which is a CountedCompleter of its own, + * having this task as its parent. + * After the first pass on the array, this task attempts to steal work from the recently forked off sub tasks, + * by traversing the linked subtasks list, unforking each subtask and calling execAndForkSubTasks() on each recursively. + * After that, the task completes itself. + * The process of completing traverses task parent hierarchy, decrementing each pending count until it either + * decrements not-zero pending count and stops or + * reaches the top, in which case it invokes {@link jsr166e.ForkJoinTask#quietlyComplete()} which causes the top level task to wake up and join successfully. + * The exceptions from the sub tasks bubble up to the top and saved in {@link #throwable}. + */ +public class ApplierCompleter extends CountedCompleter<Void> { + private final boolean runInReadAction; + private final ProgressIndicator progressIndicator; + @NotNull + private final List array; + @NotNull + private final Processor processor; + private final int lo; + private final int hi; + private final ApplierCompleter next; // keeps track of right-hand-side tasks + volatile Throwable throwable; + + // if not null, the read action has failed and this list contains unfinished subtasks + private List<ApplierCompleter> failedSubTasks; + + //private final List<ApplierCompleter> children = new ArrayList<ApplierCompleter>(); + + ApplierCompleter(ApplierCompleter parent, + boolean runInReadAction, + @NotNull ProgressIndicator progressIndicator, + @NotNull List array, + @NotNull Processor processor, + int lo, + int hi, + ApplierCompleter next) { + super(parent); + this.runInReadAction = runInReadAction; + this.progressIndicator = progressIndicator; + this.array = array; + this.processor = processor; + this.lo = lo; + this.hi = hi; + this.next = next; + } + + @Override + public void compute() { + compute(new Runnable() { + @Override + public void run() { + execAndForkSubTasks(); + } + }); + } + + private void compute(@NotNull final Runnable process) { + Runnable toRun = runInReadAction ? new Runnable() { + @Override + public void run() { + if (!ApplicationManagerEx.getApplicationEx().tryRunReadAction(process)) { + failedSubTasks = new ArrayList<ApplierCompleter>(); + failedSubTasks.add(ApplierCompleter.this); + doComplete(throwable); + } + } + } : process; + ProgressIndicator existing = ProgressManager.getInstance().getProgressIndicator(); + if (existing == progressIndicator) { + toRun.run(); + } + else { + ProgressManager.getInstance().executeProcessUnderProgress(toRun, progressIndicator); + } + } + + static class ComputationAbortedException extends RuntimeException {} + // executes tasks one by one and forks right halves if it takes too much time + // returns the linked list of forked halves - they all need to be joined; null means all tasks have been executed, nothing was forked + @Nullable + private ApplierCompleter execAndForkSubTasks() { + int hi = this.hi; + long start = System.currentTimeMillis(); + ApplierCompleter right = null; + Throwable throwable = null; + try { + for (int i = lo; i < hi; ++i) { + progressIndicator.checkCanceled(); + if (!processor.process(array.get(i))) throw new ComputationAbortedException(); + long finish = System.currentTimeMillis(); + long elapsed = finish - start; + if (elapsed > 10 && hi - i >= 2 && getSurplusQueuedTaskCount() <= JobSchedulerImpl.CORES_COUNT) { + int mid = i + hi >>> 1; + right = new ApplierCompleter(this, runInReadAction, progressIndicator, array, processor, mid, hi, right); + //children.add(right); + addToPendingCount(1); + right.fork(); + hi = mid; + start = finish; + } + } + + // traverse the list looking for a task available for stealing + if (right != null) { + right.tryToExecAllList(); + } + } + catch (Throwable e) { + cancelProgress(); + throwable = e; + } + finally { + doComplete(throwable == null ? this.throwable : throwable); + } + return right; + } + + private void doComplete(Throwable throwable) { + ApplierCompleter a = this; + ApplierCompleter child = a; + while (true) { + if (throwable != null) { + a.throwable = throwable; + } + if (a.getPendingCount() == 0) { + if (throwable == null) { + a.onCompletion(child); + } + else { + a.throwable = throwable; + // currently avoid using onExceptionalCompletion since it leaks exceptions via jsr166e.ForkJoinTask.exceptionTable + a.onCompletion(child); + //a.onExceptionalCompletion(throwable, child); + } + child = a; + a = (ApplierCompleter)a.getCompleter(); + if (a == null) { + if (throwable == null) { + child.quietlyComplete(); + } + else { + child.throwable = throwable; + // currently avoid using completeExceptionally since it leaks exceptions via jsr166e.ForkJoinTask.exceptionTable + child.quietlyComplete(); + //child.completeExceptionally(throwable); + } + break; + } + } + else if (a.decrementPendingCountUnlessZero() != 0) { + break; + } + } + } + + private void cancelProgress() { + if (!progressIndicator.isCanceled()) { + progressIndicator.cancel(); + } + } + + // tries to unfork, execute and re-link subtasks + private void tryToExecAllList() { + ApplierCompleter right = this; + while (right != null) { + if (right.tryUnfork()) { + right.execAndForkSubTasks(); + } + right = right.next; + } + } + + boolean completeTaskWhichFailToAcquireReadAction() { + if (failedSubTasks == null) { + return true; + } + final boolean[] result = {true}; + // these tasks could not be executed in the other thread; do them here + for (final ApplierCompleter task : failedSubTasks) { + task.failedSubTasks = null; + task.compute(new Runnable() { + @Override + public void run() { + for (int i = task.lo; i < task.hi; ++i) { + if (!task.processor.process(task.array.get(i))) { + result[0] = false; + break; + } + } + } + }); + assert task.failedSubTasks == null; + } + return result[0]; + } + + @Override + public String toString() { + return System.identityHashCode(this) + " ("+lo+"-"+hi+")"; + } +} diff --git a/platform/platform-impl/src/com/intellij/concurrency/JobImpl.java b/platform/platform-impl/src/com/intellij/concurrency/JobImpl.java deleted file mode 100644 index 9a7541c11187..000000000000 --- a/platform/platform-impl/src/com/intellij/concurrency/JobImpl.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright 2000-2009 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. - */ - -/* - * @author max - */ -package com.intellij.concurrency; - -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.util.Consumer; -import org.jetbrains.annotations.NotNull; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; - -public class JobImpl<T> implements Job<T> { - private static volatile long ourJobsCounter = 0; - private final long myJobIndex = ourJobsCounter++; - private final int myPriority; - private final List<PrioritizedFutureTask<T>> myFutures = new ArrayList<PrioritizedFutureTask<T>>(); - private volatile boolean canceled = false; - private final AtomicInteger runningTasks = new AtomicInteger(); - private volatile boolean scheduled; - private final boolean myFailFastOnAcquireReadAction; - - JobImpl(int priority, boolean failFastOnAcquireReadAction) { - myPriority = priority; - myFailFastOnAcquireReadAction = failFastOnAcquireReadAction; - } - - @Override - public String getTitle() { - return null; - } - - @Override - public void addTask(@NotNull Callable<T> task) { - addTask(task, null); - } - - @Override - public void addTask(@NotNull Runnable task, T result) { - addTask(Executors.callable(task, result)); - } - - @Override - public void addTask(@NotNull Runnable task) { - addTask(Executors.callable(task, (T)null)); - } - - public void addTask(@NotNull Callable<T> callable, final Consumer<Future> onDoneCallback) { - checkNotScheduled(); - - PrioritizedFutureTask<T> future = - new PrioritizedFutureTask<T>(callable, this, myJobIndex, JobSchedulerImpl.currentTaskIndex(), myPriority, myFailFastOnAcquireReadAction){ - @Override - protected void done() { - super.done(); - if (onDoneCallback != null) { - onDoneCallback.consume(this); - } - //TODO[cdr]: consider clearing thread locals: ReflectionUtil.resetThreadlocals(); - } - }; - synchronized (myFutures) { - myFutures.add(future); - } - runningTasks.incrementAndGet(); - } - - - @Override - public List<T> scheduleAndWaitForResults() throws Throwable { - checkCanSchedule(); - final Application application = ApplicationManager.getApplication(); - boolean callerHasReadAccess = application != null && application.isReadAccessAllowed(); - scheduleAndWaitForResults(callerHasReadAccess); - return null; - } - - public void scheduleAndWaitForResults(boolean runInReadAction) throws Throwable { - // Don't bother scheduling if we only have one processor or only one task - boolean reallySchedule; - PrioritizedFutureTask[] tasks = getTasks(); - synchronized (myFutures) { - reallySchedule = JobSchedulerImpl.CORES_COUNT >= 2 && myFutures.size() >= 2; - } - scheduled = true; - - if (!reallySchedule) { - for (PrioritizedFutureTask future : tasks) { - future.run(); - } - return; - } - - submitTasks(tasks, runInReadAction, false); - - // in case of imbalanced tasks one huge task can stuck running and we would fall to waitForTermination instead of doing useful work - //// http://gafter.blogspot.com/2006/11/thread-pool-puzzler.html - //for (PrioritizedFutureTask task : tasks) { - // task.run(); - //} - // - while (!isDone()) { - Runnable task = JobSchedulerImpl.stealTask(); - if (task == null) break; - - task.run(); - } - - waitForTermination(); - } - - public void waitForTermination() throws Throwable { - Throwable ex = null; - PrioritizedFutureTask[] tasks = getTasks(); - for (PrioritizedFutureTask f : tasks) { - try { - // this loop is for workaround of mysterious bug - // when sometimes future hangs inside parkAndCheckForInterrupt() during unbounded get() - while(true) { - try { - f.get(10, TimeUnit.MILLISECONDS); - break; - } - catch (TimeoutException e) { - if (f.isDone()) { - f.get(); // does awaitTermination(), and there is no chance to hang - break; - } - } - } - } - catch (CancellationException ignore) { - // already cancelled - cancel(); - } - catch (ExecutionException e) { - cancel(); - - Throwable cause = e.getCause(); - if (cause != null) { - ex = cause; - } - } - } - - if (ex != null) { - throw ex; - } - } - - @Override - public void cancel() { - checkScheduled(); - if (canceled) return; - canceled = true; - - PrioritizedFutureTask[] tasks = getTasks(); - for (PrioritizedFutureTask future : tasks) { - future.cancel(false); - } - runningTasks.set(0); - } - - @Override - public boolean isCanceled() { - checkScheduled(); - return canceled; - } - - @Override - public void schedule() { - checkCanSchedule(); - scheduled = true; - - PrioritizedFutureTask[] tasks = getTasks(); - - submitTasks(tasks, false, true); - } - - @NotNull - public PrioritizedFutureTask[] getTasks() { - PrioritizedFutureTask[] tasks; - synchronized (myFutures) { - tasks = myFutures.toArray(new PrioritizedFutureTask[myFutures.size()]); - } - return tasks; - } - - @Override - public boolean isDone() { - checkScheduled(); - - return runningTasks.get() <= 0; - } - - private void checkCanSchedule() { - checkNotScheduled(); - synchronized (myFutures) { - if (myFutures.isEmpty()) { - throw new IllegalStateException("No tasks added. You can't schedule a job which has no tasks"); - } - } - } - - private void checkNotScheduled() { - if (scheduled) { - throw new IllegalStateException("Already running. You can't call this method for a job which is already scheduled"); - } - } - - private void checkScheduled() { - if (!scheduled) { - throw new IllegalStateException("Cannot call this method for not yet started job"); - } - } - - private static void submitTasks(@NotNull PrioritizedFutureTask[] tasks, boolean runInReadAction, boolean reportExceptions) { - for (final PrioritizedFutureTask future : tasks) { - JobSchedulerImpl.submitTask(future, runInReadAction, reportExceptions); - } - } - - void taskDone() { - runningTasks.decrementAndGet(); - } -} diff --git a/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java b/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java index ee5379e4b794..f47962e2eb72 100644 --- a/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java +++ b/platform/platform-impl/src/com/intellij/concurrency/JobLauncherImpl.java @@ -19,67 +19,90 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; -import com.intellij.openapi.progress.util.ProgressWrapper; +import com.intellij.openapi.progress.util.ProgressIndicatorBase; import com.intellij.util.Consumer; +import com.intellij.util.IncorrectOperationException; import com.intellij.util.Processor; +import jsr166e.ForkJoinPool; +import jsr166e.ForkJoinTask; +import jsr166e.ForkJoinWorkerThread; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.Future; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; /** * @author cdr */ public class JobLauncherImpl extends JobLauncher { - private static <T> boolean invokeConcurrentlyForAll(@NotNull final List<? extends T> things, - boolean runInReadAction, - boolean failFastOnAcquireReadAction, - @NotNull final Processor<T> thingProcessor, - final ProgressWrapper wrapper) throws ProcessCanceledException { - final JobImpl<String> job = new JobImpl<String>(Job.DEFAULT_PRIORITY, failFastOnAcquireReadAction); - - final int chunkSize = Math.max(1, things.size() / Math.max(1, JobSchedulerImpl.CORES_COUNT / 2)); - for (int i = 0; i < things.size(); i += chunkSize) { - // this job chunk is i..i+chunkSize-1 - final int finalI = i; - job.addTask(new Runnable() { + private static final AtomicLong bits = new AtomicLong(); + private static final ForkJoinPool.ForkJoinWorkerThreadFactory FACTORY = new ForkJoinPool.ForkJoinWorkerThreadFactory() { + @Override + public ForkJoinWorkerThread newThread(ForkJoinPool pool) { + final int n = addThread(); + ForkJoinWorkerThread thread = new ForkJoinWorkerThread(pool) { @Override - public void run() { - ProgressManager.getInstance().executeProcessUnderProgress(new Runnable() { - @Override - public void run() { - try { - for (int k = finalI; k < finalI + chunkSize && k < things.size(); k++) { - T thing = things.get(k); - if (!thingProcessor.process(thing)) { - job.cancel(); - break; - } - } - } - catch (ProcessCanceledException e) { - job.cancel(); - throw e; - } - - } - }, wrapper); + protected void onTermination(Throwable exception) { + finishThread(n); + super.onTermination(exception); } - }); + }; + thread.setName("JobScheduler FJ pool "+ n +"/"+ JobSchedulerImpl.CORES_COUNT); + return thread; + } + + private int addThread() { + boolean set; + int n; + do { + long l = bits.longValue(); + long next = (l + 1) | l; + n = Long.numberOfTrailingZeros(l + 1); + set = bits.compareAndSet(l, next); + } while (!set); + return n; + } + private void finishThread(int n) { + boolean set; + do { + long l = bits.get(); + long next = l & ~(1L << n); + set = bits.compareAndSet(l, next); + } while (!set); } + }; + + private static final ForkJoinPool pool = new ForkJoinPool(JobSchedulerImpl.CORES_COUNT, FACTORY, null, false); + + private static <T> boolean invokeConcurrentlyForAll(@NotNull final List<T> things, + boolean runInReadAction, + @NotNull final Processor<? super T> thingProcessor, + @NotNull ProgressIndicator wrapper) throws ProcessCanceledException { + ApplierCompleter applier = new ApplierCompleter(null, runInReadAction, wrapper, things, thingProcessor, 0, things.size(), null); try { - job.scheduleAndWaitForResults(runInReadAction); + pool.invoke(applier); + if (applier.throwable != null) throw applier.throwable; + } + catch (ApplierCompleter.ComputationAbortedException e) { + return false; } catch (RuntimeException e) { - job.cancel(); + assert wrapper.isCanceled(); throw e; } - catch (Throwable throwable) { - job.cancel(); - throw new ProcessCanceledException(throwable); + catch (Error e) { + assert wrapper.isCanceled(); + throw e; + } + catch (Throwable e) { + assert wrapper.isCanceled(); + throw new RuntimeException(e); } - return !job.isCanceled(); + assert applier.isDone(); + return applier.completeTaskWhichFailToAcquireReadAction(); } @Override @@ -92,22 +115,34 @@ public class JobLauncherImpl extends JobLauncher { } @Override - public <T> boolean invokeConcurrentlyUnderProgress(@NotNull List<? extends T> things, + public <T> boolean invokeConcurrentlyUnderProgress(@NotNull final List<? extends T> things, ProgressIndicator progress, boolean runInReadAction, boolean failFastOnAcquireReadAction, - @NotNull Processor<T> thingProcessor) { - if (things.isEmpty()) { - return true; - } - if (things.size() == 1) { - T t = things.get(0); - return thingProcessor.process(t); + @NotNull final Processor<T> thingProcessor) throws ProcessCanceledException { + if (things.isEmpty()) return true; + // supply our own indicator even if we haven't given one - to support cancellation + final ProgressIndicator wrapper = progress == null ? new ProgressIndicatorBase() : new SensitiveProgressWrapper(progress); + + if (things.size() <= 1 || JobSchedulerImpl.CORES_COUNT <= 2) { + final AtomicBoolean result = new AtomicBoolean(true); + ProgressManager.getInstance().executeProcessUnderProgress(new Runnable() { + @Override + public void run() { + //noinspection ForLoopReplaceableByForEach + for (int i = 0; i < things.size(); i++) { + T thing = things.get(i); + if (!thingProcessor.process(thing)) { + result.set(false); + break; + } + } + } + }, wrapper); + return result.get(); } - // can be already wrapped - final ProgressWrapper wrapper = progress instanceof ProgressWrapper ? (ProgressWrapper)progress : ProgressWrapper.wrap(progress); - return invokeConcurrentlyForAll(things, runInReadAction, failFastOnAcquireReadAction, thingProcessor, wrapper); + return invokeConcurrentlyForAll(things, runInReadAction, thingProcessor, wrapper); } // This implementation is not really async @@ -130,22 +165,92 @@ public class JobLauncherImpl extends JobLauncher { @NotNull @Override - public Job<Void> submitToJobThread(int priority, @NotNull final Runnable action, Consumer<Future> onDoneCallback) { - final JobImpl<Void> job = new JobImpl<Void>(priority, false); - Callable<Void> callable = new Callable<Void>() { - @Override - public Void call() throws Exception { - try { - action.run(); - } - catch (ProcessCanceledException ignored) { - // since it's the only task in the job, nothing to cancel + public Job<Void> submitToJobThread(int priority, @NotNull final Runnable action, final Consumer<Future> onDoneCallback) { + VoidForkJoinTask task = new VoidForkJoinTask(action, onDoneCallback); + pool.submit(task); + return task; + } + + private static class VoidForkJoinTask extends ForkJoinTask<Void> implements Job<Void> { + private final Runnable myAction; + private final Consumer<Future> myOnDoneCallback; + + public VoidForkJoinTask(@NotNull Runnable action, @Nullable Consumer<Future> onDoneCallback) { + myAction = action; + myOnDoneCallback = onDoneCallback; + } + + @Override + public Void getRawResult() { + return null; + } + + @Override + protected void setRawResult(Void value) { + + } + + @Override + protected boolean exec() { + try { + myAction.run(); + complete(null); // complete manually before calling callback + } + catch (Throwable throwable) { + completeExceptionally(throwable); + } + finally { + if (myOnDoneCallback != null) { + myOnDoneCallback.consume(this); } - return null; } - }; - job.addTask(callable, onDoneCallback); - job.schedule(); - return job; + return true; + } + + //////////////// Job + @Override + public String getTitle() { + throw new IncorrectOperationException(); + } + + @Override + public boolean isCanceled() { + return isCancelled(); + } + + @Override + public void addTask(@NotNull Callable<Void> task) { + throw new IncorrectOperationException(); + } + + @Override + public void addTask(@NotNull Runnable task, Void result) { + throw new IncorrectOperationException(); + } + + @Override + public void addTask(@NotNull Runnable task) { + throw new IncorrectOperationException(); + } + + @Override + public List<Void> scheduleAndWaitForResults() throws Throwable { + throw new IncorrectOperationException(); + } + + @Override + public void cancel() { + cancel(true); + } + + @Override + public void schedule() { + throw new IncorrectOperationException(); + } + + @Override + public void waitForCompletion(int millis) throws InterruptedException, ExecutionException, TimeoutException { + get(millis, TimeUnit.MILLISECONDS); + } } } diff --git a/platform/platform-impl/src/com/intellij/concurrency/JobSchedulerImpl.java b/platform/platform-impl/src/com/intellij/concurrency/JobSchedulerImpl.java index 366615108899..7b252e9c16d0 100644 --- a/platform/platform-impl/src/com/intellij/concurrency/JobSchedulerImpl.java +++ b/platform/platform-impl/src/com/intellij/concurrency/JobSchedulerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 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,66 +19,6 @@ */ package com.intellij.concurrency; -import com.intellij.openapi.Disposable; -import org.jetbrains.annotations.NonNls; -import org.jetbrains.annotations.NotNull; - -import java.util.concurrent.PriorityBlockingQueue; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -@NonNls -public class JobSchedulerImpl extends JobScheduler implements Disposable { - public static final int CORES_COUNT = /*1;//*/ Runtime.getRuntime().availableProcessors(); - - private static final ThreadFactory WORKERS_FACTORY = new ThreadFactory() { - private int threadSeq; - - @NotNull - @Override - public synchronized Thread newThread(@NotNull final Runnable r) { - @NonNls String name = "JobScheduler pool " + threadSeq + "/" + CORES_COUNT; - final Thread thread = new Thread(r, name); - thread.setPriority(Thread.NORM_PRIORITY); - threadSeq++; - return thread; - } - }; - - private static final PriorityBlockingQueue<Runnable> ourQueue = new PriorityBlockingQueue<Runnable>(); - private static final MyExecutor ourExecutor = new MyExecutor(); - - static int currentTaskIndex() { - return ourQueue.size(); - } - - @Override - public void dispose() { - ((ThreadPoolExecutor)getScheduler()).getQueue().clear(); - } - - static Runnable stealTask() { - return ourQueue.poll(); - } - - static void submitTask(@NotNull PrioritizedFutureTask future, boolean runInReadAction, boolean reportExceptions) { - future.beforeRun(runInReadAction, reportExceptions); - ourExecutor.executeTask(future); - } - - private static class MyExecutor extends ThreadPoolExecutor { - private MyExecutor() { - super(CORES_COUNT, Integer.MAX_VALUE, 60 * 10, TimeUnit.SECONDS, ourQueue, WORKERS_FACTORY); - } - - private void executeTask(@NotNull PrioritizedFutureTask task) { - super.execute(task); - } - - @Override - public void execute(@NotNull Runnable command) { - throw new IllegalStateException("Use executeTask() to submit PrioritizedFutureTasks only"); - } - } +public abstract class JobSchedulerImpl { + public static final int CORES_COUNT = Runtime.getRuntime().availableProcessors(); } diff --git a/platform/platform-impl/src/com/intellij/concurrency/PrioritizedFutureTask.java b/platform/platform-impl/src/com/intellij/concurrency/PrioritizedFutureTask.java deleted file mode 100644 index 799750ed5339..000000000000 --- a/platform/platform-impl/src/com/intellij/concurrency/PrioritizedFutureTask.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2000-2009 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. - */ - -/* - * @author max - */ -package com.intellij.concurrency; - -import com.intellij.openapi.application.ex.ApplicationManagerEx; -import com.intellij.openapi.application.impl.ApplicationImpl; -import com.intellij.openapi.diagnostic.Logger; -import org.jetbrains.annotations.NotNull; - -import java.util.concurrent.Callable; -import java.util.concurrent.CancellationException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.FutureTask; - -class PrioritizedFutureTask<T> extends FutureTask<T> implements Comparable<PrioritizedFutureTask> { - private static final Logger LOG = Logger.getInstance("#com.intellij.concurrency.PrioritizedFutureTask"); - private final JobImpl<T> myJob; - private final long myJobIndex; - private final int myTaskIndex; - private final int myPriority; - private final boolean myFailFastOnAcquireReadAction; - private volatile boolean myRunInReadAction; - private volatile boolean myReportExceptions; - - PrioritizedFutureTask(final Callable<T> callable, - JobImpl<T> job, - long jobIndex, - int taskIndex, - int priority, - boolean failFastOnAcquireReadAction) { - super(callable); - myJob = job; - myJobIndex = jobIndex; - myTaskIndex = taskIndex; - myPriority = priority; - myFailFastOnAcquireReadAction = failFastOnAcquireReadAction; - } - - public void beforeRun(boolean runInReadAction, boolean reportExceptions) { - myRunInReadAction = runInReadAction; - myReportExceptions = reportExceptions; - } - - @Override - public void run() { - Runnable runnable = new Runnable() { - @Override - public void run() { - try { - if (myJob.isCanceled()) { - //set(null); - cancel(false); //todo cancel or set? - } - else { - PrioritizedFutureTask.super.run(); - } - } - finally { - try { - if (myReportExceptions) { - // let exceptions during execution manifest themselves - PrioritizedFutureTask.super.get(); - } - } - catch (CancellationException ignored) { - } - catch (InterruptedException e) { - LOG.error(e); - } - catch (ExecutionException e) { - LOG.error(e); - } - finally { - myJob.taskDone(); - } - } - } - }; - if (myRunInReadAction) { - // have to start "real" read action so that we cannot start write action until we are finished here - if (myFailFastOnAcquireReadAction) { - if (!ApplicationManagerEx.getApplicationEx().tryRunReadAction(runnable)) { - myJob.cancel(); - } - } - else { - // cannot run readaction here because of possible deadlock when writeaction in the queue - boolean old = ApplicationImpl.setExceptionalThreadWithReadAccessFlag(true); - try { - runnable.run(); - } - finally { - ApplicationImpl.setExceptionalThreadWithReadAccessFlag(old); - } - } - } - else { - runnable.run(); - } - } - - @Override - public int compareTo(@NotNull final PrioritizedFutureTask o) { - int priorityDelta = myPriority - o.myPriority; - if (priorityDelta != 0) return priorityDelta; - if (myJobIndex != o.myJobIndex) return myJobIndex < o.myJobIndex ? -1 : 1; - return myTaskIndex - o.myTaskIndex; - } -} diff --git a/platform/platform-impl/src/com/intellij/concurrency/SensitiveProgressWrapper.java b/platform/platform-impl/src/com/intellij/concurrency/SensitiveProgressWrapper.java new file mode 100644 index 000000000000..e471f68a58c8 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/concurrency/SensitiveProgressWrapper.java @@ -0,0 +1,34 @@ +/* + * Copyright 2000-2013 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.concurrency; + +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.util.ProgressWrapper; +import org.jetbrains.annotations.NotNull; + +/** + * Progress indicator wrapper which reacts to its own cancellation in addition to the cancellation of its wrappee. + */ +public class SensitiveProgressWrapper extends ProgressWrapper { + public SensitiveProgressWrapper(@NotNull ProgressIndicator indicator) { + super(indicator); + } + + @Override + public boolean isCanceled() { + return super.isCanceled() || getOriginalProgressIndicator().isCanceled(); + } +} diff --git a/platform/platform-impl/src/com/intellij/ide/IdeTooltipManager.java b/platform/platform-impl/src/com/intellij/ide/IdeTooltipManager.java index f960c7005076..3ba6638a4b8d 100644 --- a/platform/platform-impl/src/com/intellij/ide/IdeTooltipManager.java +++ b/platform/platform-impl/src/com/intellij/ide/IdeTooltipManager.java @@ -286,6 +286,10 @@ public class IdeTooltipManager implements ApplicationComponent, AWTEventListener effectivePoint.y = toCenterY ? bounds.height / 2 : effectivePoint.y; } + if (myCurrentComponent == tooltip.getComponent() && myCurrentTipUi != null) { + myCurrentTipUi.show(new RelativePoint(tooltip.getComponent(), effectivePoint), tooltip.getPreferredPosition()); + return; + } if (myCurrentComponent == tooltip.getComponent() && effectivePoint.equals(new Point(myX, myY))) { return; diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerMain.java b/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerMain.java index b8c36473dcf3..671ae6fd2ceb 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerMain.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/PluginManagerMain.java @@ -521,6 +521,12 @@ public abstract class PluginManagerMain implements Disposable { public static void notifyPluginsWereInstalled(@Nullable String pluginName) { + notifyPluginsWereUpdated(pluginName != null + ? "Plugin \'" + pluginName + "\' was successfully installed" + : "Plugins were installed"); + } + + public static void notifyPluginsWereUpdated(final String title) { final ApplicationEx app = ApplicationManagerEx.getApplicationEx(); final boolean restartCapable = app.isRestartCapable(); String message = @@ -530,9 +536,7 @@ public abstract class PluginManagerMain implements Disposable { message += restartCapable ? "\"restart\">Restart now" : "\"shutdown\">Shutdown"; message += "</a>"; new NotificationGroup("Plugins Lifecycle Group", NotificationDisplayType.STICKY_BALLOON, true) - .createNotification(pluginName != null - ? "Plugin \'" + pluginName + "\' was successfully installed" - : "Plugins were installed", + .createNotification(title, XmlStringUtil.wrapInHtml(message), NotificationType.INFORMATION, new NotificationListener() { @Override diff --git a/platform/platform-impl/src/com/intellij/ide/plugins/RepositoryHelper.java b/platform/platform-impl/src/com/intellij/ide/plugins/RepositoryHelper.java index 1b98915733cb..9959cef30030 100644 --- a/platform/platform-impl/src/com/intellij/ide/plugins/RepositoryHelper.java +++ b/platform/platform-impl/src/com/intellij/ide/plugins/RepositoryHelper.java @@ -45,7 +45,7 @@ public class RepositoryHelper { public static List<IdeaPluginDescriptor> loadPluginsFromRepository(@Nullable ProgressIndicator indicator) throws Exception { ApplicationInfoEx appInfo = ApplicationInfoImpl.getShadowInstance(); - String url = appInfo.getPluginsListUrl() + "?build=" + appInfo.getBuild().asString(); + String url = appInfo.getPluginsListUrl() + "?build=" + appInfo.getApiVersion(); if (indicator != null) { indicator.setText2(IdeBundle.message("progress.connecting.to.plugin.manager", appInfo.getPluginManagerUrl())); diff --git a/platform/platform-impl/src/com/intellij/ide/ui/AppearanceConfigurable.java b/platform/platform-impl/src/com/intellij/ide/ui/AppearanceConfigurable.java index 92e7a60972f4..69944b4febe3 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/AppearanceConfigurable.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/AppearanceConfigurable.java @@ -170,6 +170,9 @@ public class AppearanceConfigurable extends BaseConfigurable implements Searchab update |= settings.RIGHT_HORIZONTAL_SPLIT != myComponent.myRightLayoutCheckBox.isSelected(); settings.RIGHT_HORIZONTAL_SPLIT = myComponent.myRightLayoutCheckBox.isSelected(); + update |= settings.SHOW_EDITOR_TOOLTIP != myComponent.myEditorTooltipCheckBox.isSelected(); + settings.SHOW_EDITOR_TOOLTIP = myComponent.myEditorTooltipCheckBox.isSelected(); + update |= settings.DISABLE_MNEMONICS_IN_CONTROLS != myComponent.myDisableMnemonicInControlsCheckBox.isSelected(); settings.DISABLE_MNEMONICS_IN_CONTROLS = myComponent.myDisableMnemonicInControlsCheckBox.isSelected(); @@ -274,6 +277,7 @@ public class AppearanceConfigurable extends BaseConfigurable implements Searchab myComponent.myWidescreenLayoutCheckBox.setSelected(settings.WIDESCREEN_SUPPORT); myComponent.myLeftLayoutCheckBox.setSelected(settings.LEFT_HORIZONTAL_SPLIT); myComponent.myRightLayoutCheckBox.setSelected(settings.RIGHT_HORIZONTAL_SPLIT); + myComponent.myEditorTooltipCheckBox.setSelected(settings.SHOW_EDITOR_TOOLTIP); myComponent.myDisableMnemonicInControlsCheckBox.setSelected(settings.DISABLE_MNEMONICS_IN_CONTROLS); boolean alphaModeEnabled = WindowManagerEx.getInstanceEx().isAlphaModeSupported(); @@ -318,6 +322,7 @@ public class AppearanceConfigurable extends BaseConfigurable implements Searchab isModified |= myComponent.myWidescreenLayoutCheckBox.isSelected() != settings.WIDESCREEN_SUPPORT; isModified |= myComponent.myLeftLayoutCheckBox.isSelected() != settings.LEFT_HORIZONTAL_SPLIT; isModified |= myComponent.myRightLayoutCheckBox.isSelected() != settings.RIGHT_HORIZONTAL_SPLIT; + isModified |= myComponent.myEditorTooltipCheckBox.isSelected() != settings.SHOW_EDITOR_TOOLTIP; isModified |= myComponent.myHideIconsInQuickNavigation.isSelected() != settings.SHOW_ICONS_IN_QUICK_NAVIGATION; @@ -387,6 +392,7 @@ public class AppearanceConfigurable extends BaseConfigurable implements Searchab private JCheckBox myRightLayoutCheckBox; private JSlider myInitialTooltipDelaySlider; private ComboBox myPresentationModeFontSize; + private JCheckBox myEditorTooltipCheckBox; private JCheckBox myAllowStatusBar; private JCheckBox myAllowLineNumbers; private JCheckBox myAllowAnnotations; diff --git a/platform/platform-impl/src/com/intellij/ide/ui/AppearancePanel.form b/platform/platform-impl/src/com/intellij/ide/ui/AppearancePanel.form index f7e131228bbf..911ca64281e1 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/AppearancePanel.form +++ b/platform/platform-impl/src/com/intellij/ide/ui/AppearancePanel.form @@ -256,7 +256,7 @@ </component> </children> </grid> - <grid id="937be" layout-manager="GridLayoutManager" row-count="6" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> + <grid id="937be" layout-manager="GridLayoutManager" row-count="7" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> <margin top="0" left="0" bottom="0" right="0"/> <constraints> <grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="1" fill="3" indent="0" use-parent-layout="false"/> @@ -366,7 +366,9 @@ </component> <component id="eba02" class="javax.swing.JCheckBox" binding="myLeftLayoutCheckBox"> <constraints> - <grid row="5" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + <grid row="5" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"> + <preferred-size width="171" height="20"/> + </grid> </constraints> <properties> <text resource-bundle="messages/IdeBundle" key="checkbox.left.toolwindow.layout"/> @@ -374,12 +376,22 @@ </component> <component id="8ce51" class="javax.swing.JCheckBox" binding="myRightLayoutCheckBox"> <constraints> - <grid row="5" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + <grid row="5" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"> + <preferred-size width="177" height="20"/> + </grid> </constraints> <properties> <text resource-bundle="messages/IdeBundle" key="checkbox.right.toolwindow.layout"/> </properties> </component> + <component id="26420" class="javax.swing.JCheckBox" binding="myEditorTooltipCheckBox"> + <constraints> + <grid row="6" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/> + </constraints> + <properties> + <text resource-bundle="messages/IdeBundle" key="checkbox.show.editor.preview.popup"/> + </properties> + </component> </children> </grid> <grid id="e3cc0" layout-manager="GridLayoutManager" row-count="2" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/IntelliJLaf.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/IntelliJLaf.java new file mode 100644 index 000000000000..4cf39444d371 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/IntelliJLaf.java @@ -0,0 +1,40 @@ +/* + * Copyright 2000-2013 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.ide.ui.laf; + +import com.intellij.ide.ui.laf.darcula.DarculaLaf; + +import javax.swing.plaf.metal.DefaultMetalTheme; + +/** + * @author Konstantin Bulenkov + */ +public class IntelliJLaf extends DarculaLaf { + @Override + public String getName() { + return "IntelliJ"; + } + + @Override + protected String getPrefix() { + return "intellijlaf"; + } + + @Override + protected DefaultMetalTheme createMetalTheme() { + return new IdeaBlueMetalTheme(); + } +} diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/IntelliJLookAndFeelInfo.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/IntelliJLookAndFeelInfo.java new file mode 100644 index 000000000000..76ef1abfb7ac --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/IntelliJLookAndFeelInfo.java @@ -0,0 +1,37 @@ +/* + * Copyright 2000-2013 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.ide.ui.laf; + +import com.intellij.ide.IdeBundle; + +import javax.swing.*; + +/** + * @author Konstantin Bulenkov + */ +public class IntelliJLookAndFeelInfo extends UIManager.LookAndFeelInfo { + public IntelliJLookAndFeelInfo(){ + super(IdeBundle.message("idea.intellij.look.and.feel"), IntelliJLaf.class.getName()); + } + + public boolean equals(Object obj){ + return (obj instanceof IdeaLookAndFeelInfo); + } + + public int hashCode(){ + return getName().hashCode(); + } +}
\ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/LafManagerImpl.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/LafManagerImpl.java index 99d50c7deaa5..020313ba8f4a 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/LafManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/LafManagerImpl.java @@ -128,7 +128,11 @@ public final class LafManagerImpl extends LafManager implements ApplicationCompo lafList.add(new UIManager.LookAndFeelInfo("Default", UIManager.getSystemLookAndFeelClassName())); } else { - lafList.add(new IdeaLookAndFeelInfo()); + if (Registry.is("idea.4.5.laf.enabled")) { + lafList.add(new IdeaLookAndFeelInfo()); + } else { + lafList.add(new IntelliJLookAndFeelInfo()); + } for (UIManager.LookAndFeelInfo laf : UIManager.getInstalledLookAndFeels()) { String name = laf.getName(); if (!"Metal".equalsIgnoreCase(name) && !"CDE/Motif".equalsIgnoreCase(name)) { diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaLaf.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaLaf.java index 3dca2e53c272..a24bce894bbe 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaLaf.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaLaf.java @@ -33,6 +33,7 @@ import javax.swing.plaf.FontUIResource; import javax.swing.plaf.IconUIResource; import javax.swing.plaf.InsetsUIResource; import javax.swing.plaf.basic.BasicLookAndFeel; +import javax.swing.plaf.metal.DefaultMetalTheme; import javax.swing.plaf.metal.MetalLookAndFeel; import javax.swing.text.html.HTMLEditorKit; import javax.swing.text.html.StyleSheet; @@ -46,7 +47,7 @@ import java.util.Properties; /** * @author Konstantin Bulenkov */ -public final class DarculaLaf extends BasicLookAndFeel { +public class DarculaLaf extends BasicLookAndFeel { public static final String NAME = "Darcula"; BasicLookAndFeel base; @@ -105,7 +106,7 @@ public final class DarculaLaf extends BasicLookAndFeel { patchComboBox(metalDefaults, defaults); defaults.remove("Spinner.arrowButtonBorder"); defaults.put("Spinner.arrowButtonSize", new Dimension(16, 5)); - MetalLookAndFeel.setCurrentTheme(new DarculaMetalTheme()); + MetalLookAndFeel.setCurrentTheme(createMetalTheme()); if (SystemInfo.isWindows) { //JFrame.setDefaultLookAndFeelDecorated(true); } @@ -118,6 +119,10 @@ public final class DarculaLaf extends BasicLookAndFeel { return super.getDefaults(); } + protected DefaultMetalTheme createMetalTheme() { + return new DarculaMetalTheme(); + } + private static Font findFont(String name) { for (Font font : GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts()) { if (font.getName().equals(name)) { @@ -135,10 +140,10 @@ public final class DarculaLaf extends BasicLookAndFeel { } @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") - private static void patchStyledEditorKit() { + private void patchStyledEditorKit() { try { StyleSheet defaultStyles = new StyleSheet(); - InputStream is = DarculaLaf.class.getResourceAsStream("darcula.css"); + InputStream is = getClass().getResourceAsStream(getPrefix() + ".css"); Reader r = new BufferedReader(new InputStreamReader(is, "UTF-8")); defaultStyles.loadRules(r, null); r.close(); @@ -151,6 +156,10 @@ public final class DarculaLaf extends BasicLookAndFeel { } } + protected String getPrefix() { + return "darcula"; + } + private void call(String method) { try { final Method superMethod = BasicLookAndFeel.class.getDeclaredMethod(method); @@ -167,7 +176,7 @@ public final class DarculaLaf extends BasicLookAndFeel { } @SuppressWarnings({"HardCodedStringLiteral"}) - static void initIdeaDefaults(UIDefaults defaults) { + protected void initIdeaDefaults(UIDefaults defaults) { loadDefaults(defaults); defaults.put("Table.ancestorInputMap", new UIDefaults.LazyInputMap(new Object[] { "ctrl C", "copy", @@ -222,20 +231,20 @@ public final class DarculaLaf extends BasicLookAndFeel { } @SuppressWarnings("IOResourceOpenedButNotSafelyClosed") - private static void loadDefaults(UIDefaults defaults) { + protected void loadDefaults(UIDefaults defaults) { final Properties properties = new Properties(); final String osSuffix = SystemInfo.isMac ? "mac" : SystemInfo.isWindows ? "windows" : "linux"; try { - InputStream stream = DarculaLaf.class.getResourceAsStream("darcula.properties"); + InputStream stream = getClass().getResourceAsStream(getPrefix() + ".properties"); properties.load(stream); stream.close(); - stream = DarculaLaf.class.getResourceAsStream("darcula_" + osSuffix + ".properties"); + stream = getClass().getResourceAsStream(getPrefix() + "_" + osSuffix + ".properties"); properties.load(stream); stream.close(); HashMap<String, Object> darculaGlobalSettings = new HashMap<String, Object>(); - final String prefix = "darcula."; + final String prefix = getPrefix() + "."; for (String key : properties.stringPropertyNames()) { if (key.startsWith(prefix)) { darculaGlobalSettings.put(key.substring(prefix.length()), parseValue(key, properties.getProperty(key))); @@ -260,7 +269,7 @@ public final class DarculaLaf extends BasicLookAndFeel { catch (IOException e) {log(e);} } - private static Object parseValue(String key, @NotNull String value) { + protected Object parseValue(String key, @NotNull String value) { if (key.endsWith("Insets")) { final List<String> numbers = StringUtil.split(value, ","); return new InsetsUIResource(Integer.parseInt(numbers.get(0)), @@ -272,7 +281,7 @@ public final class DarculaLaf extends BasicLookAndFeel { return Class.forName(value).newInstance(); } catch (Exception e) {log(e);} } else { - final Color color = ColorUtil.fromHex(value, null); + final Color color = parseColor(value); final Integer invVal = getInteger(value); final Boolean boolVal = "true".equals(value) ? Boolean.TRUE : "false".equals(value) ? Boolean.FALSE : null; Icon icon = value.startsWith("AllIcons.") ? IconLoader.getIcon(value) : null; @@ -289,6 +298,21 @@ public final class DarculaLaf extends BasicLookAndFeel { return value; } + @SuppressWarnings("UseJBColor") + private static Color parseColor(String value) { + if (value != null && value.length() == 8) { + final Color color = ColorUtil.fromHex(value.substring(0, 6)); + if (color != null) { + try { + int alpha = Integer.parseInt(value.substring(6, 8), 16); + return new ColorUIResource(new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha)); + } catch (Exception ignore){} + } + return null; + } + return ColorUtil.fromHex(value, null); + } + private static Integer getInteger(String value) { try { return Integer.parseInt(value); diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaUIUtil.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaUIUtil.java index 68a12c48066c..924850c50287 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaUIUtil.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/DarculaUIUtil.java @@ -16,6 +16,7 @@ package com.intellij.ide.ui.laf.darcula; import com.intellij.ui.ColorUtil; +import com.intellij.ui.JBColor; import com.intellij.util.ui.MacUIUtil; import com.intellij.util.ui.UIUtil; @@ -25,24 +26,28 @@ import java.awt.*; * @author Konstantin Bulenkov */ public class DarculaUIUtil { - public static final Color GLOW_COLOR = new Color(96, 175, 255); + public static final Color GLOW_COLOR = new JBColor(new Color(96, 132, 212), new Color(96, 175, 255)); public static void paintFocusRing(Graphics g, int x, int y, int width, int height) { - MacUIUtil.paintFocusRing((Graphics2D)g, GLOW_COLOR, new Rectangle(x, y, width, height)); + MacUIUtil.paintFocusRing((Graphics2D)g, getGlow(), new Rectangle(x, y, width, height)); } public static void paintFocusOval(Graphics g, int x, int y, int width, int height) { - MacUIUtil.paintFocusRing((Graphics2D)g, GLOW_COLOR, new Rectangle(x, y, width, height), true); + MacUIUtil.paintFocusRing((Graphics2D)g, getGlow(), new Rectangle(x, y, width, height), true); + } + + private static Color getGlow() { + return new JBColor(new Color(35, 121, 212), new Color(96, 175, 255)); } public static void paintSearchFocusRing(Graphics2D g, Rectangle bounds) { int correction = UIUtil.isUnderDarcula() ? 50 : 0; final Color[] colors = new Color[]{ - ColorUtil.toAlpha(GLOW_COLOR, 180 - correction), - ColorUtil.toAlpha(GLOW_COLOR, 120 - correction), - ColorUtil.toAlpha(GLOW_COLOR, 70 - correction), - ColorUtil.toAlpha(GLOW_COLOR, 100 - correction), - ColorUtil.toAlpha(GLOW_COLOR, 50 - correction) + ColorUtil.toAlpha(getGlow(), 180 - correction), + ColorUtil.toAlpha(getGlow(), 120 - correction), + ColorUtil.toAlpha(getGlow(), 70 - correction), + ColorUtil.toAlpha(getGlow(), 100 - correction), + ColorUtil.toAlpha(getGlow(), 50 - correction) }; final Object oldAntialiasingValue = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING); diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/darcula.properties b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/darcula.properties index aaffeb7cc5b3..260805e419eb 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/darcula.properties +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/darcula.properties @@ -76,6 +76,21 @@ TextArea.background=45494A CheckBoxUI=com.intellij.ide.ui.laf.darcula.ui.DarculaCheckBoxUI CheckBox.border=com.intellij.ide.ui.laf.darcula.ui.DarculaCheckBoxBorder +CheckBox.darcula.inactiveFillColor=282828b4 +CheckBox.darcula.borderColor1=7878785a +CheckBox.darcula.borderColor2=78787869 +CheckBox.darcula.disabledBorderColor1=7878785a +CheckBox.darcula.disabledBorderColor2=78787869 +CheckBox.darcula.backgroundColor1=6e6e6e +CheckBox.darcula.backgroundColor2=5f5f5f +CheckBox.darcula.checkSignColor=aaaaaa +CheckBox.darcula.checkSignColorDisabled=787878 +CheckBox.darcula.shadowColor=1e1e1e +CheckBox.darcula.shadowColorDisabled=3c3c3c +CheckBox.darcula.focusedArmed.backgroundColor1=646464 +CheckBox.darcula.focusedArmed.backgroundColor2=373737 +CheckBox.darcula.focused.backgroundColor1=787878 +CheckBox.darcula.focused.backgroundColor2=4b4b4b ComboBoxUI=com.intellij.ide.ui.laf.darcula.ui.DarculaComboBoxUI ComboBox.disabledBackground=3c3f41 @@ -89,6 +104,10 @@ StatusBar.bottomColor=2c2c2c Button.border=com.intellij.ide.ui.laf.darcula.ui.DarculaButtonPainter ButtonUI=com.intellij.ide.ui.laf.darcula.ui.DarculaButtonUI +Button.darcula.color1=555a5c +Button.darcula.color2=414648 +Button.darcula.selection.color1=384f6b +Button.darcula.selection.color2=233143 MenuItem.acceleratorForeground=eeeeee PopupMenu.translucentBackground=3c3f41 diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaButtonUI.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaButtonUI.java index 06d2e49a909b..3fc3235e32e9 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaButtonUI.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaButtonUI.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -15,11 +15,15 @@ */ package com.intellij.ide.ui.laf.darcula.ui; +import com.intellij.openapi.ui.GraphicsConfig; +import com.intellij.util.ui.GraphicsUtil; import com.intellij.util.ui.UIUtil; +import sun.swing.SwingUtilities2; import javax.swing.*; import javax.swing.border.Border; import javax.swing.plaf.ComponentUI; +import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicButtonUI; import java.awt.*; @@ -35,17 +39,60 @@ public class DarculaButtonUI extends BasicButtonUI { @Override public void paint(Graphics g, JComponent c) { final Border border = c.getBorder(); + final GraphicsConfig config = GraphicsUtil.setupAAPainting(g); if (c.isEnabled() && border != null) { final Insets ins = border.getBorderInsets(c); final int yOff = (ins.top + ins.bottom) / 4; if (((JButton)c).isDefaultButton()) { - ((Graphics2D)g).setPaint(UIUtil.getGradientPaint(0, 0, new Color(0x384F6B), 0, c.getHeight(), new Color(0x233143))); + ((Graphics2D)g).setPaint(UIUtil.getGradientPaint(0, 0, getSelectedButtonColor1(), 0, c.getHeight(), getSelectedButtonColor2())); } else { - ((Graphics2D)g).setPaint(UIUtil.getGradientPaint(0, 0, new Color(85, 90, 92), 0, c.getHeight(), new Color(65, 70, 72))); + ((Graphics2D)g).setPaint(UIUtil.getGradientPaint(0, 0, getButtonColor1(), 0, c.getHeight(), getButtonColor2())); } g.fillRoundRect(4, yOff, c.getWidth() - 2 * 4, c.getHeight() - 2 * yOff, 5, 5); } + config.restore(); super.paint(g, c); } + + protected void paintText(Graphics g, JComponent c, Rectangle textRect, String text) { + AbstractButton button = (AbstractButton)c; + ButtonModel model = button.getModel(); + + if (model.isEnabled()) { + FontMetrics metrics = SwingUtilities2.getFontMetrics(c, g); + int mnemonicIndex = button.getDisplayedMnemonicIndex(); + + Color fg = button.getForeground(); + if (fg instanceof UIResource && button instanceof JButton && ((JButton)button).isDefaultButton()) { + final Color selectedFg = UIManager.getColor("Button.selectedButtonForeground"); + if (selectedFg != null) { + fg = selectedFg; + } + } + g.setColor(fg); + SwingUtilities2.drawStringUnderlineCharAt(c, g, text, mnemonicIndex, + textRect.x + getTextShiftOffset(), + textRect.y + metrics.getAscent() + getTextShiftOffset()); + } + else { + super.paintText(g, c, textRect, text); + } + } + + protected Color getButtonColor1() { + return UIManager.getColor("Button.darcula.color1"); + } + + protected Color getButtonColor2() { + return UIManager.getColor("Button.darcula.color2"); + } + + protected Color getSelectedButtonColor1() { + return UIManager.getColor("Button.darcula.selection.color1"); + } + + protected Color getSelectedButtonColor2() { + return UIManager.getColor("Button.darcula.selection.color2"); + } } diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaCheckBoxUI.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaCheckBoxUI.java index 94899dbe40c4..b1b1d582df4c 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaCheckBoxUI.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaCheckBoxUI.java @@ -98,28 +98,28 @@ public class DarculaCheckBoxUI extends MetalCheckBoxUI { final boolean armed = b.getModel().isArmed(); if (c.hasFocus()) { - g.setPaint(UIUtil.getGradientPaint(w/2, 1, armed ? Gray._100: Gray._120, w/2, h, armed ? Gray._55 : Gray._75)); + g.setPaint(UIUtil.getGradientPaint(w/2, 1, getFocusedBackgroundColor1(armed), w/2, h, getFocusedBackgroundColor2(armed))); g.fillRoundRect(0, 0, w - 2, h - 2, 4, 4); DarculaUIUtil.paintFocusRing(g, 1, 1, w - 2, h - 2); } else { - g.setPaint(UIUtil.getGradientPaint(w / 2, 1, Gray._110, w / 2, h, Gray._95)); - g.fillRoundRect(0, 0, w , h , 4, 4); + g.setPaint(UIUtil.getGradientPaint(w / 2, 1, getBackgroundColor1(), w / 2, h, getBackgroundColor2())); + g.fillRoundRect(0, 0, w, h - 1 , 4, 4); - g.setPaint(UIUtil.getGradientPaint(w / 2, 1, Gray._120.withAlpha(90), w / 2, h, Gray._105.withAlpha(90))); - g.drawRoundRect(0, 1, w, h - 1, 4, 4); + g.setPaint(UIUtil.getGradientPaint(w / 2, 1, getBorderColor1(b.isEnabled()), w / 2, h, getBorderColor2(b.isEnabled()))); + g.drawRoundRect(0, (UIUtil.isUnderDarcula() ? 1 : 0), w, h - 1, 4, 4); - g.setPaint(Gray._40.withAlpha(180)); + g.setPaint(getInactiveFillColor()); g.drawRoundRect(0, 0, w, h - 1, 4, 4); } if (b.getModel().isSelected()) { g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); g.setStroke(new BasicStroke(1 *2.0f, BasicStroke.CAP_ROUND,BasicStroke.JOIN_ROUND)); - g.setPaint(b.isEnabled() ? Gray._30 : Gray._60); + g.setPaint(getShadowColor(b.isEnabled())); g.drawLine(4, 7, 7, 11); g.drawLine(7, 11, w, 2); - g.setPaint(b.isEnabled() ? Gray._170 : Gray._120); + g.setPaint(getCheckSignColor(b.isEnabled())); g.drawLine(4, 5, 7, 9); g.drawLine(7, 9, w, 0); } @@ -142,6 +142,53 @@ public class DarculaCheckBoxUI extends MetalCheckBoxUI { } } + protected Color getInactiveFillColor() { + return getColor("inactiveFillColor", Gray._40.withAlpha(180)); + } + + protected Color getBorderColor1(boolean enabled) { + return enabled ? getColor("borderColor1", Gray._120.withAlpha(0x5a)) + : getColor("disabledBorderColor1", Gray._120.withAlpha(90)); + } + + protected Color getBorderColor2(boolean enabled) { + return enabled ? getColor("borderColor2", Gray._105.withAlpha(90)) + : getColor("disabledBorderColor2", Gray._105.withAlpha(90)); + } + + protected Color getBackgroundColor1() { + return getColor("backgroundColor1", Gray._110); + } + + protected Color getBackgroundColor2() { + return getColor("backgroundColor2", Gray._95); + } + + protected Color getCheckSignColor(boolean enabled) { + return enabled ? getColor("checkSignColor", Gray._170) + : getColor("checkSignColorDisabled", Gray._120); + } + + protected Color getShadowColor(boolean enabled) { + return enabled ? getColor("shadowColor", Gray._30) + : getColor("shadowColorDisabled", Gray._60); + } + + protected Color getFocusedBackgroundColor1(boolean armed) { + return armed ? getColor("focusedArmed.backgroundColor1", Gray._100) + : getColor("focused.backgroundColor1", Gray._120); + } + + protected Color getFocusedBackgroundColor2(boolean armed) { + return armed ? getColor("focusedArmed.backgroundColor2", Gray._55) + : getColor("focused.backgroundColor2", Gray._75); + } + + protected static Color getColor(String shortPropertyName, Color defaultValue) { + final Color color = UIManager.getColor("CheckBox.darcula." + shortPropertyName); + return color == null ? defaultValue : color; + } + @Override public Icon getDefaultIcon() { return new IconUIResource(EmptyIcon.create(20)); diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextFieldUI.java b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextFieldUI.java index f1e01d7762a8..967a24ab56f2 100644 --- a/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextFieldUI.java +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/darcula/ui/DarculaTextFieldUI.java @@ -140,8 +140,9 @@ public class DarculaTextFieldUI extends BasicTextFieldUI { Graphics2D g = (Graphics2D)graphics; final JTextComponent c = getComponent(); final Container parent = c.getParent(); - if (parent != null) { - g.setColor(parent.getBackground()); + final Rectangle r = getDrawingRect(); + if (!isSearchField(c) && parent != null) { + g.setColor(c.getBackground()); g.fillRect(0, 0, c.getWidth(), c.getHeight()); } final GraphicsConfig config = new GraphicsConfig(g); @@ -151,12 +152,12 @@ public class DarculaTextFieldUI extends BasicTextFieldUI { final Border border = c.getBorder(); if (isSearchField(c)) { g.setColor(c.getBackground()); - final Rectangle r = getDrawingRect(); + int radius = r.height-1; g.fillRoundRect(r.x, r.y, r.width, r.height-1, radius, radius); g.setColor(c.isEnabled() ? Gray._100 : new Color(0x535353)); - if (c.hasFocus()) { - DarculaUIUtil.paintSearchFocusRing(g, r); + if (c.hasFocus() && c.getClientProperty("JTextField.Search.noFocusRing") != Boolean.TRUE) { + DarculaUIUtil.paintSearchFocusRing(g, r); } else { g.drawRoundRect(r.x, r.y, r.width, r.height-1, radius, radius); } diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellijlaf.css b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellijlaf.css new file mode 100644 index 000000000000..1b9e1f0bba76 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellijlaf.css @@ -0,0 +1,383 @@ +/* + * Copyright 2000-2013 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. + */ +/* + * Copyright 2000-2013 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. + */ +body { + font-size: 14pt; + font-family: Serif; + font-weight: normal; + margin-left: 0; + margin-right: 0; + color: #bbbbbb +} + +p { + margin-top: 15 +} + +h1 { + font-size: x-large; + font-weight: bold; + margin-top: 10; + margin-bottom: 10 +} + +h2 { + font-size: large; + font-weight: bold; + margin-top: 10; + margin-bottom: 10 +} + +h3 { + font-size: medium; + font-weight: bold; + margin-top: 10; + margin-bottom: 10 +} + +h4 { + font-size: small; + font-weight: bold; + margin-top: 10; + margin-bottom: 10 +} + +h5 { + font-size: x-small; + font-weight: bold; + margin-top: 10; + margin-bottom: 10 +} + +h6 { + font-size: xx-small; + font-weight: bold; + margin-top: 10; + margin-bottom: 10 +} + +li p { + margin-top: 0; + margin-bottom: 0 +} + +td p { + margin-top: 0 +} + +menu li p { + margin-top: 0; + margin-bottom: 0 +} + +menu li { + margin-left: 0; + margin-right: 0; + margin-top: 0; + margin-bottom: 0 +} + +menu { + margin-left-ltr: 40; + margin-right-rtl: 40; + margin-top: 10; + margin-bottom: 10 +} + +dir li p { + margin-top: 0; + margin-bottom: 0 +} + +dir li { + margin-left: 0; + margin-right: 0; + margin-top: 0; + margin-bottom: 0 +} + +dir { + margin-left-ltr: 40; + margin-right-rtl: 40; + margin-top: 10; + margin-bottom: 10 +} + +dd { + margin-left-ltr: 40; + margin-right-rtl: 40; + margin-top: 0; + margin-bottom: 0 +} + +dd p { + margin-left: 0; + margin-rigth: 0; + margin-top: 0; + margin-bottom: 0 +} + +dt { + margin-top: 0; + margin-bottom: 0 +} + +dl { + margin-left: 0; + margin-top: 10; + margin-bottom: 10 +} + +ol li { + margin-left: 0; + margin-right: 0; + margin-top: 0; + margin-bottom: 0 +} + +ol { + margin-top: 10; + margin-bottom: 10; + margin-left-ltr: 50; + margin-right-rtl: 50; + list-style-type: decimal +} + +ol li p { + margin-top: 0; + margin-bottom: 0 +} + +ul li { + margin-left: 0; + margin-right: 0; + margin-top: 0; + margin-bottom: 0 +} + +ul { + margin-top: 10; + margin-bottom: 10; + margin-left-ltr: 50; + margin-right-rtl: 50; + list-style-type: disc; + -bullet-gap: 10 +} + +ul li ul li { + margin-left: 0; + margin-right: 0; + margin-top: 0; + margin-bottom: 0 +} + +ul li ul { + list-style-type: circle; + margin-left-ltr: 25; + margin-right-rtl: 25; +} + +ul li ul li ul li { + margin-left: 0; + margin-right: 0; + margin-top: 0; + margin-bottom: 0 +} + +ul li ul li ul { + list-style-type: square; + margin-left-ltr: 25; + margin-right-rtl: 25 +} + +ul li menu { + list-style-type: circle; + margin-left-ltr: 25; + margin-right-rtl: 25; +} + +ul li p { + margin-top: 0; + margin-bottom: 0 +} + +a { + color: #589df6; + text-decoration: underline +} + +address { + color: #589df6; + font-style: italic +} + +big { + font-size: x-large +} + +small { + font-size: x-small +} + +samp { + font-size: small; + font-family: Monospaced +} + +cite { + font-style: italic +} + +code { + font-size: small; + font-family: Monospaced +} + +dfn { + font-style: italic +} + +em { + font-style: italic +} + +i { + font-style: italic +} + +b { + font-weight: bold +} + +kbd { + font-size: small; + font-family: Monospaced +} + +s { + text-decoration: line-through +} + +strike { + text-decoration: line-through +} + +strong { + font-weight: bold +} + +sub { + vertical-align: sub +} + +sup { + vertical-align: sup +} + +tt { + font-family: Monospaced +} + +u { + text-decoration: underline +} + +var { + font-weight: bold; + font-style: italic +} + +table { + border-color: Gray; + border-style: outset +} + +tr { + text-align: left +} + +td { + border-color: Gray; + border-style: inset; + padding-left: 3; + padding-right: 3; + padding-top: 3; + padding-bottom: 3 +} + +th { + text-align: center; + font-weight: bold; + border-color: Gray; + border-style: inset; + padding-left: 3; + padding-right: 3; + padding-top: 3; + padding-bottom: 3 +} + +blockquote { + margin-top: 5; + margin-bottom: 5; + margin-left: 35; + margin-right: 35 +} + +center { + text-align: center +} + +pre { + margin-top: 5; + margin-bottom: 5; + font-family: Monospaced +} + +pre p { + margin-top: 0 +} + +caption { + caption-side: top; + text-align: center +} + +table { + border: none; +} + +td { + border: none; +} + +nobr { + white-space: nowrap +} + diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellijlaf.properties b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellijlaf.properties new file mode 100644 index 000000000000..255eb042fa0b --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellijlaf.properties @@ -0,0 +1,144 @@ +# suppress inspection "UnusedProperty" for whole file +intellijlaf.background=e8e8e8 +intellijlaf.selectionBackground=4696ff +intellijlaf.textBackground=cccccc +intellijlaf.foreground=000000 +intellijlaf.textForeground=000000 +intellijlaf.caretForeground=000000 +intellijlaf.inactiveBackground=ffffff +intellijlaf.inactiveForeground=000000 +intellijlaf.selectionForeground=ffffff +intellijlaf.selectionBackgroundInactive=27384C +intellijlaf.selectionInactiveBackground=27384C +intellijlaf.selectionForegroundInactive=000000 +window=e8e8e8 + +text=000000 +textText=000000 +infoText=000000 +OptionPane.messageForeground=000000 + +Menu.maxGutterIconWidth=18 +MenuItem.maxGutterIconWidth=18 +MenuItem.acceleratorDelimiter=- +MenuItem.border=com.intellij.ide.ui.laf.darcula.ui.DarculaMenuItemBorder +Menu.border=com.intellij.ide.ui.laf.darcula.ui.DarculaMenuItemBorder + +EditorPaneUI=com.intellij.ide.ui.laf.darcula.ui.DarculaEditorPaneUI + +control=cccccc +link.foreground=589df6 +ScrollBarUI=com.intellij.ide.ui.laf.darcula.ui.DarculaScrollBarUI + +TableHeaderUI=com.intellij.ide.ui.laf.darcula.DarculaTableHeaderUI +Table.gridColor=2c2c2c +Table.ascendingSortIcon=AllIcons.General.SplitUp +Table.descendingSortIcon=AllIcons.General.SplitDown +Table.background=ffffff + +#just to suppress border painters +TableHeader.cellBorder=com.intellij.ide.ui.laf.darcula.DarculaTableHeaderBorder + +TitledBorder.titleColor=000000 + +MenuBar.disabledBackground=cccccc +MenuBar.shadow=cccccc + +TabbedPaneUI=com.intellij.ide.ui.laf.darcula.ui.DarculaTabbedPaneUI +TabbedPane.tabInsets=0,4,0,4 +TabbedPane.highlight=292b2d +TabbedPane.light=444444 +TabbedPane.selected=626262 +TabbedPane.selectHighlight=3c3f41 +TabbedPane.contentBorderInsets=3,1,1,1 +TabbedPane.darkShadow=292b2d +TabbedPane.shadow=3c3f41 + +Separator.foreground=2d2d2d + +Focus.color=ff0000 + +TextField.background=ffffff +TextFieldUI=com.intellij.ide.ui.laf.darcula.ui.DarculaTextFieldUI +TextField.border=com.intellij.ide.ui.laf.darcula.ui.DarculaTextBorder + +TextArea.selectionForeground=bbbbbb +TextArea.background=ffffff + +Panel.background=e8e8e8 + +PasswordField.background=45494A +PasswordFieldUI=com.intellij.ide.ui.laf.darcula.ui.DarculaPasswordFieldUI +PasswordField.border=com.intellij.ide.ui.laf.darcula.ui.DarculaTextBorder + +ProgressBarUI=com.intellij.ide.ui.laf.darcula.ui.DarculaProgressBarUI +ProgressBar.border=com.intellij.ide.ui.laf.darcula.ui.DarculaProgressBarBorder +ProgressBar.foreground=808080 + +FormattedTextField.background=ffffff + + +CheckBoxUI=com.intellij.ide.ui.laf.darcula.ui.DarculaCheckBoxUI +CheckBox.border=com.intellij.ide.ui.laf.darcula.ui.DarculaCheckBoxBorder +CheckBox.darcula.inactiveFillColor=00000000 +CheckBox.darcula.borderColor1=111111 +CheckBox.darcula.borderColor2=111111 +CheckBox.darcula.disabledBorderColor1=999999 +CheckBox.darcula.disabledBorderColor2=999999 +CheckBox.darcula.backgroundColor1=ffffff +CheckBox.darcula.backgroundColor2=ffffff +CheckBox.darcula.checkSignColor=111111 +CheckBox.darcula.checkSignColorDisabled=999999 +CheckBox.darcula.shadowColor=55555530 +CheckBox.darcula.shadowColorDisabled=eeeeee +CheckBox.darcula.focusedArmed.backgroundColor1=ffffff +CheckBox.darcula.focusedArmed.backgroundColor2=ffffff +CheckBox.darcula.focused.backgroundColor1=eeeeee +CheckBox.darcula.focused.backgroundColor2=eeeeee + +ComboBoxUI=com.intellij.ide.ui.laf.darcula.ui.DarculaComboBoxUI +ComboBox.disabledBackground=3c3f41 +ComboBox.squareButton=false + +RadioButtonUI=com.intellij.ide.ui.laf.darcula.ui.DarculaRadioButtonUI + +StatusBar.topColor=2c2c2c +StatusBar.top2Color=2c2c2c +StatusBar.bottomColor=2c2c2c + +Button.border=com.intellij.ide.ui.laf.darcula.ui.DarculaButtonPainter +ButtonUI=com.intellij.ide.ui.laf.darcula.ui.DarculaButtonUI +Button.darcula.color1=eeeeee +Button.darcula.color2=c0c0c0 +Button.darcula.selection.color1=3a74d1 +Button.darcula.selection.color2=3a6bc6 +Button.selectedButtonForeground=ffffff + +MenuItem.acceleratorForeground=eeeeee +PopupMenu.translucentBackground=3c3f41 + +ToolTip.background=5C5C42 + +SpinnerUI=com.intellij.ide.ui.laf.darcula.ui.DarculaSpinnerUI +Spinner.border=com.intellij.ide.ui.laf.darcula.ui.DarculaSpinnerBorder +Spinner.background=3c3f41 +Spinner.arrowButtonInsets=1,1,1,1 +Spinner.editorBorderPainted=false + +SplitPane.highlight=3c3f41 + +TreeUI=com.intellij.ide.ui.laf.darcula.ui.DarculaTreeUI +Tree.background=ffffff + +List.background=ffffff + +Hyperlink.linkColor=589df6 + +#List.background=45494A +#Table.background=45494A + +#Tree.background=45494A +Tree.collapsedIcon=AllIcons.Nodes.TreeRightArrow +Tree.expandedIcon=AllIcons.Nodes.TreeDownArrow + +FileView.fileIcon=AllIcons.FileTypes.Unknown
\ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellijlaf_linux.properties b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellijlaf_linux.properties new file mode 100644 index 000000000000..85d48e5b07ab --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellijlaf_linux.properties @@ -0,0 +1,7 @@ +# suppress inspection "UnusedProperty" for whole file +darcula.selectionBackground=2F65CA +darcula.selectionForeground=bbbbbb + +PopupMenu.border=com.intellij.ide.ui.laf.darcula.ui.DarculaPopupMenuBorder + +MenuBar.border=com.intellij.ide.ui.laf.darcula.ui.DarculaMenuBarBorder diff --git a/platform/platform-impl/src/com/intellij/ide/ui/laf/intellijlaf_windows.properties b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellijlaf_windows.properties new file mode 100644 index 000000000000..50fd798d35cc --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/ui/laf/intellijlaf_windows.properties @@ -0,0 +1,10 @@ +# suppress inspection "UnusedProperty" for whole file +darcula.selectionBackground=4A6EB7 +darcula.selectionForeground=ffffff + +PopupMenu.border=com.intellij.ide.ui.laf.darcula.ui.DarculaPopupMenuBorder + +MenuBar.border=com.intellij.ide.ui.laf.darcula.ui.DarculaMenuBarBorder +#InternalFrameUI=com.intellij.ide.ui.laf.darcula.ui.DarculaInternalFrameUI +#InternalFrame.border=com.intellij.ide.ui.laf.darcula.ui.DarculaInternalBorder +#RootPaneUI=com.intellij.ide.ui.laf.darcula.ui.DarculaRootPaneUI
\ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/idea/StartupUtil.java b/platform/platform-impl/src/com/intellij/idea/StartupUtil.java index 31dd1d938891..167f16b6799e 100644 --- a/platform/platform-impl/src/com/intellij/idea/StartupUtil.java +++ b/platform/platform-impl/src/com/intellij/idea/StartupUtil.java @@ -103,8 +103,8 @@ public class StartupUtil { Logger.setFactory(LoggerFactory.class); Logger log = Logger.getInstance(Main.class); startLogging(log); - fixProcessEnvironment(log); loadSystemLibraries(log); + fixProcessEnvironment(log); if (!Main.isHeadless()) { AppUIUtil.updateWindowIcon(JOptionPane.getRootFrame()); diff --git a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionToolbarImpl.java b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionToolbarImpl.java index 92e20275a0f8..5a2f15613327 100644 --- a/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionToolbarImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/actionSystem/impl/ActionToolbarImpl.java @@ -304,7 +304,8 @@ public class ActionToolbarImpl extends JPanel implements ActionToolbar { final AnAction searchEverywhereAction = ActionManager.getInstance().getAction("SearchEverywhere"); if (searchEverywhereAction != null) { try { - final JComponent searchEverywhere = getCustomComponent(searchEverywhereAction.getClass().newInstance()); + final CustomComponentAction searchEveryWhereAction = (CustomComponentAction)searchEverywhereAction.getClass().newInstance(); + final JComponent searchEverywhere = searchEveryWhereAction.createCustomComponent(searchEverywhereAction.getTemplatePresentation()); searchEverywhere.putClientProperty("SEARCH_EVERYWHERE", Boolean.TRUE); add(searchEverywhere); } @@ -724,7 +725,11 @@ public class ActionToolbarImpl extends JPanel implements ActionToolbar { final Component component = getComponent(getComponentCount() - 1); if (component instanceof JComponent && ((JComponent)component).getClientProperty("SEARCH_EVERYWHERE") == Boolean.TRUE) { final Rectangle rect = bounds.get(bounds.size() - 1); - bounds.set(bounds.size() - 1, new Rectangle(size2Fit.width - rect.width - 5, rect.y, rect.width, rect.height)); + int max = 0; + for (int i = 0; i < bounds.size() - 2; i++) { + max = Math.max(max, bounds.get(i).height); + } + bounds.set(bounds.size() - 1, new Rectangle(size2Fit.width - 25, 0, 25, max)); } } } diff --git a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationInfoImpl.java b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationInfoImpl.java index dd2490fd916c..14346fe66407 100755 --- a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationInfoImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationInfoImpl.java @@ -49,6 +49,7 @@ public class ApplicationInfoImpl extends ApplicationInfoEx implements JDOMExtern private String myMajorVersion = null; private String myMinorVersion = null; private String myBuildNumber = null; + private String myApiVersion = null; private String myCompanyName = "JetBrains s.r.o."; private String myCompanyUrl = "http://www.jetbrains.com/"; private Color myProgressColor = null; @@ -110,6 +111,7 @@ public class ApplicationInfoImpl extends ApplicationInfoEx implements JDOMExtern @NonNls private static final String ELEMENT_BUILD = "build"; @NonNls private static final String ELEMENT_COMPANY = "company"; @NonNls private static final String ATTRIBUTE_NUMBER = "number"; + @NonNls private static final String ATTRIBUTE_API_VERSION = "apiVersion"; @NonNls private static final String ATTRIBUTE_DATE = "date"; @NonNls private static final String ATTRIBUTE_MAJOR_RELEASE_DATE = "majorReleaseDate"; @NonNls private static final String ELEMENT_LOGO = "logo"; @@ -184,6 +186,10 @@ public class ApplicationInfoImpl extends ApplicationInfoEx implements JDOMExtern @Override public BuildNumber getBuild() { + return BuildNumber.fromString(myBuildNumber, getProductPrefix()); + } + + private static String getProductPrefix() { String prefix = null; if (PlatformUtils.isCommunity()) { prefix = "IC"; @@ -191,7 +197,15 @@ public class ApplicationInfoImpl extends ApplicationInfoEx implements JDOMExtern else if (PlatformUtils.isIdea()) { prefix = "IU"; } - return BuildNumber.fromString(myBuildNumber, prefix); + return prefix; + } + + @Override + public String getApiVersion() { + if (myApiVersion != null) { + return BuildNumber.fromString(myApiVersion, getProductPrefix()).asString(); + } + return getBuild().asString(); } public String getMajorVersion() { @@ -465,7 +479,8 @@ public class ApplicationInfoImpl extends ApplicationInfoEx implements JDOMExtern Element buildElement = parentNode.getChild(ELEMENT_BUILD); if (buildElement != null) { myBuildNumber = buildElement.getAttributeValue(ATTRIBUTE_NUMBER); - PluginManagerCore.BUILD_NUMBER = myBuildNumber; + myApiVersion = buildElement.getAttributeValue(ATTRIBUTE_API_VERSION); + PluginManagerCore.BUILD_NUMBER = myApiVersion != null ? myApiVersion : myBuildNumber; String dateString = buildElement.getAttributeValue(ATTRIBUTE_DATE); if (dateString.equals("__BUILD_DATE__")) { myBuildDate = new GregorianCalendar(); diff --git a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/DefaultProjectStoreImpl.java b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/DefaultProjectStoreImpl.java index b75a716c9f24..8685b846b0a3 100644 --- a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/DefaultProjectStoreImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/DefaultProjectStoreImpl.java @@ -16,11 +16,11 @@ package com.intellij.openapi.components.impl.stores; import com.intellij.openapi.components.*; +import com.intellij.openapi.options.StreamProvider; import com.intellij.openapi.project.impl.ProjectImpl; import com.intellij.openapi.project.impl.ProjectManagerImpl; import com.intellij.openapi.util.Pair; import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.options.StreamProvider; import com.intellij.util.io.fs.IFile; import org.jdom.Document; import org.jdom.Element; @@ -133,7 +133,7 @@ public class DefaultProjectStoreImpl extends ProjectStoreImpl { @Override @Nullable - public StateStorage getFileStateStorage(String fileName) { + public StateStorage getFileStateStorage(@NotNull String fileSpec) { return storage; } diff --git a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/ProjectStateStorageManager.java b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/ProjectStateStorageManager.java index 734b242afcae..f6a4e11e5aa5 100644 --- a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/ProjectStateStorageManager.java +++ b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/ProjectStateStorageManager.java @@ -32,6 +32,7 @@ class ProjectStateStorageManager extends StateStorageManagerImpl { myProject = project; } + @Override protected StorageData createStorageData(String storageSpec) { if (storageSpec.equals(StoragePathMacros.PROJECT_FILE)) return createIprStorageData(); if (storageSpec.equals(StoragePathMacros.WORKSPACE_FILE)) return createWsStorageData(); @@ -46,6 +47,7 @@ class ProjectStateStorageManager extends StateStorageManagerImpl { return new ProjectStoreImpl.IprStorageData(ROOT_TAG_NAME, myProject); } + @Override protected String getOldStorageSpec(Object component, final String componentName, final StateStorageOperation operation) throws StateStorageException { final ComponentConfig config = myProject.getConfig(component.getClass()); @@ -65,6 +67,7 @@ class ProjectStateStorageManager extends StateStorageManagerImpl { return name; } + @Override protected String getVersionsFilePath() { return PathManager.getConfigPath() + "/componentVersions/" + "project" + myProject.getLocationHash() + ".xml"; } diff --git a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/StateStorageManager.java b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/StateStorageManager.java index 499eda682b02..0e2828e988a3 100644 --- a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/StateStorageManager.java +++ b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/StateStorageManager.java @@ -37,10 +37,10 @@ public interface StateStorageManager { TrackingPathMacroSubstitutor getMacroSubstitutor(); @Nullable - StateStorage getStateStorage(@NotNull Storage storageSpec) throws StateStorageException; + StateStorage getStateStorage(@NotNull Storage storageSpec) throws StateStorageException; @Nullable - StateStorage getFileStateStorage(String fileName); + StateStorage getFileStateStorage(@NotNull String fileSpec); Collection<String> getStorageFileNames(); @@ -48,8 +48,10 @@ public interface StateStorageManager { @NotNull ExternalizationSession startExternalization(); + @NotNull - SaveSession startSave(@NotNull ExternalizationSession externalizationSession) ; + SaveSession startSave(@NotNull ExternalizationSession externalizationSession); + void finishSave(@NotNull SaveSession saveSession); @Nullable diff --git a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/StateStorageManagerImpl.java b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/StateStorageManagerImpl.java index 25c940a36d45..c7c4aec145d0 100644 --- a/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/StateStorageManagerImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/components/impl/stores/StateStorageManagerImpl.java @@ -93,7 +93,6 @@ public abstract class StateStorageManagerImpl implements StateStorageManager, Di } @Override - @SuppressWarnings({"unchecked"}) public TrackingPathMacroSubstitutor getMacroSubstitutor() { return myPathMacroSubstitutor; } @@ -125,7 +124,7 @@ public abstract class StateStorageManagerImpl implements StateStorageManager, Di @Override @Nullable - public StateStorage getFileStateStorage(final String fileName) { + public StateStorage getFileStateStorage(@NotNull String fileName) { myStorageLock.lock(); try { StateStorage stateStorage = myStorages.get(fileName); @@ -238,7 +237,7 @@ public abstract class StateStorageManagerImpl implements StateStorageManager, Di } String extension = FileUtilRt.getExtension(new File(expandedFile).getName()); - if (!ourHeadlessEnvironment && extension.length() == 0) { + if (!ourHeadlessEnvironment && extension.isEmpty()) { throw new IllegalArgumentException("Extension is missing for storage file: " + expandedFile); } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/ContextMenuImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/ContextMenuImpl.java index 6674322c21e9..28a7c2204265 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/ContextMenuImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/ContextMenuImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -28,7 +28,6 @@ import com.intellij.openapi.editor.event.EditorMouseEvent; import com.intellij.openapi.editor.event.EditorMouseMotionAdapter; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.keymap.ex.KeymapManagerEx; -import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.impl.http.HttpVirtualFile; import com.intellij.util.ui.UIUtil; @@ -208,7 +207,7 @@ public class ContextMenuImpl extends JPanel implements Disposable { } final VirtualFile file = FileDocumentManager.getInstance().getFile(document); - return file != null && file.isValid() && (file.getFileSystem() == LocalFileSystem.getInstance() || file instanceof HttpVirtualFile); + return file != null && file.isValid() && (file.isInLocalFileSystem() || file instanceof HttpVirtualFile); } private void scheduleHide() { diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java index 55dc5d16c58b..45831364ccfb 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java @@ -1907,6 +1907,8 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi LogicalPosition clipEndPosition = xyToLogicalPosition(new Point(0, clip.y + clip.height + getLineHeight())); int clipEndOffset = logicalPositionToOffset(clipEndPosition); paintBackgrounds(g, clip, clipStartPosition, clipStartVisualPos, clipStartOffset, clipEndOffset); + if (paintPlaceholderText(g, clip)) return; + paintRectangularSelection(g); paintRightMargin(g, clip); paintCustomRenderers(g, clipStartOffset, clipEndOffset); @@ -1914,7 +1916,6 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi paintLineMarkersSeparators(g, clip, docMarkup, clipStartOffset, clipEndOffset); paintLineMarkersSeparators(g, clip, myMarkupModel, clipStartOffset, clipEndOffset); paintText(g, clip, clipStartPosition, clipStartOffset, clipEndOffset); - paintPlaceholderText(g, clip); paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip, clipStartOffset, clipEndOffset, docMarkup); BorderEffect borderEffect = new BorderEffect(this, g, clipStartOffset, clipEndOffset); borderEffect.paintHighlighters(getHighlighter()); @@ -2775,10 +2776,10 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi flushCachedChars(g); } - private void paintPlaceholderText(@NotNull Graphics g, @NotNull Rectangle clip) { + private boolean paintPlaceholderText(@NotNull Graphics g, @NotNull Rectangle clip) { CharSequence hintText = myPlaceholderText; if (myDocument.getTextLength() > 0 || hintText == null || hintText.length() == 0) { - return; + return false; } if (KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() == myEditorComponent) { @@ -2788,6 +2789,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi myLastBackgroundPosition = new Point(0, 0); myLastBackgroundWidth = myLastPaintedPlaceholderWidth; flushBackground(g, clip); + return false; } else { myLastPaintedPlaceholderWidth = drawString( @@ -2795,6 +2797,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi myFoldingModel.getPlaceholderAttributes().getForegroundColor() ); flushCachedChars(g); + return true; } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java index 174324569212..9a0b1a48bca9 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorMarkupModelImpl.java @@ -27,6 +27,7 @@ package com.intellij.openapi.editor.impl; import com.intellij.codeInsight.hint.*; import com.intellij.icons.AllIcons; import com.intellij.ide.ui.UISettings; +import com.intellij.ide.ui.UISettingsListener; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ex.ApplicationManagerEx; @@ -38,13 +39,16 @@ import com.intellij.openapi.editor.actionSystem.DocCommandGroupId; import com.intellij.openapi.editor.ex.*; import com.intellij.openapi.editor.markup.ErrorStripeRenderer; import com.intellij.openapi.editor.markup.RangeHighlighter; +import com.intellij.openapi.fileEditor.impl.EditorWindowHolder; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.popup.Balloon; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.ProperTextRange; -import com.intellij.openapi.wm.impl.IdeRootPane; +import com.intellij.openapi.util.registry.Registry; import com.intellij.ui.*; import com.intellij.ui.awt.RelativePoint; +import com.intellij.util.Alarm; +import com.intellij.util.IJSwingUtilities; import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.ui.ButtonlessScrollBarUI; @@ -58,14 +62,15 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import javax.swing.plaf.ScrollBarUI; import java.awt.*; -import java.awt.event.MouseEvent; -import java.awt.event.MouseListener; -import java.awt.event.MouseMotionListener; +import java.awt.event.*; import java.awt.geom.AffineTransform; +import java.awt.geom.Area; +import java.awt.geom.Ellipse2D; import java.awt.image.BufferedImage; import java.util.*; import java.util.List; import java.util.Queue; +import java.util.concurrent.atomic.AtomicReference; public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMarkupModel { private static final TooltipGroup ERROR_STRIPE_TOOLTIP_GROUP = new TooltipGroup("ERROR_STRIPE_TOOLTIP_GROUP", 0); @@ -89,8 +94,10 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark private int myMinMarkHeight = 3; private static final int myPreviewLines = 5;// Actually preview has myPreviewLines * 2 + 1 lines (above + below + current one) + private static final int myCachePreviewLines = 100;// Actually cache image has myCachePreviewLines * 2 + 1 lines (above + below + current one) private LightweightHint myEditorPreviewHint = null; private final EditorFragmentRenderer myEditorFragmentRenderer; + private int myRowAdjuster = 0; EditorMarkupModelImpl(@NotNull EditorImpl editor) { super(editor.getDocument()); @@ -157,7 +164,7 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark boolean isVisible = area.contains(area.x, realY);//area.y < realY && area.y + area.height > realY; TooltipRenderer bigRenderer; - if (!(UIUtil.getRootPane(myEditor.getComponent()) instanceof IdeRootPane) || isVisible) { + if (IJSwingUtilities.findParentByInterface(myEditor.getComponent(), EditorWindowHolder.class) == null || isVisible || !UISettings.getInstance().SHOW_EDITOR_TOOLTIP) { final Set<RangeHighlighter> highlighters = new THashSet<RangeHighlighter>(); getNearestHighlighters(this, me.getY(), highlighters); getNearestHighlighters((MarkupModelEx)DocumentMarkupModel.forDocument(myEditor.getDocument(), getEditor().getProject(), true), me.getY(), highlighters); @@ -181,6 +188,8 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark } return false; } else { + if (e.getX() > e.getComponent().getWidth() - 2) return false;//The most right edge is not active for lens + me = new MouseEvent(me.getComponent(), me.getID(), me.getWhen(), me.getModifiers(), me.getX(), me.getY() + myRowAdjuster, me.getClickCount(), me.isPopupTrigger()); final List<RangeHighlighterEx> highlighters = new ArrayList<RangeHighlighterEx>(); collectRangeHighlighters(this, line, highlighters); collectRangeHighlighters((MarkupModelEx)DocumentMarkupModel.forDocument(myEditor.getDocument(), getEditor().getProject(), true), line, @@ -446,7 +455,7 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark } } - private class MyErrorPanel extends ButtonlessScrollBarUI implements MouseMotionListener, MouseListener { + private class MyErrorPanel extends ButtonlessScrollBarUI implements MouseMotionListener, MouseListener, MouseWheelListener, UISettingsListener { private PopupHandler myHandler; private JButton myErrorStripeButton; private BufferedImage myCachedTrack; @@ -462,8 +471,10 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark super.installListeners(); scrollbar.addMouseMotionListener(this); scrollbar.addMouseListener(this); + scrollbar.addMouseWheelListener(this); myErrorStripeButton.addMouseMotionListener(this); myErrorStripeButton.addMouseListener(this); + UISettings.getInstance().addUISettingsListener(this); } @Override @@ -472,10 +483,18 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark scrollbar.removeMouseListener(this); myErrorStripeButton.removeMouseMotionListener(this); myErrorStripeButton.removeMouseListener(this); + UISettings.getInstance().removeUISettingsListener(this); super.uninstallListeners(); } @Override + public void uiSettingsChanged(UISettings source) { + if (!UISettings.getInstance().SHOW_EDITOR_TOOLTIP) { + hideMyEditorPreviewHint(); + } + } + + @Override protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds) { if (UISettings.getInstance().PRESENTATION_MODE) { super.paintThumb(g, c, thumbBounds); @@ -775,6 +794,14 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark } } + @Override + public void mouseWheelMoved(MouseWheelEvent e) { + if (myEditorPreviewHint == null) return; + myRowAdjuster += (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL ? e.getUnitsToScroll() * e.getScrollAmount() : + e.getWheelRotation() < 0 ? -e.getScrollAmount() : e.getScrollAmount()) / myEditor.getLineHeight(); + showToolTipByMouseMove(e); + } + private TrafficTooltipRenderer myTrafficTooltipRenderer; private void showTrafficLightTooltip(MouseEvent e) { @@ -797,16 +824,21 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark } private void cancelMyToolTips(final MouseEvent e, boolean checkIfShouldSurvive) { - if (myEditorPreviewHint != null) { - myEditorPreviewHint.hide(); - myEditorPreviewHint = null; - } + hideMyEditorPreviewHint(); final TooltipController tooltipController = TooltipController.getInstance(); if (!checkIfShouldSurvive || !tooltipController.shouldSurvive(e)) { tooltipController.cancelTooltip(ERROR_STRIPE_TOOLTIP_GROUP, e, true); } } + private void hideMyEditorPreviewHint() { + if (myEditorPreviewHint != null) { + myEditorPreviewHint.hide(); + myEditorPreviewHint = null; + myRowAdjuster = 0; + } + } + @Override public void mouseEntered(MouseEvent e) { } @@ -1016,23 +1048,44 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark } private class EditorFragmentRenderer implements TooltipRenderer { private int myLine; - private Collection<RangeHighlighterEx> myHighlighters; + private final List<RangeHighlighterEx> myHighlighters = new ArrayList<RangeHighlighterEx>(); private BufferedImage myImage; + private BufferedImage myCacheImage; + private int myCacheStartLine; + private int myCacheEndLine; private int myStartLine; private int myEndLine; private int myRelativeY; + private boolean myDelayed = false; + private final AtomicReference<Point> myPointHolder = new AtomicReference<Point>(); + private final AtomicReference<HintHint> myHintHolder = new AtomicReference<HintHint>(); private EditorFragmentRenderer() { update(-1, Collections.<RangeHighlighterEx>emptyList()); } - public void update(int currentLine, Collection<RangeHighlighterEx> rangeHighlighters) { - myLine = currentLine; - myHighlighters = rangeHighlighters; + void update(int currentLine, Collection<RangeHighlighterEx> rangeHighlighters) { + myLine = Math.min(myEditor.getDocument().getLineCount() - 1, Math.max(0, currentLine + myRowAdjuster)); + myHighlighters.clear(); myImage = null; if (currentLine ==-1) return; myStartLine = Math.max(0, myLine - myPreviewLines); myEndLine = Math.min(myEditor.getDocument().getLineCount() - 1, myLine + myPreviewLines + 1); + int popupStartOffset = myEditor.getDocument().getLineStartOffset(myStartLine); + int popupEndOffset = myEditor.getDocument().getLineEndOffset(myEndLine); + for (RangeHighlighterEx rangeHighlighter : rangeHighlighters) { + if (rangeHighlighter.getEndOffset()> popupStartOffset && rangeHighlighter.getStartOffset() < popupEndOffset) { + myHighlighters.add(rangeHighlighter); + } + } + Collections.sort(myHighlighters, new Comparator<RangeHighlighterEx>() { + public int compare(RangeHighlighterEx ex1, RangeHighlighterEx ex2) { + LogicalPosition startPos1 = myEditor.offsetToLogicalPosition(ex1.getAffectedAreaStartOffset()); + LogicalPosition startPos2 = myEditor.offsetToLogicalPosition(ex2.getAffectedAreaStartOffset()); + if (startPos1.line != startPos2.line) return 0; + return startPos1.column - startPos2.column; + } + }); } @Override @@ -1040,65 +1093,70 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark @NotNull Point p, boolean alignToRight, @NotNull TooltipGroup group, - @NotNull HintHint hintInfo) { + @NotNull final HintHint hintInfo) { final HintManagerImpl hintManager = HintManagerImpl.getInstanceImpl(); + boolean needDelay = false; if (myEditorPreviewHint == null) { + needDelay = true; final JPanel editorFragmentPreviewPanel = new JPanel() { private static final int R = 6; - private static final int LEFT_INDENT = BalloonImpl.ARC + 5; + private static final int LEFT_INDENT = BalloonImpl.ARC;// + 5; @Override public Dimension getPreferredSize() { int width = myEditor.getGutterComponentEx().getWidth(); width += Math.min(myEditor.getScrollingModel().getVisibleArea().width, myEditor.getContentComponent().getWidth()); + if (UISettings.getInstance().HIDE_TOOL_STRIPES) width -=2; return new Dimension(width - BalloonImpl.POINTER_WIDTH - LEFT_INDENT, myEditor.getLineHeight() * (myEndLine - myStartLine)); } @Override protected void paintComponent(Graphics g) { if (myLine ==-1) return; + Dimension size = getPreferredSize(); + EditorGutterComponentEx gutterComponentEx = myEditor.getGutterComponentEx(); + int gutterWidth = gutterComponentEx.getWidth(); + if (myCacheImage == null || myCacheStartLine > myStartLine || myCacheEndLine < myEndLine) { + int oldCacheLineCount = myCacheEndLine - myCacheStartLine; + myCacheStartLine = Math.max(0, myLine - myCachePreviewLines); + myCacheEndLine = Math.min(myEditor.getDocument().getLineCount() - 1, myLine + myCachePreviewLines + 1); + if (myCacheImage == null || oldCacheLineCount != (myCacheEndLine - myCacheStartLine)) { + myCacheImage = UIUtil.createImage(size.width, myEditor.getLineHeight() * 2 * myCachePreviewLines + 1, BufferedImage.TYPE_INT_RGB); + } + Graphics2D cg = myCacheImage.createGraphics(); + final AffineTransform t = cg.getTransform(); + UISettings.setupAntialiasing(cg); + int lineShift = -myEditor.getLineHeight() * myCacheStartLine; + + AffineTransform translateInstance = AffineTransform.getTranslateInstance(0, lineShift); + translateInstance.preConcatenate(t); + cg.setTransform(translateInstance); + + cg.setClip(0, 0, gutterWidth, gutterComponentEx.getHeight()); + gutterComponentEx.paint(cg); + translateInstance = AffineTransform.getTranslateInstance(gutterWidth, lineShift); + translateInstance.preConcatenate(t); + cg.setTransform(translateInstance); + EditorComponentImpl contentComponent = myEditor.getContentComponent(); + cg.setClip(0, 0, contentComponent.getWidth(), contentComponent.getHeight()); + contentComponent.paint(cg); + } if (myImage == null) { myRelativeY = SwingUtilities.convertPoint(this, 0, 0, myEditor.getScrollPane()).y; - Dimension size = getPreferredSize(); myImage = UIUtil.createImage(size.width, size.height, BufferedImage.TYPE_INT_RGB); Graphics2D g2d = myImage.createGraphics(); - AffineTransform transform = g2d.getTransform(); + final AffineTransform transform = g2d.getTransform(); UISettings.setupAntialiasing(g2d); + GraphicsUtil.setupAAPainting(g2d); g2d.setColor(myEditor.getBackgroundColor()); g2d.fillRect(0, 0, getWidth(), getHeight()); - int lineShift = -myEditor.getLineHeight() * myStartLine;//first fragment line offset - int popupStartOffset = myEditor.getDocument().getLineStartOffset(myStartLine); - int popupEndOffset = myEditor.getDocument().getLineEndOffset(myEndLine); - List<RangeHighlighterEx> exs = new ArrayList<RangeHighlighterEx>(); - for (RangeHighlighterEx rangeHighlighter : myHighlighters) { - if (rangeHighlighter.getEndOffset()> popupStartOffset && rangeHighlighter.getStartOffset() < popupEndOffset) { - exs.add(rangeHighlighter); - } - } - AffineTransform translateInstance = AffineTransform.getTranslateInstance(-LEFT_INDENT, lineShift); - translateInstance.preConcatenate(transform); - g2d.setTransform(translateInstance); - EditorGutterComponentEx gutterComponentEx = myEditor.getGutterComponentEx(); - int width = gutterComponentEx.getWidth(); - g2d.setClip(0, 0, width, gutterComponentEx.getHeight()); - gutterComponentEx.paint(g2d); - JComponent contentComponent = myEditor.getContentComponent(); - g2d.setClip(width, 0, contentComponent.getWidth(), contentComponent.getHeight()); - translateInstance = AffineTransform.getTranslateInstance(width - LEFT_INDENT, lineShift); + AffineTransform translateInstance = AffineTransform.getTranslateInstance(-LEFT_INDENT + gutterWidth, myEditor.getLineHeight() * (myCacheStartLine - myStartLine)); translateInstance.preConcatenate(transform); g2d.setTransform(translateInstance); - contentComponent.paint(g2d); - Collections.sort(exs, new Comparator<RangeHighlighterEx>() { - public int compare(RangeHighlighterEx ex1, RangeHighlighterEx ex2) { - LogicalPosition startPos1 = myEditor.offsetToLogicalPosition(ex1.getAffectedAreaStartOffset()); - LogicalPosition startPos2 = myEditor.offsetToLogicalPosition(ex2.getAffectedAreaStartOffset()); - if (startPos1.line != startPos2.line) return 0; - return startPos1.column - startPos2.column; - } - }); + UIUtil.drawImage(g2d, myCacheImage, -gutterWidth, 0, null); TIntIntHashMap rightEdges = new TIntIntHashMap(); - for (RangeHighlighterEx ex : exs) { - //int hStartOffset = ex.getAffectedAreaStartOffset(); + int h = myEditor.getLineHeight() - 2; + for (RangeHighlighterEx ex : myHighlighters) { int hEndOffset = ex.getAffectedAreaEndOffset(); Object tooltip = ex.getErrorStripeTooltip(); if (tooltip == null) continue; @@ -1111,41 +1169,93 @@ public class EditorMarkupModelImpl extends MarkupModelImpl implements EditorMark Point placeToShow = myEditor.logicalPositionToXY(logicalPosition); logicalPosition = myEditor.xyToLogicalPosition(placeToShow);//wraps&foldings workaround placeToShow.x += R * 3 / 2; - placeToShow.y += myEditor.getLineHeight() - R/2; + placeToShow.y -= myCacheStartLine * myEditor.getLineHeight(); int w = g2d.getFontMetrics().stringWidth(s); int a = g2d.getFontMetrics().getAscent(); - int h = myEditor.getLineHeight(); int rightEdge = rightEdges.get(logicalPosition.line); placeToShow.x = Math.max(placeToShow.x, rightEdge); rightEdge = Math.max(rightEdge, placeToShow.x + w + 3 * R); rightEdges.put(logicalPosition.line, rightEdge); - GraphicsUtil.setupAAPainting(g2d); g2d.setColor(MessageType.WARNING.getPopupBackground()); - g2d.fillRoundRect(placeToShow.x - R, placeToShow.y - a, w + 2 * R, h, R, R); + g2d.fillRoundRect(placeToShow.x, placeToShow.y, w + 2 * R, h, R, R); g2d.setColor(new JBColor(JBColor.GRAY, Gray._200)); - g2d.drawRoundRect(placeToShow.x - R, placeToShow.y - a, w + 2 * R, h, R, R); + g2d.drawRoundRect(placeToShow.x, placeToShow.y, w + 2 * R, h, R, R); g2d.setColor(JBColor.foreground()); - g2d.drawString(s, placeToShow.x, placeToShow.y); + g2d.drawString(s, placeToShow.x + R, placeToShow.y - g2d.getFontMetrics().getDescent() + R/2 + a); } } UIUtil.drawImage(g, myImage, 0, 0, this); + //Add glass effect + GraphicsUtil.setupAAPainting(g); + Shape s = new Rectangle(0, 0, size.width, size.height); + Graphics2D g2 = (Graphics2D)g; + double cx = size.width / 2; + double cy = 0; + double rx = size.width / 10; + int ry = myEditor.getLineHeight() * 2; + g2.setPaint(new GradientPaint(0, 0, new Color(255, 255, 255, 80), 0, ry, new Color(255, 255, 255, 40))); + double pseudoMajorAxis = size.width - rx * 9 / 5; + Shape topShape1 = new Ellipse2D.Double(cx - rx - pseudoMajorAxis / 2, cy - ry, 2 * rx, 2 * ry); + Shape topShape2 = new Ellipse2D.Double(cx - rx + pseudoMajorAxis / 2, cy - ry, 2 * rx, 2 * ry); + Area topArea = new Area(topShape1); + topArea.add(new Area(topShape2)); + topArea.add(new Area(new Rectangle.Double(cx - pseudoMajorAxis / 2, cy, pseudoMajorAxis, ry))); + g2.fill(topArea); + Area bottomArea = new Area(s); + bottomArea.subtract(topArea); + g2.setPaint(new GradientPaint(0, size.height - ry, new Color(0, 0, 0, 10), 0, size.height, new Color(255, 255, 255, 30))); + g2.fill(bottomArea); + } + }; + myEditorPreviewHint = new LightweightHint(editorFragmentPreviewPanel) { + + @Override + public void hide(boolean ok) { + super.hide(ok); + if (myCacheImage != null) { + myCacheImage = null; + myCacheStartLine = -1; + myCacheEndLine = -1; + } + myDelayed = false; } }; - myEditorPreviewHint = new LightweightHint(editorFragmentPreviewPanel); } - Point point = hintInfo.getOriginalPoint(); + Point point = new Point(hintInfo.getOriginalPoint()); hintInfo.setTextBg(myEditor.getColorsScheme().getDefaultBackground()); hintInfo.setBorderColor(new JBColor(Gray._0, Gray._111)); point = SwingUtilities.convertPoint(((EditorImpl)editor).getVerticalScrollBar(), point, myEditor.getComponent().getRootPane()); - hintManager.showEditorHint(myEditorPreviewHint, myEditor, point, HintManager.HIDE_BY_ANY_KEY | - HintManager.HIDE_BY_TEXT_CHANGE | - HintManager.HIDE_BY_MOUSEOVER | - HintManager.HIDE_BY_ESCAPE | - HintManager.HIDE_BY_SCROLLING, 0, false, hintInfo); + myPointHolder.set(point); + myHintHolder.set(hintInfo); + if (needDelay) { + myDelayed = true; + Alarm alarm = new Alarm(); + alarm.addRequest(new Runnable() { + @Override + public void run() { + if (myEditorPreviewHint == null || !myDelayed) return; + hintManager.showEditorHint(myEditorPreviewHint, myEditor, myPointHolder.get(), HintManager.HIDE_BY_ANY_KEY | + HintManager.HIDE_BY_TEXT_CHANGE | + HintManager.HIDE_BY_MOUSEOVER | + HintManager.HIDE_BY_ESCAPE | + HintManager.HIDE_BY_SCROLLING, 0, false, + myHintHolder.get()); + myDelayed = false; + } + }, Registry.intValue("ide.tooltip.initialDelay")); + } + else if (!myDelayed) { + hintManager.showEditorHint(myEditorPreviewHint, myEditor, point, HintManager.HIDE_BY_ANY_KEY | + HintManager.HIDE_BY_TEXT_CHANGE | + HintManager.HIDE_BY_MOUSEOVER | + HintManager.HIDE_BY_ESCAPE | + HintManager.HIDE_BY_SCROLLING, 0, false, + hintInfo); + } return myEditorPreviewHint; } } diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorWindow.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorWindow.java index 6a1e53fb7b0e..d4daeac2e34c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorWindow.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorWindow.java @@ -21,7 +21,6 @@ import com.intellij.ide.ui.UISettings; import com.intellij.openapi.actionSystem.CommonDataKeys; import com.intellij.openapi.actionSystem.DataKey; import com.intellij.openapi.actionSystem.DataProvider; -import com.intellij.openapi.actionSystem.PlatformDataKeys; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; @@ -643,7 +642,9 @@ public class EditorWindow { UIUtil.invokeLaterIfNeeded(new Runnable() { @Override public void run() { - myTabbedPane.setSelectedIndex(index, focusEditor); + if (myTabbedPane != null) { + myTabbedPane.setSelectedIndex(index, focusEditor); + } } }); } diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorsSplitters.java b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorsSplitters.java index 26825b10eb81..8ebab174da4c 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorsSplitters.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/EditorsSplitters.java @@ -17,7 +17,6 @@ package com.intellij.openapi.fileEditor.impl; import com.intellij.ide.actions.ShowFilePathAction; import com.intellij.ide.ui.UISettings; -import com.intellij.openapi.actionSystem.CustomShortcutSet; import com.intellij.openapi.actionSystem.IdeActions; import com.intellij.openapi.actionSystem.KeyboardShortcut; import com.intellij.openapi.actionSystem.Shortcut; @@ -28,6 +27,7 @@ import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.impl.text.FileDropHandler; import com.intellij.openapi.keymap.KeymapManager; import com.intellij.openapi.keymap.KeymapUtil; +import com.intellij.openapi.keymap.MacKeymapUtil; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Splitter; import com.intellij.openapi.util.*; @@ -40,6 +40,7 @@ import com.intellij.openapi.wm.ex.WindowManagerEx; import com.intellij.openapi.wm.impl.FrameTitleBuilder; import com.intellij.openapi.wm.impl.IdeFrameImpl; import com.intellij.openapi.wm.impl.IdePanePanel; +import com.intellij.ui.ColorUtil; import com.intellij.ui.Gray; import com.intellij.ui.JBColor; import com.intellij.ui.awt.RelativePoint; @@ -156,16 +157,21 @@ public class EditorsSplitters extends IdePanePanel { g.drawLine(0, 0, getWidth(), 0); } + boolean isDarkBackground = ColorUtil.isDark(getBackground().darker()); + if (showEmptyText()) { UIUtil.applyRenderingHints(g); - g.setColor(new JBColor(Gray._100, Gray._160)); + g.setColor(new JBColor(isDarkBackground ? Gray._230 : Gray._100, Gray._160)); g.setFont(UIUtil.getLabelFont().deriveFont(UIUtil.isUnderDarcula() ? 24f : 18f)); - final UIUtil.TextPainter painter = new UIUtil.TextPainter().withShadow(true).withLineSpacing(1.4f); - painter.appendLine("No files are open").underlined(new JBColor(Gray._150, Gray._100)); + final UIUtil.TextPainter painter = new UIUtil.TextPainter().withLineSpacing(1.4f); + if (!isDarkBackground) { + painter.withShadow(true); + } + painter.appendLine("No files are open").underlined(new JBColor(isDarkBackground ? Gray._210 : Gray._150, Gray._100)); if (Registry.is("search.everywhere.enabled")) { - painter.appendLine("Search Everywhere with " + KeymapUtil.getShortcutText(CustomShortcutSet.fromString("shift SPACE").getShortcuts()[0])) + painter.appendLine("Search Everywhere with Double " + (SystemInfo.isMac ? MacKeymapUtil.SHIFT : "Shift")) .smaller().withBullet(); } diff --git a/platform/platform-impl/src/com/intellij/openapi/options/ex/ConfigurableWrapper.java b/platform/platform-impl/src/com/intellij/openapi/options/ex/ConfigurableWrapper.java index e981d43b83f5..877e75cde10d 100644 --- a/platform/platform-impl/src/com/intellij/openapi/options/ex/ConfigurableWrapper.java +++ b/platform/platform-impl/src/com/intellij/openapi/options/ex/ConfigurableWrapper.java @@ -76,7 +76,7 @@ public class ConfigurableWrapper implements SearchableConfigurable { private final ConfigurableEP myEp; - public ConfigurableWrapper(ConfigurableEP ep) { + private ConfigurableWrapper(ConfigurableEP ep) { myEp = ep; } @@ -162,7 +162,7 @@ public class ConfigurableWrapper implements SearchableConfigurable { private Configurable[] myKids; - public CompositeWrapper(ConfigurableEP ep, Configurable... kids) { + private CompositeWrapper(ConfigurableEP ep, Configurable... kids) { super(ep); if (ep.dynamic) { kids = ((Composite)getConfigurable()).getConfigurables(); diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/PluginDownloader.java b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/PluginDownloader.java index c79b5cc9ed57..839b9daac2f2 100644 --- a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/PluginDownloader.java +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/PluginDownloader.java @@ -362,7 +362,7 @@ public class PluginDownloader { } if (url == null) { String uuid = UpdateChecker.getInstallationUID(PropertiesComponent.getInstance()); - String buildNumber = ApplicationInfo.getInstance().getBuild().asString(); + String buildNumber = ApplicationInfo.getInstance().getApiVersion(); url = RepositoryHelper.getDownloadUrl() + URLEncoder.encode(descriptor.getPluginId().getIdString(), "UTF8") + "&build=" + buildNumber + "&uuid=" + URLEncoder.encode(uuid, "UTF8"); } diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/PluginAdvertiserEditorNotificationProvider.java b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/PluginAdvertiserEditorNotificationProvider.java new file mode 100644 index 000000000000..7110799f3173 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/PluginAdvertiserEditorNotificationProvider.java @@ -0,0 +1,153 @@ +/* + * Copyright 2000-2013 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.updateSettings.impl.pluginsAdvertisement; + +import com.intellij.ide.plugins.*; +import com.intellij.openapi.extensions.PluginId; +import com.intellij.openapi.fileEditor.FileEditor; +import com.intellij.openapi.fileTypes.FileTypeFactory; +import com.intellij.openapi.fileTypes.PlainTextFileType; +import com.intellij.openapi.progress.ProgressIndicator; +import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.progress.Task; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.updateSettings.impl.PluginDownloader; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.EditorNotificationPanel; +import com.intellij.ui.EditorNotifications; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * User: anna + * Date: 10/11/13 + */ +public class PluginAdvertiserEditorNotificationProvider extends EditorNotifications.Provider<EditorNotificationPanel> { + private static final Key<EditorNotificationPanel> KEY = Key.create("file.type.associations.detected"); + private final Project myProject; + private final EditorNotifications myNotifications; + private final Set<String> myEnabledExtensions = new HashSet<String>(); + + public PluginAdvertiserEditorNotificationProvider(Project project, final EditorNotifications notifications) { + myProject = project; + myNotifications = notifications; + } + + @Override + public Key<EditorNotificationPanel> getKey() { + return KEY; + } + + @Nullable + @Override + public EditorNotificationPanel createNotificationPanel(VirtualFile file, FileEditor fileEditor) { + if (file.getFileType() != PlainTextFileType.INSTANCE) return null; + + final String extension = file.getExtension(); + if (myEnabledExtensions.contains(extension) || + UnknownFeaturesCollector.getInstance(myProject).isIgnored(createExtensionFeature(extension))) return null; + + final PluginsAdvertiser.KnownExtensions knownExtensions = PluginsAdvertiser.loadExtensions(); + if (knownExtensions != null) { + final Set<String> plugins = knownExtensions.find(extension); + if (plugins != null && !plugins.isEmpty()) { + return createPanel(extension, plugins); + } + } + return null; + } + + @NotNull + private EditorNotificationPanel createPanel(final String extension, final Set<String> plugins) { + final EditorNotificationPanel panel = new EditorNotificationPanel(); + panel.setText("Plugins supporting *." + extension + " are found"); + final IdeaPluginDescriptor disabledPlugin = getDisabledPlugin(plugins); + if (disabledPlugin != null) { + panel.createActionLabel("Enable " + disabledPlugin.getName() + " plugin", new Runnable() { + @Override + public void run() { + myEnabledExtensions.add(extension); + PluginManagerCore.enablePlugin(disabledPlugin.getPluginId().getIdString()); + myNotifications.updateAllNotifications(); + PluginManagerMain.notifyPluginsWereUpdated("Plugin was successfully enabled"); + } + }); + } else { + panel.createActionLabel("Install plugins", new Runnable() { + @Override + public void run() { + ProgressManager.getInstance().run(new Task.Modal(null, "Search for plugins in repository", true) { + private final Set<PluginDownloader> myPlugins = new HashSet<PluginDownloader>(); + private List<IdeaPluginDescriptor> myAllPlugins; + + @Override + public void run(@NotNull ProgressIndicator indicator) { + try { + myAllPlugins = RepositoryHelper.loadPluginsFromRepository(indicator); + for (IdeaPluginDescriptor loadedPlugin : myAllPlugins) { + if (plugins.contains(loadedPlugin.getPluginId().getIdString())) { + myPlugins.add(PluginDownloader.createDownloader(loadedPlugin)); + } + } + } + catch (Exception ignore) { + } + } + + @Override + public void onSuccess() { + final PluginsAdvertiserDialog advertiserDialog = new PluginsAdvertiserDialog(null, myPlugins.toArray(new PluginDownloader[myPlugins.size()]), myAllPlugins); + advertiserDialog.show(); + if (advertiserDialog.isOK()) { + myEnabledExtensions.add(extension); + myNotifications.updateAllNotifications(); + } + } + }); + } + }); + } + panel.createActionLabel("Ignore extension", new Runnable() { + @Override + public void run() { + final UnknownFeaturesCollector collectorSuggester = UnknownFeaturesCollector.getInstance(myProject); + collectorSuggester.ignoreFeature(createExtensionFeature(extension)); + myNotifications.updateAllNotifications(); + } + }); + return panel; + } + + @Nullable + private static IdeaPluginDescriptor getDisabledPlugin(Set<String> plugins) { + final List<String> disabledPlugins = new ArrayList<String>(PluginManagerCore.getDisabledPlugins()); + disabledPlugins.retainAll(plugins); + if (disabledPlugins.size() == 1) { + return PluginManager.getPlugin(PluginId.getId(disabledPlugins.get(0))); + } + return null; + } + + private static UnknownFeature createExtensionFeature(String extension) { + return new UnknownFeature(FileTypeFactory.FILE_TYPE_FACTORY_EP.getName(), extension); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/PluginsAdvertiser.java b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/PluginsAdvertiser.java index 10cd1ee4470e..40d1eb8cde6a 100644 --- a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/PluginsAdvertiser.java +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/PluginsAdvertiser.java @@ -18,50 +18,64 @@ package com.intellij.openapi.updateSettings.impl.pluginsAdvertisement; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import com.intellij.ide.plugins.*; +import com.google.gson.stream.JsonReader; +import com.intellij.ide.plugins.IdeaPluginDescriptor; +import com.intellij.ide.plugins.PluginManagerCore; +import com.intellij.ide.plugins.RepositoryHelper; import com.intellij.notification.*; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationInfo; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.PathManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.extensions.PluginId; +import com.intellij.openapi.fileTypes.FileTypeFactory; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; import com.intellij.openapi.project.Project; import com.intellij.openapi.startup.StartupActivity; import com.intellij.openapi.updateSettings.impl.PluginDownloader; +import com.intellij.openapi.updateSettings.impl.UpdateSettings; +import com.intellij.openapi.util.JDOMUtil; +import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.net.HttpConfigurable; +import com.intellij.util.xmlb.XmlSerializer; +import com.intellij.util.xmlb.annotations.MapAnnotation; +import com.intellij.util.xmlb.annotations.Tag; +import org.jdom.Document; import org.jetbrains.annotations.NotNull; import javax.swing.*; import javax.swing.event.HyperlinkEvent; +import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; public class PluginsAdvertiser implements StartupActivity { private static final Logger LOG = Logger.getInstance("#" + PluginsAdvertiser.class.getName()); + private static final String FEATURE_IMPLEMENTATIONS_URL = "http://plugins.jetbrains.com/feature/getImplementations?"; + private static final String CASHED_EXTENSIONS = "extensions.xml"; public static List<PluginId> retrieve(UnknownFeature unknownFeature) { final String featureType = unknownFeature.getFeatureType(); final String implementationName = unknownFeature.getImplementationName(); - final String buildNumber = ApplicationInfo.getInstance().getBuild().asString(); - final String pluginRepositoryUrl = "http://plugins.jetbrains.com/feature/getImplementations?" + + final String buildNumber = ApplicationInfo.getInstance().getApiVersion(); + final String pluginRepositoryUrl = FEATURE_IMPLEMENTATIONS_URL + "featureType=" + featureType + - "&implementationName=" + implementationName + + "&implementationName=" + implementationName.replaceAll("#", "%23") + "&build=" + buildNumber; try { HttpURLConnection connection = HttpConfigurable.getInstance().openHttpConnection(pluginRepositoryUrl); connection.connect(); final InputStreamReader streamReader = new InputStreamReader(connection.getInputStream()); try { - final JsonElement jsonRootElement = new JsonParser().parse(streamReader); + final JsonReader jsonReader = new JsonReader(streamReader); + jsonReader.setLenient(true); + final JsonElement jsonRootElement = new JsonParser().parse(jsonReader); final List<PluginId> result = new ArrayList<PluginId>(); for (JsonElement jsonElement : jsonRootElement.getAsJsonArray()) { final JsonObject jsonObject = jsonElement.getAsJsonObject(); @@ -80,11 +94,70 @@ public class PluginsAdvertiser implements StartupActivity { } } + private static Map<String, Set<PluginId>> loadSupportedExtensions() { + final String pluginRepositoryUrl = FEATURE_IMPLEMENTATIONS_URL + "featureType=" + FileTypeFactory.FILE_TYPE_FACTORY_EP.getName(); + try { + HttpURLConnection connection = HttpConfigurable.getInstance().openHttpConnection(pluginRepositoryUrl); + connection.connect(); + final InputStreamReader streamReader = new InputStreamReader(connection.getInputStream()); + try { + final JsonReader jsonReader = new JsonReader(streamReader); + jsonReader.setLenient(true); + final JsonElement jsonRootElement = new JsonParser().parse(jsonReader); + final Map<String, Set<PluginId>> result = new HashMap<String, Set<PluginId>>(); + for (JsonElement jsonElement : jsonRootElement.getAsJsonArray()) { + final JsonObject jsonObject = jsonElement.getAsJsonObject(); + final JsonElement ext = jsonObject.get("implementationName"); + final String extension = StringUtil.unquoteString(ext.toString()); + Set<PluginId> pluginIds = result.get(extension); + if (pluginIds == null) { + pluginIds = new HashSet<PluginId>(); + result.put(extension, pluginIds); + } + pluginIds.add(PluginId.getId(StringUtil.unquoteString(jsonObject.get("pluginId").toString()))); + } + saveExtensions(result); + return result; + } + finally { + streamReader.close(); + } + } + catch (IOException e) { + LOG.info(e); + return null; + } + } + + public static KnownExtensions loadExtensions() { + try { + File file = new File(PathManager.getPluginsPath(), CASHED_EXTENSIONS); + if (file.isFile()) { + final Document document = JDOMUtil.loadDocument(file); + return XmlSerializer.deserialize(document, KnownExtensions.class); + } + } + catch (Exception ignore) { + } + return null; + } + + private static void saveExtensions(Map<String, Set<PluginId>> extensions) throws IOException { + File plugins = new File(PathManager.getPluginsPath(), CASHED_EXTENSIONS); + if (!plugins.isFile()) { + FileUtil.ensureCanCreateFile(plugins); + } + plugins.deleteOnExit(); + JDOMUtil.writeDocument(new Document(XmlSerializer.serialize(new KnownExtensions(extensions))), plugins, "\n"); + } + @Override public void runActivity(@NotNull final Project project) { + if (!UpdateSettings.getInstance().CHECK_NEEDED) return; final UnknownFeaturesCollector collectorSuggester = UnknownFeaturesCollector.getInstance(project); final Set<UnknownFeature> unknownFeatures = collectorSuggester.getUnknownFeatures(); - if (unknownFeatures.isEmpty()) return; + final KnownExtensions extensions = loadExtensions(); + if (extensions != null && unknownFeatures.isEmpty()) return; final Runnable runnable = new Runnable() { public void run() { final Application application = ApplicationManager.getApplication(); @@ -95,6 +168,9 @@ public class PluginsAdvertiser implements StartupActivity { @Override public void run(@NotNull ProgressIndicator indicator) { + if (extensions == null) { + loadSupportedExtensions(); + } int idx = 0; final Set<PluginId> ids = new HashSet<PluginId>(); for (UnknownFeature feature : unknownFeatures) { @@ -109,7 +185,7 @@ public class PluginsAdvertiser implements StartupActivity { } } } - indicator.setFraction(idx++ / unknownFeatures.size()); + indicator.setFraction(((double) idx++) / unknownFeatures.size()); } try { @@ -141,11 +217,12 @@ public class PluginsAdvertiser implements StartupActivity { for (UnknownFeature feature : unknownFeatures) { collectorSuggester.ignoreFeature(feature); } + notification.expire(); } else if ("configure".equals(description)) { LOG.assertTrue(myAllPlugins != null); + notification.expire(); new PluginsAdvertiserDialog(myProject, myPlugins.toArray(new PluginDownloader[myPlugins.size()]), myAllPlugins).show(); } - notification.expire(); } } }).notify(project); @@ -157,5 +234,42 @@ public class PluginsAdvertiser implements StartupActivity { SwingUtilities.invokeLater(runnable); } + + @Tag("exts") + public static class KnownExtensions { + @MapAnnotation(surroundWithTag = false, surroundKeyWithTag = false, surroundValueWithTag = false) + public Map<String, PluginSet> myExtensions = new HashMap<String, PluginSet>(); + + public KnownExtensions() { + } + + public KnownExtensions(Map<String, Set<PluginId>> extensions) { + for (String ext : extensions.keySet()) { + myExtensions.put(ext, new PluginSet(extensions.get(ext))); + } + } + + public Set<String> find(String extension) { + final PluginSet pluginSet = myExtensions.get(extension); + if (pluginSet != null) { + return pluginSet.myPlugins; + } + return null; + } + } + + @Tag("plugins") + public static class PluginSet { + public Set<String> myPlugins = new HashSet<String>(); + + public PluginSet() { + } + + public PluginSet(Set<PluginId> plugins) { + for (PluginId plugin : plugins) { + myPlugins.add(plugin.toString()); + } + } + } } diff --git a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/UnknownFeaturesCollector.java b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/UnknownFeaturesCollector.java index 0bd1410f012e..a7714b1e04e8 100644 --- a/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/UnknownFeaturesCollector.java +++ b/platform/platform-impl/src/com/intellij/openapi/updateSettings/impl/pluginsAdvertisement/UnknownFeaturesCollector.java @@ -51,11 +51,15 @@ public class UnknownFeaturesCollector implements PersistentStateComponent<Elemen public void registerUnknownFeature(String featureType, String implementationName) { final UnknownFeature feature = new UnknownFeature(featureType, implementationName); - if (!myIgnoredUnknownFeatures.contains(feature)) { + if (!isIgnored(feature)) { myUnknownFeatures.add(feature); } } - + + public boolean isIgnored(UnknownFeature feature) { + return myIgnoredUnknownFeatures.contains(feature); + } + public void ignoreFeature(UnknownFeature feature) { myIgnoredUnknownFeatures.add(feature); } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/ex/temp/TempFileSystem.java b/platform/platform-impl/src/com/intellij/openapi/vfs/ex/temp/TempFileSystem.java index 1ac43ab7c5d6..92e7ed73e486 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/ex/temp/TempFileSystem.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/ex/temp/TempFileSystem.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -21,13 +21,14 @@ import com.intellij.openapi.util.io.BufferExposingByteArrayInputStream; import com.intellij.openapi.util.io.FileAttributes; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.impl.local.LocalFileSystemBase; import com.intellij.openapi.vfs.newvfs.ManagingFS; -import com.intellij.openapi.vfs.newvfs.NewVirtualFileSystem; import com.intellij.openapi.vfs.newvfs.RefreshQueue; import com.intellij.openapi.vfs.newvfs.VfsImplUtil; import com.intellij.openapi.vfs.newvfs.impl.FakeVirtualFile; import com.intellij.openapi.vfs.newvfs.persistent.FSRecords; import com.intellij.util.ArrayUtil; +import com.intellij.util.IncorrectOperationException; import com.intellij.util.LocalTimeCounter; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -35,12 +36,14 @@ import org.jetbrains.annotations.Nullable; import java.io.*; import java.util.ArrayList; +import java.util.Collection; import java.util.List; +import java.util.Set; /** * @author max */ -public class TempFileSystem extends NewVirtualFileSystem { +public class TempFileSystem extends LocalFileSystemBase { private final FSItem myRoot = new FSDir(null, "/"); public static TempFileSystem getInstance() { @@ -180,7 +183,7 @@ public class TempFileSystem extends NewVirtualFileSystem { } @Override - public void setTimeStamp(@NotNull final VirtualFile file, final long timeStamp) throws IOException { + public void setTimeStamp(@NotNull final VirtualFile file, final long timeStamp) { final FSItem fsItem = convert(file); assert fsItem != null; @@ -370,4 +373,28 @@ public class TempFileSystem extends NewVirtualFileSystem { final long length = item instanceof FSFile ? ((FSFile)item).myContent.length : 0; return new FileAttributes(item.isDirectory(), false, false, false, length, item.myTimestamp, item.myWritable); } + + @NotNull + @Override + public Set<WatchRequest> addRootsToWatch(@NotNull Collection<String> rootPaths, boolean watchRecursively) { + throw new IncorrectOperationException(); + } + + @Override + public void removeWatchedRoots(@NotNull Collection<WatchRequest> watchRequests) { + throw new IncorrectOperationException(); + } + + @Override + public Set<WatchRequest> replaceWatchedRoots(@NotNull Collection<WatchRequest> watchRequests, + @Nullable Collection<String> recursiveRoots, + @Nullable Collection<String> flatRoots) { + throw new IncorrectOperationException(); + } + + @Nullable + @Override + protected String normalize(@NotNull String path) { + return path; + } } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/FileWatcher.java b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/FileWatcher.java index 09ca88b9fc7f..972165f76751 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/FileWatcher.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/impl/local/FileWatcher.java @@ -67,6 +67,12 @@ public class FileWatcher { public final List<String> dirtyPaths = newArrayList(); public final List<String> dirtyPathsRecursive = newArrayList(); public final List<String> dirtyDirectories = newArrayList(); + + private static DirtyPaths EMPTY = new DirtyPaths(); + + private boolean isEmpty() { + return dirtyPaths.isEmpty() && dirtyPathsRecursive.isEmpty() && dirtyDirectories.isEmpty(); + } } private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vfs.impl.local.FileWatcher"); @@ -91,14 +97,9 @@ public class FileWatcher { private final Object myLock = new Object(); private DirtyPaths myDirtyPaths = new DirtyPaths(); - private final String[] myLastChangedPathes = new String[2]; + private final String[] myLastChangedPaths = new String[2]; private int myLastChangedPathIndex; - /** @deprecated use {@linkplain com.intellij.openapi.vfs.impl.local.LocalFileSystemImpl#getFileWatcher()} (to remove in IDEA 13) */ - public static FileWatcher getInstance() { - return ((LocalFileSystemImpl)LocalFileSystem.getInstance()).getFileWatcher(); - } - FileWatcher(@NotNull ManagingFS managingFS) { myManagingFS = managingFS; @@ -153,11 +154,16 @@ public class FileWatcher { @NotNull public DirtyPaths getDirtyPaths() { synchronized (myLock) { - DirtyPaths dirtyPaths = myDirtyPaths; - myDirtyPaths = new DirtyPaths(); - myLastChangedPathIndex = 0; - for(int i = 0; i < myLastChangedPathes.length; ++i) myLastChangedPathes[i] = null; - return dirtyPaths; + if (!myDirtyPaths.isEmpty()) { + DirtyPaths dirtyPaths = myDirtyPaths; + myDirtyPaths = new DirtyPaths(); + myLastChangedPathIndex = 0; + for (int i = 0; i < myLastChangedPaths.length; ++i) myLastChangedPaths[i] = null; + return dirtyPaths; + } + else { + return DirtyPaths.EMPTY; + } } } @@ -213,7 +219,7 @@ public class FileWatcher { } private static boolean isUpToDate(File executable) { - long length = SystemInfo.isWindows ? 70216 : + long length = SystemInfo.isWindows ? 71208 : SystemInfo.isMac ? 13924 : SystemInfo.isLinux ? SystemInfo.isAMD64 ? 29155 : 22791 : -1; @@ -377,13 +383,13 @@ public class FileWatcher { if (fastPath && !changedPaths.isEmpty()) break; for (String root : flatWatchRoots) { - if (FileUtil.pathsEqual(path, root)) { + if (FileUtil.namesEqual(path, root)) { changedPaths.add(path); continue ext; } if (isExact) { String parentPath = new File(path).getParent(); - if (parentPath != null && FileUtil.pathsEqual(parentPath, root)) { + if (parentPath != null && FileUtil.namesEqual(parentPath, root)) { changedPaths.add(path); continue ext; } @@ -397,7 +403,7 @@ public class FileWatcher { } if (!isExact) { String parentPath = new File(root).getParent(); - if (parentPath != null && FileUtil.pathsEqual(path, parentPath)) { + if (parentPath != null && FileUtil.namesEqual(path, parentPath)) { changedPaths.add(root); continue ext; } @@ -551,20 +557,22 @@ public class FileWatcher { LOG.info("Change requests:" + myChangeRequests + ", filtered:" + myFilteredRequests); } - for(int i = 0; i < myLastChangedPathes.length; ++i) { + for(int i = 0; i < myLastChangedPaths.length; ++i) { int last = myLastChangedPathIndex - i - 1; - if (last < 0) last += myLastChangedPathes.length; - String lastChangedPath = myLastChangedPathes[last]; + if (last < 0) last += myLastChangedPaths.length; + String lastChangedPath = myLastChangedPaths[last]; if (lastChangedPath != null && lastChangedPath.equals(path)) { ++myFilteredRequests; return; } } - myLastChangedPathes[myLastChangedPathIndex ++] = path; - if (myLastChangedPathIndex == myLastChangedPathes.length) myLastChangedPathIndex = 0; + myLastChangedPaths[myLastChangedPathIndex ++] = path; + if (myLastChangedPathIndex == myLastChangedPaths.length) myLastChangedPathIndex = 0; } } + int length = path.length(); + if (length > 1 && path.charAt(length - 1) == '/') path = path.substring(0, length - 1); boolean exactPath = op != WatcherOp.DIRTY && op != WatcherOp.RECDIRTY; Collection<String> paths = checkWatchable(path, exactPath, false); diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/RefreshSessionImpl.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/RefreshSessionImpl.java index d58cbb4b4e9e..a516ec88b0f1 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/RefreshSessionImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/RefreshSessionImpl.java @@ -103,6 +103,7 @@ public class RefreshSessionImpl extends RefreshSession { @Override public void launch() { + if (!myIsRecursive) LOG.info("[CR-IC-2706] " + myWorkQueue); mySemaphore.down(); ((RefreshQueueImpl)RefreshQueue.getInstance()).execute(this); } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/VfsImplUtil.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/VfsImplUtil.java index bc5afa0b08a6..b2a4ab36af52 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/VfsImplUtil.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/VfsImplUtil.java @@ -124,7 +124,7 @@ public class VfsImplUtil { @Nullable private static Pair<NewVirtualFile, Iterable<String>> prepare(@NotNull NewVirtualFileSystem vfs, @NotNull String path) { String normalizedPath = normalize(vfs, path); - if (StringUtil.isEmptyOrSpaces(normalizedPath) || normalizedPath == null) { + if (StringUtil.isEmptyOrSpaces(normalizedPath)) { return null; } diff --git a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/RefreshWorker.java b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/RefreshWorker.java index 42495b57f2c0..5cb9149e4997 100644 --- a/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/RefreshWorker.java +++ b/platform/platform-impl/src/com/intellij/openapi/vfs/newvfs/persistent/RefreshWorker.java @@ -228,7 +228,9 @@ public class RefreshWorker { } } - file.markClean(); + if (myIsRecursive || !file.isDirectory()) { + file.markClean(); + } } } diff --git a/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowsPane.java b/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowsPane.java index 1e7e32b751cd..d819a63adda3 100644 --- a/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowsPane.java +++ b/platform/platform-impl/src/com/intellij/openapi/wm/impl/ToolWindowsPane.java @@ -740,13 +740,20 @@ public final class ToolWindowsPane extends JBLayeredPane implements Disposable { splitter.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { + if (!Splitter.PROP_ORIENTATION.equals(evt.getPropertyName())) return; + boolean isSplitterHorizontalNow = !splitter.isVertical(); if (anchor == ToolWindowAnchor.LEFT) { - UISettings.getInstance().LEFT_HORIZONTAL_SPLIT = !splitter.isVertical(); + if (UISettings.getInstance().LEFT_HORIZONTAL_SPLIT != isSplitterHorizontalNow) { + UISettings.getInstance().LEFT_HORIZONTAL_SPLIT = isSplitterHorizontalNow; + UISettings.getInstance().fireUISettingsChanged(); + } } if (anchor == ToolWindowAnchor.RIGHT) { - UISettings.getInstance().RIGHT_HORIZONTAL_SPLIT = !splitter.isVertical(); + if (UISettings.getInstance().RIGHT_HORIZONTAL_SPLIT != isSplitterHorizontalNow) { + UISettings.getInstance().RIGHT_HORIZONTAL_SPLIT = isSplitterHorizontalNow; + UISettings.getInstance().fireUISettingsChanged(); + } } - UISettings.getInstance().fireUISettingsChanged(); } }); } diff --git a/platform/platform-impl/src/com/intellij/ui/FinderRecursivePanel.java b/platform/platform-impl/src/com/intellij/ui/FinderRecursivePanel.java new file mode 100644 index 000000000000..6af352487f55 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ui/FinderRecursivePanel.java @@ -0,0 +1,496 @@ +package com.intellij.ui; + +import com.intellij.icons.AllIcons; +import com.intellij.ide.CopyProvider; +import com.intellij.ide.DataManager; +import com.intellij.openapi.Disposable; +import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.ide.CopyPasteManager; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.wm.IdeFocusManager; +import com.intellij.pom.Navigatable; +import com.intellij.psi.PsiElement; +import com.intellij.ui.components.JBLabel; +import com.intellij.ui.components.JBList; +import com.intellij.util.ArrayUtil; +import com.intellij.util.Function; +import com.intellij.util.SmartList; +import com.intellij.util.ui.UIUtil; +import com.intellij.util.ui.update.MergingUpdateQueue; +import com.intellij.util.ui.update.Update; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import java.awt.*; +import java.awt.datatransfer.StringSelection; +import java.awt.event.KeyEvent; +import java.awt.event.MouseEvent; +import java.util.List; + +public class FinderRecursivePanel<T> extends JBSplitter implements DataProvider, Disposable { + + @NotNull private final Project myProject; + @Nullable private final String myGroupId; + + private FinderRecursivePanel myParent = null; + private JComponent myChild = null; + + private JBList myList; + private final CollectionListModel<T> myListModel = new CollectionListModel<T>(); + + private final MergingUpdateQueue myMergingUpdateQueue; + + private MyListSelectionListener myListener; + + private CopyProvider myCopyProvider = new CopyProvider() { + @Override + public void performCopy(@NotNull DataContext dataContext) { + final T value = getSelectedValue(); + CopyPasteManager.getInstance().setContents(new StringSelection(getItemText(value))); + } + + @Override + public boolean isCopyEnabled(@NotNull DataContext dataContext) { + return getSelectedValue() != null; + } + + @Override + public boolean isCopyVisible(@NotNull DataContext dataContext) { + return false; + } + }; + + public FinderRecursivePanel(@NotNull FinderRecursivePanel parent) { + this(parent.getProject(), parent, parent.getGroupId()); + } + + public FinderRecursivePanel(@NotNull Project project, @Nullable String groupId) { + this(project, null, groupId); + } + + protected FinderRecursivePanel(@NotNull Project project, + @Nullable FinderRecursivePanel parent, + @Nullable String groupId) { + super(false, 0f); + + myProject = project; + myParent = parent; + myGroupId = groupId; + myMergingUpdateQueue = new MergingUpdateQueue("FinderRecursivePanel", 100, true, this); + + if (myParent != null) { + Disposer.register(myParent, this); + } + Disposer.register(this, myMergingUpdateQueue); + } + + public void init() { + setFirstComponent(createLeftComponent()); + setSecondComponent(createDefaultRightComponent()); + + if (getGroupId() != null) { + setAndLoadSplitterProportionKey(getGroupId() + "[" + getIndex() + "]"); + } + setDividerWidth(3); + setShowDividerIcon(false); + setShowDividerControls(true); + + updatePanel(); + } + + protected JComponent createLeftComponent() { + myList = createList(); + + return ScrollPaneFactory.createScrollPane(myList, + ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED, + ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + } + + protected String getListEmptyText() { + return "No entries"; + } + + protected JBList createList() { + final JBList list = new JBList(myListModel); + list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + list.setEmptyText(getListEmptyText()); + list.setCellRenderer(createListCellRenderer()); + + installListActions(list); + myListener = new MyListSelectionListener(); + list.addListSelectionListener(myListener); + ListScrollingUtil.installActions(list); + + installSpeedSearch(list); + + installEditOnDoubleClick(list); + return list; + } + + private void installListActions(JBList list) { + AnAction previousPanelAction = new AnAction("Previous", null, AllIcons.Actions.Back) { + @Override + public void update(AnActionEvent e) { + e.getPresentation().setEnabled(!isRootPanel()); + } + + @Override + public void actionPerformed(AnActionEvent e) { + IdeFocusManager.getInstance(myProject).requestFocus(myParent.getList(), true); + } + }; + previousPanelAction.registerCustomShortcutSet(KeyEvent.VK_LEFT, 0, list); + + AnAction nextPanelAction = new AnAction("Next", null, AllIcons.Actions.Forward) { + @Override + public void update(AnActionEvent e) { + final T value = getSelectedValue(); + e.getPresentation().setEnabled(value != null && + hasChildren(value) && + getSecondComponent() instanceof FinderRecursivePanel); + } + + @Override + public void actionPerformed(AnActionEvent e) { + FinderRecursivePanel finderRecursivePanel = (FinderRecursivePanel)getSecondComponent(); + JBList jbList = finderRecursivePanel.getList(); + if (!jbList.isEmpty()) { + if (jbList.getSelectedValue() == null) { + jbList.setSelectedIndex(0); + finderRecursivePanel.updateRightComponent(true); + } + } + IdeFocusManager.getInstance(myProject).requestFocus(jbList, true); + } + }; + nextPanelAction.registerCustomShortcutSet(KeyEvent.VK_RIGHT, 0, list); + + AnAction editAction = new AnAction("Edit", null, AllIcons.Actions.Edit) { + + @Override + public void update(AnActionEvent e) { + e.getPresentation().setEnabled(getSelectedValue() != null); + } + + @Override + public void actionPerformed(AnActionEvent e) { + performEditAction(); + } + }; + editAction.registerCustomShortcutSet(CommonShortcuts.ENTER, list); + + AnAction[] actions = new AnAction[]{ + previousPanelAction, + nextPanelAction, + Separator.getInstance(), + editAction}; + + final AnAction[] customActions = getCustomListActions(); + if (customActions.length > 0) { + actions = ArrayUtil.append(actions, Separator.getInstance()); + actions = ArrayUtil.mergeArrays(actions, customActions); + } + + ActionGroup contextActionGroup = new DefaultActionGroup(actions); + PopupHandler.installUnknownPopupHandler(list, contextActionGroup, ActionManager.getInstance()); + } + + protected AnAction[] getCustomListActions() { + return AnAction.EMPTY_ARRAY; + } + + private void installSpeedSearch(JBList list) { + final ListSpeedSearch search = new ListSpeedSearch(list, new Function<Object, String>() { + @Override + public String fun(Object o) { + //noinspection unchecked + return getItemText((T)o); + } + }); + search.setComparator(new SpeedSearchComparator(false)); + } + + private void installEditOnDoubleClick(JBList list) { + new DoubleClickListener() { + @Override + protected boolean onDoubleClick(MouseEvent event) { + performEditAction(); + return true; + } + }.installOn(list); + } + + protected boolean performEditAction() { + Navigatable data = CommonDataKeys.NAVIGATABLE.getData(DataManager.getInstance().getDataContext(getList())); + if (data != null) { + data.navigate(true); + } + return false; + } + + protected ListCellRenderer createListCellRenderer() { + return new ColoredListCellRenderer() { + + public Component getListCellRendererComponent(JList list, + Object value, + int index, + boolean isSelected, + boolean cellHasFocus) { + mySelected = isSelected; + myForeground = UIUtil.getTreeTextForeground(); + mySelectionForeground = cellHasFocus ? list.getSelectionForeground() : UIUtil.getTreeTextForeground(); + + clear(); + setFont(UIUtil.getListFont()); + + final T t = (T)value; + setIcon(getItemIcon(t)); + append(getItemText(t)); + + doCustomizeCellRenderer(this, list, t, index, isSelected, cellHasFocus); + + Color bg = isSelected ? UIUtil.getTreeSelectionBackground(cellHasFocus) : UIUtil.getTreeTextBackground(); + setBackground(bg); + + if (hasChildren(t)) { + JPanel result = new JPanel(new BorderLayout(0, 0)); + JLabel childrenLabel = new JLabel(); + childrenLabel.setOpaque(true); + childrenLabel.setVisible(true); + childrenLabel.setBackground(bg); + + final boolean isDark = ColorUtil.isDark(UIUtil.getListSelectionBackground()); + childrenLabel.setIcon(isSelected ? isDark ? AllIcons.Icons.Ide.NextStepInverted + : AllIcons.Icons.Ide.NextStep + : AllIcons.Icons.Ide.NextStepGrayed); + result.add(this, BorderLayout.CENTER); + result.add(childrenLabel, BorderLayout.EAST); + return result; + } + return this; + } + + @Override + protected final void customizeCellRenderer(JList list, Object value, int index, boolean selected, boolean hasFocus) { + } + }; + } + + protected void doCustomizeCellRenderer(SimpleColoredComponent comp, JList list, T value, int index, boolean selected, boolean hasFocus) { + } + + protected boolean hasChildren(T t) { + return false; + } + + protected JBList getList() { + return myList; + } + + @NotNull + protected String getItemText(T t) { + return t.toString(); + } + + @Nullable + protected Icon getItemIcon(T t) { + return null; + } + + @Nullable + @Override + public Object getData(@NonNls String dataId) { + Object selectedValue = getSelectedValue(); + if (PlatformDataKeys.COPY_PROVIDER.is(dataId) && selectedValue != null) { + return myCopyProvider; + } + if (CommonDataKeys.PSI_ELEMENT.is(dataId) && selectedValue instanceof PsiElement) { + return selectedValue; + } + if (CommonDataKeys.NAVIGATABLE.is(dataId) && selectedValue instanceof Navigatable) { + return selectedValue; + } + if (selectedValue instanceof DataProvider) { + return ((DataProvider)selectedValue).getData(dataId); + } + return null; + } + + @Override + public void dispose() { + myMergingUpdateQueue.cancelAllUpdates(); + } + + @SuppressWarnings("unchecked") + public T getSelectedValue() { + return (T)getList().getSelectedValue(); + } + + @NotNull + public Project getProject() { + return myProject; + } + + public FinderRecursivePanel getParentPanel() { + return myParent; + } + + @Nullable + public String getGroupId() { + return myGroupId; + } + + @Nullable + protected JComponent createRightComponent(T t) { + return new JPanel(); + } + + protected JComponent createDefaultRightComponent() { + final JBLabel label = new JBLabel("Nothing selected", SwingConstants.CENTER); + label.setFontColor(UIUtil.FontColor.BRIGHTER); + return label; + } + + public void updatePanel() { + myList.setPaintBusy(true); + myMergingUpdateQueue.queue(new Update("update") { + @Override + public void run() { + final T oldValue = getSelectedValue(); + final int oldIndex = getList().getSelectedIndex(); + + ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { + @Override + public void run() { + ApplicationManager.getApplication().runReadAction(new Runnable() { + @Override + public void run() { + try { + SwingUtilities.invokeLater(new Runnable() { + @Override + public void run() { + mergeListItems(myListModel, getListItems()); + + if (myList.getSelectedIndex() < 0) { + myList.setSelectedIndex(myListModel.getSize() > oldIndex ? oldIndex : 0); + } + else { + Object newValue = myList.getSelectedValue(); + updateRightComponent(oldValue == null || oldValue != newValue); + } + } + }); + } + finally { + myList.setPaintBusy(false); + } + } + }); + } + }); + } + }); + } + + public static <T> void mergeListItems(@NotNull CollectionListModel<T> listModel, @NotNull List<T> newItems) { + // remove items + for (int i = listModel.getSize() - 1; i >= 0; i--) { + if (!newItems.contains(listModel.getElementAt(i))) { + listModel.remove(i); + } + } + // add items + List<T> items = listModel.getItems(); + for (int i = 0; i < newItems.size(); i++) { + T newItem = newItems.get(i); + if (!items.contains(newItem)) { + listModel.add(i, newItem); + } + } + } + + public void updateRightComponent(boolean force) { + if (force) { + createRightComponent(); + } + else if (myChild instanceof FinderRecursivePanel) { + ((FinderRecursivePanel)myChild).updatePanel(); + } + } + + private void createRightComponent() { + T value = getSelectedValue(); + if (value != null) { + if (myChild instanceof Disposable) { + Disposer.dispose((Disposable)myChild); + } + myChild = createRightComponent(value); + if (myChild instanceof FinderRecursivePanel) { + ((FinderRecursivePanel)myChild).init(); + } + + setSecondComponent(myChild); + } + } + + /** + * Called in read action. + * + * @return Items for list. + */ + @NotNull + protected List<T> getListItems() { + return new SmartList<T>(); + } + + protected int getIndex() { + int index = 0; + FinderRecursivePanel parent = myParent; + while (parent != null) { + index++; + parent = parent.getParentPanel(); + } + + return index; + } + + protected boolean isRootPanel() { + return getParentPanel() == null; + } + + @Override + public void doLayout() { + if (myProportion == 0) { + int total = getOrientation() ? getHeight() : getWidth(); + float proportion = (float)getFirstComponentPreferredSize() / (total - getDividerWidth()); + if (proportion > .0f && proportion < 1.0f) { + setProportion(proportion); + } + } + + super.doLayout(); + } + + protected int getFirstComponentPreferredSize() { + return 200; + } + + @Nullable + protected JComponent getChildPanel() { + return myChild; + } + + private class MyListSelectionListener implements ListSelectionListener { + @Override + public void valueChanged(ListSelectionEvent event) { + if (event.getValueIsAdjusting()) return; + updateRightComponent(true); + } + } +} diff --git a/platform/platform-impl/src/com/intellij/ui/popup/PopupComponent.java b/platform/platform-impl/src/com/intellij/ui/popup/PopupComponent.java index 245a1bc029d8..ee6970495ec9 100644 --- a/platform/platform-impl/src/com/intellij/ui/popup/PopupComponent.java +++ b/platform/platform-impl/src/com/intellij/ui/popup/PopupComponent.java @@ -136,12 +136,23 @@ public interface PopupComponent { if (!myRequestFocus) { myDialog.setFocusableWindowState(false); } - if (UIUtil.isUnderDarcula()) { - AWTUtilities.setWindowOpaque(myDialog, false); + + try { + if (UIUtil.isUnderDarcula()) { + AWTUtilities.setWindowOpaque(myDialog, false); + } } + catch (Exception ignore) { + } + myDialog.setVisible(true); - if (UIUtil.isUnderDarcula()) { - AWTUtilities.setWindowOpaque(myDialog, true); + + try { + if (UIUtil.isUnderDarcula()) { + AWTUtilities.setWindowOpaque(myDialog, true); + } + } + catch (Exception ignore) { } SwingUtilities.invokeLater(new Runnable() { public void run() { diff --git a/platform/platform-impl/src/com/intellij/util/IJSwingUtilities.java b/platform/platform-impl/src/com/intellij/util/IJSwingUtilities.java index 9458305d1aa0..459bade75457 100644 --- a/platform/platform-impl/src/com/intellij/util/IJSwingUtilities.java +++ b/platform/platform-impl/src/com/intellij/util/IJSwingUtilities.java @@ -179,6 +179,11 @@ public class IJSwingUtilities { @Nullable public static <T extends Component> T findParentOfType(Component focusOwner, Class<T> aClass) { return (T)ContainerUtil.find(getParents(focusOwner), (FilteringIterator.InstanceOf<T>)FilteringIterator.instanceOf(aClass)); + + } + @Nullable + public static Component findParentByInterface(Component focusOwner, Class aClass) { + return ContainerUtil.find(getParents(focusOwner), FilteringIterator.instanceOf(aClass)); } public static void adjustComponentsOnMac(@Nullable JComponent component) { diff --git a/platform/platform-resources-en/src/messages/ApplicationBundle.properties b/platform/platform-resources-en/src/messages/ApplicationBundle.properties index bee939b62427..cb5b618baa3c 100644 --- a/platform/platform-resources-en/src/messages/ApplicationBundle.properties +++ b/platform/platform-resources-en/src/messages/ApplicationBundle.properties @@ -605,12 +605,21 @@ arrangement.settings.text.general.order=Order: arrangement.settings.text.general.xml.namespace=Namespace: arrangement.settings.text.entry.type.xml.tag=tag arrangement.settings.text.entry.type.xml.attribute=attribute +arrangement.settings.additional.force.combobox.name=Force rearrange: +arrangement.settings.additional.force.rearrange.always=Always +arrangement.settings.additional.force.rearrange.never=Never +arrangement.settings.additional.force.rearrange.according.to.dialog=Use current mode (toggled in the Reformat Code dialog) +arrangement.settings.additional.title=Additional settings + + checkbox.spaces.around.lambda.arrow=Lambda arrow checkbox.spaces.around.method.ref.dbl.colon.arrow=Method reference double colon settings.code.style.general.formatter.control=Formatter Control settings.code.style.general.enable.formatter.tags=Enable formatter markers in comments -settings.code.style.general.formatter.off.tag="Formatter off" marker\: -settings.code.style.general.formatter.on.tag="Formatter on" marker\: -settings.code.style.general.formatter.marker.regexp=Accept regular expressions -settings.code.style.general.formatter.marker.invalid.regexp=Invalid regular expression
\ No newline at end of file +settings.code.style.general.formatter.off.tag=Formatter off\: +settings.code.style.general.formatter.on.tag=Formatter on\: +settings.code.style.general.formatter.marker.regexp=Regular expressions +settings.code.style.general.formatter.marker.invalid.regexp=Invalid regular expression +settings.code.style.general.formatter.marker.title=Markers +settings.code.style.general.formatter.marker.options.title=Options
\ No newline at end of file diff --git a/platform/platform-resources-en/src/messages/IdeBundle.properties b/platform/platform-resources-en/src/messages/IdeBundle.properties index 67349d01c798..b89d9d33ba5f 100644 --- a/platform/platform-resources-en/src/messages/IdeBundle.properties +++ b/platform/platform-resources-en/src/messages/IdeBundle.properties @@ -327,6 +327,7 @@ title.new.annotation.type=New @interface title.cannot.create.annotation.type=Cannot Create @interface action.create.new.class=Create New Class action.create.new.class.description=Create new Java class +action.create.new.package-info.description=Create new package-info.java prompt.enter.new.class.name=Enter a new class name: title.new.class=New Class progress.creating.class=Creating class {0} @@ -630,6 +631,7 @@ checkbox.show.tool.window.bars=Show tool window bars checkbox.widescreen.tool.window.layout=Widescreen tool window layout checkbox.left.toolwindow.layout=Side-by-side layout on the left checkbox.right.toolwindow.layout=Side-by-side layout on the right +checkbox.show.editor.preview.popup=Show editor preview tooltip checkbox.show.tool.window.numbers=Show tool window numbers checkbox.animate.windows=Animate windows group.transparency=Transparency @@ -649,6 +651,7 @@ checkbox.use.antialiased.font.in.editor=Use anti-aliased font # this string must start with "IDEA" idea.default.look.and.feel=IDEA (4.5 default) +idea.intellij.look.and.feel=IntelliJ idea.dark.look.and.feel=Darcula confirm.set.look.and.feel=Change &theme error.cannot.set.look.and.feel=<html><body>Cannot set {0} theme:<br>{1}</body></html> diff --git a/platform/platform-resources-en/src/messages/SMTestsRunnerBundle.properties b/platform/platform-resources-en/src/messages/SMTestsRunnerBundle.properties index 6965dd6640e3..51a4ef7ed62d 100644 --- a/platform/platform-resources-en/src/messages/SMTestsRunnerBundle.properties +++ b/platform/platform-resources-en/src/messages/SMTestsRunnerBundle.properties @@ -13,7 +13,7 @@ sm.test.runner.ui.tests.tree.presentation.labels.instantiating.tests=Instantiati sm.test.runner.ui.tests.tree.presentation.labels.not.test.results=No Test Results sm.test.runner.ui.tests.tree.presentation.labels.was.terminated=Terminated sm.test.runner.ui.tests.tree.presentation.labels.no.tests.were.found=No tests were found -sm.test.runner.ui.tests.tree.presentation.labels.test.reporter.not.attached=Unable to attach test reporter to test framework or test framework quit unexpectedly +sm.test.runner.ui.tests.tree.presentation.labels.test.reporter.not.attached=Test framework quit unexpectedly sm.test.runner.ui.tests.tree.presentation.labels.no.tests.were.found.with.errors=No tests were found. Errors occurred sm.test.runner.ui.tests.tree.presentation.labels.empty.test.suite=Empty test suite sm.test.runner.ui.tests.tree.presentation.labels.all.tests.passed=All Tests Passed diff --git a/platform/platform-resources-en/src/messages/XDebuggerBundle.properties b/platform/platform-resources-en/src/messages/XDebuggerBundle.properties index 8e0d36d3e49a..985993c4c910 100644 --- a/platform/platform-resources-en/src/messages/XDebuggerBundle.properties +++ b/platform/platform-resources-en/src/messages/XDebuggerBundle.properties @@ -57,6 +57,7 @@ button.text.code.fragment.mode=Code Fragment &Mode button.text.expression.mode=Expression &Mode xdebugger.label.text.code.fragment=Code fragment: xdebugger.evaluate.result=result +xdebugger.evaluate.stack.frame.has.not.evaluator=Cannot evaluate, current stack frame doesn't support evaluation xdebugger.popup.value.tree.set.root.action.tooltip=Set As Root diff --git a/platform/platform-resources-en/src/misc/registry.properties b/platform/platform-resources-en/src/misc/registry.properties index 2116da2c7846..6887f6b5ebf0 100644 --- a/platform/platform-resources-en/src/misc/registry.properties +++ b/platform/platform-resources-en/src/misc/registry.properties @@ -70,7 +70,7 @@ ide.appIcon.progress=true ide.appIcon.badge=true ide.appIcon.requestAttention=true -ide.windowSystem.hScrollChars=15 +ide.windowSystem.hScrollChars=5 ide.windowSystem.vScrollChars=5 ide.windowSystem.focusAppOnStartup=true ide.windowSystem.autoShowProcessPopup=false @@ -325,4 +325,5 @@ bigger.font.in.project.view=false bigger.font.in.project.view.description=Increases font size in Project View darcula.use.native.fonts.on.linux=true darcula.use.native.fonts.on.linux.description=If false, uses DejaVu Sans 13pt +idea.4.5.laf.enabled=true diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index c948106faed7..12869478ffcc 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -299,6 +299,7 @@ <postStartupActivity implementation="org.jetbrains.ide.BuiltInServerManagerImpl$MyPostStartupActivity"/> <editorNotificationProvider implementation="com.intellij.ide.FileChangedNotificationProvider"/> + <editorNotificationProvider implementation="com.intellij.openapi.updateSettings.impl.pluginsAdvertisement.PluginAdvertiserEditorNotificationProvider"/> <getDataRule key="context.ProjectFileDirectory" implementationClass="com.intellij.ide.impl.dataRules.ProjectFileDirectoryRule"/> diff --git a/platform/platform-resources/src/META-INF/VcsExtensionPoints.xml b/platform/platform-resources/src/META-INF/VcsExtensionPoints.xml index 2cc7f4b4022c..274036923c65 100644 --- a/platform/platform-resources/src/META-INF/VcsExtensionPoints.xml +++ b/platform/platform-resources/src/META-INF/VcsExtensionPoints.xml @@ -57,7 +57,5 @@ <extensionPoint name="vcs.taskHandler" interface="com.intellij.openapi.vcs.VcsTaskHandler" area="IDEA_PROJECT"/> <extensionPoint name="vcs.rootFinder" interface="com.intellij.openapi.vcs.VcsRootFinder" area="IDEA_PROJECT"/> - - <extensionPoint name="logProvider" interface="com.intellij.vcs.log.VcsLogProvider" area="IDEA_PROJECT"/> </extensionPoints> </idea-plugin> diff --git a/platform/platform-resources/src/idea/Keymap_Default.xml b/platform/platform-resources/src/idea/Keymap_Default.xml index ff87611f1776..f05fc7e789e3 100644 --- a/platform/platform-resources/src/idea/Keymap_Default.xml +++ b/platform/platform-resources/src/idea/Keymap_Default.xml @@ -3,9 +3,6 @@ <action id="ShowNavBar"> <keyboard-shortcut first-keystroke="alt HOME"/> </action> - <action id="SearchEverywhere"> - <keyboard-shortcut first-keystroke="shift SPACE"/> - </action> <action id="FileChooser.TogglePathShowing"> <keyboard-shortcut first-keystroke="control P"/> </action> diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/FileWatcherTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/FileWatcherTest.java index 4dc17f00dfad..a941eb4c703b 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/FileWatcherTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/FileWatcherTest.java @@ -674,6 +674,12 @@ public class FileWatcherTest extends PlatformLangTestCase { } } + public void testPartialRefresh() throws Exception { + // tests the same scenario with an active file watcher (prevents explicit marking of refreshed paths) + File top = createTestDir("top"); + LocalFileSystemTest.doTestPartialRefresh(top); + } + @NotNull private LocalFileSystem.WatchRequest watch(File watchFile) { diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/LocalFileSystemTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/LocalFileSystemTest.java index 7ee40baacf3f..ca6b5ae32ff3 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/LocalFileSystemTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/vfs/local/LocalFileSystemTest.java @@ -437,4 +437,40 @@ public class LocalFileSystemTest extends PlatformLangTestCase { assertTrue(sourceFile.isValid()); assertEquals(newName, sourceFile.getName()); } + + public void testPartialRefresh() throws Exception { + File top = createTempDirectory(false); + doTestPartialRefresh(top); + } + + public static void doTestPartialRefresh(File top) throws IOException { + File sub = IoTestUtil.createTestDir(top, "sub"); + File file = IoTestUtil.createTestFile(top, "sub.txt"); + LocalFileSystem lfs = LocalFileSystem.getInstance(); + NewVirtualFile topDir = (NewVirtualFile)lfs.refreshAndFindFileByIoFile(top); + assertNotNull(topDir); + NewVirtualFile subDir = (NewVirtualFile)lfs.refreshAndFindFileByIoFile(sub); + assertNotNull(subDir); + NewVirtualFile subFile = (NewVirtualFile)lfs.refreshAndFindFileByIoFile(file); + assertNotNull(subFile); + topDir.refresh(false, true); + assertFalse(topDir.isDirty()); + assertFalse(subDir.isDirty()); + assertFalse(subFile.isDirty()); + + subFile.markDirty(); + subDir.markDirty(); + assertTrue(topDir.isDirty()); + assertTrue(subFile.isDirty()); + assertTrue(subDir.isDirty()); + + topDir.refresh(false, false); + assertFalse(subFile.isDirty()); + assertTrue(subDir.isDirty()); // should stay unvisited after non-recursive refresh + + topDir.refresh(false, true); + assertFalse(topDir.isDirty()); + assertFalse(subFile.isDirty()); + assertFalse(subDir.isDirty()); + } } diff --git a/platform/projectModel-api/src/com/intellij/openapi/projectRoots/ProjectJdkTable.java b/platform/projectModel-api/src/com/intellij/openapi/projectRoots/ProjectJdkTable.java index 8fee9a1a98da..8c95d40fb068 100644 --- a/platform/projectModel-api/src/com/intellij/openapi/projectRoots/ProjectJdkTable.java +++ b/platform/projectModel-api/src/com/intellij/openapi/projectRoots/ProjectJdkTable.java @@ -93,5 +93,5 @@ public abstract class ProjectJdkTable { public abstract Sdk createSdk(final String name, final SdkTypeId sdkType); - public static Topic<Listener> JDK_TABLE_TOPIC = Topic.create("Project JDK table", Listener.class); + public static final Topic<Listener> JDK_TABLE_TOPIC = Topic.create("Project JDK table", Listener.class); } diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/PushedFilePropertiesUpdater.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/PushedFilePropertiesUpdater.java index 6d5d68f636e2..12cadcd8e54c 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/PushedFilePropertiesUpdater.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/PushedFilePropertiesUpdater.java @@ -115,12 +115,7 @@ public class PushedFilePropertiesUpdater { ProjectRootManager.getInstance(project).getFileIndex().iterateContentUnderDirectory(dir, new ContentIterator() { @Override public boolean processFile(final VirtualFile fileOrDir) { - final boolean isDir = fileOrDir.isDirectory(); - for (FilePropertyPusher<Object> pusher : pushers) { - if (!isDir && (pusher.pushDirectoriesOnly() || !pusher.acceptsFile(fileOrDir))) continue; - else if (isDir && !pusher.acceptsDirectory(fileOrDir, myProject)) continue; - findAndUpdateValue(project, fileOrDir, pusher, null); - } + applyPushersToFile(fileOrDir, pushers, null); return true; } }); @@ -159,13 +154,7 @@ public class PushedFilePropertiesUpdater { index.iterateContentUnderDirectory(root, new ContentIterator() { @Override public boolean processFile(final VirtualFile fileOrDir) { - final boolean isDir = fileOrDir.isDirectory(); - for (int i = 0, pushersLength = pushers.length; i < pushersLength; i++) { - final FilePropertyPusher<Object> pusher = pushers[i]; - if (!isDir && (pusher.pushDirectoriesOnly() || !pusher.acceptsFile(fileOrDir))) continue; - else if (isDir && !pusher.acceptsDirectory(fileOrDir, myProject)) continue; - findAndUpdateValue(myProject, fileOrDir, pusher, moduleValues[i]); - } + applyPushersToFile(fileOrDir, pushers, moduleValues); return true; } }); @@ -173,6 +162,16 @@ public class PushedFilePropertiesUpdater { } } + private void applyPushersToFile(VirtualFile fileOrDir, FilePropertyPusher[] pushers, Object[] moduleValues) { + final boolean isDir = fileOrDir.isDirectory(); + for (int i = 0, pushersLength = pushers.length; i < pushersLength; i++) { + final FilePropertyPusher<Object> pusher = pushers[i]; + if (!isDir && (pusher.pushDirectoriesOnly() || !pusher.acceptsFile(fileOrDir))) continue; + else if (isDir && !pusher.acceptsDirectory(fileOrDir, myProject)) continue; + findAndUpdateValue(myProject, fileOrDir, pusher, moduleValues != null ? moduleValues[i]:null); + } + } + public static <T> void findAndUpdateValue(final Project project, final VirtualFile fileOrDir, final FilePropertyPusher<T> pusher, final T moduleValue) { final T value = findPusherValuesUpwards(project, fileOrDir, pusher, moduleValue); updateValue(fileOrDir, value, pusher); diff --git a/platform/smRunner/testSrc/com/intellij/execution/testframework/sm/runner/ui/TestsPresentationUtilTest.java b/platform/smRunner/testSrc/com/intellij/execution/testframework/sm/runner/ui/TestsPresentationUtilTest.java index ad5d8f0cbd71..a263bb186ef9 100644 --- a/platform/smRunner/testSrc/com/intellij/execution/testframework/sm/runner/ui/TestsPresentationUtilTest.java +++ b/platform/smRunner/testSrc/com/intellij/execution/testframework/sm/runner/ui/TestsPresentationUtilTest.java @@ -17,6 +17,7 @@ package com.intellij.execution.testframework.sm.runner.ui; import com.intellij.execution.executors.DefaultDebugExecutor; import com.intellij.execution.testframework.PoolOfTestIcons; +import com.intellij.execution.testframework.sm.SMTestsRunnerBundle; import com.intellij.execution.testframework.sm.UITestUtil; import com.intellij.execution.testframework.sm.runner.BaseSMTRunnerTestCase; import com.intellij.execution.testframework.sm.runner.SMTRunnerConsoleProperties; @@ -575,7 +576,7 @@ public class TestsPresentationUtilTest extends BaseSMTRunnerTestCase { assertEquals(PoolOfTestIcons.NOT_RAN, myRenderer.getIcon()); assertOneElement(myFragContainer.getFragments()); - assertEquals("Unable to attach test reporter to test framework or test framework quit unexpectedly", myFragContainer.getTextAt(0)); + assertEquals(SMTestsRunnerBundle.message("sm.test.runner.ui.tests.tree.presentation.labels.test.reporter.not.attached"), myFragContainer.getTextAt(0)); assertEquals(SimpleTextAttributes.ERROR_ATTRIBUTES, myFragContainer.getAttribsAt(0)); } diff --git a/platform/testFramework/src/com/intellij/testFramework/FileStructureTestBase.java b/platform/testFramework/src/com/intellij/testFramework/FileStructureTestBase.java index 269ed4ca8546..e31569a394a6 100644 --- a/platform/testFramework/src/com/intellij/testFramework/FileStructureTestBase.java +++ b/platform/testFramework/src/com/intellij/testFramework/FileStructureTestBase.java @@ -42,10 +42,9 @@ public abstract class FileStructureTestBase extends CodeInsightFixtureTestCase { public void setUp() throws Exception { super.setUp(); myFixture.configureByFile(getFileName(getFileExtension())); - myPopup = ViewStructureAction.createPopup(myFixture.getEditor(), - myFixture.getProject(), - null, - TextEditorProvider.getInstance().getTextEditor(myFixture.getEditor())); + myPopup = ViewStructureAction.createPopup( + myFixture.getProject(), + TextEditorProvider.getInstance().getTextEditor(myFixture.getEditor())); assert myPopup != null; myPopup.createCenterPanel(); getBuilder().getUi().getUpdater().setPassThroughMode(true); diff --git a/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java b/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java index ee0051b80099..fac110eec634 100644 --- a/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java +++ b/platform/testFramework/src/com/intellij/testFramework/PlatformTestUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -44,6 +44,7 @@ import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileFilter; +import com.intellij.openapi.vfs.ex.temp.TempFileSystem; import com.intellij.util.Alarm; import com.intellij.util.ArrayUtil; import com.intellij.util.ThrowableRunnable; @@ -608,13 +609,14 @@ public class PlatformTestUtil { FileDocumentManager.getInstance().saveAllDocuments(); VirtualFile[] childrenAfter = dirAfter.getChildren(); - if (dirAfter.isInLocalFileSystem()) { + + if (dirAfter.isInLocalFileSystem() && dirAfter.getFileSystem() != TempFileSystem.getInstance()) { File[] ioAfter = new File(dirAfter.getPath()).listFiles(); shallowCompare(childrenAfter, ioAfter); } VirtualFile[] childrenBefore = dirBefore.getChildren(); - if (dirBefore.isInLocalFileSystem()) { + if (dirBefore.isInLocalFileSystem() && dirBefore.getFileSystem() != TempFileSystem.getInstance()) { File[] ioBefore = new File(dirBefore.getPath()).listFiles(); shallowCompare(childrenBefore, ioBefore); } diff --git a/platform/testFramework/src/com/intellij/testFramework/PsiTestUtil.java b/platform/testFramework/src/com/intellij/testFramework/PsiTestUtil.java index 7d3567902f14..fd1e91d428eb 100644 --- a/platform/testFramework/src/com/intellij/testFramework/PsiTestUtil.java +++ b/platform/testFramework/src/com/intellij/testFramework/PsiTestUtil.java @@ -333,7 +333,7 @@ public class PsiTestUtil { final String[] classRoots, final String[] sourceRoots) { final String parentUrl = - VirtualFileManager.constructUrl(classRoots[0].endsWith(".jar!/") ? JarFileSystem.PROTOCOL : LocalFileSystem.PROTOCOL, libDir); + VirtualFileManager.constructUrl((classRoots.length > 0 ? classRoots[0]:sourceRoots[0]).endsWith(".jar!/") ? JarFileSystem.PROTOCOL : LocalFileSystem.PROTOCOL, libDir); List<String> classesUrls = new ArrayList<String>(); List<String> sourceUrls = new ArrayList<String>(); for (String classRoot : classRoots) { diff --git a/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestUtil.java b/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestUtil.java index 464b268d2331..dd06c0d0a23d 100644 --- a/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestUtil.java +++ b/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -43,14 +43,18 @@ import com.intellij.psi.PsiElement; import com.intellij.psi.codeStyle.CodeStyleManager; import com.intellij.refactoring.rename.inplace.InplaceRefactoring; import com.intellij.refactoring.rename.inplace.VariableInplaceRenameHandler; +import com.intellij.testFramework.TestDataFile; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; import org.junit.Assert; +import java.io.File; import java.util.List; +import static junit.framework.Assert.assertTrue; + /** * @author Dmitry Avdeev */ @@ -90,7 +94,7 @@ public class CodeInsightTestUtil { } public static void doWordSelectionTest(@NotNull final CodeInsightTestFixture fixture, - @NotNull final String before, final String... after) { + @TestDataFile @NotNull final String before, @TestDataFile final String... after) { assert after != null && after.length > 0; fixture.configureByFile(before); @@ -101,6 +105,27 @@ public class CodeInsightTestUtil { fixture.checkResultByFile(file, false); } } + + public static void doWordSelectionTestOnDirectory(@NotNull final CodeInsightTestFixture fixture, + @TestDataFile @NotNull final String directoryName, + @NotNull final String filesExtension) { + final SelectWordHandler action = new SelectWordHandler(null); + fixture.copyDirectoryToProject(directoryName, directoryName); + fixture.configureByFile(directoryName + "/before." + filesExtension); + int i = 1; + while (true) { + final String fileName = directoryName + "/after" + i + "." + filesExtension; + if (new File(fixture.getTestDataPath() + "/" + fileName).exists()) { + action.execute(fixture.getEditor(), DataManager.getInstance().getDataContext(fixture.getEditor().getComponent())); + fixture.checkResultByFile(fileName); + i++; + } + else { + break; + } + } + assertTrue("At least one 'after'-file required", i > 1); + } public static void doSurroundWithTest(@NotNull final CodeInsightTestFixture fixture, @NotNull final Surrounder surrounder, @NotNull final String before, @NotNull final String after) { diff --git a/platform/util-rt/src/com/intellij/openapi/util/io/FileUtilRt.java b/platform/util-rt/src/com/intellij/openapi/util/io/FileUtilRt.java index 3e8395d82c8c..d63c9bca7f48 100644 --- a/platform/util-rt/src/com/intellij/openapi/util/io/FileUtilRt.java +++ b/platform/util-rt/src/com/intellij/openapi/util/io/FileUtilRt.java @@ -444,19 +444,15 @@ public class FileUtilRt { public static boolean delete(@NotNull File file) { if (file.isDirectory()) { - if (!deleteChildren(file)) return false; - } - return deleteFile(file); - } - - protected static boolean deleteChildren(@NotNull File file) { - File[] files = file.listFiles(); - if (files != null) { - for (File child : files) { - if (!delete(child)) return false; + File[] files = file.listFiles(); + if (files != null) { + for (File child : files) { + if (!delete(child)) return false; + } } } - return true; + + return deleteFile(file); } public interface RepeatableIOOperation<T, E extends Throwable> { diff --git a/platform/util/src/com/intellij/icons/AllIcons.java b/platform/util/src/com/intellij/icons/AllIcons.java index 0bd1a9c89f99..8027d91d483a 100644 --- a/platform/util/src/com/intellij/icons/AllIcons.java +++ b/platform/util/src/com/intellij/icons/AllIcons.java @@ -69,6 +69,7 @@ public class AllIcons { public static final Icon Filter_small = IconLoader.getIcon("/actions/filter_small.png"); // 16x16 public static final Icon Find = IconLoader.getIcon("/actions/find.png"); // 16x16 public static final Icon FindPlain = IconLoader.getIcon("/actions/findPlain.png"); // 16x16 + public static final Icon FindWhite = IconLoader.getIcon("/actions/findWhite.png"); // 16x16 public static final Icon ForceRefresh = IconLoader.getIcon("/actions/forceRefresh.png"); // 16x16 public static final Icon Forward = IconLoader.getIcon("/actions/forward.png"); // 16x16 public static final Icon GC = IconLoader.getIcon("/actions/gc.png"); // 16x16 @@ -856,8 +857,10 @@ public class AllIcons { public static final Icon TestSourceFolder = IconLoader.getIcon("/nodes/testSourceFolder.png"); // 16x16 public static final Icon TreeClosed = IconLoader.getIcon("/nodes/TreeClosed.png"); // 16x16 public static final Icon TreeCollapseNode = IconLoader.getIcon("/nodes/treeCollapseNode.png"); // 16x16 + public static final Icon TreeDownArrow = IconLoader.getIcon("/nodes/treeDownArrow.png"); // 11x11 public static final Icon TreeExpandNode = IconLoader.getIcon("/nodes/treeExpandNode.png"); // 16x16 public static final Icon TreeOpen = IconLoader.getIcon("/nodes/TreeOpen.png"); // 16x16 + public static final Icon TreeRightArrow = IconLoader.getIcon("/nodes/treeRightArrow.png"); // 11x11 public static final Icon Undeploy = IconLoader.getIcon("/nodes/undeploy.png"); // 16x16 public static final Icon UnknownJdk = IconLoader.getIcon("/nodes/unknownJdk.png"); // 16x16 public static final Icon UpFolder = IconLoader.getIcon("/nodes/upFolder.png"); // 16x16 diff --git a/platform/util/src/com/intellij/openapi/util/NullableComputable.java b/platform/util/src/com/intellij/openapi/util/NullableComputable.java index d87ef8668dc8..be75adbfd538 100644 --- a/platform/util/src/com/intellij/openapi/util/NullableComputable.java +++ b/platform/util/src/com/intellij/openapi/util/NullableComputable.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -22,6 +22,7 @@ package com.intellij.openapi.util; import org.jetbrains.annotations.Nullable; public interface NullableComputable<T> extends Computable<T> { + @Override @Nullable T compute(); }
\ No newline at end of file diff --git a/platform/util/src/com/intellij/openapi/util/io/FileAttributes.java b/platform/util/src/com/intellij/openapi/util/io/FileAttributes.java index 1064b748c2e0..910f3add5742 100644 --- a/platform/util/src/com/intellij/openapi/util/io/FileAttributes.java +++ b/platform/util/src/com/intellij/openapi/util/io/FileAttributes.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2013 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,25 +29,27 @@ public final class FileAttributes { public enum Type { FILE, DIRECTORY, SPECIAL } public static final byte SYM_LINK = 0x01; - public static final byte HIDDEN = 0x20; + public static final byte HIDDEN = 0x02; + public static final byte READ_ONLY = 0x04; - @MagicConstant(flags = {SYM_LINK, HIDDEN}) + @MagicConstant(flags = {SYM_LINK, HIDDEN, READ_ONLY}) public @interface Flags { } - public static final int OWNER_READ = 0400; - public static final int OWNER_WRITE = 0200; - public static final int OWNER_EXECUTE = 0100; - public static final int GROUP_READ = 0040; - public static final int GROUP_WRITE = 0020; - public static final int GROUP_EXECUTE = 0010; - public static final int OTHERS_READ = 0004; - public static final int OTHERS_WRITE = 0002; - public static final int OTHERS_EXECUTE = 0001; - - @MagicConstant(flags = {OWNER_READ, OWNER_WRITE, OWNER_EXECUTE, GROUP_READ, GROUP_WRITE, GROUP_EXECUTE, OTHERS_READ, OTHERS_WRITE, OTHERS_EXECUTE}) + /** @deprecated (to remove in IDEA 14) */ @SuppressWarnings("unused") public static final int OWNER_READ = 0400; + /** @deprecated (to remove in IDEA 14) */ @SuppressWarnings("unused") public static final int OWNER_WRITE = 0200; + /** @deprecated (to remove in IDEA 14) */ @SuppressWarnings("unused") public static final int OWNER_EXECUTE = 0100; + /** @deprecated (to remove in IDEA 14) */ @SuppressWarnings("unused") public static final int GROUP_READ = 0040; + /** @deprecated (to remove in IDEA 14) */ @SuppressWarnings("unused") public static final int GROUP_WRITE = 0020; + /** @deprecated (to remove in IDEA 14) */ @SuppressWarnings("unused") public static final int GROUP_EXECUTE = 0010; + /** @deprecated (to remove in IDEA 14) */ @SuppressWarnings("unused") public static final int OTHERS_READ = 0004; + /** @deprecated (to remove in IDEA 14) */ @SuppressWarnings("unused") public static final int OTHERS_WRITE = 0002; + /** @deprecated (to remove in IDEA 14) */ @SuppressWarnings("unused") public static final int OTHERS_EXECUTE = 0001; + + /** @deprecated (to remove in IDEA 14) */ + @SuppressWarnings("unused") public @interface Permissions { } - public static final FileAttributes BROKEN_SYMLINK = new FileAttributes(null, SYM_LINK, 0, 0, -1); + public static final FileAttributes BROKEN_SYMLINK = new FileAttributes(null, SYM_LINK, 0, 0); /** * {@code null} means unknown type - typically broken symlink. @@ -70,55 +72,39 @@ public final class FileAttributes { */ public final long lastModified; - /** - * UNIX permission bits (for Windows only OWNER_WRITE matters and OWNER_READ/EXECUTE are always set), or {@code -1} if not supported.<br/> - * For symlinks - permissions of a link target. - */ - @Permissions - public final int permissions; - - - public FileAttributes(final boolean isDirectory, - final boolean isSpecial, - final boolean isSymlink, - final boolean isHidden, - final long length, - final long lastModified, - final boolean isWritable) { - this(type(isDirectory, isSpecial), flags(isSymlink, isHidden), length, lastModified, - OWNER_READ | OWNER_EXECUTE | (isWritable ? OWNER_WRITE : 0)); + /** @deprecated use {@linkplain #isWritable()} (to remove in IDEA 14) */ + @SuppressWarnings("unused") public final int permissions; + + + public FileAttributes(boolean directory, boolean special, boolean symlink, boolean hidden, long length, long lastModified, boolean writable) { + this(type(directory, special), flags(symlink, hidden, !writable), length, lastModified); } - public FileAttributes(final boolean isDirectory, - final boolean isSpecial, - final boolean isSymlink, - final long length, - final long lastModified, - @Permissions final int permissions) { - this(type(isDirectory, isSpecial), flags(isSymlink, false), length, lastModified, permissions); + /** @deprecated use {@linkplain #FileAttributes(boolean, boolean, boolean, boolean, long, long, boolean)} (to remove in IDEA 14) */ + @SuppressWarnings("unused") + public FileAttributes(boolean directory, boolean special, boolean symlink, long length, long lastModified, int permissions) { + this(type(directory, special), flags(symlink, false, true), length, lastModified); } - private FileAttributes(@Nullable final Type type, - @Flags final byte flags, - final long length, - final long lastModified, - @Permissions final int permissions) { + @SuppressWarnings("deprecation") + private FileAttributes(@Nullable Type type, @Flags byte flags, long length, long lastModified) { this.type = type; this.flags = flags; this.length = length; this.lastModified = lastModified; - this.permissions = permissions; + this.permissions = -1; } - private static Type type(final boolean isDirectory, final boolean isSpecial) { + private static Type type(boolean isDirectory, boolean isSpecial) { return isDirectory ? Type.DIRECTORY : isSpecial ? Type.SPECIAL : Type.FILE; } @Flags - private static byte flags(final boolean isSymlink, final boolean isHidden) { + private static byte flags(boolean isSymlink, boolean isHidden, boolean isReadOnly) { @Flags byte flags = 0; if (isSymlink) flags |= SYM_LINK; if (isHidden) flags |= HIDDEN; + if (isReadOnly) flags |= READ_ONLY; return flags; } @@ -143,7 +129,7 @@ public final class FileAttributes { } public boolean isWritable() { - return permissions == -1 || isSet(permissions, OWNER_WRITE) || isSet(permissions, GROUP_WRITE) || isSet(permissions, OTHERS_WRITE); + return !isSet(flags, READ_ONLY); } @Override @@ -155,7 +141,6 @@ public final class FileAttributes { if (flags != that.flags) return false; if (lastModified != that.lastModified) return false; if (length != that.length) return false; - if (permissions != that.permissions) return false; if (type != that.type) return false; return true; @@ -167,13 +152,13 @@ public final class FileAttributes { result = 31 * result + (int)flags; result = 31 * result + (int)(length ^ (length >>> 32)); result = 31 * result + (int)(lastModified ^ (lastModified >>> 32)); - result = 31 * result + permissions; return result; } @Override public String toString() { - final StringBuilder sb = new StringBuilder(); + StringBuilder sb = new StringBuilder(); + sb.append("[type:"); if (type == Type.FILE) sb.append('f'); else if (type == Type.DIRECTORY) sb.append('d'); @@ -183,14 +168,12 @@ public final class FileAttributes { if (isSet(flags, SYM_LINK)) sb.append('l'); if (isSet(flags, HIDDEN)) sb.append('.'); + if (isSet(flags, READ_ONLY)) sb.append(" ro"); + sb.append(" length:").append(length); sb.append(" modified:").append(lastModified); - if (permissions != -1) { - sb.append(" mode:").append(Integer.toOctalString(permissions)); - } - sb.append(']'); return sb.toString(); } diff --git a/platform/util/src/com/intellij/openapi/util/io/FileSystemUtil.java b/platform/util/src/com/intellij/openapi/util/io/FileSystemUtil.java index 55e09d3db198..1908f811e94c 100644 --- a/platform/util/src/com/intellij/openapi/util/io/FileSystemUtil.java +++ b/platform/util/src/com/intellij/openapi/util/io/FileSystemUtil.java @@ -49,17 +49,17 @@ public class FileSystemUtil { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.util.io.FileSystemUtil"); - private interface Mediator { + private abstract static class Mediator { @Nullable - FileAttributes getAttributes(@NotNull String path) throws Exception; + protected abstract FileAttributes getAttributes(@NotNull String path) throws Exception; @Nullable - String resolveSymLink(@NotNull String path) throws Exception; + protected abstract String resolveSymLink(@NotNull String path) throws Exception; - void setPermissions(@NotNull String path, int permissions) throws Exception; + protected boolean clonePermissions(@NotNull String source, @NotNull String target) throws Exception { return false; } @NotNull - String getName(); + private String getName() { return getClass().getSimpleName().replace("MediatorImpl", ""); } } @NotNull @@ -101,7 +101,7 @@ public class FileSystemUtil { "Failed to load filesystem access layer (" + SystemInfo.OS_NAME + ", " + SystemInfo.JAVA_VERSION + ", " + forceUseNio2 + ")"; LOG.error(message, error); - return new StandardMediatorImpl(); + return new FallbackMediatorImpl(); } private static Mediator check(final Mediator mediator) throws Exception { @@ -110,16 +110,6 @@ public class FileSystemUtil { return mediator; } - @TestOnly - static void resetMediator() { - ourMediator = getMediator(); - } - - @TestOnly - static String getMediatorName() { - return ourMediator.getName(); - } - private FileSystemUtil() { } @Nullable @@ -180,35 +170,42 @@ public class FileSystemUtil { return resolveSymLink(file.getAbsolutePath()); } + /** @deprecated use {@link #clonePermissions(String, String)} (to remove in IDEA 14) */ + @SuppressWarnings("UnusedDeclaration") public static int getPermissions(@NotNull String path) { - final FileAttributes attributes = getAttributes(path); - return attributes != null ? attributes.permissions : -1; + return -1; } + /** @deprecated use {@link #clonePermissions(String, String)} (to remove in IDEA 14) */ @SuppressWarnings("UnusedDeclaration") public static int getPermissions(@NotNull File file) { - return getPermissions(file.getAbsolutePath()); + return -1; } - public static void setPermissions(@NotNull String path, int permissions) { - if (SystemInfo.isUnix) { - try { - ourMediator.setPermissions(path, permissions); - } - catch (Exception e) { - LOG.warn(e); - } - } - } + /** @deprecated use {@link #clonePermissions(String, String)} (to remove in IDEA 14) */ + @SuppressWarnings("UnusedDeclaration") + public static void setPermissions(@NotNull String path, int permissions) { } + + /** @deprecated use {@link #clonePermissions(String, String)} (to remove in IDEA 14) */ + @SuppressWarnings({"UnusedDeclaration", "deprecation"}) + public static void setPermissions(@NotNull File file, int permissions) { } - public static void setPermissions(@NotNull File file, int permissions) { - setPermissions(file.getAbsolutePath(), permissions); + /** + * Gives the second file permissions of the first one if possible; returns true if succeed. + * Will do nothing on Windows. + */ + public static boolean clonePermissions(@NotNull String source, @NotNull String target) { + try { + return ourMediator.clonePermissions(source, target); + } + catch (Exception e) { + LOG.warn(e); + return false; + } } - // todo[r.sh] remove reflection after migration to JDK 7 - @SuppressWarnings("OctalInteger") - private static class Nio2MediatorImpl implements Mediator { + private static class Nio2MediatorImpl extends Mediator { private final Object myDefaultFileSystem; private final Method myGetPath; private final Method myIsSymbolicLink; @@ -220,10 +217,13 @@ public class FileSystemUtil { private final String mySchema; private Nio2MediatorImpl() throws Exception { - if (Patches.USE_REFLECTION_TO_ACCESS_JDK7) { + //noinspection ConstantConditions + assert Patches.USE_REFLECTION_TO_ACCESS_JDK7; + myDefaultFileSystem = Class.forName("java.nio.file.FileSystems").getMethod("getDefault").invoke(null); - myGetPath = Class.forName("java.nio.file.FileSystem").getMethod("getPath", String.class, String[].class); + final Class<?> fsClass = Class.forName("java.nio.file.FileSystem"); + myGetPath = fsClass.getMethod("getPath", String.class, String[].class); myGetPath.setAccessible(true); final Class<?> pathClass = Class.forName("java.nio.file.Path"); @@ -247,16 +247,15 @@ public class FileSystemUtil { myToMillis.setAccessible(true); mySchema = SystemInfo.isWindows ? "dos:*" : "posix:*"; - } } @Override - public FileAttributes getAttributes(@NotNull final String path) throws Exception { + protected FileAttributes getAttributes(@NotNull String path) throws Exception { try { - final Object pathObj = myGetPath.invoke(myDefaultFileSystem, path, ArrayUtil.EMPTY_STRING_ARRAY); + Object pathObj = myGetPath.invoke(myDefaultFileSystem, path, ArrayUtil.EMPTY_STRING_ARRAY); Map attributes = (Map)myReadAttributes.invoke(null, pathObj, mySchema, myNoFollowLinkOptions); - final boolean isSymbolicLink = (Boolean)attributes.get("isSymbolicLink"); + boolean isSymbolicLink = (Boolean)attributes.get("isSymbolicLink"); if (isSymbolicLink) { try { attributes = (Map)myReadAttributes.invoke(null, pathObj, mySchema, myLinkOptions); @@ -269,18 +268,18 @@ public class FileSystemUtil { } } - final boolean isDirectory = (Boolean)attributes.get("isDirectory"); - final boolean isOther = (Boolean)attributes.get("isOther"); - final long size = (Long)attributes.get("size"); - final long lastModified = (Long)myToMillis.invoke(attributes.get("lastModifiedTime")); + boolean isDirectory = (Boolean)attributes.get("isDirectory"); + boolean isOther = (Boolean)attributes.get("isOther"); + long size = (Long)attributes.get("size"); + long lastModified = (Long)myToMillis.invoke(attributes.get("lastModifiedTime")); if (SystemInfo.isWindows) { - final boolean isHidden = (Boolean)attributes.get("hidden"); - final boolean isWritable = !(Boolean)attributes.get("readonly"); + boolean isHidden = new File(path).getParent() == null ? false : (Boolean)attributes.get("hidden"); + boolean isWritable = !(Boolean)attributes.get("readonly"); return new FileAttributes(isDirectory, isOther, isSymbolicLink, isHidden, size, lastModified, isWritable); } else { - final int permissions = decodePermissions(attributes.get("permissions")); - return new FileAttributes(isDirectory, isOther, isSymbolicLink, size, lastModified, permissions); + boolean isWritable = new File(path).canWrite(); + return new FileAttributes(isDirectory, isOther, isSymbolicLink, false, size, lastModified, isWritable); } } catch (InvocationTargetException e) { @@ -295,176 +294,132 @@ public class FileSystemUtil { } @Override - public String resolveSymLink(@NotNull final String path) throws Exception { + protected String resolveSymLink(@NotNull final String path) throws Exception { if (!new File(path).exists()) return null; - assert Patches.USE_REFLECTION_TO_ACCESS_JDK7; - final Object pathObj = myGetPath.invoke(myDefaultFileSystem, path, ArrayUtil.EMPTY_STRING_ARRAY); final Method toRealPath = pathObj.getClass().getMethod("toRealPath", myLinkOptions.getClass()); toRealPath.setAccessible(true); return toRealPath.invoke(pathObj, myLinkOptions).toString(); - } @Override - public void setPermissions(@NotNull final String path, final int permissions) throws Exception { - final Object pathObj = myGetPath.invoke(myDefaultFileSystem, path, ArrayUtil.EMPTY_STRING_ARRAY); - final Object attribute = encodePermissions(permissions); - if (attribute != null) { - mySetAttribute.invoke(null, pathObj, "posix:permissions", attribute, myLinkOptions); - } - } - - @NotNull - @Override - public String getName() { - return "NIO2"; - } - - private static final Map<String, Integer> ATTRIBUTES_MAP; - static { - ATTRIBUTES_MAP = new HashMap<String, Integer>(); - ATTRIBUTES_MAP.put("OWNER_READ", FileAttributes.OWNER_READ); - ATTRIBUTES_MAP.put("OWNER_WRITE", FileAttributes.OWNER_WRITE); - ATTRIBUTES_MAP.put("OWNER_EXECUTE", FileAttributes.OWNER_EXECUTE); - ATTRIBUTES_MAP.put("GROUP_READ", FileAttributes.GROUP_READ); - ATTRIBUTES_MAP.put("GROUP_WRITE", FileAttributes.GROUP_WRITE); - ATTRIBUTES_MAP.put("GROUP_EXECUTE", FileAttributes.GROUP_EXECUTE); - ATTRIBUTES_MAP.put("OTHERS_READ", FileAttributes.OTHERS_READ); - ATTRIBUTES_MAP.put("OTHERS_WRITE", FileAttributes.OTHERS_WRITE); - ATTRIBUTES_MAP.put("OTHERS_EXECUTE", FileAttributes.OTHERS_EXECUTE); - } - - @FileAttributes.Permissions - private static int decodePermissions(final Object o) { - if (!(o instanceof Collection)) return -1; - - int value = 0; - for (Object attr : (Collection)o) { - final Integer bit = ATTRIBUTES_MAP.get(attr.toString()); - if (bit != null) value |= bit; - } - //noinspection MagicConstant - return value; - } - - @Nullable - @SuppressWarnings("unchecked") - private static Object encodePermissions(final int value) { - try { - final Class aClass = Class.forName("java.nio.file.attribute.PosixFilePermission"); - final Set values = new HashSet(); - for (Map.Entry<String, Integer> entry : ATTRIBUTES_MAP.entrySet()) { - if ((value & entry.getValue()) > 0) { - final String name = entry.getKey(); - values.add(Enum.valueOf(aClass, name)); + protected boolean clonePermissions(@NotNull String source, @NotNull String target) throws Exception { + if (SystemInfo.isUnix) { + Object pathObj = myGetPath.invoke(myDefaultFileSystem, source, ArrayUtil.EMPTY_STRING_ARRAY); + Map attributes = (Map)myReadAttributes.invoke(null, pathObj, "posix:permissions", myLinkOptions); + if (attributes != null && SystemProperties.getUserName().equals(attributes.get("posix:owner"))) { + Object permissions = attributes.get("permissions"); + if (permissions instanceof Collection) { + mySetAttribute.invoke(null, pathObj, "posix:permissions", permissions, myLinkOptions); + return true; } } - return values; } - catch (Exception ignore) { } - return null; + return false; } } - private static class IdeaWin32MediatorImpl implements Mediator { + private static class IdeaWin32MediatorImpl extends Mediator { private IdeaWin32 myInstance = IdeaWin32.getInstance(); @Override - public FileAttributes getAttributes(@NotNull final String path) throws Exception { + protected FileAttributes getAttributes(@NotNull final String path) throws Exception { final FileInfo fileInfo = myInstance.getInfo(path); return fileInfo != null ? fileInfo.toFileAttributes() : null; } @Override - public String resolveSymLink(@NotNull final String path) throws Exception { + protected String resolveSymLink(@NotNull final String path) throws Exception { return myInstance.resolveSymLink(path); } - - @Override - public void setPermissions(@NotNull final String path, final int permissions) throws Exception { - } - - @NotNull - @Override - public String getName() { - return "IdeaWin32"; - } } - // thanks to SVNKit for the idea - private static class JnaUnixMediatorImpl implements Mediator { + // thanks to SVNKit for the idea of platform-specific offsets + private static class JnaUnixMediatorImpl extends Mediator { @SuppressWarnings({"OctalInteger", "SpellCheckingInspection"}) private interface LibC extends Library { - // from stat(2) int S_MASK = 0177777; int S_IFLNK = 0120000; // symbolic link int S_IFREG = 0100000; // regular file int S_IFDIR = 0040000; // directory int PERM_MASK = 0777; + int WRITE_MASK = 0222; + int W_OK = 2; // write permission flag for access(2) + int getuid(); + int getgid(); int lstat(String path, Pointer stat); int stat(String path, Pointer stat); int __lxstat64(int ver, String path, Pointer stat); int __xstat64(int ver, String path, Pointer stat); int chmod(String path, int mode); + int access(String path, int mode); } + private static final int[] LINUX_32 = {16, 44, 72, 24, 28}; + private static final int[] LINUX_64 = {24, 48, 88, 28, 32}; + private static final int[] BSD_32 = { 8, 48, 32, 12, 16}; + private static final int[] BSD_64 = { 8, 72, 40, 12, 16}; + private static final int[] SUN_OS_32 = {20, 48, 64, 28, 32}; + private static final int[] SUN_OS_64 = {16, 40, 64, 24, 28}; + + private static final int OFF_MODE = 0; + private static final int OFF_SIZE = 1; + private static final int OFF_TIME = 2; + private static final int OFF_UID = 3; + private static final int OFF_GID = 4; + private final LibC myLibC; - private final int myModeOffset; - private final int mySizeOffset; - private final int myTimeOffset; + private final int[] myOffsets; + private final int myUid; + private final int myGid; private final boolean myCoarseTs = SystemProperties.getBooleanProperty(COARSE_TIMESTAMP, false); private JnaUnixMediatorImpl() throws Exception { - myModeOffset = SystemInfo.isLinux ? (SystemInfo.is32Bit ? 16 : 24) : - SystemInfo.isMac | SystemInfo.isFreeBSD ? 8 : - SystemInfo.isSolaris ? (SystemInfo.is32Bit ? 20 : 16) : - -1; - mySizeOffset = SystemInfo.isLinux ? (SystemInfo.is32Bit ? 44 : 48) : - SystemInfo.isMac | SystemInfo.isFreeBSD ? (SystemInfo.is32Bit ? 48 : 72) : - SystemInfo.isSolaris ? (SystemInfo.is32Bit ? 48 : 40) : - -1; - myTimeOffset = SystemInfo.isLinux ? (SystemInfo.is32Bit ? 72 : 88) : - SystemInfo.isMac | SystemInfo.isFreeBSD ? (SystemInfo.is32Bit ? 32 : 40) : - SystemInfo.isSolaris ? 64 : - -1; - if (myModeOffset < 0) throw new IllegalStateException("Unsupported OS: " + SystemInfo.OS_NAME); + myOffsets = SystemInfo.isLinux ? (SystemInfo.is32Bit ? LINUX_32 : LINUX_64) : + SystemInfo.isMac | SystemInfo.isFreeBSD ? (SystemInfo.is32Bit ? BSD_32 : BSD_64) : + SystemInfo.isSolaris ? (SystemInfo.is32Bit ? SUN_OS_32 : SUN_OS_64) : + null; + if (myOffsets == null || myOffsets.length != 5) throw new IllegalStateException("Unsupported OS: " + SystemInfo.OS_NAME); myLibC = (LibC)Native.loadLibrary("c", LibC.class); + myUid = myLibC.getuid(); + myGid = myLibC.getgid(); } @Override - public FileAttributes getAttributes(@NotNull final String path) throws Exception { + protected FileAttributes getAttributes(@NotNull String path) throws Exception { Memory buffer = new Memory(256); int res = SystemInfo.isLinux ? myLibC.__lxstat64(0, path, buffer) : myLibC.lstat(path, buffer); if (res != 0) return null; - int mode = (SystemInfo.isLinux ? buffer.getInt(myModeOffset) : buffer.getShort(myModeOffset)) & LibC.S_MASK; + int mode = (SystemInfo.isLinux ? buffer.getInt(myOffsets[OFF_MODE]) : buffer.getShort(myOffsets[OFF_MODE])) & LibC.S_MASK; boolean isSymlink = (mode & LibC.S_IFLNK) == LibC.S_IFLNK; if (isSymlink) { res = SystemInfo.isLinux ? myLibC.__xstat64(0, path, buffer) : myLibC.stat(path, buffer); if (res != 0) { return FileAttributes.BROKEN_SYMLINK; } - mode = (SystemInfo.isLinux ? buffer.getInt(myModeOffset) : buffer.getShort(myModeOffset)) & LibC.S_MASK; + mode = (SystemInfo.isLinux ? buffer.getInt(myOffsets[OFF_MODE]) : buffer.getShort(myOffsets[OFF_MODE])) & LibC.S_MASK; } boolean isDirectory = (mode & LibC.S_IFDIR) == LibC.S_IFDIR; boolean isSpecial = !isDirectory && (mode & LibC.S_IFREG) == 0; - long size = buffer.getLong(mySizeOffset); - long mTime1 = SystemInfo.is32Bit ? buffer.getInt(myTimeOffset) : buffer.getLong(myTimeOffset); - long mTime2 = myCoarseTs ? 0 : SystemInfo.is32Bit ? buffer.getInt(myTimeOffset + 4) : buffer.getLong(myTimeOffset + 8); + long size = buffer.getLong(myOffsets[OFF_SIZE]); + long mTime1 = SystemInfo.is32Bit ? buffer.getInt(myOffsets[OFF_TIME]) : buffer.getLong(myOffsets[OFF_TIME]); + long mTime2 = myCoarseTs ? 0 : SystemInfo.is32Bit ? buffer.getInt(myOffsets[OFF_TIME] + 4) : buffer.getLong(myOffsets[OFF_TIME] + 8); long mTime = mTime1 * 1000 + mTime2 / 1000000; - @FileAttributes.Permissions int permissions = mode & LibC.PERM_MASK; - return new FileAttributes(isDirectory, isSpecial, isSymlink, size, mTime, permissions); + + boolean writable = ownFile(buffer) ? (mode & LibC.WRITE_MASK) != 0 : myLibC.access(path, LibC.W_OK) == 0; + + return new FileAttributes(isDirectory, isSpecial, isSymlink, false, size, mTime, writable); } @Override - public String resolveSymLink(@NotNull final String path) throws Exception { + protected String resolveSymLink(@NotNull final String path) throws Exception { try { return new File(path).getCanonicalPath(); } @@ -479,19 +434,24 @@ public class FileSystemUtil { } @Override - public void setPermissions(@NotNull final String path, final int permissions) throws Exception { - myLibC.chmod(path, permissions & LibC.PERM_MASK); + protected boolean clonePermissions(@NotNull String source, @NotNull String target) throws Exception { + Memory buffer = new Memory(256); + int res = SystemInfo.isLinux ? myLibC.__xstat64(0, source, buffer) : myLibC.stat(source, buffer); + if (res == 0 && ownFile(buffer)) { + int permissions = (SystemInfo.isLinux ? buffer.getInt(myOffsets[OFF_MODE]) : buffer.getShort(myOffsets[OFF_MODE])) & LibC.PERM_MASK; + return myLibC.chmod(target, permissions) == 0; + } + + return false; } - @NotNull - @Override - public String getName() { - return "JnaUnix"; + private boolean ownFile(Memory buffer) { + return buffer.getInt(myOffsets[OFF_UID]) == myUid && buffer.getInt(myOffsets[OFF_GID]) == myGid; } } - private static class StandardMediatorImpl implements Mediator { + private static class FallbackMediatorImpl extends Mediator { // from java.io.FileSystem private static final int BA_REGULAR = 0x02; private static final int BA_DIRECTORY = 0x04; @@ -500,7 +460,7 @@ public class FileSystemUtil { private final Object myFileSystem; private final Method myGetBooleanAttributes; - private StandardMediatorImpl() { + private FallbackMediatorImpl() { Object fileSystem; Method getBooleanAttributes; try { @@ -520,7 +480,7 @@ public class FileSystemUtil { } @Override - public FileAttributes getAttributes(@NotNull final String path) throws Exception { + protected FileAttributes getAttributes(@NotNull final String path) throws Exception { final File file = new File(path); if (myFileSystem != null) { final int flags = (Integer)myGetBooleanAttributes.invoke(myFileSystem, file); @@ -544,18 +504,18 @@ public class FileSystemUtil { } @Override - public String resolveSymLink(@NotNull final String path) throws Exception { + protected String resolveSymLink(@NotNull final String path) throws Exception { return new File(path).getCanonicalPath(); } + } - @Override - public void setPermissions(@NotNull final String path, final int permissions) throws Exception { - } + @TestOnly + static void resetMediator() { + ourMediator = getMediator(); + } - @NotNull - @Override - public String getName() { - return "fallback"; - } + @TestOnly + static String getMediatorName() { + return ourMediator.getName(); } } diff --git a/platform/util/src/com/intellij/openapi/util/io/FileUtil.java b/platform/util/src/com/intellij/openapi/util/io/FileUtil.java index 9a6363e7891d..cd3668c73250 100644 --- a/platform/util/src/com/intellij/openapi/util/io/FileUtil.java +++ b/platform/util/src/com/intellij/openapi/util/io/FileUtil.java @@ -417,7 +417,12 @@ public class FileUtil extends FileUtilRt { if (attributes == null) return true; if (attributes.isDirectory() && !attributes.isSymLink()) { - if (!deleteChildren(file)) return false; + File[] files = file.listFiles(); + if (files != null) { + for (File child : files) { + if (!delete(child)) return false; + } + } } return deleteFile(file); @@ -484,11 +489,7 @@ public class FileUtil extends FileUtilRt { } if (SystemInfo.isUnix && fromFile.canExecute()) { - final int oldPermissions = FileSystemUtil.getPermissions(fromFile); - final int newPermissions = FileSystemUtil.getPermissions(toFile); - if (oldPermissions != -1 && newPermissions != -1) { - FileSystemUtil.setPermissions(toFile, oldPermissions | newPermissions); - } + FileSystemUtil.clonePermissions(fromFile.getPath(), toFile.getPath()); } } @@ -1047,9 +1048,9 @@ public class FileUtil extends FileUtilRt { writeToFile(file, text, 0, text.length, append); } - private static void writeToFile(@NotNull File file, @NotNull byte[] text, final int off, final int len, boolean append) - throws IOException { + private static void writeToFile(@NotNull File file, @NotNull byte[] text, int off, int len, boolean append) throws IOException { createParentDirs(file); + OutputStream stream = new FileOutputStream(file, append); try { stream.write(text, off, len); diff --git a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java index 7fcfc157409b..76595763405f 100644 --- a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java +++ b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java @@ -2219,7 +2219,8 @@ public class StringUtil extends StringUtilRt { private static int naturalCompare(@NotNull String string1, @NotNull String string2, boolean caseSensitive) { final int string1Length = string1.length(); final int string2Length = string2.length(); - for (int i = 0, j = 0; i < string1Length && j < string2Length; i++, j++) { + int i = 0, j = 0; + for (; i < string1Length && j < string2Length; i++, j++) { char ch1 = string1.charAt(i); char ch2 = string2.charAt(j); if ((isDigit(ch1) || ch1 == ' ') && (isDigit(ch2) || ch2 == ' ')) { @@ -2230,36 +2231,30 @@ public class StringUtil extends StringUtilRt { ch1 = string1.charAt(startNum1); } int startNum2 = j; - while (ch2 == ' ' || ch2 == '0') { + while (ch2 == ' ' || ch2 == '0') { // skip leading spaces and zeros startNum2++; if (startNum2 >= string2Length) break; ch2 = string2.charAt(startNum2); } i = startNum1; j = startNum2; + // find end index of number while (i < string1Length && isDigit(string1.charAt(i))) i++; while (j < string2Length && isDigit(string2.charAt(j))) j++; - String digits1 = string1.substring(startNum1, i); - String digits2 = string2.substring(startNum2, j); - if (digits1.length() != digits2.length()) { - return digits1.length() - digits2.length(); - } - int numberDiff = digits1.compareTo(digits2); - if (numberDiff != 0) { - return numberDiff; - } - i--; - j--; final int lengthDiff = (i - startNum1) - (j - startNum2); if (lengthDiff != 0) { + // numbers with more digits are always greater than shorter numbers return lengthDiff; } for (; startNum1 < i; startNum1++, startNum2++) { + // compare numbers with equal digit count final int diff = string1.charAt(startNum1) - string2.charAt(startNum2); if (diff != 0) { return diff; } } + i--; + j--; } else { if (caseSensitive) { @@ -2279,6 +2274,15 @@ public class StringUtil extends StringUtilRt { } } } + // After the loop the end of one of the strings might not have been reached, if the other + // string ends with a number and the strings are equal until the end of that number. When + // there are more characters in the string, then it is greater. + if (i < string1Length) { + return 1; + } + else if (j < string2Length) { + return -1; + } if (!caseSensitive && string1Length == string2Length) { // do case sensitive compare if case insensitive strings are equal return naturalCompare(string1, string2, true); diff --git a/platform/util/src/com/intellij/util/containers/ConcurrentHashMap.java b/platform/util/src/com/intellij/util/containers/ConcurrentHashMap.java index 19affacd0fdb..d70ef92d98e7 100644 --- a/platform/util/src/com/intellij/util/containers/ConcurrentHashMap.java +++ b/platform/util/src/com/intellij/util/containers/ConcurrentHashMap.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -17,1503 +17,7158 @@ package com.intellij.util.containers; import com.intellij.util.ConcurrencyUtil; +import com.intellij.util.IncorrectOperationException; import gnu.trove.TObjectHashingStrategy; +import jsr166e.CountedCompleter; +import jsr166e.ForkJoinPool; import org.jetbrains.annotations.NotNull; - -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; +import sun.misc.Unsafe; + +import java.io.*; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.security.AccessController; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; import java.util.*; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.LockSupport; import java.util.concurrent.locks.ReentrantLock; +/** + * A hash table supporting full concurrency of retrievals and + * high expected concurrency for updates. This class obeys the + * same functional specification as {@link java.util.Hashtable}, and + * includes versions of methods corresponding to each method of + * {@code Hashtable}. However, even though all operations are + * thread-safe, retrieval operations do <em>not</em> entail locking, + * and there is <em>not</em> any support for locking the entire table + * in a way that prevents all access. This class is fully + * interoperable with {@code Hashtable} in programs that rely on its + * thread safety but not on its synchronization details. + * + * <p>Retrieval operations (including {@code get}) generally do not + * block, so may overlap with update operations (including {@code put} + * and {@code remove}). Retrievals reflect the results of the most + * recently <em>completed</em> update operations holding upon their + * onset. (More formally, an update operation for a given key bears a + * <em>happens-before</em> relation with any (non-null) retrieval for + * that key reporting the updated value.) For aggregate operations + * such as {@code putAll} and {@code clear}, concurrent retrievals may + * reflect insertion or removal of only some entries. Similarly, + * Iterators and Enumerations return elements reflecting the state of + * the hash table at some point at or since the creation of the + * iterator/enumeration. They do <em>not</em> throw {@link + * ConcurrentModificationException}. However, iterators are designed + * to be used by only one thread at a time. Bear in mind that the + * results of aggregate status methods including {@code size}, {@code + * isEmpty}, and {@code containsValue} are typically useful only when + * a map is not undergoing concurrent updates in other threads. + * Otherwise the results of these methods reflect transient states + * that may be adequate for monitoring or estimation purposes, but not + * for program control. + * + * <p>The table is dynamically expanded when there are too many + * collisions (i.e., keys that have distinct hash codes but fall into + * the same slot modulo the table size), with the expected average + * effect of maintaining roughly two bins per mapping (corresponding + * to a 0.75 load factor threshold for resizing). There may be much + * variance around this average as mappings are added and removed, but + * overall, this maintains a commonly accepted time/space tradeoff for + * hash tables. However, resizing this or any other kind of hash + * table may be a relatively slow operation. When possible, it is a + * good idea to provide a size estimate as an optional {@code + * initialCapacity} constructor argument. An additional optional + * {@code loadFactor} constructor argument provides a further means of + * customizing initial table capacity by specifying the table density + * to be used in calculating the amount of space to allocate for the + * given number of elements. Also, for compatibility with previous + * versions of this class, constructors may optionally specify an + * expected {@code concurrencyLevel} as an additional hint for + * internal sizing. Note that using many keys with exactly the same + * {@code hashCode()} is a sure way to slow down performance of any + * hash table. To ameliorate impact, when keys are {@link Comparable}, + * this class may use comparison order among keys to help break ties. + * + * <p>A {@link Set} projection of a ConcurrentHashMapV8 may be created + * (using {@link #newKeySet()} or {@link #newKeySet(int)}), or viewed + * (using {@link #keySet(Object)} when only keys are of interest, and the + * mapped values are (perhaps transiently) not used or all take the + * same mapping value. + * + * <p>This class and its views and iterators implement all of the + * <em>optional</em> methods of the {@link Map} and {@link Iterator} + * interfaces. + * + * <p>Like {@link Hashtable} but unlike {@link java.util.HashMap}, this class + * does <em>not</em> allow {@code null} to be used as a key or value. + * + * <p>ConcurrentHashMapV8s support a set of sequential and parallel bulk + * operations that are designed + * to be safely, and often sensibly, applied even with maps that are + * being concurrently updated by other threads; for example, when + * computing a snapshot summary of the values in a shared registry. + * There are three kinds of operation, each with four forms, accepting + * functions with Keys, Values, Entries, and (Key, Value) arguments + * and/or return values. Because the elements of a ConcurrentHashMapV8 + * are not ordered in any particular way, and may be processed in + * different orders in different parallel executions, the correctness + * of supplied functions should not depend on any ordering, or on any + * other objects or values that may transiently change while + * computation is in progress; and except for forEach actions, should + * ideally be side-effect-free. Bulk operations on {@link java.util.Map.Entry} + * objects do not support method {@code setValue}. + * + * <ul> + * <li> forEach: Perform a given action on each element. + * A variant form applies a given transformation on each element + * before performing the action.</li> + * + * <li> search: Return the first available non-null result of + * applying a given function on each element; skipping further + * search when a result is found.</li> + * + * <li> reduce: Accumulate each element. The supplied reduction + * function cannot rely on ordering (more formally, it should be + * both associative and commutative). There are five variants: + * + * <ul> + * + * <li> Plain reductions. (There is not a form of this method for + * (key, value) function arguments since there is no corresponding + * return type.)</li> + * + * <li> Mapped reductions that accumulate the results of a given + * function applied to each element.</li> + * + * <li> Reductions to scalar doubles, longs, and ints, using a + * given basis value.</li> + * + * </ul> + * </li> + * </ul> + * + * <p>These bulk operations accept a {@code parallelismThreshold} + * argument. Methods proceed sequentially if the current map size is + * estimated to be less than the given threshold. Using a value of + * {@code Long.MAX_VALUE} suppresses all parallelism. Using a value + * of {@code 1} results in maximal parallelism by partitioning into + * enough subtasks to fully utilize the {@link + * ForkJoinPool#commonPool()} that is used for all parallel + * computations. Normally, you would initially choose one of these + * extreme values, and then measure performance of using in-between + * values that trade off overhead versus throughput. + * + * <p>The concurrency properties of bulk operations follow + * from those of ConcurrentHashMapV8: Any non-null result returned + * from {@code get(key)} and related access methods bears a + * happens-before relation with the associated insertion or + * update. The result of any bulk operation reflects the + * composition of these per-element relations (but is not + * necessarily atomic with respect to the map as a whole unless it + * is somehow known to be quiescent). Conversely, because keys + * and values in the map are never null, null serves as a reliable + * atomic indicator of the current lack of any result. To + * maintain this property, null serves as an implicit basis for + * all non-scalar reduction operations. For the double, long, and + * int versions, the basis should be one that, when combined with + * any other value, returns that other value (more formally, it + * should be the identity element for the reduction). Most common + * reductions have these properties; for example, computing a sum + * with basis 0 or a minimum with basis MAX_VALUE. + * + * <p>Search and transformation functions provided as arguments + * should similarly return null to indicate the lack of any result + * (in which case it is not used). In the case of mapped + * reductions, this also enables transformations to serve as + * filters, returning null (or, in the case of primitive + * specializations, the identity basis) if the element should not + * be combined. You can create compound transformations and + * filterings by composing them yourself under this "null means + * there is nothing there now" rule before using them in search or + * reduce operations. + * + * <p>Methods accepting and/or returning Entry arguments maintain + * key-value associations. They may be useful for example when + * finding the key for the greatest value. Note that "plain" Entry + * arguments can be supplied using {@code new + * AbstractMap.SimpleEntry(k,v)}. + * + * <p>Bulk operations may complete abruptly, throwing an + * exception encountered in the application of a supplied + * function. Bear in mind when handling such exceptions that other + * concurrently executing functions could also have thrown + * exceptions, or would have done so if the first exception had + * not occurred. + * + * <p>Speedups for parallel compared to sequential forms are common + * but not guaranteed. Parallel operations involving brief functions + * on small maps may execute more slowly than sequential forms if the + * underlying work to parallelize the computation is more expensive + * than the computation itself. Similarly, parallelization may not + * lead to much actual parallelism if all processors are busy + * performing unrelated tasks. + * + * <p>All arguments to all task methods must be non-null. + * + * <p><em>jsr166e note: During transition, this class + * uses nested functional interfaces with different names but the + * same forms as those expected for JDK8.</em> + * + * <p>This class is a member of the + * <a href="{@docRoot}/../technotes/guides/collections/index.html"> + * Java Collections Framework</a>. + * + * @since 1.5 + * @author Doug Lea + * @param <K> the type of keys maintained by this map + * @param <V> the type of mapped values + */ + +// IJ specific: +// copied from JDK1.8 ConcurrentHashMap except: // added hashing strategy argument // added cacheOrGet convenience method -// changed DEFAULT_SEGMENTS to 2 from 16 public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>, Serializable, TObjectHashingStrategy<K> { private static final long serialVersionUID = 7249069246763182397L; + @NotNull private final TObjectHashingStrategy<K> myHashingStrategy; + + /** + * An object for traversing and partitioning elements of a source. + * This interface provides a subset of the functionality of JDK8 + * java.util.Spliterator. + */ + public interface ConcurrentHashMapSpliterator<T> { + /** + * If possible, returns a new spliterator covering + * approximately one half of the elements, which will not be + * covered by this spliterator. Returns null if cannot be + * split. + */ + ConcurrentHashMapSpliterator<T> trySplit(); + + /** + * Returns an estimate of the number of elements covered by + * this Spliterator. + */ + long estimateSize(); - /* - * The basic strategy is to subdivide the table among Segments, - * each of which itself is a concurrently readable hash table. + /** + * Applies the action to each untraversed element + */ + void forEachRemaining(Action<? super T> action); + + /** + * If an element remains, applies the action and returns true. */ + boolean tryAdvance(Action<? super T> action); + } - /* ---------------- Constants -------------- */ + // Sams /** - * The default initial number of table slots for this table. - * Used when not otherwise specified in constructor. + * Interface describing a void action of one argument */ - static int DEFAULT_INITIAL_CAPACITY = 16; + public interface Action<A> { + void apply(A a); + } /** - * The maximum capacity, used if a higher value is implicitly - * specified by either of the constructors with arguments. MUST - * be a power of two <= 1<<30 to ensure that entries are indexible - * using ints. + * Interface describing a void action of two arguments */ - static final int MAXIMUM_CAPACITY = 1 << 30; + public interface BiAction<A, B> { + void apply(A a, B b); + } /** - * The default load factor for this table. Used when not - * otherwise specified in constructor. + * Interface describing a function of one argument */ - public static final float DEFAULT_LOAD_FACTOR = 0.75f; + public interface Fun<A, T> { + T apply(A a); + } /** - * The default number of concurrency control segments. + * Interface describing a function of two arguments */ - static final int DEFAULT_SEGMENTS = Math.min(2, Runtime.getRuntime().availableProcessors()); // CHANGED FROM 16 + public interface BiFun<A, B, T> { + T apply(A a, B b); + } /** - * The maximum number of segments to allow; used to bound - * constructor arguments. + * Interface describing a function mapping its argument to a double */ - static final int MAX_SEGMENTS = 1 << 16; // slightly conservative + public interface ObjectToDouble<A> { + double apply(A a); + } /** - * Number of unsynchronized retries in size and containsValue - * methods before resorting to locking. This is used to avoid - * unbounded retries if tables undergo continuous modification - * which would make it impossible to obtain an accurate result. + * Interface describing a function mapping its argument to a long */ - private static final int RETRIES_BEFORE_LOCK = 2; + public interface ObjectToLong<A> { + long apply(A a); + } - /* ---------------- Fields -------------- */ + /** + * Interface describing a function mapping its argument to an int + */ + public interface ObjectToInt<A> { + int apply(A a); + } /** - * Mask value for indexing into segments. The upper bits of a - * key's hash code are used to choose the segment. + * Interface describing a function mapping two arguments to a double */ - private final int segmentMask; + public interface ObjectByObjectToDouble<A, B> { + double apply(A a, B b); + } /** - * Shift value for indexing within segments. + * Interface describing a function mapping two arguments to a long */ - private final int segmentShift; + public interface ObjectByObjectToLong<A, B> { + long apply(A a, B b); + } /** - * The segments, each of which is a specialized hash table + * Interface describing a function mapping two arguments to an int */ - private final Segment[] segments; + public interface ObjectByObjectToInt<A, B> { + int apply(A a, B b); + } - private transient Set<K> keySet; - private transient Set<Entry<K, V>> entrySet; - private transient Collection<V> values; - private final TObjectHashingStrategy<K> myHashingStrategy; + /** + * Interface describing a function mapping two doubles to a double + */ + public interface DoubleByDoubleToDouble { + double apply(double a, double b); + } - /* ---------------- Small Utilities -------------- */ + /** + * Interface describing a function mapping two longs to a long + */ + public interface LongByLongToLong { + long apply(long a, long b); + } /** - * Returns the segment that should be used for key with given hash - * - * @param hash the hash code for the key - * @return the segment + * Interface describing a function mapping two ints to an int */ - private Segment<K, V> segmentFor(int hash) { - return segments[hash >>> segmentShift & segmentMask]; + public interface IntByIntToInt { + int apply(int a, int b); } - /* ---------------- Inner Classes -------------- */ + /* + * Overview: + * + * The primary design goal of this hash table is to maintain + * concurrent readability (typically method get(), but also + * iterators and related methods) while minimizing update + * contention. Secondary goals are to keep space consumption about + * the same or better than java.util.HashMap, and to support high + * initial insertion rates on an empty table by many threads. + * + * This map usually acts as a binned (bucketed) hash table. Each + * key-value mapping is held in a Node. Most nodes are instances + * of the basic Node class with hash, key, value, and next + * fields. However, various subclasses exist: TreeNodes are + * arranged in balanced trees, not lists. TreeBins hold the roots + * of sets of TreeNodes. ForwardingNodes are placed at the heads + * of bins during resizing. ReservationNodes are used as + * placeholders while establishing values in computeIfAbsent and + * related methods. The types TreeBin, ForwardingNode, and + * ReservationNode do not hold normal user keys, values, or + * hashes, and are readily distinguishable during search etc + * because they have negative hash fields and null key and value + * fields. (These special nodes are either uncommon or transient, + * so the impact of carrying around some unused fields is + * insignificant.) + * + * The table is lazily initialized to a power-of-two size upon the + * first insertion. Each bin in the table normally contains a + * list of Nodes (most often, the list has only zero or one Node). + * Table accesses require volatile/atomic reads, writes, and + * CASes. Because there is no other way to arrange this without + * adding further indirections, we use intrinsics + * (sun.misc.Unsafe) operations. + * + * We use the top (sign) bit of Node hash fields for control + * purposes -- it is available anyway because of addressing + * constraints. Nodes with negative hash fields are specially + * handled or ignored in map methods. + * + * Insertion (via put or its variants) of the first node in an + * empty bin is performed by just CASing it to the bin. This is + * by far the most common case for put operations under most + * key/hash distributions. Other update operations (insert, + * delete, and replace) require locks. We do not want to waste + * the space required to associate a distinct lock object with + * each bin, so instead use the first node of a bin list itself as + * a lock. Locking support for these locks relies on builtin + * "synchronized" monitors. + * + * Using the first node of a list as a lock does not by itself + * suffice though: When a node is locked, any update must first + * validate that it is still the first node after locking it, and + * retry if not. Because new nodes are always appended to lists, + * once a node is first in a bin, it remains first until deleted + * or the bin becomes invalidated (upon resizing). + * + * The main disadvantage of per-bin locks is that other update + * operations on other nodes in a bin list protected by the same + * lock can stall, for example when user equals() or mapping + * functions take a long time. However, statistically, under + * random hash codes, this is not a common problem. Ideally, the + * frequency of nodes in bins follows a Poisson distribution + * (http://en.wikipedia.org/wiki/Poisson_distribution) with a + * parameter of about 0.5 on average, given the resizing threshold + * of 0.75, although with a large variance because of resizing + * granularity. Ignoring variance, the expected occurrences of + * list size k are (exp(-0.5) * pow(0.5, k) / factorial(k)). The + * first values are: + * + * 0: 0.60653066 + * 1: 0.30326533 + * 2: 0.07581633 + * 3: 0.01263606 + * 4: 0.00157952 + * 5: 0.00015795 + * 6: 0.00001316 + * 7: 0.00000094 + * 8: 0.00000006 + * more: less than 1 in ten million + * + * Lock contention probability for two threads accessing distinct + * elements is roughly 1 / (8 * #elements) under random hashes. + * + * Actual hash code distributions encountered in practice + * sometimes deviate significantly from uniform randomness. This + * includes the case when N > (1<<30), so some keys MUST collide. + * Similarly for dumb or hostile usages in which multiple keys are + * designed to have identical hash codes or ones that differs only + * in masked-out high bits. So we use a secondary strategy that + * applies when the number of nodes in a bin exceeds a + * threshold. These TreeBins use a balanced tree to hold nodes (a + * specialized form of red-black trees), bounding search time to + * O(log N). Each search step in a TreeBin is at least twice as + * slow as in a regular list, but given that N cannot exceed + * (1<<64) (before running out of addresses) this bounds search + * steps, lock hold times, etc, to reasonable constants (roughly + * 100 nodes inspected per operation worst case) so long as keys + * are Comparable (which is very common -- String, Long, etc). + * TreeBin nodes (TreeNodes) also maintain the same "next" + * traversal pointers as regular nodes, so can be traversed in + * iterators in the same way. + * + * The table is resized when occupancy exceeds a percentage + * threshold (nominally, 0.75, but see below). Any thread + * noticing an overfull bin may assist in resizing after the + * initiating thread allocates and sets up the replacement + * array. However, rather than stalling, these other threads may + * proceed with insertions etc. The use of TreeBins shields us + * from the worst case effects of overfilling while resizes are in + * progress. Resizing proceeds by transferring bins, one by one, + * from the table to the next table. To enable concurrency, the + * next table must be (incrementally) prefilled with place-holders + * serving as reverse forwarders to the old table. Because we are + * using power-of-two expansion, the elements from each bin must + * either stay at same index, or move with a power of two + * offset. We eliminate unnecessary node creation by catching + * cases where old nodes can be reused because their next fields + * won't change. On average, only about one-sixth of them need + * cloning when a table doubles. The nodes they replace will be + * garbage collectable as soon as they are no longer referenced by + * any reader thread that may be in the midst of concurrently + * traversing table. Upon transfer, the old table bin contains + * only a special forwarding node (with hash field "MOVED") that + * contains the next table as its key. On encountering a + * forwarding node, access and update operations restart, using + * the new table. + * + * Each bin transfer requires its bin lock, which can stall + * waiting for locks while resizing. However, because other + * threads can join in and help resize rather than contend for + * locks, average aggregate waits become shorter as resizing + * progresses. The transfer operation must also ensure that all + * accessible bins in both the old and new table are usable by any + * traversal. This is arranged by proceeding from the last bin + * (table.length - 1) up towards the first. Upon seeing a + * forwarding node, traversals (see class Traverser) arrange to + * move to the new table without revisiting nodes. However, to + * ensure that no intervening nodes are skipped, bin splitting can + * only begin after the associated reverse-forwarders are in + * place. + * + * The traversal scheme also applies to partial traversals of + * ranges of bins (via an alternate Traverser constructor) + * to support partitioned aggregate operations. Also, read-only + * operations give up if ever forwarded to a null table, which + * provides support for shutdown-style clearing, which is also not + * currently implemented. + * + * Lazy table initialization minimizes footprint until first use, + * and also avoids resizings when the first operation is from a + * putAll, constructor with map argument, or deserialization. + * These cases attempt to override the initial capacity settings, + * but harmlessly fail to take effect in cases of races. + * + * The element count is maintained using a specialization of + * LongAdder. We need to incorporate a specialization rather than + * just use a LongAdder in order to access implicit + * contention-sensing that leads to creation of multiple + * CounterCells. The counter mechanics avoid contention on + * updates but can encounter cache thrashing if read too + * frequently during concurrent access. To avoid reading so often, + * resizing under contention is attempted only upon adding to a + * bin already holding two or more nodes. Under uniform hash + * distributions, the probability of this occurring at threshold + * is around 13%, meaning that only about 1 in 8 puts check + * threshold (and after resizing, many fewer do so). + * + * TreeBins use a special form of comparison for search and + * related operations (which is the main reason we cannot use + * existing collections such as TreeMaps). TreeBins contain + * Comparable elements, but may contain others, as well as + * elements that are Comparable but not necessarily Comparable for + * the same T, so we cannot invoke compareTo among them. To handle + * this, the tree is ordered primarily by hash value, then by + * Comparable.compareTo order if applicable. On lookup at a node, + * if elements are not comparable or compare as 0 then both left + * and right children may need to be searched in the case of tied + * hash values. (This corresponds to the full list search that + * would be necessary if all elements were non-Comparable and had + * tied hashes.) On insertion, to keep a total ordering (or as + * close as is required here) across rebalancings, we compare + * classes and identityHashCodes as tie-breakers. The red-black + * balancing code is updated from pre-jdk-collections + * (http://gee.cs.oswego.edu/dl/classes/collections/RBCell.java) + * based in turn on Cormen, Leiserson, and Rivest "Introduction to + * Algorithms" (CLR). + * + * TreeBins also require an additional locking mechanism. While + * list traversal is always possible by readers even during + * updates, tree traversal is not, mainly because of tree-rotations + * that may change the root node and/or its linkages. TreeBins + * include a simple read-write lock mechanism parasitic on the + * main bin-synchronization strategy: Structural adjustments + * associated with an insertion or removal are already bin-locked + * (and so cannot conflict with other writers) but must wait for + * ongoing readers to finish. Since there can be only one such + * waiter, we use a simple scheme using a single "waiter" field to + * block writers. However, readers need never block. If the root + * lock is held, they proceed along the slow traversal path (via + * next-pointers) until the lock becomes available or the list is + * exhausted, whichever comes first. These cases are not fast, but + * maximize aggregate expected throughput. + * + * Maintaining API and serialization compatibility with previous + * versions of this class introduces several oddities. Mainly: We + * leave untouched but unused constructor arguments refering to + * concurrencyLevel. We accept a loadFactor constructor argument, + * but apply it only to initial table capacity (which is the only + * time that we can guarantee to honor it.) We also declare an + * unused "Segment" class that is instantiated in minimal form + * only when serializing. + * + * Also, solely for compatibility with previous versions of this + * class, it extends AbstractMap, even though all of its methods + * are overridden, so it is just useless baggage. + * + * This file is organized to make things a little easier to follow + * while reading than they might otherwise: First the main static + * declarations and utilities, then fields, then main public + * methods (with a few factorings of multiple public methods into + * internal ones), then sizing methods, trees, traversers, and + * bulk operations. + */ + + /* ---------------- Constants -------------- */ /** - * ConcurrentHashMap list entry. Note that this is never exported - * out as a user-visible Map.Entry. - * <p/> - * Because the value field is volatile, not final, it is legal wrt - * the Java Memory Model for an unsynchronized reader to see null - * instead of initial value when read via a data race. Although a - * reordering leading to this is not likely to ever actually - * occur, the Segment.readValueUnderLock method is used as a - * backup in case a null (pre-initialized) value is ever seen in - * an unsynchronized access method. - */ - private static final class HashEntry<K, V> { - private final K key; - private final int hash; - private volatile V value; - private final HashEntry<K, V> next; - - private HashEntry(K key, int hash, HashEntry<K, V> next, V value) { - this.key = key; + * The largest possible table capacity. This value must be + * exactly 1<<30 to stay within Java array allocation and indexing + * bounds for power of two table sizes, and is further required + * because the top two bits of 32bit hash fields are used for + * control purposes. + */ + private static final int MAXIMUM_CAPACITY = 1 << 30; + + /** + * The default initial table capacity. Must be a power of 2 + * (i.e., at least 1) and at most MAXIMUM_CAPACITY. + */ + static final int DEFAULT_CAPACITY = 16; + + /** + * The largest possible (non-power of two) array size. + * Needed by toArray and related methods. + */ + static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; + + /** + * The default concurrency level for this table. Unused but + * defined for compatibility with previous versions of this class. + */ + private static final int DEFAULT_CONCURRENCY_LEVEL = 16; + + /** + * The load factor for this table. Overrides of this value in + * constructors affect only the initial table capacity. The + * actual floating point value isn't normally used -- it is + * simpler to use expressions such as {@code n - (n >>> 2)} for + * the associated resizing threshold. + */ + static final float LOAD_FACTOR = 0.75f; + + /** + * The bin count threshold for using a tree rather than list for a + * bin. Bins are converted to trees when adding an element to a + * bin with at least this many nodes. The value must be greater + * than 2, and should be at least 8 to mesh with assumptions in + * tree removal about conversion back to plain bins upon + * shrinkage. + */ + static final int TREEIFY_THRESHOLD = 8; + + /** + * The bin count threshold for untreeifying a (split) bin during a + * resize operation. Should be less than TREEIFY_THRESHOLD, and at + * most 6 to mesh with shrinkage detection under removal. + */ + static final int UNTREEIFY_THRESHOLD = 6; + + /** + * The smallest table capacity for which bins may be treeified. + * (Otherwise the table is resized if too many nodes in a bin.) + * The value should be at least 4 * TREEIFY_THRESHOLD to avoid + * conflicts between resizing and treeification thresholds. + */ + static final int MIN_TREEIFY_CAPACITY = 64; + + /** + * Minimum number of rebinnings per transfer step. Ranges are + * subdivided to allow multiple resizer threads. This value + * serves as a lower bound to avoid resizers encountering + * excessive memory contention. The value should be at least + * DEFAULT_CAPACITY. + */ + private static final int MIN_TRANSFER_STRIDE = 16; + + /* + * Encodings for Node hash fields. See above for explanation. + */ + static final int MOVED = -1; // hash for forwarding nodes + static final int TREEBIN = -2; // hash for roots of trees + static final int RESERVED = -3; // hash for transient reservations + static final int HASH_BITS = 0x7fffffff; // usable bits of normal node hash + + /** + * Number of CPUS, to place bounds on some sizings + */ + static final int NCPU = Runtime.getRuntime().availableProcessors(); + + /** + * For serialization compatibility. + */ + private static final ObjectStreamField[] serialPersistentFields = { + new ObjectStreamField("segments", Segment[].class), + new ObjectStreamField("segmentMask", Integer.TYPE), + new ObjectStreamField("segmentShift", Integer.TYPE) + }; + + /* ---------------- Nodes -------------- */ + + /** + * Key-value entry. This class is never exported out as a + * user-mutable Map.Entry (i.e., one supporting setValue; see + * MapEntry below), but can be used for read-only traversals used + * in bulk tasks. Subclasses of Node with a negative hash field + * are special, and contain null keys and values (but are never + * exported). Otherwise, keys and vals are never null. + */ + static class Node<K, V> implements Map.Entry<K, V> { + final int hash; + final K key; + volatile V val; + volatile Node<K, V> next; + protected final TObjectHashingStrategy<K> myHashingStrategy; + + Node(int hash, K key, V val, Node<K, V> next, @NotNull TObjectHashingStrategy<K> hashingStrategy) { this.hash = hash; + this.key = key; + this.val = val; this.next = next; - this.value = value; + myHashingStrategy = hashingStrategy; } - } - /** - * Segments are specialized versions of hash tables. This - * subclasses from ReentrantLock opportunistically, just to - * simplify some locking and avoid separate construction. - */ - private static final class Segment<K, V> extends ReentrantLock implements Serializable { - /* - * Segments maintain a table of entry lists that are ALWAYS - * kept in a consistent state, so can be read without locking. - * Next fields of nodes are immutable (final). All list - * additions are performed at the front of each bin. This - * makes it easy to check changes, and also fast to traverse. - * When nodes would otherwise be changed, new nodes are - * created to replace them. This works well for hash tables - * since the bin lists tend to be short. (The average length - * is less than two for the default load factor threshold.) - * - * Read operations can thus proceed without locking, but rely - * on selected uses of volatiles to ensure that completed - * write operations performed by other threads are - * noticed. For most purposes, the "count" field, tracking the - * number of elements, serves as that volatile variable - * ensuring visibility. This is convenient because this field - * needs to be read in many read operations anyway: - * - * - All (unsynchronized) read operations must first read the - * "count" field, and should not look at table entries if - * it is 0. - * - * - All (synchronized) write operations should write to - * the "count" field after structurally changing any bin. - * The operations must not take any action that could even - * momentarily cause a concurrent read operation to see - * inconsistent data. This is made easier by the nature of - * the read operations in Map. For example, no operation - * can reveal that the table has grown but the threshold - * has not yet been updated, so there are no atomicity - * requirements for this with respect to reads. - * - * As a guide, all critical volatile reads and writes to the - * count field are marked in code comments. - */ + @Override + public final K getKey() { + return key; + } - private static final long serialVersionUID = 2249069246763182397L; + @Override + public final V getValue() { + return val; + } - /** - * The number of elements in this segment's region. - */ - private transient volatile int count; + public final int hashCode() { + return key.hashCode() ^ val.hashCode(); + } - /** - * Number of updates that alter the size of the table. This is - * used during bulk-read methods to make sure they see a - * consistent snapshot: If modCounts change during a traversal - * of segments computing size or checking containsValue, then - * we might have an inconsistent view of state so (usually) - * must retry. - */ - private transient int modCount; + public final String toString() { + return key + "=" + val; + } - /** - * The table is rehashed when its size exceeds this threshold. - * (The value of this field is always (int)(capacity * - * loadFactor).) - */ - private transient int threshold; + @Override + public final V setValue(V value) { + throw new UnsupportedOperationException(); + } - /** - * The per-segment table. Declared as a raw type, casted - * to HashEntry<K,V> on each use. - */ - private transient volatile HashEntry[] table; + public final boolean equals(Object o) { + Object k; + Object v; + Object u; + Map.Entry<?, ?> e; + return o instanceof Entry && + (k = (e = (Entry<?, ?>)o).getKey()) != null && + (v = e.getValue()) != null && + (k == key || myHashingStrategy.equals((K)k, key)) && + (v == (u = val) || v.equals(u)); + } /** - * The load factor for the hash table. Even though this value - * is same for all segments, it is replicated to avoid needing - * links to outer object. - * - * @serial + * Virtualized support for map.get(); overridden in subclasses. */ - private final float loadFactor; - private final TObjectHashingStrategy<K> myHashingStrategy; + Node<K, V> find(int h, Object k) { + Node<K, V> e = this; + if (k != null) { + do { + K ek; + if (e.hash == h && + ((ek = e.key) == k || ek != null && myHashingStrategy.equals((K)k,ek))) { + return e; + } + } + while ((e = e.next) != null); + } + return null; + } + } - private Segment(int initialCapacity, float lf, TObjectHashingStrategy<K> hashingStrategy) { - loadFactor = lf; - myHashingStrategy = hashingStrategy; - setTable(new HashEntry[initialCapacity]); + /* ---------------- Static utilities -------------- */ + + /** + * Spreads (XORs) higher bits of hash to lower and also forces top + * bit to 0. Because the table uses power-of-two masking, sets of + * hashes that vary only in bits above the current mask will + * always collide. (Among known examples are sets of Float keys + * holding consecutive whole numbers in small tables.) So we + * apply a transform that spreads the impact of higher bits + * downward. There is a tradeoff between speed, utility, and + * quality of bit-spreading. Because many common sets of hashes + * are already reasonably distributed (so don't benefit from + * spreading), and because we use trees to handle large sets of + * collisions in bins, we just XOR some shifted bits in the + * cheapest possible way to reduce systematic lossage, as well as + * to incorporate impact of the highest bits that would otherwise + * never be used in index calculations because of table bounds. + */ + static int spread(int h) { + return (h ^ h >>> 16) & HASH_BITS; + } + + /** + * Returns a power of two table size for the given desired capacity. + * See Hackers Delight, sec 3.2 + */ + private static int tableSizeFor(int c) { + int n = c - 1; + n |= n >>> 1; + n |= n >>> 2; + n |= n >>> 4; + n |= n >>> 8; + n |= n >>> 16; + return n < 0 ? 1 : n >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : n + 1; + } + + /** + * Returns x's Class if it is of the form "class C implements + * Comparable<C>", else null. + */ + static Class<?> comparableClassFor(Object x) { + if (x instanceof Comparable) { + Class<?> c; + if ((c = x.getClass()) == String.class) // bypass checks + { + return c; + } + Type[] ts; + if ((ts = c.getGenericInterfaces()) != null) { + for (Type t : ts) { + ParameterizedType p; + Type[] as; + if (t instanceof ParameterizedType && + (p = (ParameterizedType)t).getRawType() == + Comparable.class && + (as = p.getActualTypeArguments()) != null && + as.length == 1 && as[0] == c) // type arg is c + { + return c; + } + } + } } + return null; + } - /** - * Set table to new HashEntry array. - * Call only while holding lock or in constructor. - */ - private void setTable(HashEntry[] newTable) { - threshold = (int)(newTable.length * loadFactor); - table = newTable; + /** + * Returns k.compareTo(x) if x matches kc (k's screened comparable + * class), else 0. + */ + @SuppressWarnings({"rawtypes", "unchecked"}) // for cast to Comparable + static int compareComparables(Class<?> kc, Object k, Object x) { + return x == null || x.getClass() != kc ? 0 : + ((Comparable)k).compareTo(x); + } + + /* ---------------- Table element access -------------- */ + + /* + * Volatile access methods are used for table elements as well as + * elements of in-progress next table while resizing. All uses of + * the tab arguments must be null checked by callers. All callers + * also paranoically precheck that tab's length is not zero (or an + * equivalent check), thus ensuring that any index argument taking + * the form of a hash value anded with (length - 1) is a valid + * index. Note that, to be correct wrt arbitrary concurrency + * errors by users, these checks must operate on local variables, + * which accounts for some odd-looking inline assignments below. + * Note that calls to setTabAt always occur within locked regions, + * and so in principle require only release ordering, not need + * full volatile semantics, but are currently coded as volatile + * writes to be conservative. + */ + + @SuppressWarnings("unchecked") + static <K, V> Node<K, V> tabAt(Node<K, V>[] tab, int i) { + return (Node<K, V>)U.getObjectVolatile(tab, ((long)i << ASHIFT) + ABASE); + } + + static <K, V> boolean casTabAt(Node<K, V>[] tab, int i, + Node<K, V> c, Node<K, V> v) { + return U.compareAndSwapObject(tab, ((long)i << ASHIFT) + ABASE, c, v); + } + + static <K, V> void setTabAt(Node<K, V>[] tab, int i, Node<K, V> v) { + U.putObjectVolatile(tab, ((long)i << ASHIFT) + ABASE, v); + } + + /* ---------------- Fields -------------- */ + + /** + * The array of bins. Lazily initialized upon first insertion. + * Size is always a power of two. Accessed directly by iterators. + */ + transient volatile Node<K, V>[] table; + + /** + * The next table to use; non-null only while resizing. + */ + private transient volatile Node<K, V>[] nextTable; + + /** + * Base counter value, used mainly when there is no contention, + * but also as a fallback during table initialization + * races. Updated via CAS. + */ + private transient volatile long baseCount; + + /** + * Table initialization and resizing control. When negative, the + * table is being initialized or resized: -1 for initialization, + * else -(1 + the number of active resizing threads). Otherwise, + * when table is null, holds the initial table size to use upon + * creation, or 0 for default. After initialization, holds the + * next element count value upon which to resize the table. + */ + private transient volatile int sizeCtl; + + /** + * The next table index (plus one) to split while resizing. + */ + private transient volatile int transferIndex; + + /** + * The least available table index to split while resizing. + */ + private transient volatile int transferOrigin; + + /** + * Spinlock (locked via CAS) used when resizing and/or creating CounterCells. + */ + private transient volatile int cellsBusy; + + /** + * Table of counter cells. When non-null, size is a power of 2. + */ + private transient volatile CounterCell[] counterCells; + + // views + private transient KeySetView<K, V> keySet; + private transient ValuesView<K, V> values; + private transient EntrySetView<K, V> entrySet; + + + /* ---------------- Public operations -------------- */ + + /** + * Creates a new, empty map with the default initial table size (16). + */ + public ConcurrentHashMap() { + myHashingStrategy = this; + } + + public ConcurrentHashMap(@NotNull TObjectHashingStrategy<K> hashingStrategy) { + myHashingStrategy = hashingStrategy; + } + + /** + * Creates a new, empty map with an initial table size + * accommodating the specified number of elements without the need + * to dynamically resize. + * + * @param initialCapacity The implementation performs internal + * sizing to accommodate this many elements. + * @throws IllegalArgumentException if the initial capacity of + * elements is negative + */ + public ConcurrentHashMap(int initialCapacity) { + if (initialCapacity < 0) { + throw new IllegalArgumentException(); } + int cap = initialCapacity >= MAXIMUM_CAPACITY >>> 1 ? + MAXIMUM_CAPACITY : + tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1); + sizeCtl = cap; + myHashingStrategy = this; + } - /** - * Return properly casted first entry of bin for given hash - */ - private HashEntry<K, V> getFirst(int hash) { - HashEntry[] tab = table; - return (HashEntry<K, V>)tab[hash & tab.length - 1]; + /** + * Creates a new map with the same mappings as the given map. + * + * @param m the map + */ + public ConcurrentHashMap(Map<? extends K, ? extends V> m) { + sizeCtl = DEFAULT_CAPACITY; + myHashingStrategy = this; + putAll(m); + } + + /** + * Creates a new, empty map with an initial table size based on + * the given number of elements ({@code initialCapacity}) and + * initial table density ({@code loadFactor}). + * + * @param initialCapacity the initial capacity. The implementation + * performs internal sizing to accommodate this many elements, + * given the specified load factor. + * @param loadFactor the load factor (table density) for + * establishing the initial table size + * @throws IllegalArgumentException if the initial capacity of + * elements is negative or the load factor is nonpositive + * @since 1.6 + */ + public ConcurrentHashMap(int initialCapacity, float loadFactor) { + this(initialCapacity, loadFactor, 1); + } + + /** + * Creates a new, empty map with an initial table size based on + * the given number of elements ({@code initialCapacity}), table + * density ({@code loadFactor}), and number of concurrently + * updating threads ({@code concurrencyLevel}). + * + * @param initialCapacity the initial capacity. The implementation + * performs internal sizing to accommodate this many elements, + * given the specified load factor. + * @param loadFactor the load factor (table density) for + * establishing the initial table size + * @param concurrencyLevel the estimated number of concurrently + * updating threads. The implementation may use this value as + * a sizing hint. + * @throws IllegalArgumentException if the initial capacity is + * negative or the load factor or concurrencyLevel are + * nonpositive + */ + public ConcurrentHashMap(int initialCapacity, + float loadFactor, int concurrencyLevel) { + this(initialCapacity, loadFactor, concurrencyLevel, THIS); + } + private static final TObjectHashingStrategy THIS = new TObjectHashingStrategy() { + @Override + public int computeHashCode(Object object) { + throw new IncorrectOperationException(); } - /** - * Read value field of an entry under lock. Called if value - * field ever appears to be null. This is possible only if a - * compiler happens to reorder a HashEntry initialization with - * its table assignment, which is legal under memory model - * but is not known to ever occur. - */ - private V readValueUnderLock(HashEntry<K, V> e) { - lock(); - try { - return e.value; + @Override + public boolean equals(Object o1, Object o2) { + throw new IncorrectOperationException(); + } + }; + public ConcurrentHashMap(int initialCapacity, + float loadFactor, int concurrencyLevel, @NotNull TObjectHashingStrategy<K> hashingStrategy) { + myHashingStrategy = hashingStrategy == THIS ? this : hashingStrategy; + if (!(loadFactor > 0.0f) || initialCapacity < 0 || concurrencyLevel <= 0) { + throw new IllegalArgumentException(); + } + if (initialCapacity < concurrencyLevel) // Use at least as many bins + { + initialCapacity = concurrencyLevel; // as estimated threads + } + long size = (long)(1.0 + (long)initialCapacity / loadFactor); + int cap = size >= (long)MAXIMUM_CAPACITY ? + MAXIMUM_CAPACITY : tableSizeFor((int)size); + sizeCtl = cap; + } + + // Original (since JDK1.2) Map methods + + /** + * {@inheritDoc} + */ + @Override + public int size() { + long n = sumCount(); + return n < 0L ? 0 : + n > (long)Integer.MAX_VALUE ? Integer.MAX_VALUE : + (int)n; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isEmpty() { + return sumCount() <= 0L; // ignore transient negative values + } + + /** + * Returns the value to which the specified key is mapped, + * or {@code null} if this map contains no mapping for the key. + * <p/> + * <p>More formally, if this map contains a mapping from a key + * {@code k} to a value {@code v} such that {@code key.equals(k)}, + * then this method returns {@code v}; otherwise it returns + * {@code null}. (There can be at most one such mapping.) + * + * @throws NullPointerException if the specified key is null + */ + @Override + public V get(Object key) { + Node<K, V>[] tab; + Node<K, V> e; + int n; + int h = hash((K)key); + if ((tab = table) != null && (n = tab.length) > 0 && + (e = tabAt(tab, n - 1 & h)) != null) { + int eh; + K ek; + if ((eh = e.hash) == h) { + if ((ek = e.key) == key || ek != null && myHashingStrategy.equals((K)key,ek)) { + return e.val; + } } - finally { - unlock(); + else if (eh < 0) { + Node<K, V> p; + return (p = e.find(h, key)) != null ? p.val : null; + } + while ((e = e.next) != null) { + if (e.hash == h && + ((ek = e.key) == key || ek != null && myHashingStrategy.equals((K)key,ek))) { + return e.val; + } } } + return null; + } + + /** + * Tests if the specified object is a key in this table. + * + * @param key possible key + * @return {@code true} if and only if the specified object + * is a key in this table, as determined by the + * {@code equals} method; {@code false} otherwise + * @throws NullPointerException if the specified key is null + */ + @Override + public boolean containsKey(Object key) { + return get(key) != null; + } - /* Specialized implementations of map methods */ + /** + * Returns {@code true} if this map maps one or more keys to the + * specified value. Note: This method may require a full traversal + * of the map, and is much slower than method {@code containsKey}. + * + * @param value value whose presence in this map is to be tested + * @return {@code true} if this map maps one or more keys to the + * specified value + * @throws NullPointerException if the specified value is null + */ + @Override + public boolean containsValue(Object value) { + if (value == null) { + throw new NullPointerException(); + } + Node<K, V>[] t; + if ((t = table) != null) { + Traverser<K, V> it = new Traverser<K, V>(t, t.length, 0, t.length); + for (Node<K, V> p; (p = it.advance()) != null; ) { + V v; + if ((v = p.val) == value || v != null && value.equals(v)) { + return true; + } + } + } + return false; + } + + /** + * Maps the specified key to the specified value in this table. + * Neither the key nor the value can be null. + * <p/> + * <p>The value can be retrieved by calling the {@code get} method + * with a key that is equal to the original key. + * + * @param key key with which the specified value is to be associated + * @param value value to be associated with the specified key + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key} + * @throws NullPointerException if the specified key or value is null + */ + @Override + public V put(K key, V value) { + return putVal(key, value, false); + } - private V get(K key, int hash) { - if (count != 0) { // read-volatile - HashEntry<K, V> e = getFirst(hash); - while (e != null) { - if (e.hash == hash && myHashingStrategy.equals(key, e.key)) { - V v = e.value; - if (v != null) { - return v; + /** + * Implementation for put and putIfAbsent + */ + final V putVal(K key, V value, boolean onlyIfAbsent) { + if (key == null || value == null) throw new NullPointerException(); + int hash = hash(key); + int binCount = 0; + for (Node<K, V>[] tab = table; ; ) { + Node<K, V> f; + int n; + int i; + int fh; + if (tab == null || (n = tab.length) == 0) { + tab = initTable(); + } + else if ((f = tabAt(tab, i = n - 1 & hash)) == null) { + if (casTabAt(tab, i, null, + new Node<K, V>(hash, key, value, null,myHashingStrategy))) { + break; // no lock when adding to empty bin + } + } + else if ((fh = f.hash) == MOVED) { + tab = helpTransfer(tab, f); + } + else { + V oldVal = null; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh >= 0) { + binCount = 1; + for (Node<K, V> e = f; ; ++binCount) { + K ek; + if (e.hash == hash && + ((ek = e.key) == key || + ek != null && myHashingStrategy.equals(key,ek))) { + oldVal = e.val; + if (!onlyIfAbsent) { + e.val = value; + } + break; + } + Node<K, V> pred = e; + if ((e = e.next) == null) { + pred.next = new Node<K, V>(hash, key, + value, null,myHashingStrategy); + break; + } + } + } + else if (f instanceof TreeBin) { + binCount = 2; + Node<K, V> p; + if ((p = ((TreeBin<K, V>)f).putTreeVal(hash, key, + value)) != null) { + oldVal = p.val; + if (!onlyIfAbsent) { + p.val = value; + } + } } - return readValueUnderLock(e); // recheck } - e = e.next; + } + if (binCount != 0) { + if (binCount >= TREEIFY_THRESHOLD) { + treeifyBin(tab, i); + } + if (oldVal != null) { + return oldVal; + } + break; } } - return null; } + addCount(1L, binCount); + return null; + } - private boolean containsKey(K key, int hash) { - if (count != 0) { // read-volatile - HashEntry<K, V> e = getFirst(hash); - while (e != null) { - if (e.hash == hash && myHashingStrategy.equals(key, e.key)) { - return true; + /** + * Copies all of the mappings from the specified map to this one. + * These mappings replace any mappings that this map had for any of the + * keys currently in the specified map. + * + * @param m mappings to be stored in this map + */ + @Override + public void putAll(@NotNull Map<? extends K, ? extends V> m) { + tryPresize(m.size()); + for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) { + putVal(e.getKey(), e.getValue(), false); + } + } + + /** + * Removes the key (and its corresponding value) from this map. + * This method does nothing if the key is not in the map. + * + * @param key the key that needs to be removed + * @return the previous value associated with {@code key}, or + * {@code null} if there was no mapping for {@code key} + * @throws NullPointerException if the specified key is null + */ + @Override + public V remove(Object key) { + return replaceNode(key, null, null); + } + + /** + * Implementation for the four public remove/replace methods: + * Replaces node value with v, conditional upon match of cv if + * non-null. If resulting value is null, delete. + */ + final V replaceNode(Object key, V value, Object cv) { + int hash = hash((K)key); + for (Node<K, V>[] tab = table; ; ) { + Node<K, V> f; + int n; + int i; + int fh; + if (tab == null || (n = tab.length) == 0 || + (f = tabAt(tab, i = n - 1 & hash)) == null) { + break; + } + else if ((fh = f.hash) == MOVED) { + tab = helpTransfer(tab, f); + } + else { + V oldVal = null; + boolean validated = false; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh >= 0) { + validated = true; + for (Node<K, V> e = f, pred = null; ; ) { + K ek; + if (e.hash == hash && + ((ek = e.key) == key || + ek != null && myHashingStrategy.equals((K)key,ek))) { + V ev = e.val; + if (cv == null || cv == ev || + ev != null && cv.equals(ev)) { + oldVal = ev; + if (value != null) { + e.val = value; + } + else if (pred != null) { + pred.next = e.next; + } + else { + setTabAt(tab, i, e.next); + } + } + break; + } + pred = e; + if ((e = e.next) == null) { + break; + } + } + } + else if (f instanceof TreeBin) { + validated = true; + TreeBin<K, V> t = (TreeBin<K, V>)f; + TreeNode<K, V> r; + TreeNode<K, V> p; + if ((r = t.root) != null && + (p = r.findTreeNode(hash, key, null)) != null) { + V pv = p.val; + if (cv == null || cv == pv || + pv != null && cv.equals(pv)) { + oldVal = pv; + if (value != null) { + p.val = value; + } + else if (t.removeTreeNode(p)) { + setTabAt(tab, i, untreeify(t.first)); + } + } + } + } } - e = e.next; + } + if (validated) { + if (oldVal != null) { + if (value == null) { + addCount(-1L, -1); + } + return oldVal; + } + break; } } - return false; } + return null; + } - private boolean containsValue(Object value) { - if (count != 0) { // read-volatile - HashEntry[] tab = table; - for (HashEntry tabEntry : tab) { - for (HashEntry<K, V> e = (HashEntry<K, V>)tabEntry; - e != null; - e = e.next) { - V v = e.value; - if (v == null) // recheck - { - v = readValueUnderLock(e); - } - if (value.equals(v)) { - return true; + /** + * Removes all of the mappings from this map. + */ + @Override + public void clear() { + long delta = 0L; // negative number of deletions + int i = 0; + Node<K, V>[] tab = table; + while (tab != null && i < tab.length) { + int fh; + Node<K, V> f = tabAt(tab, i); + if (f == null) { + ++i; + } + else if ((fh = f.hash) == MOVED) { + tab = helpTransfer(tab, f); + i = 0; // restart + } + else { + synchronized (f) { + if (tabAt(tab, i) == f) { + Node<K, V> p = fh >= 0 ? f : + f instanceof TreeBin ? + ((TreeBin<K, V>)f).first : null; + while (p != null) { + --delta; + p = p.next; } + setTabAt(tab, i++, null); } } } - return false; } + if (delta != 0L) { + addCount(delta, -1); + } + } + + /** + * Returns a {@link Set} view of the keys contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. The set supports element + * removal, which removes the corresponding mapping from this map, + * via the {@code Iterator.remove}, {@code Set.remove}, + * {@code removeAll}, {@code retainAll}, and {@code clear} + * operations. It does not support the {@code add} or + * {@code addAll} operations. + * <p/> + * <p>The view's {@code iterator} is a "weakly consistent" iterator + * that will never throw {@link ConcurrentModificationException}, + * and guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not guaranteed to) + * reflect any modifications subsequent to construction. + * + * @return the set view + */ + @NotNull + @Override + public KeySetView<K, V> keySet() { + KeySetView<K, V> ks; + return (ks = keySet) != null ? ks : (keySet = new KeySetView<K, V>(this, null)); + } + + /** + * Returns a {@link Collection} view of the values contained in this map. + * The collection is backed by the map, so changes to the map are + * reflected in the collection, and vice-versa. The collection + * supports element removal, which removes the corresponding + * mapping from this map, via the {@code Iterator.remove}, + * {@code Collection.remove}, {@code removeAll}, + * {@code retainAll}, and {@code clear} operations. It does not + * support the {@code add} or {@code addAll} operations. + * <p/> + * <p>The view's {@code iterator} is a "weakly consistent" iterator + * that will never throw {@link ConcurrentModificationException}, + * and guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not guaranteed to) + * reflect any modifications subsequent to construction. + * + * @return the collection view + */ + @NotNull + @Override + public Collection<V> values() { + ValuesView<K, V> vs; + return (vs = values) != null ? vs : (values = new ValuesView<K, V>(this)); + } + + /** + * Returns a {@link Set} view of the mappings contained in this map. + * The set is backed by the map, so changes to the map are + * reflected in the set, and vice-versa. The set supports element + * removal, which removes the corresponding mapping from the map, + * via the {@code Iterator.remove}, {@code Set.remove}, + * {@code removeAll}, {@code retainAll}, and {@code clear} + * operations. + * <p/> + * <p>The view's {@code iterator} is a "weakly consistent" iterator + * that will never throw {@link ConcurrentModificationException}, + * and guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not guaranteed to) + * reflect any modifications subsequent to construction. + * + * @return the set view + */ + @NotNull + @Override + public Set<Map.Entry<K, V>> entrySet() { + EntrySetView<K, V> es; + return (es = entrySet) != null ? es : (entrySet = new EntrySetView<K, V>(this)); + } + + /** + * Returns the hash code value for this {@link Map}, i.e., + * the sum of, for each key-value pair in the map, + * {@code key.hashCode() ^ value.hashCode()}. + * + * @return the hash code value for this map + */ + public int hashCode() { + int h = 0; + Node<K, V>[] t; + if ((t = table) != null) { + Traverser<K, V> it = new Traverser<K, V>(t, t.length, 0, t.length); + for (Node<K, V> p; (p = it.advance()) != null; ) { + h += hash(p.key) ^ p.val.hashCode(); + } + } + return h; + } - private boolean replace(K key, int hash, V oldValue, V newValue) { - lock(); - try { - HashEntry<K, V> e = getFirst(hash); - while (e != null && (e.hash != hash || !myHashingStrategy.equals(key, e.key))) { - e = e.next; + /** + * Returns a string representation of this map. The string + * representation consists of a list of key-value mappings (in no + * particular order) enclosed in braces ("{@code {}}"). Adjacent + * mappings are separated by the characters {@code ", "} (comma + * and space). Each key-value mapping is rendered as the key + * followed by an equals sign ("{@code =}") followed by the + * associated value. + * + * @return a string representation of this map + */ + public String toString() { + Node<K, V>[] t; + int f = (t = table) == null ? 0 : t.length; + Traverser<K, V> it = new Traverser<K, V>(t, f, 0, f); + StringBuilder sb = new StringBuilder(); + sb.append('{'); + Node<K, V> p; + if ((p = it.advance()) != null) { + for (; ; ) { + K k = p.key; + V v = p.val; + sb.append(k == this ? "(this Map)" : k); + sb.append('='); + sb.append(v == this ? "(this Map)" : v); + if ((p = it.advance()) == null) { + break; } + sb.append(',').append(' '); + } + } + return sb.append('}').toString(); + } - boolean replaced = false; - if (e != null && oldValue.equals(e.value)) { - replaced = true; - e.value = newValue; + /** + * Compares the specified object with this map for equality. + * Returns {@code true} if the given object is a map with the same + * mappings as this map. This operation may return misleading + * results if either map is concurrently modified during execution + * of this method. + * + * @param o object to be compared for equality with this map + * @return {@code true} if the specified object is equal to this map + */ + public boolean equals(Object o) { + if (o != this) { + if (!(o instanceof Map)) { + return false; + } + Map<?, ?> m = (Map<?, ?>)o; + Node<K, V>[] t; + int f = (t = table) == null ? 0 : t.length; + Traverser<K, V> it = new Traverser<K, V>(t, f, 0, f); + for (Node<K, V> p; (p = it.advance()) != null; ) { + V val = p.val; + Object v = m.get(p.key); + if (v == null || v != val && !v.equals(val)) { + return false; } - return replaced; } - finally { - unlock(); + for (Map.Entry<?, ?> e : m.entrySet()) { + Object mk; + Object mv; + Object v; + if ((mk = e.getKey()) == null || + (mv = e.getValue()) == null || + (v = get(mk)) == null || + mv != v && !mv.equals(v)) { + return false; + } } } + return true; + } - private V replace(K key, int hash, V newValue) { - lock(); - try { - HashEntry<K, V> e = getFirst(hash); - while (e != null && (e.hash != hash || !myHashingStrategy.equals(key, e.key))) { - e = e.next; + /** + * Stripped-down version of helper class used in previous version, + * declared for the sake of serialization compatibility + */ + static class Segment<K, V> extends ReentrantLock implements Serializable { + private static final long serialVersionUID = 2249069246763182397L; + final float loadFactor; + + Segment(float lf) { + loadFactor = lf; + } + } + + /** + * Saves the state of the {@code ConcurrentHashMapV8} instance to a + * stream (i.e., serializes it). + * + * @param s the stream + * @throws IOException if an I/O error occurs + * @serialData the key (Object) and value (Object) + * for each key-value mapping, followed by a null pair. + * The key-value mappings are emitted in no particular order. + */ + private void writeObject(ObjectOutputStream s) + throws IOException { + // For serialization compatibility + // Emulate segment calculation from previous version of this class + int sshift = 0; + int ssize = 1; + while (ssize < DEFAULT_CONCURRENCY_LEVEL) { + ++sshift; + ssize <<= 1; + } + int segmentShift = 32 - sshift; + int segmentMask = ssize - 1; + @SuppressWarnings("unchecked") Segment<K, V>[] segments = (Segment<K, V>[]) + new Segment<?, ?>[DEFAULT_CONCURRENCY_LEVEL]; + for (int i = 0; i < segments.length; ++i) { + segments[i] = new Segment<K, V>(LOAD_FACTOR); + } + s.putFields().put("segments", segments); + s.putFields().put("segmentShift", segmentShift); + s.putFields().put("segmentMask", segmentMask); + s.writeFields(); + + Node<K, V>[] t; + if ((t = table) != null) { + Traverser<K, V> it = new Traverser<K, V>(t, t.length, 0, t.length); + for (Node<K, V> p; (p = it.advance()) != null; ) { + s.writeObject(p.key); + s.writeObject(p.val); + } + } + s.writeObject(null); + s.writeObject(null); + segments = null; // throw away + } + + /** + * Reconstitutes the instance from a stream (that is, deserializes it). + * + * @param s the stream + * @throws ClassNotFoundException if the class of a serialized object + * could not be found + * @throws IOException if an I/O error occurs + */ + private void readObject(ObjectInputStream s) + throws IOException, ClassNotFoundException { + /* + * To improve performance in typical cases, we create nodes + * while reading, then place in table once size is known. + * However, we must also validate uniqueness and deal with + * overpopulated bins while doing so, which requires + * specialized versions of putVal mechanics. + */ + sizeCtl = -1; // force exclusion for table construction + s.defaultReadObject(); + long size = 0L; + Node<K, V> p = null; + for (; ; ) { + @SuppressWarnings("unchecked") K k = (K)s.readObject(); + @SuppressWarnings("unchecked") V v = (V)s.readObject(); + if (k != null && v != null) { + p = new Node<K, V>(hash(k), k, v, p,myHashingStrategy); + ++size; + } + else { + break; + } + } + if (size == 0L) { + sizeCtl = 0; + } + else { + int n; + if (size >= (long)(MAXIMUM_CAPACITY >>> 1)) { + n = MAXIMUM_CAPACITY; + } + else { + int sz = (int)size; + n = tableSizeFor(sz + (sz >>> 1) + 1); + } + @SuppressWarnings({"rawtypes", "unchecked"}) + Node<K, V>[] tab = (Node<K, V>[])new Node[n]; + int mask = n - 1; + long added = 0L; + while (p != null) { + boolean insertAtFront; + Node<K, V> next = p.next; + Node<K, V> first; + int h = p.hash; + int j = h & mask; + if ((first = tabAt(tab, j)) == null) { + insertAtFront = true; } + else { + K k = p.key; + if (first.hash < 0) { + TreeBin<K, V> t = (TreeBin<K, V>)first; + if (t.putTreeVal(h, k, p.val) == null) { + ++added; + } + insertAtFront = false; + } + else { + insertAtFront = true; + Node<K, V> q; + int binCount = 0; + for (q = first; q != null; q = q.next) { + K qk; + if (q.hash == h && + ((qk = q.key) == k || + qk != null && myHashingStrategy.equals(k,qk))) { + insertAtFront = false; + break; + } + ++binCount; + } + if (insertAtFront && binCount >= TREEIFY_THRESHOLD) { + insertAtFront = false; + ++added; + p.next = first; + TreeNode<K, V> hd = null; + TreeNode<K, V> tl = null; + for (q = p; q != null; q = q.next) { + TreeNode<K, V> t = new TreeNode<K, V> + (q.hash, q.key, q.val, null, null, myHashingStrategy); + if ((t.prev = tl) == null) { + hd = t; + } + else { + tl.next = t; + } + tl = t; + } + setTabAt(tab, j, new TreeBin<K, V>(hd, myHashingStrategy)); + } + } + } + if (insertAtFront) { + ++added; + p.next = first; + setTabAt(tab, j, p); + } + p = next; + } + table = tab; + sizeCtl = n - (n >>> 2); + baseCount = added; + } + } - V oldValue = null; - if (e != null) { - oldValue = e.value; - e.value = newValue; + // ConcurrentMap methods + + /** + * {@inheritDoc} + * + * @return the previous value associated with the specified key, + * or {@code null} if there was no mapping for the key + * @throws NullPointerException if the specified key or value is null + */ + @Override + public V putIfAbsent(@NotNull K key, V value) { + return putVal(key, value, true); + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException if the specified key is null + */ + @Override + public boolean remove(@NotNull Object key, Object value) { + return value != null && replaceNode(key, null, value) != null; + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException if any of the arguments are null + */ + @Override + public boolean replace(@NotNull K key, @NotNull V oldValue, @NotNull V newValue) { + return replaceNode(key, newValue, oldValue) != null; + } + + /** + * {@inheritDoc} + * + * @return the previous value associated with the specified key, + * or {@code null} if there was no mapping for the key + * @throws NullPointerException if the specified key or value is null + */ + @Override + public V replace(@NotNull K key, @NotNull V value) { + return replaceNode(key, value, null); + } + + // Overrides of JDK8+ Map extension method defaults + + /** + * Returns the value to which the specified key is mapped, or the + * given default value if this map contains no mapping for the + * key. + * + * @param key the key whose associated value is to be returned + * @param defaultValue the value to return if this map contains + * no mapping for the given key + * @return the mapping for the key, if present; else the default value + * @throws NullPointerException if the specified key is null + */ +// TODO no method in JDK6 +// @Override + public V getOrDefault(Object key, V defaultValue) { + V v; + return (v = get(key)) == null ? defaultValue : v; + } + + public void forEach(BiAction<? super K, ? super V> action) { + if (action == null) throw new NullPointerException(); + Node<K, V>[] t; + if ((t = table) != null) { + Traverser<K, V> it = new Traverser<K, V>(t, t.length, 0, t.length); + for (Node<K, V> p; (p = it.advance()) != null; ) { + action.apply(p.key, p.val); + } + } + } + + public void replaceAll(BiFun<? super K, ? super V, ? extends V> function) { + if (function == null) throw new NullPointerException(); + Node<K, V>[] t; + if ((t = table) != null) { + Traverser<K, V> it = new Traverser<K, V>(t, t.length, 0, t.length); + for (Node<K, V> p; (p = it.advance()) != null; ) { + V oldValue = p.val; + for (K key = p.key; ; ) { + V newValue = function.apply(key, oldValue); + if (newValue == null) { + throw new NullPointerException(); + } + if (replaceNode(key, newValue, oldValue) != null || + (oldValue = get(key)) == null) { + break; + } } - return oldValue; } - finally { - unlock(); + } + } + + /** + * If the specified key is not already associated with a value, + * attempts to compute its value using the given mapping function + * and enters it into this map unless {@code null}. The entire + * method invocation is performed atomically, so the function is + * applied at most once per key. Some attempted update operations + * on this map by other threads may be blocked while computation + * is in progress, so the computation should be short and simple, + * and must not attempt to update any other mappings of this map. + * + * @param key key with which the specified value is to be associated + * @param mappingFunction the function to compute a value + * @return the current (existing or computed) value associated with + * the specified key, or null if the computed value is null + * @throws NullPointerException if the specified key or mappingFunction + * is null + * @throws IllegalStateException if the computation detectably + * attempts a recursive update to this map that would + * otherwise never complete + * @throws RuntimeException or Error if the mappingFunction does so, + * in which case the mapping is left unestablished + */ + public V computeIfAbsent(K key, Fun<? super K, ? extends V> mappingFunction) { + if (key == null || mappingFunction == null) { + throw new NullPointerException(); + } + int h = hash(key); + V val = null; + int binCount = 0; + for (Node<K, V>[] tab = table; ; ) { + Node<K, V> f; + int n; + int i; + int fh; + if (tab == null || (n = tab.length) == 0) { + tab = initTable(); + } + else if ((f = tabAt(tab, i = n - 1 & h)) == null) { + Node<K, V> r = new ReservationNode<K, V>(); + synchronized (r) { + if (casTabAt(tab, i, null, r)) { + binCount = 1; + Node<K, V> node = null; + try { + if ((val = mappingFunction.apply(key)) != null) { + node = new Node<K, V>(h, key, val, null,myHashingStrategy); + } + } + finally { + setTabAt(tab, i, node); + } + } + } + if (binCount != 0) { + break; + } + } + else if ((fh = f.hash) == MOVED) { + tab = helpTransfer(tab, f); + } + else { + boolean added = false; + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh >= 0) { + binCount = 1; + for (Node<K, V> e = f; ; ++binCount) { + K ek; + if (e.hash == h && + ((ek = e.key) == key || + ek != null && myHashingStrategy.equals(key,ek))) { + val = e.val; + break; + } + Node<K, V> pred = e; + if ((e = e.next) == null) { + if ((val = mappingFunction.apply(key)) != null) { + added = true; + pred.next = new Node<K, V>(h, key, val, null,myHashingStrategy); + } + break; + } + } + } + else if (f instanceof TreeBin) { + binCount = 2; + TreeBin<K, V> t = (TreeBin<K, V>)f; + TreeNode<K, V> r; + TreeNode<K, V> p; + if ((r = t.root) != null && + (p = r.findTreeNode(h, key, null)) != null) { + val = p.val; + } + else if ((val = mappingFunction.apply(key)) != null) { + added = true; + t.putTreeVal(h, key, val); + } + } + } + } + if (binCount != 0) { + if (binCount >= TREEIFY_THRESHOLD) { + treeifyBin(tab, i); + } + if (!added) { + return val; + } + break; + } } } + if (val != null) { + addCount(1L, binCount); + } + return val; + } - private V put(K key, int hash, V value, boolean onlyIfAbsent) { - lock(); - try { - int c = count; - if (c++ > threshold) // ensure capacity - { - rehash(); + /** + * If the value for the specified key is present, attempts to + * compute a new mapping given the key and its current mapped + * value. The entire method invocation is performed atomically. + * Some attempted update operations on this map by other threads + * may be blocked while computation is in progress, so the + * computation should be short and simple, and must not attempt to + * update any other mappings of this map. + * + * @param key key with which a value may be associated + * @param remappingFunction the function to compute a value + * @return the new value associated with the specified key, or null if none + * @throws NullPointerException if the specified key or remappingFunction + * is null + * @throws IllegalStateException if the computation detectably + * attempts a recursive update to this map that would + * otherwise never complete + * @throws RuntimeException or Error if the remappingFunction does so, + * in which case the mapping is unchanged + */ + public V computeIfPresent(K key, BiFun<? super K, ? super V, ? extends V> remappingFunction) { + if (key == null || remappingFunction == null) { + throw new NullPointerException(); + } + int h = hash(key); + V val = null; + int delta = 0; + int binCount = 0; + for (Node<K, V>[] tab = table; ; ) { + Node<K, V> f; + int n; + int i; + int fh; + if (tab == null || (n = tab.length) == 0) { + tab = initTable(); + } + else if ((f = tabAt(tab, i = n - 1 & h)) == null) { + break; + } + else if ((fh = f.hash) == MOVED) { + tab = helpTransfer(tab, f); + } + else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh >= 0) { + binCount = 1; + for (Node<K, V> e = f, pred = null; ; ++binCount) { + K ek; + if (e.hash == h && + ((ek = e.key) == key || + ek != null && myHashingStrategy.equals(key,ek))) { + val = remappingFunction.apply(key, e.val); + if (val != null) { + e.val = val; + } + else { + delta = -1; + Node<K, V> en = e.next; + if (pred != null) { + pred.next = en; + } + else { + setTabAt(tab, i, en); + } + } + break; + } + pred = e; + if ((e = e.next) == null) { + break; + } + } + } + else if (f instanceof TreeBin) { + binCount = 2; + TreeBin<K, V> t = (TreeBin<K, V>)f; + TreeNode<K, V> r; + TreeNode<K, V> p; + if ((r = t.root) != null && + (p = r.findTreeNode(h, key, null)) != null) { + val = remappingFunction.apply(key, p.val); + if (val != null) { + p.val = val; + } + else { + delta = -1; + if (t.removeTreeNode(p)) { + setTabAt(tab, i, untreeify(t.first)); + } + } + } + } + } } - HashEntry[] tab = table; - int index = hash & tab.length - 1; - HashEntry<K, V> first = (HashEntry<K, V>)tab[index]; - HashEntry<K, V> e = first; - while (e != null && (e.hash != hash || !myHashingStrategy.equals(key, e.key))) { - e = e.next; + if (binCount != 0) { + break; } + } + } + if (delta != 0) { + addCount((long)delta, binCount); + } + return val; + } - V oldValue; - if (e != null) { - oldValue = e.value; - if (!onlyIfAbsent) { - e.value = value; + /** + * Attempts to compute a mapping for the specified key and its + * current mapped value (or {@code null} if there is no current + * mapping). The entire method invocation is performed atomically. + * Some attempted update operations on this map by other threads + * may be blocked while computation is in progress, so the + * computation should be short and simple, and must not attempt to + * update any other mappings of this Map. + * + * @param key key with which the specified value is to be associated + * @param remappingFunction the function to compute a value + * @return the new value associated with the specified key, or null if none + * @throws NullPointerException if the specified key or remappingFunction + * is null + * @throws IllegalStateException if the computation detectably + * attempts a recursive update to this map that would + * otherwise never complete + * @throws RuntimeException or Error if the remappingFunction does so, + * in which case the mapping is unchanged + */ + public V compute(K key, + BiFun<? super K, ? super V, ? extends V> remappingFunction) { + if (key == null || remappingFunction == null) { + throw new NullPointerException(); + } + int h = hash(key); + V val = null; + int delta = 0; + int binCount = 0; + for (Node<K, V>[] tab = table; ; ) { + Node<K, V> f; + int n; + int i; + int fh; + if (tab == null || (n = tab.length) == 0) { + tab = initTable(); + } + else if ((f = tabAt(tab, i = n - 1 & h)) == null) { + Node<K, V> r = new ReservationNode<K, V>(); + synchronized (r) { + if (casTabAt(tab, i, null, r)) { + binCount = 1; + Node<K, V> node = null; + try { + if ((val = remappingFunction.apply(key, null)) != null) { + delta = 1; + node = new Node<K, V>(h, key, val, null,myHashingStrategy); + } + } + finally { + setTabAt(tab, i, node); + } } } - else { - oldValue = null; - ++modCount; - tab[index] = new HashEntry<K, V>(key, hash, first, value); - count = c; // write-volatile + if (binCount != 0) { + break; } - return oldValue; } - finally { - unlock(); + else if ((fh = f.hash) == MOVED) { + tab = helpTransfer(tab, f); + } + else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh >= 0) { + binCount = 1; + for (Node<K, V> e = f, pred = null; ; ++binCount) { + K ek; + if (e.hash == h && + ((ek = e.key) == key || + ek != null && myHashingStrategy.equals(key,ek))) { + val = remappingFunction.apply(key, e.val); + if (val != null) { + e.val = val; + } + else { + delta = -1; + Node<K, V> en = e.next; + if (pred != null) { + pred.next = en; + } + else { + setTabAt(tab, i, en); + } + } + break; + } + pred = e; + if ((e = e.next) == null) { + val = remappingFunction.apply(key, null); + if (val != null) { + delta = 1; + pred.next = + new Node<K, V>(h, key, val, null,myHashingStrategy); + } + break; + } + } + } + else if (f instanceof TreeBin) { + binCount = 1; + TreeBin<K, V> t = (TreeBin<K, V>)f; + TreeNode<K, V> r; + TreeNode<K, V> p; + if ((r = t.root) != null) { + p = r.findTreeNode(h, key, null); + } + else { + p = null; + } + V pv = p == null ? null : p.val; + val = remappingFunction.apply(key, pv); + if (val != null) { + if (p != null) { + p.val = val; + } + else { + delta = 1; + t.putTreeVal(h, key, val); + } + } + else if (p != null) { + delta = -1; + if (t.removeTreeNode(p)) { + setTabAt(tab, i, untreeify(t.first)); + } + } + } + } + } + if (binCount != 0) { + if (binCount >= TREEIFY_THRESHOLD) { + treeifyBin(tab, i); + } + break; + } + } + } + if (delta != 0) { + addCount((long)delta, binCount); + } + return val; + } + + /** + * If the specified key is not already associated with a + * (non-null) value, associates it with the given value. + * Otherwise, replaces the value with the results of the given + * remapping function, or removes if {@code null}. The entire + * method invocation is performed atomically. Some attempted + * update operations on this map by other threads may be blocked + * while computation is in progress, so the computation should be + * short and simple, and must not attempt to update any other + * mappings of this Map. + * + * @param key key with which the specified value is to be associated + * @param value the value to use if absent + * @param remappingFunction the function to recompute a value if present + * @return the new value associated with the specified key, or null if none + * @throws NullPointerException if the specified key or the + * remappingFunction is null + * @throws RuntimeException or Error if the remappingFunction does so, + * in which case the mapping is unchanged + */ + public V merge(K key, V value, BiFun<? super V, ? super V, ? extends V> remappingFunction) { + if (key == null || value == null || remappingFunction == null) { + throw new NullPointerException(); + } + int h = hash(key); + V val = null; + int delta = 0; + int binCount = 0; + for (Node<K, V>[] tab = table; ; ) { + Node<K, V> f; + int n; + int i; + int fh; + if (tab == null || (n = tab.length) == 0) { + tab = initTable(); + } + else if ((f = tabAt(tab, i = n - 1 & h)) == null) { + if (casTabAt(tab, i, null, new Node<K, V>(h, key, value, null,myHashingStrategy))) { + delta = 1; + val = value; + break; + } + } + else if ((fh = f.hash) == MOVED) { + tab = helpTransfer(tab, f); + } + else { + synchronized (f) { + if (tabAt(tab, i) == f) { + if (fh >= 0) { + binCount = 1; + for (Node<K, V> e = f, pred = null; ; ++binCount) { + K ek; + if (e.hash == h && + ((ek = e.key) == key || + ek != null && myHashingStrategy.equals(key, ek))) { + val = remappingFunction.apply(e.val, value); + if (val != null) { + e.val = val; + } + else { + delta = -1; + Node<K, V> en = e.next; + if (pred != null) { + pred.next = en; + } + else { + setTabAt(tab, i, en); + } + } + break; + } + pred = e; + if ((e = e.next) == null) { + delta = 1; + val = value; + pred.next = + new Node<K, V>(h, key, val, null,myHashingStrategy); + break; + } + } + } + else if (f instanceof TreeBin) { + binCount = 2; + TreeBin<K, V> t = (TreeBin<K, V>)f; + TreeNode<K, V> r = t.root; + TreeNode<K, V> p = r == null ? null : + r.findTreeNode(h, key, null); + val = p == null ? value : + remappingFunction.apply(p.val, value); + if (val != null) { + if (p != null) { + p.val = val; + } + else { + delta = 1; + t.putTreeVal(h, key, val); + } + } + else if (p != null) { + delta = -1; + if (t.removeTreeNode(p)) { + setTabAt(tab, i, untreeify(t.first)); + } + } + } + } + } + if (binCount != 0) { + if (binCount >= TREEIFY_THRESHOLD) { + treeifyBin(tab, i); + } + break; + } + } + } + if (delta != 0) { + addCount((long)delta, binCount); + } + return val; + } + + // Hashtable legacy methods + + /** + * Legacy method testing if some key maps into the specified value + * in this table. This method is identical in functionality to + * {@link #containsValue(Object)}, and exists solely to ensure + * full compatibility with class {@link java.util.Hashtable}, + * which supported this method prior to introduction of the + * Java Collections framework. + * + * @param value a value to search for + * @return {@code true} if and only if some key maps to the + * {@code value} argument in this table as + * determined by the {@code equals} method; + * {@code false} otherwise + * @throws NullPointerException if the specified value is null + */ + @Deprecated + public boolean contains(Object value) { + return containsValue(value); + } + + /** + * Returns an enumeration of the keys in this table. + * + * @return an enumeration of the keys in this table + * @see #keySet() + */ + public Enumeration<K> keys() { + Node<K, V>[] t; + int f = (t = table) == null ? 0 : t.length; + return new KeyIterator<K, V>(t, f, 0, f, this); + } + + /** + * Returns an enumeration of the values in this table. + * + * @return an enumeration of the values in this table + * @see #values() + */ + public Enumeration<V> elements() { + Node<K, V>[] t; + int f = (t = table) == null ? 0 : t.length; + return new ValueIterator<K, V>(t, f, 0, f, this); + } + + // ConcurrentHashMapV8-only methods + + /** + * Returns the number of mappings. This method should be used + * instead of {@link #size} because a ConcurrentHashMapV8 may + * contain more mappings than can be represented as an int. The + * value returned is an estimate; the actual count may differ if + * there are concurrent insertions or removals. + * + * @return the number of mappings + * @since 1.8 + */ + public long mappingCount() { + long n = sumCount(); + return n < 0L ? 0L : n; // ignore transient negative values + } + + /** + * Creates a new {@link Set} backed by a ConcurrentHashMapV8 + * from the given type to {@code Boolean.TRUE}. + * + * @return the new set + * @since 1.8 + */ + public static <K> KeySetView<K, Boolean> newKeySet() { + return new KeySetView<K, Boolean> + (new ConcurrentHashMap<K, Boolean>(), Boolean.TRUE); + } + + /** + * Creates a new {@link Set} backed by a ConcurrentHashMapV8 + * from the given type to {@code Boolean.TRUE}. + * + * @param initialCapacity The implementation performs internal + * sizing to accommodate this many elements. + * @return the new set + * @throws IllegalArgumentException if the initial capacity of + * elements is negative + * @since 1.8 + */ + public static <K> KeySetView<K, Boolean> newKeySet(int initialCapacity) { + return new KeySetView<K, Boolean> + (new ConcurrentHashMap<K, Boolean>(initialCapacity), Boolean.TRUE); + } + + /** + * Returns a {@link Set} view of the keys in this map, using the + * given common mapped value for any additions (i.e., {@link + * Collection#add} and {@link Collection#addAll(Collection)}). + * This is of course only appropriate if it is acceptable to use + * the same value for all additions from this view. + * + * @param mappedValue the mapped value to use for any additions + * @return the set view + * @throws NullPointerException if the mappedValue is null + */ + public KeySetView<K, V> keySet(V mappedValue) { + if (mappedValue == null) { + throw new NullPointerException(); + } + return new KeySetView<K, V>(this, mappedValue); + } + + /* ---------------- Special Nodes -------------- */ + + /** + * A node inserted at head of bins during transfer operations. + */ + static final class ForwardingNode<K, V> extends Node<K, V> { + final Node<K, V>[] nextTable; + + ForwardingNode(Node<K, V>[] tab, TObjectHashingStrategy<K> hashingStrategy) { + super(MOVED, null, null, null, hashingStrategy); + nextTable = tab; + } + + @Override + Node<K, V> find(int h, Object k) { + // loop to avoid arbitrarily deep recursion on forwarding nodes + outer: + for (Node<K, V>[] tab = nextTable; ; ) { + Node<K, V> e; + int n; + if (k == null || tab == null || (n = tab.length) == 0 || + (e = tabAt(tab, n - 1 & h)) == null) { + return null; + } + for (; ; ) { + int eh; + K ek; + if ((eh = e.hash) == h && + ((ek = e.key) == k || ek != null && myHashingStrategy.equals((K)k, ek))) { + return e; + } + if (eh < 0) { + if (e instanceof ForwardingNode) { + tab = ((ForwardingNode<K, V>)e).nextTable; + continue outer; + } + else { + return e.find(h, k); + } + } + if ((e = e.next) == null) { + return null; + } + } + } + } + } + + /** + * A place-holder node used in computeIfAbsent and compute + */ + static final class ReservationNode<K, V> extends Node<K, V> { + ReservationNode() { + super(RESERVED, null, null, null,null); + } + + @Override + Node<K, V> find(int h, Object k) { + return null; + } + } + + /* ---------------- Table Initialization and Resizing -------------- */ + + /** + * Initializes table, using the size recorded in sizeCtl. + */ + private Node<K, V>[] initTable() { + Node<K, V>[] tab; + while ((tab = table) == null || tab.length == 0) { + int sc; + if ((sc = sizeCtl) < 0) { + Thread.yield(); // lost initialization race; just spin + } + else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { + try { + if ((tab = table) == null || tab.length == 0) { + int n = sc > 0 ? sc : DEFAULT_CAPACITY; + @SuppressWarnings({"rawtypes", "unchecked"}) + Node<K, V>[] nt = (Node<K, V>[])new Node[n]; + table = tab = nt; + sc = n - (n >>> 2); + } + } + finally { + sizeCtl = sc; + } + break; } } + return tab; + } - private void rehash() { - HashEntry[] oldTable = table; - int oldCapacity = oldTable.length; - if (oldCapacity >= MAXIMUM_CAPACITY) { + /** + * Adds to count, and if table is too small and not already + * resizing, initiates transfer. If already resizing, helps + * perform transfer if work is available. Rechecks occupancy + * after a transfer to see if another resize is already needed + * because resizings are lagging additions. + * + * @param x the count to add + * @param check if <0, don't check resize, if <= 1 only check if uncontended + */ + private void addCount(long x, int check) { + CounterCell[] as; + long b; + long s; + if ((as = counterCells) != null || + !U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) { + CounterHashCode hc; + CounterCell a; + long v; + int m; + boolean uncontended = true; + if ((hc = threadCounterHashCode.get()) == null || + as == null || (m = as.length - 1) < 0 || + (a = as[m & hc.code]) == null || + !(uncontended = + U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) { + fullAddCount(x, hc, uncontended); + return; + } + if (check <= 1) { return; } + s = sumCount(); + } + if (check >= 0) { + Node<K, V>[] tab; + int sc; + while (s >= (long)(sc = sizeCtl) && (tab = table) != null && + tab.length < MAXIMUM_CAPACITY) { + if (sc < 0) { + Node<K, V>[] nt; + if (sc == -1 || transferIndex <= transferOrigin || + (nt = nextTable) == null) { + break; + } + if (U.compareAndSwapInt(this, SIZECTL, sc, sc - 1)) { + transfer(tab, nt); + } + } + else if (U.compareAndSwapInt(this, SIZECTL, sc, -2)) { + transfer(tab, null); + } + s = sumCount(); + } + } + } - /* - * Reclassify nodes in each list to new Map. Because we are - * using power-of-two expansion, the elements from each bin - * must either stay at same index, or move with a power of two - * offset. We eliminate unnecessary node creation by catching - * cases where old nodes can be reused because their next - * fields won't change. Statistically, at the default - * threshold, only about one-sixth of them need cloning when - * a table doubles. The nodes they replace will be garbage - * collectable as soon as they are no longer referenced by any - * reader thread that may be in the midst of traversing table - * right now. - */ - - HashEntry[] newTable = new HashEntry[oldCapacity << 1]; - threshold = (int)(newTable.length * loadFactor); - int sizeMask = newTable.length - 1; - for (int i = 0; i < oldCapacity; i++) { - // We need to guarantee that any existing reads of old Map can - // proceed. So we cannot yet null out each bin. - HashEntry<K, V> e = (HashEntry<K, V>)oldTable[i]; + /** + * Helps transfer if a resize is in progress. + */ + final Node<K, V>[] helpTransfer(Node<K, V>[] tab, Node<K, V> f) { + Node<K, V>[] nextTab; + if (f instanceof ForwardingNode && + (nextTab = ((ForwardingNode<K, V>)f).nextTable) != null) { + int sc; + if (nextTab == nextTable && tab == table && + transferIndex > transferOrigin && (sc = sizeCtl) < -1 && + U.compareAndSwapInt(this, SIZECTL, sc, sc - 1)) { + transfer(tab, nextTab); + } + return nextTab; + } + return table; + } - if (e != null) { - HashEntry<K, V> next = e.next; - int idx = e.hash & sizeMask; + /** + * Tries to presize table to accommodate the given number of elements. + * + * @param size number of elements (doesn't need to be perfectly accurate) + */ + private void tryPresize(int size) { + int c = size >= MAXIMUM_CAPACITY >>> 1 ? MAXIMUM_CAPACITY : + tableSizeFor(size + (size >>> 1) + 1); + int sc; + while ((sc = sizeCtl) >= 0) { + Node<K, V>[] tab = table; + int n; + if (tab == null || (n = tab.length) == 0) { + n = sc > c ? sc : c; + if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { + try { + if (table == tab) { + @SuppressWarnings({"rawtypes", "unchecked"}) + Node<K, V>[] nt = (Node<K, V>[])new Node[n]; + table = nt; + sc = n - (n >>> 2); + } + } + finally { + sizeCtl = sc; + } + } + } + else if (c <= sc || n >= MAXIMUM_CAPACITY) { + break; + } + else if (tab == table && + U.compareAndSwapInt(this, SIZECTL, sc, -2)) { + transfer(tab, null); + } + } + } + + /** + * Moves and/or copies the nodes in each bin to new table. See + * above for explanation. + */ + private void transfer(Node<K, V>[] tab, Node<K, V>[] nextTab) { + int n = tab.length; + int stride; + if ((stride = NCPU > 1 ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE) { + stride = MIN_TRANSFER_STRIDE; // subdivide range + } + if (nextTab == null) { // initiating + try { + @SuppressWarnings({"rawtypes", "unchecked"}) + Node<K, V>[] nt = (Node<K, V>[])new Node[n << 1]; + nextTab = nt; + } + catch (Throwable ex) { // try to cope with OOME + sizeCtl = Integer.MAX_VALUE; + return; + } + nextTable = nextTab; + transferOrigin = n; + transferIndex = n; + ForwardingNode<K, V> rev = new ForwardingNode<K, V>(tab, myHashingStrategy); + for (int k = n; k > 0; ) { // progressively reveal ready slots + int nextk = k > stride ? k - stride : 0; + for (int m = nextk; m < k; ++m) { + nextTab[m] = rev; + } + for (int m = n + nextk; m < n + k; ++m) { + nextTab[m] = rev; + } + U.putOrderedInt(this, TRANSFERORIGIN, k = nextk); + } + } + int nextn = nextTab.length; + ForwardingNode<K, V> fwd = new ForwardingNode<K, V>(nextTab, myHashingStrategy); + boolean advance = true; + boolean finishing = false; // to ensure sweep before committing nextTab + for (int i = 0, bound = 0; ; ) { + while (advance) { + int nextIndex; + int nextBound; + if (--i >= bound || finishing) { + advance = false; + } + else if ((nextIndex = transferIndex) <= transferOrigin) { + i = -1; + advance = false; + } + else if (U.compareAndSwapInt + (this, TRANSFERINDEX, nextIndex, + nextBound = nextIndex > stride ? + nextIndex - stride : 0)) { + bound = nextBound; + i = nextIndex - 1; + advance = false; + } + } + int fh; + Node<K, V> f; + if (i < 0 || i >= n || i + n >= nextn) { + if (finishing) { + nextTable = null; + table = nextTab; + sizeCtl = (n << 1) - (n >>> 1); + return; + } + for (int sc; ; ) { + if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, ++sc)) { + if (sc != -1) { + return; + } + finishing = advance = true; + i = n; // recheck before commit + break; + } + } + } + else if ((f = tabAt(tab, i)) == null) { + if (casTabAt(tab, i, null, fwd)) { + setTabAt(nextTab, i, null); + setTabAt(nextTab, i + n, null); + advance = true; + } + } + else if ((fh = f.hash) == MOVED) { + advance = true; // already processed + } + else { + synchronized (f) { + if (tabAt(tab, i) == f) { + Node<K, V> ln; + Node<K, V> hn; + if (fh >= 0) { + int runBit = fh & n; + Node<K, V> lastRun = f; + for (Node<K, V> p = f.next; p != null; p = p.next) { + int b = p.hash & n; + if (b != runBit) { + runBit = b; + lastRun = p; + } + } + if (runBit == 0) { + ln = lastRun; + hn = null; + } + else { + hn = lastRun; + ln = null; + } + for (Node<K, V> p = f; p != lastRun; p = p.next) { + int ph = p.hash; + K pk = p.key; + V pv = p.val; + if ((ph & n) == 0) { + ln = new Node<K, V>(ph, pk, pv, ln,myHashingStrategy); + } + else { + hn = new Node<K, V>(ph, pk, pv, hn,myHashingStrategy); + } + } + setTabAt(nextTab, i, ln); + setTabAt(nextTab, i + n, hn); + setTabAt(tab, i, fwd); + advance = true; + } + else if (f instanceof TreeBin) { + TreeBin<K, V> t = (TreeBin<K, V>)f; + TreeNode<K, V> lo = null; + TreeNode<K, V> loTail = null; + TreeNode<K, V> hi = null; + TreeNode<K, V> hiTail = null; + int lc = 0; + int hc = 0; + for (Node<K, V> e = t.first; e != null; e = e.next) { + int h = e.hash; + TreeNode<K, V> p = new TreeNode<K, V> + (h, e.key, e.val, null, null, myHashingStrategy); + if ((h & n) == 0) { + if ((p.prev = loTail) == null) { + lo = p; + } + else { + loTail.next = p; + } + loTail = p; + ++lc; + } + else { + if ((p.prev = hiTail) == null) { + hi = p; + } + else { + hiTail.next = p; + } + hiTail = p; + ++hc; + } + } + ln = lc <= UNTREEIFY_THRESHOLD ? untreeify(lo) : + hc != 0 ? new TreeBin<K, V>(lo, myHashingStrategy) : t; + hn = hc <= UNTREEIFY_THRESHOLD ? untreeify(hi) : + lc != 0 ? new TreeBin<K, V>(hi, myHashingStrategy) : t; + setTabAt(nextTab, i, ln); + setTabAt(nextTab, i + n, hn); + setTabAt(tab, i, fwd); + advance = true; + } + } + } + } + } + } + + /* ---------------- Conversion from/to TreeBins -------------- */ - // Single node on list - if (next == null) { - newTable[idx] = e; + /** + * Replaces all linked nodes in bin at given index unless table is + * too small, in which case resizes instead. + */ + private void treeifyBin(Node<K, V>[] tab, int index) { + if (tab != null) { + Node<K, V> b; + if (tab.length < MIN_TREEIFY_CAPACITY) { + int sc; + if (tab == table && (sc = sizeCtl) >= 0 && + U.compareAndSwapInt(this, SIZECTL, sc, -2)) { + transfer(tab, null); + } + } + else if ((b = tabAt(tab, index)) != null && b.hash >= 0) { + synchronized (b) { + if (tabAt(tab, index) == b) { + TreeNode<K, V> hd = null; + TreeNode<K, V> tl = null; + for (Node<K, V> e = b; e != null; e = e.next) { + TreeNode<K, V> p = + new TreeNode<K, V>(e.hash, e.key, e.val, + null, null, myHashingStrategy); + if ((p.prev = tl) == null) { + hd = p; + } + else { + tl.next = p; + } + tl = p; + } + setTabAt(tab, index, new TreeBin<K, V>(hd, myHashingStrategy)); } + } + } + } + } + + /** + * Returns a list on non-TreeNodes replacing those in given list. + */ + static <K, V> Node<K, V> untreeify(Node<K, V> b) { + Node<K, V> hd = null; + Node<K, V> tl = null; + for (Node<K, V> q = b; q != null; q = q.next) { + Node<K, V> p = new Node<K, V>(q.hash, q.key, q.val, null,q.myHashingStrategy); + if (tl == null) { + hd = p; + } + else { + tl.next = p; + } + tl = p; + } + return hd; + } + /* ---------------- TreeNodes -------------- */ + + /** + * Nodes for use in TreeBins + */ + static final class TreeNode<K, V> extends Node<K, V> { + TreeNode<K, V> parent; // red-black tree links + TreeNode<K, V> left; + TreeNode<K, V> right; + TreeNode<K, V> prev; // needed to unlink next upon deletion + boolean red; + + TreeNode(int hash, K key, V val, Node<K, V> next, + TreeNode<K, V> parent, TObjectHashingStrategy<K> hashingStrategy) { + super(hash, key, val, next, hashingStrategy); + this.parent = parent; + } + + @Override + Node<K, V> find(int h, Object k) { + return findTreeNode(h, k, null); + } + + /** + * Returns the TreeNode (or null if not found) for the given key + * starting at given root. + */ + final TreeNode<K, V> findTreeNode(int h, Object k, Class<?> kc) { + if (k != null) { + TreeNode<K, V> p = this; + do { + int ph; + int dir; + K pk; + TreeNode<K, V> q; + TreeNode<K, V> pl = p.left; + TreeNode<K, V> pr = p.right; + if ((ph = p.hash) > h) { + p = pl; + } + else if (ph < h) { + p = pr; + } + else if ((pk = p.key) == k || pk != null && myHashingStrategy.equals((K)k,pk)) { + return p; + } + else if (pl == null) { + p = pr; + } + else if (pr == null) { + p = pl; + } + else if ((kc != null || + (kc = comparableClassFor(k)) != null) && + (dir = compareComparables(kc, k, pk)) != 0) { + p = dir < 0 ? pl : pr; + } + else if ((q = pr.findTreeNode(h, k, kc)) != null) { + return q; + } else { - // Reuse trailing consecutive sequence at same slot - HashEntry<K, V> lastRun = e; - int lastIdx = idx; - for (HashEntry<K, V> last = next; - last != null; - last = last.next) { - int k = last.hash & sizeMask; - if (k != lastIdx) { - lastIdx = k; - lastRun = last; + p = pl; + } + } + while (p != null); + } + return null; + } + } + + /* ---------------- TreeBins -------------- */ + + /** + * TreeNodes used at the heads of bins. TreeBins do not hold user + * keys or values, but instead point to list of TreeNodes and + * their root. They also maintain a parasitic read-write lock + * forcing writers (who hold bin lock) to wait for readers (who do + * not) to complete before tree restructuring operations. + */ + static final class TreeBin<K, V> extends Node<K, V> { + TreeNode<K, V> root; + volatile TreeNode<K, V> first; + volatile Thread waiter; + volatile int lockState; + // values for lockState + static final int WRITER = 1; // set while holding write lock + static final int WAITER = 2; // set when waiting for write lock + static final int READER = 4; // increment value for setting read lock + + /** + * Tie-breaking utility for ordering insertions when equal + * hashCodes and non-comparable. We don't require a total + * order, just a consistent insertion rule to maintain + * equivalence across rebalancings. Tie-breaking further than + * necessary simplifies testing a bit. + */ + static int tieBreakOrder(Object a, Object b) { + int d; + if (a == null || b == null || + (d = a.getClass().getName(). + compareTo(b.getClass().getName())) == 0) { + d = System.identityHashCode(a) <= System.identityHashCode(b) ? + -1 : 1; + } + return d; + } + + /** + * Creates bin with initial set of nodes headed by b. + */ + TreeBin(TreeNode<K, V> b, TObjectHashingStrategy<K> hashingStrategy) { + super(TREEBIN, null, null, null, hashingStrategy); + first = b; + TreeNode<K, V> r = null; + for (TreeNode<K, V> x = b, next; x != null; x = next) { + next = (TreeNode<K, V>)x.next; + x.left = x.right = null; + if (r == null) { + x.parent = null; + x.red = false; + r = x; + } + else { + K k = x.key; + int h = x.hash; + Class<?> kc = null; + for (TreeNode<K, V> p = r; ; ) { + int dir; + int ph; + K pk = p.key; + if ((ph = p.hash) > h) { + dir = -1; + } + else if (ph < h) { + dir = 1; + } + else if (kc == null && + (kc = comparableClassFor(k)) == null || + (dir = compareComparables(kc, k, pk)) == 0) { + dir = tieBreakOrder(k, pk); + } + TreeNode<K, V> xp = p; + if ((p = dir <= 0 ? p.left : p.right) == null) { + x.parent = xp; + if (dir <= 0) { + xp.left = x; + } + else { + xp.right = x; } + r = balanceInsertion(r, x); + break; } - newTable[lastIdx] = lastRun; + } + } + } + root = r; + assert checkInvariants(root); + } - // Clone all remaining nodes - for (HashEntry<K, V> p = e; p != lastRun; p = p.next) { - int k = p.hash & sizeMask; - HashEntry<K, V> n = (HashEntry<K, V>)newTable[k]; - newTable[k] = new HashEntry<K, V>(p.key, p.hash, - n, p.value); + /** + * Acquires write lock for tree restructuring. + */ + private void lockRoot() { + if (!U.compareAndSwapInt(this, LOCKSTATE, 0, WRITER)) { + contendedLock(); // offload to separate method + } + } + + /** + * Releases write lock for tree restructuring. + */ + private void unlockRoot() { + lockState = 0; + } + + /** + * Possibly blocks awaiting root lock. + */ + private void contendedLock() { + boolean waiting = false; + for (int s; ; ) { + if (((s = lockState) & WRITER) == 0) { + if (U.compareAndSwapInt(this, LOCKSTATE, s, WRITER)) { + if (waiting) { + waiter = null; + } + return; + } + } + else if ((s & WAITER) == 0) { + if (U.compareAndSwapInt(this, LOCKSTATE, s, s | WAITER)) { + waiting = true; + waiter = Thread.currentThread(); + } + } + else if (waiting) { + LockSupport.park(this); + } + } + } + + /** + * Returns matching node or null if none. Tries to search + * using tree comparisons from root, but continues linear + * search when lock not available. + */ + @Override + final Node<K, V> find(int h, Object k) { + if (k != null) { + for (Node<K, V> e = first; e != null; e = e.next) { + int s; + if (((s = lockState) & (WAITER | WRITER)) != 0) { + K ek; + if (e.hash == h && + ((ek = e.key) == k || ek != null && myHashingStrategy.equals((K)k,ek))) { + return e; + } + } + else if (U.compareAndSwapInt(this, LOCKSTATE, s, + s + READER)) { + TreeNode<K, V> p; + try { + TreeNode<K, V> r; + p = (r = root) == null ? null : + r.findTreeNode(h, k, null); + } + finally { + int ls; + do { + } + while (!U.compareAndSwapInt + (this, LOCKSTATE, + ls = lockState, ls - READER)); + Thread w; + if (ls == (READER | WAITER) && (w = waiter) != null) { + LockSupport.unpark(w); + } + } + return p; + } + } + } + return null; + } + + /** + * Finds or adds a node. + * + * @return null if added + */ + final TreeNode<K, V> putTreeVal(int h, K k, V v) { + Class<?> kc = null; + boolean searched = false; + for (TreeNode<K, V> p = root; ; ) { + int dir; + int ph; + K pk; + if (p == null) { + first = root = new TreeNode<K, V>(h, k, v, null, null, myHashingStrategy); + break; + } + else if ((ph = p.hash) > h) { + dir = -1; + } + else if (ph < h) { + dir = 1; + } + else if ((pk = p.key) == k || pk != null && myHashingStrategy.equals(k,pk)) { + return p; + } + else if (kc == null && + (kc = comparableClassFor(k)) == null || + (dir = compareComparables(kc, k, pk)) == 0) { + if (!searched) { + searched = true; + TreeNode<K, V> q; + TreeNode<K, V> ch; + if ((ch = p.left) != null && + (q = ch.findTreeNode(h, k, kc)) != null || + (ch = p.right) != null && + (q = ch.findTreeNode(h, k, kc)) != null) { + return q; } } + dir = tieBreakOrder(k, pk); + } + + TreeNode<K, V> xp = p; + if ((p = dir <= 0 ? p.left : p.right) == null) { + TreeNode<K, V> x; + TreeNode<K, V> f = first; + first = x = new TreeNode<K, V>(h, k, v, f, xp, myHashingStrategy); + if (f != null) { + f.prev = x; + } + if (dir <= 0) { + xp.left = x; + } + else { + xp.right = x; + } + if (!xp.red) { + x.red = true; + } + else { + lockRoot(); + try { + root = balanceInsertion(root, x); + } + finally { + unlockRoot(); + } + } + break; } } - table = newTable; + assert checkInvariants(root); + return null; } /** - * Remove; match on key only if value null, else match both. + * Removes the given node, that must be present before this + * call. This is messier than typical red-black deletion code + * because we cannot swap the contents of an interior node + * with a leaf successor that is pinned by "next" pointers + * that are accessible independently of lock. So instead we + * swap the tree linkages. + * + * @return true if now too small, so should be untreeified */ - private V remove(K key, int hash, V value) { - lock(); + final boolean removeTreeNode(TreeNode<K, V> p) { + TreeNode<K, V> next = (TreeNode<K, V>)p.next; + TreeNode<K, V> pred = p.prev; // unlink traversal pointers + if (pred == null) { + first = next; + } + else { + pred.next = next; + } + if (next != null) { + next.prev = pred; + } + if (first == null) { + root = null; + return true; + } + TreeNode<K, V> r; + TreeNode<K, V> rl; + if ((r = root) == null || r.right == null || // too small + (rl = r.left) == null || rl.left == null) { + return true; + } + lockRoot(); try { - int c = count - 1; - HashEntry[] tab = table; - int index = hash & tab.length - 1; - HashEntry<K, V> first = (HashEntry<K, V>)tab[index]; - HashEntry<K, V> e = first; - while (e != null && (e.hash != hash || !myHashingStrategy.equals(key, e.key))) { - e = e.next; + TreeNode<K, V> replacement; + TreeNode<K, V> pl = p.left; + TreeNode<K, V> pr = p.right; + if (pl != null && pr != null) { + TreeNode<K, V> s = pr; + TreeNode<K, V> sl; + while ((sl = s.left) != null) // find successor + { + s = sl; + } + boolean c = s.red; + s.red = p.red; + p.red = c; // swap colors + TreeNode<K, V> sr = s.right; + TreeNode<K, V> pp = p.parent; + if (s == pr) { // p was s's direct parent + p.parent = s; + s.right = p; + } + else { + TreeNode<K, V> sp = s.parent; + if ((p.parent = sp) != null) { + if (s == sp.left) { + sp.left = p; + } + else { + sp.right = p; + } + } + if ((s.right = pr) != null) { + pr.parent = s; + } + } + p.left = null; + if ((p.right = sr) != null) { + sr.parent = p; + } + if ((s.left = pl) != null) { + pl.parent = s; + } + if ((s.parent = pp) == null) { + r = s; + } + else if (p == pp.left) { + pp.left = s; + } + else { + pp.right = s; + } + if (sr != null) { + replacement = sr; + } + else { + replacement = p; + } + } + else if (pl != null) { + replacement = pl; + } + else if (pr != null) { + replacement = pr; + } + else { + replacement = p; + } + if (replacement != p) { + TreeNode<K, V> pp = replacement.parent = p.parent; + if (pp == null) { + r = replacement; + } + else if (p == pp.left) { + pp.left = replacement; + } + else { + pp.right = replacement; + } + p.left = p.right = p.parent = null; } - V oldValue = null; - if (e != null) { - V v = e.value; - if (value == null || value.equals(v)) { - oldValue = v; - // All entries following removed node can stay - // in list, but all preceding ones need to be - // cloned. - ++modCount; - HashEntry<K, V> newFirst = e.next; - for (HashEntry<K, V> p = first; p != e; p = p.next) { - newFirst = new HashEntry<K, V>(p.key, p.hash, newFirst, p.value); + root = p.red ? r : balanceDeletion(r, replacement); + + if (p == replacement) { // detach pointers + TreeNode<K, V> pp; + if ((pp = p.parent) != null) { + if (p == pp.left) { + pp.left = null; } - tab[index] = newFirst; - count = c; // write-volatile + else if (p == pp.right) { + pp.right = null; + } + p.parent = null; } } - return oldValue; } finally { - unlock(); + unlockRoot(); } + assert checkInvariants(root); + return false; } - private void clear() { - if (count != 0) { - lock(); - try { - HashEntry[] tab = table; - for (int i = 0; i < tab.length; i++) { - tab[i] = null; + /* ------------------------------------------------------------ */ + // Red-black tree methods, all adapted from CLR + + static <K, V> TreeNode<K, V> rotateLeft(TreeNode<K, V> root, + TreeNode<K, V> p) { + TreeNode<K, V> r; + if (p != null && (r = p.right) != null) { + TreeNode<K, V> rl; + if ((rl = p.right = r.left) != null) { + rl.parent = p; + } + TreeNode<K, V> pp; + if ((pp = r.parent = p.parent) == null) { + (root = r).red = false; + } + else if (pp.left == p) { + pp.left = r; + } + else { + pp.right = r; + } + r.left = p; + p.parent = r; + } + return root; + } + + static <K, V> TreeNode<K, V> rotateRight(TreeNode<K, V> root, + TreeNode<K, V> p) { + TreeNode<K, V> l; + if (p != null && (l = p.left) != null) { + TreeNode<K, V> lr; + if ((lr = p.left = l.right) != null) { + lr.parent = p; + } + TreeNode<K, V> pp; + if ((pp = l.parent = p.parent) == null) { + (root = l).red = false; + } + else if (pp.right == p) { + pp.right = l; + } + else { + pp.left = l; + } + l.right = p; + p.parent = l; + } + return root; + } + + static <K, V> TreeNode<K, V> balanceInsertion(TreeNode<K, V> root, + TreeNode<K, V> x) { + x.red = true; + for (TreeNode<K, V> xp, xpp, xppl, xppr; ; ) { + if ((xp = x.parent) == null) { + x.red = false; + return x; + } + else if (!xp.red || (xpp = xp.parent) == null) { + return root; + } + if (xp == (xppl = xpp.left)) { + if ((xppr = xpp.right) != null && xppr.red) { + xppr.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } + else { + if (x == xp.right) { + root = rotateLeft(root, x = xp); + xpp = (xp = x.parent) == null ? null : xp.parent; + } + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + root = rotateRight(root, xpp); + } + } } - ++modCount; - count = 0; // write-volatile } - finally { - unlock(); + else { + if (xppl != null && xppl.red) { + xppl.red = false; + xp.red = false; + xpp.red = true; + x = xpp; + } + else { + if (x == xp.left) { + root = rotateRight(root, x = xp); + xpp = (xp = x.parent) == null ? null : xp.parent; + } + if (xp != null) { + xp.red = false; + if (xpp != null) { + xpp.red = true; + root = rotateLeft(root, xpp); + } + } + } } } } - } - /* ---------------- Public operations -------------- */ + static <K, V> TreeNode<K, V> balanceDeletion(TreeNode<K, V> root, + TreeNode<K, V> x) { + for (TreeNode<K, V> xp, xpl, xpr; ; ) { + if (x == null || x == root) { + return root; + } + else if ((xp = x.parent) == null) { + x.red = false; + return x; + } + else if (x.red) { + x.red = false; + return root; + } + else if ((xpl = xp.left) == x) { + if ((xpr = xp.right) != null && xpr.red) { + xpr.red = false; + xp.red = true; + root = rotateLeft(root, xp); + xpr = (xp = x.parent) == null ? null : xp.right; + } + if (xpr == null) { + x = xp; + } + else { + TreeNode<K, V> sl = xpr.left; + TreeNode<K, V> sr = xpr.right; + if ((sr == null || !sr.red) && + (sl == null || !sl.red)) { + xpr.red = true; + x = xp; + } + else { + if (sr == null || !sr.red) { + if (sl != null) { + sl.red = false; + } + xpr.red = true; + root = rotateRight(root, xpr); + xpr = (xp = x.parent) == null ? + null : xp.right; + } + if (xpr != null) { + xpr.red = xp == null ? false : xp.red; + if ((sr = xpr.right) != null) { + sr.red = false; + } + } + if (xp != null) { + xp.red = false; + root = rotateLeft(root, xp); + } + x = root; + } + } + } + else { // symmetric + if (xpl != null && xpl.red) { + xpl.red = false; + xp.red = true; + root = rotateRight(root, xp); + xpl = (xp = x.parent) == null ? null : xp.left; + } + if (xpl == null) { + x = xp; + } + else { + TreeNode<K, V> sl = xpl.left; + TreeNode<K, V> sr = xpl.right; + if ((sl == null || !sl.red) && + (sr == null || !sr.red)) { + xpl.red = true; + x = xp; + } + else { + if (sl == null || !sl.red) { + if (sr != null) { + sr.red = false; + } + xpl.red = true; + root = rotateLeft(root, xpl); + xpl = (xp = x.parent) == null ? + null : xp.left; + } + if (xpl != null) { + xpl.red = xp == null ? false : xp.red; + if ((sl = xpl.left) != null) { + sl.red = false; + } + } + if (xp != null) { + xp.red = false; + root = rotateRight(root, xp); + } + x = root; + } + } + } + } + } + + /** + * Recursive invariant check + */ + static <K, V> boolean checkInvariants(TreeNode<K, V> t) { + TreeNode<K, V> tp = t.parent; + TreeNode<K, V> tl = t.left; + TreeNode<K, V> tr = t.right; + TreeNode<K, V> tb = t.prev; + TreeNode<K, V> tn = (TreeNode<K, V>)t.next; + if (tb != null && tb.next != t) { + return false; + } + if (tn != null && tn.prev != t) { + return false; + } + if (tp != null && t != tp.left && t != tp.right) { + return false; + } + if (tl != null && (tl.parent != t || tl.hash > t.hash)) { + return false; + } + if (tr != null && (tr.parent != t || tr.hash < t.hash)) { + return false; + } + if (t.red && tl != null && tl.red && tr != null && tr.red) { + return false; + } + if (tl != null && !checkInvariants(tl)) { + return false; + } + if (tr != null && !checkInvariants(tr)) { + return false; + } + return true; + } + + private static final Unsafe U; + private static final long LOCKSTATE; - public ConcurrentHashMap(TObjectHashingStrategy<K> hashingStrategy) { - this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_SEGMENTS, hashingStrategy); + static { + try { + U = getUnsafe(); + Class<?> k = TreeBin.class; + LOCKSTATE = U.objectFieldOffset + (k.getDeclaredField("lockState")); + } + catch (Exception e) { + throw new Error(e); + } + } } + /* ----------------Table Traversal -------------- */ + /** - * Creates a new, empty map with the specified initial - * capacity, load factor, and concurrency level. - * - * @param initialCapacity the initial capacity. The implementation - * performs internal sizing to accommodate this many elements. - * @param loadFactor the load factor threshold, used to control resizing. - * Resizing may be performed when the average number of elements per - * bin exceeds this threshold. - * @param concurrencyLevel the estimated number of concurrently - * updating threads. The implementation performs internal sizing - * to try to accommodate this many threads. - * @throws IllegalArgumentException if the initial capacity is - * negative or the load factor or concurrencyLevel are - * nonpositive. + * Encapsulates traversal for methods such as containsValue; also + * serves as a base class for other iterators and spliterators. + * <p/> + * Method advance visits once each still-valid node that was + * reachable upon iterator construction. It might miss some that + * were added to a bin after the bin was visited, which is OK wrt + * consistency guarantees. Maintaining this property in the face + * of possible ongoing resizes requires a fair amount of + * bookkeeping state that is difficult to optimize away amidst + * volatile accesses. Even so, traversal maintains reasonable + * throughput. + * <p/> + * Normally, iteration proceeds bin-by-bin traversing lists. + * However, if the table has been resized, then all future steps + * must traverse both the bin at the current index as well as at + * (index + baseSize); and so on for further resizings. To + * paranoically cope with potential sharing by users of iterators + * across threads, iteration terminates if a bounds checks fails + * for a table read. */ - public ConcurrentHashMap(int initialCapacity, - float loadFactor, int concurrencyLevel) { - this(initialCapacity, loadFactor, concurrencyLevel, null); + static class Traverser<K, V> { + Node<K, V>[] tab; // current table; updated if resized + Node<K, V> next; // the next entry to use + int index; // index of bin to use next + int baseIndex; // current index of initial table + int baseLimit; // index bound for initial table + final int baseSize; // initial table size + + Traverser(Node<K, V>[] tab, int size, int index, int limit) { + this.tab = tab; + baseSize = size; + baseIndex = this.index = index; + baseLimit = limit; + next = null; + } + + /** + * Advances if possible, returning next valid node, or null if none. + */ + final Node<K, V> advance() { + Node<K, V> e; + if ((e = next) != null) { + e = e.next; + } + for (; ; ) { + if (e != null) { + return next = e; + } + Node<K, V>[] t; + int i; + int n; + if (baseIndex >= baseLimit || (t = tab) == null || + (n = t.length) <= (i = index) || i < 0) { + return next = null; + } + if ((e = tabAt(t, index)) != null && e.hash < 0) { + if (e instanceof ForwardingNode) { + tab = ((ForwardingNode<K, V>)e).nextTable; + e = null; + continue; + } + else if (e instanceof TreeBin) { + e = ((TreeBin<K, V>)e).first; + } + else { + e = null; + } + } + if ((index += baseSize) >= n) { + index = ++baseIndex; // visit upper slots if present + } + } + } } - public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel, TObjectHashingStrategy<K> hashingStrategy) { - if (!(loadFactor > 0) || initialCapacity < 0 || concurrencyLevel <= 0) { - throw new IllegalArgumentException(); + /** + * Base of key, value, and entry Iterators. Adds fields to + * Traverser to support iterator.remove. + */ + static class BaseIterator<K, V> extends Traverser<K, V> { + final ConcurrentHashMap<K, V> map; + Node<K, V> lastReturned; + + BaseIterator(Node<K, V>[] tab, int size, int index, int limit, + ConcurrentHashMap<K, V> map) { + super(tab, size, index, limit); + this.map = map; + advance(); } - if (concurrencyLevel > MAX_SEGMENTS) { - concurrencyLevel = MAX_SEGMENTS; + public final boolean hasNext() { + return next != null; } - // Find power-of-two sizes best matching arguments - int ssize = 1; - while (ssize < concurrencyLevel) { - ssize <<= 1; + public final boolean hasMoreElements() { + return next != null; } - segmentShift = - 12; // the middle of the hash is much more random that its HSB. Especially when we use TObjectHashingStrategy.CANONICAl as a hash provider - segmentMask = ssize - 1; - segments = new Segment[ssize]; - if (initialCapacity > MAXIMUM_CAPACITY) { - initialCapacity = MAXIMUM_CAPACITY; + public final void remove() { + Node<K, V> p; + if ((p = lastReturned) == null) { + throw new IllegalStateException(); + } + lastReturned = null; + map.replaceNode(p.key, null, null); } - int c = initialCapacity / ssize; - if (c * ssize < initialCapacity) { - ++c; + } + + static final class KeyIterator<K, V> extends BaseIterator<K, V> + implements Iterator<K>, Enumeration<K> { + KeyIterator(Node<K, V>[] tab, int index, int size, int limit, + ConcurrentHashMap<K, V> map) { + super(tab, index, size, limit, map); } - int cap = 1; - while (cap < c) { - cap <<= 1; + + @Override + public final K next() { + Node<K, V> p; + if ((p = next) == null) { + throw new NoSuchElementException(); + } + K k = p.key; + lastReturned = p; + advance(); + return k; } - hashingStrategy = hashingStrategy == null ? this : hashingStrategy; - for (int i = 0; i < segments.length; ++i) { - segments[i] = new Segment<K, V>(cap, loadFactor, hashingStrategy); + @Override + public final K nextElement() { + return next(); } - myHashingStrategy = hashingStrategy; } - /** - * Creates a new, empty map with the specified initial - * capacity, and with default load factor and concurrencyLevel. - * - * @param initialCapacity the initial capacity. The implementation - * performs internal sizing to accommodate this many elements. - * @throws IllegalArgumentException if the initial capacity of - * elements is negative. - */ - public ConcurrentHashMap(int initialCapacity) { - this(initialCapacity, DEFAULT_LOAD_FACTOR, DEFAULT_SEGMENTS); + static final class ValueIterator<K, V> extends BaseIterator<K, V> + implements Iterator<V>, Enumeration<V> { + ValueIterator(Node<K, V>[] tab, int index, int size, int limit, + ConcurrentHashMap<K, V> map) { + super(tab, index, size, limit, map); + } + + @Override + public final V next() { + Node<K, V> p; + if ((p = next) == null) { + throw new NoSuchElementException(); + } + V v = p.val; + lastReturned = p; + advance(); + return v; + } + + @Override + public final V nextElement() { + return next(); + } } - /** - * Creates a new, empty map with a default initial capacity, - * load factor, and concurrencyLevel. - */ - public ConcurrentHashMap() { - this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR, DEFAULT_SEGMENTS); + static final class EntryIterator<K, V> extends BaseIterator<K, V> + implements Iterator<Map.Entry<K, V>> { + EntryIterator(Node<K, V>[] tab, int index, int size, int limit, + ConcurrentHashMap<K, V> map) { + super(tab, index, size, limit, map); + } + + @Override + public final Map.Entry<K, V> next() { + Node<K, V> p; + if ((p = next) == null) { + throw new NoSuchElementException(); + } + K k = p.key; + V v = p.val; + lastReturned = p; + advance(); + return new MapEntry<K, V>(k, v, map); + } } /** - * Creates a new map with the same mappings as the given map. The - * map is created with a capacity of twice the number of mappings in - * the given map or 11 (whichever is greater), and a default load factor - * and concurrencyLevel. - * - * @param t the map + * Exported Entry for EntryIterator */ - public ConcurrentHashMap(Map<? extends K, ? extends V> t) { - this(Math.max((int)(t.size() / DEFAULT_LOAD_FACTOR) + 1, - 11), - DEFAULT_LOAD_FACTOR, DEFAULT_SEGMENTS); - putAll(t); + static final class MapEntry<K, V> implements Map.Entry<K, V> { + final K key; // non-null + V val; // non-null + final ConcurrentHashMap<K, V> map; + + MapEntry(K key, V val, ConcurrentHashMap<K, V> map) { + this.key = key; + this.val = val; + this.map = map; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + return val; + } + + public int hashCode() { + return key.hashCode() ^ val.hashCode(); + } + + public String toString() { + return key + "=" + val; + } + + public boolean equals(Object o) { + Object k; + Object v; + Map.Entry<?, ?> e; + return o instanceof Entry && + (k = (e = (Entry<?, ?>)o).getKey()) != null && + (v = e.getValue()) != null && + (k == key || map.myHashingStrategy.equals((K)k,key)) && + (v == val || v.equals(val)); + } + + /** + * Sets our entry's value and writes through to the map. The + * value to return is somewhat arbitrary here. Since we do not + * necessarily track asynchronous changes, the most recent + * "previous" value could be different from what we return (or + * could even have been removed, in which case the put will + * re-establish). We do not and cannot guarantee more. + */ + @Override + public V setValue(V value) { + if (value == null) throw new NullPointerException(); + V v = val; + val = value; + map.put(key, value); + return v; + } } - // inherit Map javadoc - @Override - public boolean isEmpty() { - final Segment[] segments = this.segments; - /* - * We keep track of per-segment modCounts to avoid ABA - * problems in which an element in one segment was added and - * in another removed during traversal, in which case the - * table was never actually empty at any point. Note the - * similar use of modCounts in the size() and containsValue() - * methods, which are the only other methods also susceptible - * to ABA problems. - */ - int[] mc = new int[segments.length]; - int mcsum = 0; - for (int i = 0; i < segments.length; ++i) { - if (segments[i].count != 0) { + static final class KeySpliterator<K, V> extends Traverser<K, V> + implements ConcurrentHashMapSpliterator<K> { + long est; // size estimate + + KeySpliterator(Node<K, V>[] tab, int size, int index, int limit, + long est) { + super(tab, size, index, limit); + this.est = est; + } + + @Override + public ConcurrentHashMapSpliterator<K> trySplit() { + int i; + int f; + int h; + return (h = (i = baseIndex) + (f = baseLimit) >>> 1) <= i ? null : + new KeySpliterator<K, V>(tab, baseSize, baseLimit = h, + f, est >>>= 1); + } + + @Override + public void forEachRemaining(Action<? super K> action) { + if (action == null) throw new NullPointerException(); + for (Node<K, V> p; (p = advance()) != null; ) { + action.apply(p.key); + } + } + + @Override + public boolean tryAdvance(Action<? super K> action) { + if (action == null) throw new NullPointerException(); + Node<K, V> p; + if ((p = advance()) == null) { return false; } - else { - mcsum += mc[i] = segments[i].modCount; + action.apply(p.key); + return true; + } + + @Override + public long estimateSize() { + return est; + } + } + + static final class ValueSpliterator<K, V> extends Traverser<K, V> + implements ConcurrentHashMapSpliterator<V> { + long est; // size estimate + + ValueSpliterator(Node<K, V>[] tab, int size, int index, int limit, + long est) { + super(tab, size, index, limit); + this.est = est; + } + + @Override + public ConcurrentHashMapSpliterator<V> trySplit() { + int i; + int f; + int h; + return (h = (i = baseIndex) + (f = baseLimit) >>> 1) <= i ? null : + new ValueSpliterator<K, V>(tab, baseSize, baseLimit = h, + f, est >>>= 1); + } + + @Override + public void forEachRemaining(Action<? super V> action) { + if (action == null) throw new NullPointerException(); + for (Node<K, V> p; (p = advance()) != null; ) { + action.apply(p.val); } } - // If mcsum happens to be zero, then we know we got a snapshot - // before any modifications at all were made. This is - // probably common enough to bother tracking. - if (mcsum != 0) { - for (int i = 0; i < segments.length; ++i) { - if (segments[i].count != 0 || - mc[i] != segments[i].modCount) { - return false; - } + + @Override + public boolean tryAdvance(Action<? super V> action) { + if (action == null) throw new NullPointerException(); + Node<K, V> p; + if ((p = advance()) == null) { + return false; } + action.apply(p.val); + return true; + } + + @Override + public long estimateSize() { + return est; } - return true; } - // inherit Map javadoc - @Override - public int size() { - final Segment[] segments = this.segments; - long sum = 0; - long check = 0; - int[] mc = new int[segments.length]; - // Try a few times to get accurate count. On failure due to - // continuous async changes in table, resort to locking. - for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) { - check = 0; - sum = 0; - int mcsum = 0; - for (int i = 0; i < segments.length; ++i) { - sum += segments[i].count; - mcsum += mc[i] = segments[i].modCount; - } - if (mcsum != 0) { - for (int i = 0; i < segments.length; ++i) { - check += segments[i].count; - if (mc[i] != segments[i].modCount) { - check = -1; // force retry - break; - } - } + static final class EntrySpliterator<K, V> extends Traverser<K, V> + implements ConcurrentHashMapSpliterator<Map.Entry<K, V>> { + final ConcurrentHashMap<K, V> map; // To export MapEntry + long est; // size estimate + + EntrySpliterator(Node<K, V>[] tab, int size, int index, int limit, + long est, ConcurrentHashMap<K, V> map) { + super(tab, size, index, limit); + this.map = map; + this.est = est; + } + + @Override + public ConcurrentHashMapSpliterator<Map.Entry<K, V>> trySplit() { + int i; + int f; + int h; + return (h = (i = baseIndex) + (f = baseLimit) >>> 1) <= i ? null : + new EntrySpliterator<K, V>(tab, baseSize, baseLimit = h, + f, est >>>= 1, map); + } + + @Override + public void forEachRemaining(Action<? super Map.Entry<K, V>> action) { + if (action == null) throw new NullPointerException(); + for (Node<K, V> p; (p = advance()) != null; ) { + action.apply(new MapEntry<K, V>(p.key, p.val, map)); } - if (check == sum) { - break; + } + + @Override + public boolean tryAdvance(Action<? super Map.Entry<K, V>> action) { + if (action == null) throw new NullPointerException(); + Node<K, V> p; + if ((p = advance()) == null) { + return false; } + action.apply(new MapEntry<K, V>(p.key, p.val, map)); + return true; } - if (check != sum) { // Resort to locking all segments - sum = 0; - for (Segment segment : segments) segment.lock(); - for (Segment segment : segments) sum += segment.count; - for (Segment segment : segments) segment.unlock(); + + @Override + public long estimateSize() { + return est; } - return sum > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int)sum; } + // Parallel bulk operations /** - * Returns the value to which the specified key is mapped in this table. + * Computes initial batch value for bulk tasks. The returned value + * is approximately exp2 of the number of times (minus one) to + * split task by two before executing leaf action. This value is + * faster to compute and more convenient to use as a guide to + * splitting than is the depth, since it is used while dividing by + * two anyway. + */ + final int batchFor(long b) { + long n; + if (b == Long.MAX_VALUE || (n = sumCount()) <= 1L || n < b) { + return 0; + } + int sp = ForkJoinPool.getCommonPoolParallelism() << 2; // slack of 4 + return b <= 0L || (n /= b) >= sp ? sp : (int)n; + } + + /** + * Performs the given action for each (key, value). * - * @param key a key in the table. - * @return the value to which the key is mapped in this table; - * <tt>null</tt> if the key is not mapped to any value in - * this table. - * @throws NullPointerException if the key is - * <tt>null</tt>. + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param action the action + * @since 1.8 */ - @Override - public V get(Object key) { - int hash = myHashingStrategy.computeHashCode((K)key); // throws NullPointerException if key null - return segmentFor(hash).get((K)key, hash); + public void forEach(long parallelismThreshold, + BiAction<? super K, ? super V> action) { + if (action == null) throw new NullPointerException(); + new ForEachMappingTask<K, V> + (null, batchFor(parallelismThreshold), 0, 0, table, + action).invoke(); } /** - * Tests if the specified object is a key in this table. + * Performs the given action for each non-null transformation + * of each (key, value). * - * @param key possible key. - * @return <tt>true</tt> if and only if the specified object - * is a key in this table, as determined by the - * <tt>equals</tt> method; <tt>false</tt> otherwise. - * @throws NullPointerException if the key is - * <tt>null</tt>. + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element, or null if there is no transformation (in + * which case the action is not applied) + * @param action the action + * @since 1.8 */ - @Override - public boolean containsKey(Object key) { - int hash = myHashingStrategy.computeHashCode((K)key); // throws NullPointerException if key null - return segmentFor(hash).containsKey((K)key, hash); + public <U> void forEach(long parallelismThreshold, + BiFun<? super K, ? super V, ? extends U> transformer, + Action<? super U> action) { + if (transformer == null || action == null) { + throw new NullPointerException(); + } + new ForEachTransformedMappingTask<K, V, U> + (null, batchFor(parallelismThreshold), 0, 0, table, + transformer, action).invoke(); } /** - * Returns <tt>true</tt> if this map maps one or more keys to the - * specified value. Note: This method requires a full internal - * traversal of the hash table, and so is much slower than - * method <tt>containsKey</tt>. + * Returns a non-null result from applying the given search + * function on each (key, value), or null if none. Upon + * success, further element processing is suppressed and the + * results of any other parallel invocations of the search + * function are ignored. * - * @param value value whose presence in this map is to be tested. - * @return <tt>true</tt> if this map maps one or more keys to the - * specified value. - * @throws NullPointerException if the value is <tt>null</tt>. + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param searchFunction a function returning a non-null + * result on success, else null + * @return a non-null result from applying the given search + * function on each (key, value), or null if none + * @since 1.8 */ - @Override - public boolean containsValue(@NotNull Object value) { - // See explanation of modCount use above - - final Segment[] segments = this.segments; - int[] mc = new int[segments.length]; - - // Try a few times without locking - for (int k = 0; k < RETRIES_BEFORE_LOCK; ++k) { - int sum = 0; - int mcsum = 0; - for (int i = 0; i < segments.length; ++i) { - int c = segments[i].count; - mcsum += mc[i] = segments[i].modCount; - if (segments[i].containsValue(value)) { - return true; - } - } - boolean cleanSweep = true; - if (mcsum != 0) { - for (int i = 0; i < segments.length; ++i) { - int c = segments[i].count; - if (mc[i] != segments[i].modCount) { - cleanSweep = false; - break; - } - } - } - if (cleanSweep) { - return false; - } + public <U> U search(long parallelismThreshold, + BiFun<? super K, ? super V, ? extends U> searchFunction) { + if (searchFunction == null) throw new NullPointerException(); + return new SearchMappingsTask<K, V, U> + (null, batchFor(parallelismThreshold), 0, 0, table, + searchFunction, new AtomicReference<U>()).invoke(); + } + + /** + * Returns the result of accumulating the given transformation + * of all (key, value) pairs using the given reducer to + * combine values, or null if none. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element, or null if there is no transformation (in + * which case it is not combined) + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all (key, value) pairs + * @since 1.8 + */ + public <U> U reduce(long parallelismThreshold, + BiFun<? super K, ? super V, ? extends U> transformer, + BiFun<? super U, ? super U, ? extends U> reducer) { + if (transformer == null || reducer == null) { + throw new NullPointerException(); } - // Resort to locking all segments - for (Segment segment : segments) { - segment.lock(); + return new MapReduceMappingsTask<K, V, U> + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, reducer).invoke(); + } + + /** + * Returns the result of accumulating the given transformation + * of all (key, value) pairs using the given reducer to + * combine values, and the given basis as an identity value. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element + * @param basis the identity (initial default value) for the reduction + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all (key, value) pairs + * @since 1.8 + */ + public double reduceToDouble(long parallelismThreshold, + ObjectByObjectToDouble<? super K, ? super V> transformer, + double basis, + DoubleByDoubleToDouble reducer) { + if (transformer == null || reducer == null) { + throw new NullPointerException(); } - boolean found = false; - try { - for (Segment segment : segments) { - if (segment.containsValue(value)) { - found = true; - break; - } - } + return new MapReduceMappingsToDoubleTask<K, V> + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, basis, reducer).invoke(); + } + + /** + * Returns the result of accumulating the given transformation + * of all (key, value) pairs using the given reducer to + * combine values, and the given basis as an identity value. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element + * @param basis the identity (initial default value) for the reduction + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all (key, value) pairs + * @since 1.8 + */ + public long reduceToLong(long parallelismThreshold, + ObjectByObjectToLong<? super K, ? super V> transformer, + long basis, + LongByLongToLong reducer) { + if (transformer == null || reducer == null) { + throw new NullPointerException(); } - finally { - for (Segment segment : segments) { - segment.unlock(); - } + return new MapReduceMappingsToLongTask<K, V> + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, basis, reducer).invoke(); + } + + /** + * Returns the result of accumulating the given transformation + * of all (key, value) pairs using the given reducer to + * combine values, and the given basis as an identity value. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element + * @param basis the identity (initial default value) for the reduction + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all (key, value) pairs + * @since 1.8 + */ + public int reduceToInt(long parallelismThreshold, + ObjectByObjectToInt<? super K, ? super V> transformer, + int basis, + IntByIntToInt reducer) { + if (transformer == null || reducer == null) { + throw new NullPointerException(); } - return found; + return new MapReduceMappingsToIntTask<K, V> + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, basis, reducer).invoke(); } /** - * Legacy method testing if some key maps into the specified value - * in this table. This method is identical in functionality to - * {@link #containsValue}, and exists solely to ensure - * full compatibility with class {@link java.util.Hashtable}, - * which supported this method prior to introduction of the - * Java Collections framework. + * Performs the given action for each key. * - * @param value a value to search for. - * @return <tt>true</tt> if and only if some key maps to the - * <tt>value</tt> argument in this table as - * determined by the <tt>equals</tt> method; - * <tt>false</tt> otherwise. - * @throws NullPointerException if the value is <tt>null</tt>. + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param action the action + * @since 1.8 */ - public boolean contains(Object value) { - return containsValue(value); + public void forEachKey(long parallelismThreshold, + Action<? super K> action) { + if (action == null) throw new NullPointerException(); + new ForEachKeyTask<K, V> + (null, batchFor(parallelismThreshold), 0, 0, table, + action).invoke(); } /** - * Maps the specified <tt>key</tt> to the specified - * <tt>value</tt> in this table. Neither the key nor the - * value can be <tt>null</tt>. - * <p/> - * <p> The value can be retrieved by calling the <tt>get</tt> method - * with a key that is equal to the original key. + * Performs the given action for each non-null transformation + * of each key. * - * @param key the table key. - * @param value the value. - * @return the previous value of the specified key in this table, - * or <tt>null</tt> if it did not have one. - * @throws NullPointerException if the key or value is - * <tt>null</tt>. + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element, or null if there is no transformation (in + * which case the action is not applied) + * @param action the action + * @since 1.8 */ - @Override - public V put(K key, @NotNull V value) { - int hash = myHashingStrategy.computeHashCode(key); - return segmentFor(hash).put(key, hash, value, false); - } - - /** - * If the specified key is not already associated - * with a value, associate it with the given value. - * This is equivalent to - * <pre> - * if (!map.containsKey(key)) - * return map.put(key, value); - * else - * return map.get(key); - * </pre> - * Except that the action is performed atomically. - * - * @param key key with which the specified value is to be associated. - * @param value value to be associated with the specified key. - * @return previous value associated with specified key, or <tt>null</tt> - * if there was no mapping for key. - * @throws NullPointerException if the specified key or value is - * <tt>null</tt>. + public <U> void forEachKey(long parallelismThreshold, + Fun<? super K, ? extends U> transformer, + Action<? super U> action) { + if (transformer == null || action == null) { + throw new NullPointerException(); + } + new ForEachTransformedKeyTask<K, V, U> + (null, batchFor(parallelismThreshold), 0, 0, table, + transformer, action).invoke(); + } + + /** + * Returns a non-null result from applying the given search + * function on each key, or null if none. Upon success, + * further element processing is suppressed and the results of + * any other parallel invocations of the search function are + * ignored. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param searchFunction a function returning a non-null + * result on success, else null + * @return a non-null result from applying the given search + * function on each key, or null if none + * @since 1.8 */ - @Override - public V putIfAbsent(@NotNull K key, @NotNull V value) { - int hash = myHashingStrategy.computeHashCode(key); - return segmentFor(hash).put(key, hash, value, true); + public <U> U searchKeys(long parallelismThreshold, + Fun<? super K, ? extends U> searchFunction) { + if (searchFunction == null) throw new NullPointerException(); + return new SearchKeysTask<K, V, U> + (null, batchFor(parallelismThreshold), 0, 0, table, + searchFunction, new AtomicReference<U>()).invoke(); } + /** + * Returns the result of accumulating all keys using the given + * reducer to combine values, or null if none. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param reducer a commutative associative combining function + * @return the result of accumulating all keys using the given + * reducer to combine values, or null if none + * @since 1.8 + */ + public K reduceKeys(long parallelismThreshold, + BiFun<? super K, ? super K, ? extends K> reducer) { + if (reducer == null) throw new NullPointerException(); + return new ReduceKeysTask<K, V> + (null, batchFor(parallelismThreshold), 0, 0, table, + null, reducer).invoke(); + } /** - * Copies all of the mappings from the specified map to this one. - * <p/> - * These mappings replace any mappings that this map had for any of the - * keys currently in the specified Map. + * Returns the result of accumulating the given transformation + * of all keys using the given reducer to combine values, or + * null if none. * - * @param t Mappings to be stored in this map. + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element, or null if there is no transformation (in + * which case it is not combined) + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all keys + * @since 1.8 */ - @Override - public void putAll(@NotNull Map<? extends K, ? extends V> t) { - for ( - Iterator<? extends Entry<? extends K, ? extends V>> it = (Iterator<? extends Entry<? extends K, ? extends V>>)t.entrySet().iterator(); - it.hasNext(); ) { - Entry<? extends K, ? extends V> e = it.next(); - put(e.getKey(), e.getValue()); + public <U> U reduceKeys(long parallelismThreshold, + Fun<? super K, ? extends U> transformer, + BiFun<? super U, ? super U, ? extends U> reducer) { + if (transformer == null || reducer == null) { + throw new NullPointerException(); } + return new MapReduceKeysTask<K, V, U> + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, reducer).invoke(); } /** - * Removes the key (and its corresponding value) from this - * table. This method does nothing if the key is not in the table. + * Returns the result of accumulating the given transformation + * of all keys using the given reducer to combine values, and + * the given basis as an identity value. * - * @param key the key that needs to be removed. - * @return the value to which the key had been mapped in this table, - * or <tt>null</tt> if the key did not have a mapping. - * @throws NullPointerException if the key is - * <tt>null</tt>. + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element + * @param basis the identity (initial default value) for the reduction + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all keys + * @since 1.8 */ - @Override - public V remove(Object key) { - int hash = myHashingStrategy.computeHashCode((K)key); - return segmentFor(hash).remove((K)key, hash, null); + public double reduceKeysToDouble(long parallelismThreshold, + ObjectToDouble<? super K> transformer, + double basis, + DoubleByDoubleToDouble reducer) { + if (transformer == null || reducer == null) { + throw new NullPointerException(); + } + return new MapReduceKeysToDoubleTask<K, V> + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, basis, reducer).invoke(); } /** - * Remove entry for key only if currently mapped to given value. - * Acts as - * <pre> - * if (map.get(key).equals(value)) { - * map.remove(key); - * return true; - * } else return false; - * </pre> - * except that the action is performed atomically. + * Returns the result of accumulating the given transformation + * of all keys using the given reducer to combine values, and + * the given basis as an identity value. * - * @param key key with which the specified value is associated. - * @param value value associated with the specified key. - * @return true if the value was removed - * @throws NullPointerException if the specified key is - * <tt>null</tt>. + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element + * @param basis the identity (initial default value) for the reduction + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all keys + * @since 1.8 */ - @Override - public boolean remove(@NotNull Object key, Object value) { - int hash = myHashingStrategy.computeHashCode((K)key); - return remove((K)key, hash, (V)value); + public long reduceKeysToLong(long parallelismThreshold, + ObjectToLong<? super K> transformer, + long basis, + LongByLongToLong reducer) { + if (transformer == null || reducer == null) { + throw new NullPointerException(); + } + return new MapReduceKeysToLongTask<K, V> + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, basis, reducer).invoke(); } - public boolean remove(@NotNull K key, int hash, V value) { - return segmentFor(hash).remove(key, hash, value) != null; + /** + * Returns the result of accumulating the given transformation + * of all keys using the given reducer to combine values, and + * the given basis as an identity value. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element + * @param basis the identity (initial default value) for the reduction + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all keys + * @since 1.8 + */ + public int reduceKeysToInt(long parallelismThreshold, + ObjectToInt<? super K> transformer, + int basis, + IntByIntToInt reducer) { + if (transformer == null || reducer == null) { + throw new NullPointerException(); + } + return new MapReduceKeysToIntTask<K, V> + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, basis, reducer).invoke(); } /** - * Replace entry for key only if currently mapped to given value. - * Acts as - * <pre> - * if (map.get(key).equals(oldValue)) { - * map.put(key, newValue); - * return true; - * } else return false; - * </pre> - * except that the action is performed atomically. + * Performs the given action for each value. * - * @param key key with which the specified value is associated. - * @param oldValue value expected to be associated with the specified key. - * @param newValue value to be associated with the specified key. - * @return true if the value was replaced - * @throws NullPointerException if the specified key or values are - * <tt>null</tt>. + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param action the action + * @since 1.8 */ - @Override - public boolean replace(@NotNull K key, @NotNull V oldValue, @NotNull V newValue) { - int hash = myHashingStrategy.computeHashCode(key); - return segmentFor(hash).replace(key, hash, oldValue, newValue); + public void forEachValue(long parallelismThreshold, + Action<? super V> action) { + if (action == null) { + throw new NullPointerException(); + } + new ForEachValueTask<K, V> + (null, batchFor(parallelismThreshold), 0, 0, table, + action).invoke(); } /** - * Replace entry for key only if currently mapped to some value. - * Acts as - * <pre> - * if ((map.containsKey(key)) { - * return map.put(key, value); - * } else return null; - * </pre> - * except that the action is performed atomically. + * Performs the given action for each non-null transformation + * of each value. * - * @param key key with which the specified value is associated. - * @param value value to be associated with the specified key. - * @return previous value associated with specified key, or <tt>null</tt> - * if there was no mapping for key. - * @throws NullPointerException if the specified key or value is - * <tt>null</tt>. + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element, or null if there is no transformation (in + * which case the action is not applied) + * @param action the action + * @since 1.8 */ - @Override - public V replace(@NotNull K key, @NotNull V value) { - int hash = myHashingStrategy.computeHashCode(key); - return segmentFor(hash).replace(key, hash, value); + public <U> void forEachValue(long parallelismThreshold, + Fun<? super V, ? extends U> transformer, + Action<? super U> action) { + if (transformer == null || action == null) { + throw new NullPointerException(); + } + new ForEachTransformedValueTask<K, V, U> + (null, batchFor(parallelismThreshold), 0, 0, table, + transformer, action).invoke(); } + /** + * Returns a non-null result from applying the given search + * function on each value, or null if none. Upon success, + * further element processing is suppressed and the results of + * any other parallel invocations of the search function are + * ignored. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param searchFunction a function returning a non-null + * result on success, else null + * @return a non-null result from applying the given search + * function on each value, or null if none + * @since 1.8 + */ + public <U> U searchValues(long parallelismThreshold, + Fun<? super V, ? extends U> searchFunction) { + if (searchFunction == null) throw new NullPointerException(); + return new SearchValuesTask<K, V, U> + (null, batchFor(parallelismThreshold), 0, 0, table, + searchFunction, new AtomicReference<U>()).invoke(); + } /** - * Removes all mappings from this map. + * Returns the result of accumulating all values using the + * given reducer to combine values, or null if none. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param reducer a commutative associative combining function + * @return the result of accumulating all values + * @since 1.8 */ - @Override - public void clear() { - for (Segment segment : segments) segment.clear(); + public V reduceValues(long parallelismThreshold, + BiFun<? super V, ? super V, ? extends V> reducer) { + if (reducer == null) throw new NullPointerException(); + return new ReduceValuesTask<K, V> + (null, batchFor(parallelismThreshold), 0, 0, table, + null, reducer).invoke(); } /** - * Returns a set view of the keys contained in this map. The set is - * backed by the map, so changes to the map are reflected in the set, and - * vice-versa. The set supports element removal, which removes the - * corresponding mapping from this map, via the <tt>Iterator.remove</tt>, - * <tt>Set.remove</tt>, <tt>removeAll</tt>, <tt>retainAll</tt>, and - * <tt>clear</tt> operations. It does not support the <tt>add</tt> or - * <tt>addAll</tt> operations. - * The view's returned <tt>iterator</tt> is a "weakly consistent" iterator that - * will never throw {@link java.util.ConcurrentModificationException}, - * and guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not guaranteed to) - * reflect any modifications subsequent to construction. + * Returns the result of accumulating the given transformation + * of all values using the given reducer to combine values, or + * null if none. * - * @return a set view of the keys contained in this map. + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element, or null if there is no transformation (in + * which case it is not combined) + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all values + * @since 1.8 */ - @NotNull - @Override - public Set<K> keySet() { - Set<K> ks = keySet; - return ks != null ? ks : (keySet = new KeySet()); + public <U> U reduceValues(long parallelismThreshold, + Fun<? super V, ? extends U> transformer, + BiFun<? super U, ? super U, ? extends U> reducer) { + if (transformer == null || reducer == null) { + throw new NullPointerException(); + } + return new MapReduceValuesTask<K, V, U> + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, reducer).invoke(); + } + + /** + * Returns the result of accumulating the given transformation + * of all values using the given reducer to combine values, + * and the given basis as an identity value. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element + * @param basis the identity (initial default value) for the reduction + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all values + * @since 1.8 + */ + public double reduceValuesToDouble(long parallelismThreshold, + ObjectToDouble<? super V> transformer, + double basis, + DoubleByDoubleToDouble reducer) { + if (transformer == null || reducer == null) { + throw new NullPointerException(); + } + return new MapReduceValuesToDoubleTask<K, V> + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, basis, reducer).invoke(); } + /** + * Returns the result of accumulating the given transformation + * of all values using the given reducer to combine values, + * and the given basis as an identity value. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element + * @param basis the identity (initial default value) for the reduction + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all values + * @since 1.8 + */ + public long reduceValuesToLong(long parallelismThreshold, + ObjectToLong<? super V> transformer, + long basis, + LongByLongToLong reducer) { + if (transformer == null || reducer == null) { + throw new NullPointerException(); + } + return new MapReduceValuesToLongTask<K, V> + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, basis, reducer).invoke(); + } /** - * Returns a collection view of the values contained in this map. The - * collection is backed by the map, so changes to the map are reflected in - * the collection, and vice-versa. The collection supports element - * removal, which removes the corresponding mapping from this map, via the - * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>, - * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations. - * It does not support the <tt>add</tt> or <tt>addAll</tt> operations. - * The view's returned <tt>iterator</tt> is a "weakly consistent" iterator that - * will never throw {@link java.util.ConcurrentModificationException}, - * and guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not guaranteed to) - * reflect any modifications subsequent to construction. + * Returns the result of accumulating the given transformation + * of all values using the given reducer to combine values, + * and the given basis as an identity value. * - * @return a collection view of the values contained in this map. + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element + * @param basis the identity (initial default value) for the reduction + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all values + * @since 1.8 */ - @NotNull - @Override - public Collection<V> values() { - Collection<V> vs = values; - return vs == null ? (values = new Values()) : vs; + public int reduceValuesToInt(long parallelismThreshold, + ObjectToInt<? super V> transformer, + int basis, + IntByIntToInt reducer) { + if (transformer == null || reducer == null) { + throw new NullPointerException(); + } + return new MapReduceValuesToIntTask<K, V> + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, basis, reducer).invoke(); } + /** + * Performs the given action for each entry. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param action the action + * @since 1.8 + */ + public void forEachEntry(long parallelismThreshold, + Action<? super Map.Entry<K, V>> action) { + if (action == null) throw new NullPointerException(); + new ForEachEntryTask<K, V>(null, batchFor(parallelismThreshold), 0, 0, table, + action).invoke(); + } /** - * Returns a collection view of the mappings contained in this map. Each - * element in the returned collection is a <tt>Map.Entry</tt>. The - * collection is backed by the map, so changes to the map are reflected in - * the collection, and vice-versa. The collection supports element - * removal, which removes the corresponding mapping from the map, via the - * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>, - * <tt>removeAll</tt>, <tt>retainAll</tt>, and <tt>clear</tt> operations. - * It does not support the <tt>add</tt> or <tt>addAll</tt> operations. - * The view's returned <tt>iterator</tt> is a "weakly consistent" iterator that - * will never throw {@link java.util.ConcurrentModificationException}, - * and guarantees to traverse elements as they existed upon - * construction of the iterator, and may (but is not guaranteed to) - * reflect any modifications subsequent to construction. + * Performs the given action for each non-null transformation + * of each entry. * - * @return a collection view of the mappings contained in this map. + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element, or null if there is no transformation (in + * which case the action is not applied) + * @param action the action + * @since 1.8 */ - @NotNull - @Override - public Set<Entry<K, V>> entrySet() { - Set<Entry<K, V>> es = entrySet; - return es != null ? es : (entrySet = (Set<Entry<K, V>>)(Set)new EntrySet()); + public <U> void forEachEntry(long parallelismThreshold, + Fun<Map.Entry<K, V>, ? extends U> transformer, + Action<? super U> action) { + if (transformer == null || action == null) { + throw new NullPointerException(); + } + new ForEachTransformedEntryTask<K, V, U> + (null, batchFor(parallelismThreshold), 0, 0, table, + transformer, action).invoke(); } + /** + * Returns a non-null result from applying the given search + * function on each entry, or null if none. Upon success, + * further element processing is suppressed and the results of + * any other parallel invocations of the search function are + * ignored. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param searchFunction a function returning a non-null + * result on success, else null + * @return a non-null result from applying the given search + * function on each entry, or null if none + * @since 1.8 + */ + public <U> U searchEntries(long parallelismThreshold, + Fun<Map.Entry<K, V>, ? extends U> searchFunction) { + if (searchFunction == null) throw new NullPointerException(); + return new SearchEntriesTask<K, V, U> + (null, batchFor(parallelismThreshold), 0, 0, table, + searchFunction, new AtomicReference<U>()).invoke(); + } /** - * Returns an enumeration of the keys in this table. + * Returns the result of accumulating all entries using the + * given reducer to combine values, or null if none. * - * @return an enumeration of the keys in this table. - * @see #keySet + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param reducer a commutative associative combining function + * @return the result of accumulating all entries + * @since 1.8 */ - public Enumeration<K> keys() { - return new KeyIterator(); + public Map.Entry<K, V> reduceEntries(long parallelismThreshold, + BiFun<Map.Entry<K, V>, Map.Entry<K, V>, ? extends Map.Entry<K, V>> reducer) { + if (reducer == null) throw new NullPointerException(); + return new ReduceEntriesTask<K, V> + (null, batchFor(parallelismThreshold), 0, 0, table, + null, reducer).invoke(); } /** - * Returns an enumeration of the values in this table. + * Returns the result of accumulating the given transformation + * of all entries using the given reducer to combine values, + * or null if none. * - * @return an enumeration of the values in this table. - * @see #values + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element, or null if there is no transformation (in + * which case it is not combined) + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all entries + * @since 1.8 */ - public Enumeration<V> elements() { - return new ValueIterator(); + public <U> U reduceEntries(long parallelismThreshold, + Fun<Map.Entry<K, V>, ? extends U> transformer, + BiFun<? super U, ? super U, ? extends U> reducer) { + if (transformer == null || reducer == null) { + throw new NullPointerException(); + } + return new MapReduceEntriesTask<K, V, U> + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, reducer).invoke(); } - /* ---------------- Iterator Support -------------- */ + /** + * Returns the result of accumulating the given transformation + * of all entries using the given reducer to combine values, + * and the given basis as an identity value. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element + * @param basis the identity (initial default value) for the reduction + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all entries + * @since 1.8 + */ + public double reduceEntriesToDouble(long parallelismThreshold, + ObjectToDouble<Map.Entry<K, V>> transformer, + double basis, + DoubleByDoubleToDouble reducer) { + if (transformer == null || reducer == null) { + throw new NullPointerException(); + } + return new MapReduceEntriesToDoubleTask<K, V> + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, basis, reducer).invoke(); + } - private abstract class HashIterator { - private int nextSegmentIndex; - private int nextTableIndex; - private HashEntry[] currentTable; - private HashEntry<K, V> nextEntry; - HashEntry<K, V> lastReturned; + /** + * Returns the result of accumulating the given transformation + * of all entries using the given reducer to combine values, + * and the given basis as an identity value. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element + * @param basis the identity (initial default value) for the reduction + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all entries + * @since 1.8 + */ + public long reduceEntriesToLong(long parallelismThreshold, + ObjectToLong<Map.Entry<K, V>> transformer, + long basis, + LongByLongToLong reducer) { + if (transformer == null || reducer == null) { + throw new NullPointerException(); + } + return new MapReduceEntriesToLongTask<K, V> + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, basis, reducer).invoke(); + } - private HashIterator() { - nextSegmentIndex = segments.length - 1; - nextTableIndex = -1; - advance(); + /** + * Returns the result of accumulating the given transformation + * of all entries using the given reducer to combine values, + * and the given basis as an identity value. + * + * @param parallelismThreshold the (estimated) number of elements + * needed for this operation to be executed in parallel + * @param transformer a function returning the transformation + * for an element + * @param basis the identity (initial default value) for the reduction + * @param reducer a commutative associative combining function + * @return the result of accumulating the given transformation + * of all entries + * @since 1.8 + */ + public int reduceEntriesToInt(long parallelismThreshold, + ObjectToInt<Map.Entry<K, V>> transformer, + int basis, + IntByIntToInt reducer) { + if (transformer == null || reducer == null) { + throw new NullPointerException(); } + return new MapReduceEntriesToIntTask<K, V> + (null, batchFor(parallelismThreshold), 0, 0, table, + null, transformer, basis, reducer).invoke(); + } + + + /* ----------------Views -------------- */ - public boolean hasMoreElements() { - return hasNext(); + /** + * Base class for views. + */ + abstract static class CollectionView<K, V, E> + implements Collection<E>, Serializable { + private static final long serialVersionUID = 7249069246763182397L; + final ConcurrentHashMap<K, V> map; + + CollectionView(ConcurrentHashMap<K, V> map) { + this.map = map; } - private void advance() { - if (nextEntry != null && (nextEntry = nextEntry.next) != null) { - return; + /** + * Returns the map backing this view. + * + * @return the map backing this view + */ + public ConcurrentHashMap<K, V> getMap() { + return map; + } + + /** + * Removes all of the elements from this view, by removing all + * the mappings from the map backing this view. + */ + @Override + public final void clear() { + map.clear(); + } + + @Override + public final int size() { + return map.size(); + } + + @Override + public final boolean isEmpty() { + return map.isEmpty(); + } + + // implementations below rely on concrete classes supplying these + // abstract methods + + /** + * Returns a "weakly consistent" iterator that will never + * throw {@link ConcurrentModificationException}, and + * guarantees to traverse elements as they existed upon + * construction of the iterator, and may (but is not + * guaranteed to) reflect any modifications subsequent to + * construction. + */ + @NotNull + @Override + public abstract Iterator<E> iterator(); + + @Override + public abstract boolean contains(Object o); + + @Override + public abstract boolean remove(Object o); + + private static final String oomeMsg = "Required array size too large"; + + @NotNull + @Override + public final Object[] toArray() { + long sz = map.mappingCount(); + if (sz > MAX_ARRAY_SIZE) { + throw new OutOfMemoryError(oomeMsg); } + int n = (int)sz; + Object[] r = new Object[n]; + int i = 0; + for (E e : this) { + if (i == n) { + if (n >= MAX_ARRAY_SIZE) { + throw new OutOfMemoryError(oomeMsg); + } + if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) { + n = MAX_ARRAY_SIZE; + } + else { + n += (n >>> 1) + 1; + } + r = Arrays.copyOf(r, n); + } + r[i++] = e; + } + return i == n ? r : Arrays.copyOf(r, i); + } - while (nextTableIndex >= 0) { - if ((nextEntry = (HashEntry<K, V>)currentTable[nextTableIndex--]) != null) { - return; + @NotNull + @Override + @SuppressWarnings("unchecked") + public final <T> T[] toArray(@NotNull T[] a) { + long sz = map.mappingCount(); + if (sz > MAX_ARRAY_SIZE) { + throw new OutOfMemoryError(oomeMsg); + } + int m = (int)sz; + T[] r = a.length >= m ? a : + (T[])Array + .newInstance(a.getClass().getComponentType(), m); + int n = r.length; + int i = 0; + for (E e : this) { + if (i == n) { + if (n >= MAX_ARRAY_SIZE) { + throw new OutOfMemoryError(oomeMsg); + } + if (n >= MAX_ARRAY_SIZE - (MAX_ARRAY_SIZE >>> 1) - 1) { + n = MAX_ARRAY_SIZE; + } + else { + n += (n >>> 1) + 1; + } + r = Arrays.copyOf(r, n); } + r[i++] = (T)e; } + if (a == r && i < n) { + r[i] = null; // null-terminate + return r; + } + return i == n ? r : Arrays.copyOf(r, i); + } - while (nextSegmentIndex >= 0) { - Segment seg = segments[nextSegmentIndex--]; - if (seg.count != 0) { - currentTable = seg.table; - for (int j = currentTable.length - 1; j >= 0; --j) { - if ((nextEntry = (HashEntry<K, V>)currentTable[j]) != null) { - nextTableIndex = j - 1; - return; - } + /** + * Returns a string representation of this collection. + * The string representation consists of the string representations + * of the collection's elements in the order they are returned by + * its iterator, enclosed in square brackets ({@code "[]"}). + * Adjacent elements are separated by the characters {@code ", "} + * (comma and space). Elements are converted to strings as by + * {@link String#valueOf(Object)}. + * + * @return a string representation of this collection + */ + public final String toString() { + StringBuilder sb = new StringBuilder(); + sb.append('['); + Iterator<E> it = iterator(); + if (it.hasNext()) { + for (; ; ) { + Object e = it.next(); + sb.append(e == this ? "(this Collection)" : e); + if (!it.hasNext()) { + break; } + sb.append(',').append(' '); } } + return sb.append(']').toString(); } - public boolean hasNext() { - return nextEntry != null; + @Override + public final boolean containsAll(@NotNull Collection<?> c) { + if (c != this) { + for (Object e : c) { + if (e == null || !contains(e)) { + return false; + } + } + } + return true; } - HashEntry<K, V> nextEntry() { - if (nextEntry == null) { - throw new NoSuchElementException(); + @Override + public final boolean removeAll(@NotNull Collection<?> c) { + boolean modified = false; + for (Iterator<E> it = iterator(); it.hasNext(); ) { + if (c.contains(it.next())) { + it.remove(); + modified = true; + } } - lastReturned = nextEntry; - advance(); - return lastReturned; + return modified; } - public void remove() { - if (lastReturned == null) { - throw new IllegalStateException(); + @Override + public final boolean retainAll(@NotNull Collection<?> c) { + boolean modified = false; + for (Iterator<E> it = iterator(); it.hasNext(); ) { + if (!c.contains(it.next())) { + it.remove(); + modified = true; + } } - ConcurrentHashMap.this.remove(lastReturned.key); - lastReturned = null; + return modified; } } - private final class KeyIterator extends HashIterator implements Iterator<K>, Enumeration<K> { + /** + * A view of a ConcurrentHashMapV8 as a {@link Set} of keys, in + * which additions may optionally be enabled by mapping to a + * common value. This class cannot be directly instantiated. + * See {@link #keySet() keySet()}, + * {@link #keySet(Object) keySet(V)}, + * {@link #newKeySet() newKeySet()}, + * {@link #newKeySet(int) newKeySet(int)}. + * + * @since 1.8 + */ + public static class KeySetView<K, V> extends CollectionView<K, V, K> + implements Set<K>, Serializable { + private static final long serialVersionUID = 7249069246763182397L; + private final V value; + + KeySetView(ConcurrentHashMap<K, V> map, V value) { // non-public + super(map); + this.value = value; + } + + /** + * Returns the default mapped value for additions, + * or {@code null} if additions are not supported. + * + * @return the default mapped value for additions, or {@code null} + * if not supported + */ + public V getMappedValue() { + return value; + } + + /** + * {@inheritDoc} + * + * @throws NullPointerException if the specified key is null + */ + @Override + public boolean contains(Object o) { + return map.containsKey(o); + } + + /** + * Removes the key from this map view, by removing the key (and its + * corresponding value) from the backing map. This method does + * nothing if the key is not in the map. + * + * @param o the key to be removed from the backing map + * @return {@code true} if the backing map contained the specified key + * @throws NullPointerException if the specified key is null + */ + @Override + public boolean remove(Object o) { + return map.remove(o) != null; + } + + /** + * @return an iterator over the keys of the backing map + */ + @NotNull + @Override + public Iterator<K> iterator() { + Node<K, V>[] t; + ConcurrentHashMap<K, V> m = map; + int f = (t = m.table) == null ? 0 : t.length; + return new KeyIterator<K, V>(t, f, 0, f, m); + } + + /** + * Adds the specified key to this set view by mapping the key to + * the default mapped value in the backing map, if defined. + * + * @param e key to be added + * @return {@code true} if this set changed as a result of the call + * @throws NullPointerException if the specified key is null + * @throws UnsupportedOperationException if no default mapped value + * for additions was provided + */ + @Override + public boolean add(K e) { + V v; + if ((v = value) == null) { + throw new UnsupportedOperationException(); + } + return map.putVal(e, v, true) == null; + } + + /** + * Adds all of the elements in the specified collection to this set, + * as if by calling {@link #add} on each one. + * + * @param c the elements to be inserted into this set + * @return {@code true} if this set changed as a result of the call + * @throws NullPointerException if the collection or any of its + * elements are {@code null} + * @throws UnsupportedOperationException if no default mapped value + * for additions was provided + */ + @Override + public boolean addAll(@NotNull Collection<? extends K> c) { + V v; + if ((v = value) == null) { + throw new UnsupportedOperationException(); + } + boolean added = false; + for (K e : c) { + if (map.putVal(e, v, true) == null) { + added = true; + } + } + return added; + } + + public int hashCode() { + int h = 0; + for (K e : this) { + h += e.hashCode(); + } + return h; + } + + public boolean equals(Object o) { + Set<?> c; + return o instanceof Set && + ((c = (Set<?>)o) == this || + containsAll(c) && c.containsAll(this)); + } + + + public void forEach(Action<? super K> action) { + if (action == null) throw new NullPointerException(); + Node<K, V>[] t; + if ((t = map.table) != null) { + Traverser<K, V> it = new Traverser<K, V>(t, t.length, 0, t.length); + for (Node<K, V> p; (p = it.advance()) != null; ) { + action.apply(p.key); + } + } + } + } + + /** + * A view of a ConcurrentHashMapV8 as a {@link Collection} of + * values, in which additions are disabled. This class cannot be + * directly instantiated. See {@link #values()}. + */ + static final class ValuesView<K, V> extends CollectionView<K, V, V> + implements Collection<V>, Serializable { + private static final long serialVersionUID = 2249069246763182397L; + + ValuesView(ConcurrentHashMap<K, V> map) { + super(map); + } + + @Override + public final boolean contains(Object o) { + return map.containsValue(o); + } + + @Override + public final boolean remove(Object o) { + if (o != null) { + for (Iterator<V> it = iterator(); it.hasNext(); ) { + if (o.equals(it.next())) { + it.remove(); + return true; + } + } + } + return false; + } + + @NotNull @Override - public K next() { - return super.nextEntry().key; + public final Iterator<V> iterator() { + ConcurrentHashMap<K, V> m = map; + Node<K, V>[] t; + int f = (t = m.table) == null ? 0 : t.length; + return new ValueIterator<K, V>(t, f, 0, f, m); } @Override - public K nextElement() { - return super.nextEntry().key; + public final boolean add(V e) { + throw new UnsupportedOperationException(); + } + + @Override + public final boolean addAll(@NotNull Collection<? extends V> c) { + throw new UnsupportedOperationException(); + } + + + public void forEach(Action<? super V> action) { + if (action == null) throw new NullPointerException(); + Node<K, V>[] t; + if ((t = map.table) != null) { + Traverser<K, V> it = new Traverser<K, V>(t, t.length, 0, t.length); + for (Node<K, V> p; (p = it.advance()) != null; ) { + action.apply(p.val); + } + } } } - private final class ValueIterator extends HashIterator implements Iterator<V>, Enumeration<V> { + /** + * A view of a ConcurrentHashMapV8 as a {@link Set} of (key, value) + * entries. This class cannot be directly instantiated. See + * {@link #entrySet()}. + */ + static final class EntrySetView<K, V> extends CollectionView<K, V, Map.Entry<K, V>> + implements Set<Map.Entry<K, V>>, Serializable { + private static final long serialVersionUID = 2249069246763182397L; + + EntrySetView(ConcurrentHashMap<K, V> map) { + super(map); + } + + @Override + public boolean contains(Object o) { + Object k; + Object v; + Object r; + Map.Entry<?, ?> e; + return o instanceof Entry && + (k = (e = (Entry<?, ?>)o).getKey()) != null && + (r = map.get(k)) != null && + (v = e.getValue()) != null && + (v == r || v.equals(r)); + } + + @Override + public boolean remove(Object o) { + Object k; + Object v; + Map.Entry<?, ?> e; + return o instanceof Entry && + (k = (e = (Entry<?, ?>)o).getKey()) != null && + (v = e.getValue()) != null && + map.remove(k, v); + } + + /** + * @return an iterator over the entries of the backing map + */ + @NotNull + @Override + public Iterator<Map.Entry<K, V>> iterator() { + ConcurrentHashMap<K, V> m = map; + Node<K, V>[] t; + int f = (t = m.table) == null ? 0 : t.length; + return new EntryIterator<K, V>(t, f, 0, f, m); + } + @Override - public V next() { - return super.nextEntry().value; + public boolean add(Entry<K, V> e) { + return map.putVal(e.getKey(), e.getValue(), false) == null; } @Override - public V nextElement() { - return super.nextEntry().value; + public boolean addAll(@NotNull Collection<? extends Entry<K, V>> c) { + boolean added = false; + for (Entry<K, V> e : c) { + if (add(e)) { + added = true; + } + } + return added; + } + + public final int hashCode() { + int h = 0; + Node<K, V>[] t; + if ((t = map.table) != null) { + Traverser<K, V> it = new Traverser<K, V>(t, t.length, 0, t.length); + for (Node<K, V> p; (p = it.advance()) != null; ) { + h += p.hashCode(); + } + } + return h; + } + + public final boolean equals(Object o) { + Set<?> c; + return o instanceof Set && + ((c = (Set<?>)o) == this || + containsAll(c) && c.containsAll(this)); + } + + + public void forEach(Action<? super Map.Entry<K, V>> action) { + if (action == null) throw new NullPointerException(); + Node<K, V>[] t; + if ((t = map.table) != null) { + Traverser<K, V> it = new Traverser<K, V>(t, t.length, 0, t.length); + for (Node<K, V> p; (p = it.advance()) != null; ) { + action.apply(new MapEntry<K, V>(p.key, p.val, map)); + } + } } } + // ------------------------------------------------------- /** - * Entry iterator. Exported Entry objects must write-through - * changes in setValue, even if the nodes have been cloned. So we - * cannot return internal HashEntry objects. Instead, the iterator - * itself acts as a forwarding pseudo-entry. + * Base class for bulk tasks. Repeats some fields and code from + * class Traverser, because we need to subclass CountedCompleter. + */ + abstract static class BulkTask<K, V, R> extends CountedCompleter<R> { + Node<K, V>[] tab; // same as Traverser + Node<K, V> next; + int index; + int baseIndex; + int baseLimit; + final int baseSize; + int batch; // split control + + BulkTask(BulkTask<K, V, ?> par, int b, int i, int f, Node<K, V>[] t) { + super(par); + batch = b; + index = baseIndex = i; + if ((tab = t) == null) { + baseSize = baseLimit = 0; + } + else if (par == null) { + baseSize = baseLimit = t.length; + } + else { + baseLimit = f; + baseSize = par.baseSize; + } + } + + /** + * Same as Traverser version + */ + final Node<K, V> advance() { + Node<K, V> e; + if ((e = next) != null) { + e = e.next; + } + for (; ; ) { + if (e != null) { + return next = e; + } + Node<K, V>[] t; + int i; + int n; + if (baseIndex >= baseLimit || (t = tab) == null || + (n = t.length) <= (i = index) || i < 0) { + return next = null; + } + if ((e = tabAt(t, index)) != null && e.hash < 0) { + if (e instanceof ForwardingNode) { + tab = ((ForwardingNode<K, V>)e).nextTable; + e = null; + continue; + } + else if (e instanceof TreeBin) { + e = ((TreeBin<K, V>)e).first; + } + else { + e = null; + } + } + if ((index += baseSize) >= n) { + index = ++baseIndex; // visit upper slots if present + } + } + } + } + + /* + * Task classes. Coded in a regular but ugly format/style to + * simplify checks that each variant differs in the right way from + * others. The null screenings exist because compilers cannot tell + * that we've already null-checked task arguments, so we force + * simplest hoisted bypass to help avoid convoluted traps. */ - private final class EntryIterator extends HashIterator implements Entry<K, V>, Iterator<Entry<K, V>> { + @SuppressWarnings("serial") + static final class ForEachKeyTask<K, V> + extends BulkTask<K, V, Void> { + final Action<? super K> action; + + ForEachKeyTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + Action<? super K> action) { + super(p, b, i, f, t); + this.action = action; + } + @Override - public Entry<K, V> next() { - nextEntry(); - return this; + public final void compute() { + final Action<? super K> action; + if ((action = this.action) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + new ForEachKeyTask<K, V> + (this, batch >>>= 1, baseLimit = h, f, tab, + action).fork(); + } + for (Node<K, V> p; (p = advance()) != null; ) { + action.apply(p.key); + } + propagateCompletion(); + } + } + } + + @SuppressWarnings("serial") + static final class ForEachValueTask<K, V> + extends BulkTask<K, V, Void> { + final Action<? super V> action; + + ForEachValueTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + Action<? super V> action) { + super(p, b, i, f, t); + this.action = action; } @Override - public K getKey() { - if (lastReturned == null) { - throw new IllegalStateException("Entry was removed"); + public final void compute() { + final Action<? super V> action; + if ((action = this.action) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + new ForEachValueTask<K, V> + (this, batch >>>= 1, baseLimit = h, f, tab, + action).fork(); + } + for (Node<K, V> p; (p = advance()) != null; ) { + action.apply(p.val); + } + propagateCompletion(); } - return lastReturned.key; + } + } + + @SuppressWarnings("serial") + static final class ForEachEntryTask<K, V> + extends BulkTask<K, V, Void> { + final Action<? super Entry<K, V>> action; + + ForEachEntryTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + Action<? super Entry<K, V>> action) { + super(p, b, i, f, t); + this.action = action; } @Override - public V getValue() { - if (lastReturned == null) { - throw new IllegalStateException("Entry was removed"); + public final void compute() { + final Action<? super Entry<K, V>> action; + if ((action = this.action) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + new ForEachEntryTask<K, V> + (this, batch >>>= 1, baseLimit = h, f, tab, + action).fork(); + } + for (Node<K, V> p; (p = advance()) != null; ) { + action.apply(p); + } + propagateCompletion(); } - return get(lastReturned.key); + } + } + + @SuppressWarnings("serial") + static final class ForEachMappingTask<K, V> + extends BulkTask<K, V, Void> { + final BiAction<? super K, ? super V> action; + + ForEachMappingTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + BiAction<? super K, ? super V> action) { + super(p, b, i, f, t); + this.action = action; } @Override - public V setValue(V value) { - if (lastReturned == null) { - throw new IllegalStateException("Entry was removed"); + public final void compute() { + final BiAction<? super K, ? super V> action; + if ((action = this.action) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + new ForEachMappingTask<K, V> + (this, batch >>>= 1, baseLimit = h, f, tab, + action).fork(); + } + for (Node<K, V> p; (p = advance()) != null; ) { + action.apply(p.key, p.val); + } + propagateCompletion(); } - return put(lastReturned.key, value); } + } - public boolean equals(Object o) { - // If not acting as entry, just use default. - if (lastReturned == null) { - return super.equals(o); + @SuppressWarnings("serial") + static final class ForEachTransformedKeyTask<K, V, U> + extends BulkTask<K, V, Void> { + final Fun<? super K, ? extends U> transformer; + final Action<? super U> action; + + ForEachTransformedKeyTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + Fun<? super K, ? extends U> transformer, Action<? super U> action) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + final Fun<? super K, ? extends U> transformer; + final Action<? super U> action; + if ((transformer = this.transformer) != null && + (action = this.action) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + new ForEachTransformedKeyTask<K, V, U> + (this, batch >>>= 1, baseLimit = h, f, tab, + transformer, action).fork(); + } + for (Node<K, V> p; (p = advance()) != null; ) { + U u; + if ((u = transformer.apply(p.key)) != null) { + action.apply(u); + } + } + propagateCompletion(); } - if (!(o instanceof Entry)) { - return false; + } + } + + @SuppressWarnings("serial") + static final class ForEachTransformedValueTask<K, V, U> + extends BulkTask<K, V, Void> { + final Fun<? super V, ? extends U> transformer; + final Action<? super U> action; + + ForEachTransformedValueTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + Fun<? super V, ? extends U> transformer, Action<? super U> action) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; + } + + @Override + public final void compute() { + final Fun<? super V, ? extends U> transformer; + final Action<? super U> action; + if ((transformer = this.transformer) != null && + (action = this.action) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + new ForEachTransformedValueTask<K, V, U> + (this, batch >>>= 1, baseLimit = h, f, tab, + transformer, action).fork(); + } + for (Node<K, V> p; (p = advance()) != null; ) { + U u; + if ((u = transformer.apply(p.val)) != null) { + action.apply(u); + } + } + propagateCompletion(); } - Entry e = (Entry)o; - K o1 = getKey(); - K o2 = (K)e.getKey(); - return (o1 == null ? o2 == null : myHashingStrategy.equals(o1, o2)) && eq(getValue(), e.getValue()); + } + } + + @SuppressWarnings("serial") + static final class ForEachTransformedEntryTask<K, V, U> + extends BulkTask<K, V, Void> { + final Fun<Map.Entry<K, V>, ? extends U> transformer; + final Action<? super U> action; + + ForEachTransformedEntryTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + Fun<Map.Entry<K, V>, ? extends U> transformer, Action<? super U> action) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; } - public int hashCode() { - // If not acting as entry, just use default. - if (lastReturned == null) { - return super.hashCode(); + @Override + public final void compute() { + final Fun<Map.Entry<K, V>, ? extends U> transformer; + final Action<? super U> action; + if ((transformer = this.transformer) != null && + (action = this.action) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + new ForEachTransformedEntryTask<K, V, U> + (this, batch >>>= 1, baseLimit = h, f, tab, + transformer, action).fork(); + } + for (Node<K, V> p; (p = advance()) != null; ) { + U u; + if ((u = transformer.apply(p)) != null) { + action.apply(u); + } + } + propagateCompletion(); } + } + } - Object k = getKey(); - Object v = getValue(); - return (k == null ? 0 : k.hashCode()) ^ - (v == null ? 0 : v.hashCode()); + @SuppressWarnings("serial") + static final class ForEachTransformedMappingTask<K, V, U> + extends BulkTask<K, V, Void> { + final BiFun<? super K, ? super V, ? extends U> transformer; + final Action<? super U> action; + + ForEachTransformedMappingTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + BiFun<? super K, ? super V, ? extends U> transformer, + Action<? super U> action) { + super(p, b, i, f, t); + this.transformer = transformer; + this.action = action; } - public String toString() { - // If not acting as entry, just use default. - if (lastReturned == null) { - return super.toString(); + @Override + public final void compute() { + final BiFun<? super K, ? super V, ? extends U> transformer; + final Action<? super U> action; + if ((transformer = this.transformer) != null && + (action = this.action) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + new ForEachTransformedMappingTask<K, V, U> + (this, batch >>>= 1, baseLimit = h, f, tab, + transformer, action).fork(); + } + for (Node<K, V> p; (p = advance()) != null; ) { + U u; + if ((u = transformer.apply(p.key, p.val)) != null) { + action.apply(u); + } + } + propagateCompletion(); } - else { - return getKey() + "=" + getValue(); + } + } + + @SuppressWarnings("serial") + static final class SearchKeysTask<K, V, U> + extends BulkTask<K, V, U> { + final Fun<? super K, ? extends U> searchFunction; + final AtomicReference<U> result; + + SearchKeysTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + Fun<? super K, ? extends U> searchFunction, + AtomicReference<U> result) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + + @Override + public final U getRawResult() { + return result.get(); + } + + @Override + public final void compute() { + final Fun<? super K, ? extends U> searchFunction; + final AtomicReference<U> result; + if ((searchFunction = this.searchFunction) != null && + (result = this.result) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + if (result.get() != null) { + return; + } + addToPendingCount(1); + new SearchKeysTask<K, V, U> + (this, batch >>>= 1, baseLimit = h, f, tab, + searchFunction, result).fork(); + } + while (result.get() == null) { + Node<K, V> p; + if ((p = advance()) == null) { + propagateCompletion(); + break; + } + U u; + if ((u = searchFunction.apply(p.key)) != null) { + if (result.compareAndSet(null, u)) { + quietlyCompleteRoot(); + } + break; + } + } } } + } + + @SuppressWarnings("serial") + static final class SearchValuesTask<K, V, U> + extends BulkTask<K, V, U> { + final Fun<? super V, ? extends U> searchFunction; + final AtomicReference<U> result; + + SearchValuesTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + Fun<? super V, ? extends U> searchFunction, + AtomicReference<U> result) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } - private boolean eq(Object o1, Object o2) { - return o1 == null ? o2 == null : o1.equals(o2); + @Override + public final U getRawResult() { + return result.get(); + } + + @Override + public final void compute() { + final Fun<? super V, ? extends U> searchFunction; + final AtomicReference<U> result; + if ((searchFunction = this.searchFunction) != null && + (result = this.result) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + if (result.get() != null) { + return; + } + addToPendingCount(1); + new SearchValuesTask<K, V, U> + (this, batch >>>= 1, baseLimit = h, f, tab, + searchFunction, result).fork(); + } + while (result.get() == null) { + Node<K, V> p; + if ((p = advance()) == null) { + propagateCompletion(); + break; + } + U u; + if ((u = searchFunction.apply(p.val)) != null) { + if (result.compareAndSet(null, u)) { + quietlyCompleteRoot(); + } + break; + } + } + } } } - private final class KeySet extends AbstractSet<K> { - @NotNull + @SuppressWarnings("serial") + static final class SearchEntriesTask<K, V, U> + extends BulkTask<K, V, U> { + final Fun<Entry<K, V>, ? extends U> searchFunction; + final AtomicReference<U> result; + + SearchEntriesTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + Fun<Entry<K, V>, ? extends U> searchFunction, + AtomicReference<U> result) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; + } + @Override - public Iterator<K> iterator() { - return new KeyIterator(); + public final U getRawResult() { + return result.get(); } @Override - public int size() { - return ConcurrentHashMap.this.size(); + public final void compute() { + final Fun<Entry<K, V>, ? extends U> searchFunction; + final AtomicReference<U> result; + if ((searchFunction = this.searchFunction) != null && + (result = this.result) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + if (result.get() != null) { + return; + } + addToPendingCount(1); + new SearchEntriesTask<K, V, U> + (this, batch >>>= 1, baseLimit = h, f, tab, + searchFunction, result).fork(); + } + while (result.get() == null) { + Node<K, V> p; + if ((p = advance()) == null) { + propagateCompletion(); + break; + } + U u; + if ((u = searchFunction.apply(p)) != null) { + if (result.compareAndSet(null, u)) { + quietlyCompleteRoot(); + } + return; + } + } + } + } + } + + @SuppressWarnings("serial") + static final class SearchMappingsTask<K, V, U> + extends BulkTask<K, V, U> { + final BiFun<? super K, ? super V, ? extends U> searchFunction; + final AtomicReference<U> result; + + SearchMappingsTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + BiFun<? super K, ? super V, ? extends U> searchFunction, + AtomicReference<U> result) { + super(p, b, i, f, t); + this.searchFunction = searchFunction; + this.result = result; } @Override - public boolean contains(Object o) { - return containsKey(o); + public final U getRawResult() { + return result.get(); } @Override - public boolean remove(Object o) { - return ConcurrentHashMap.this.remove(o) != null; + public final void compute() { + final BiFun<? super K, ? super V, ? extends U> searchFunction; + final AtomicReference<U> result; + if ((searchFunction = this.searchFunction) != null && + (result = this.result) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + if (result.get() != null) { + return; + } + addToPendingCount(1); + new SearchMappingsTask<K, V, U> + (this, batch >>>= 1, baseLimit = h, f, tab, + searchFunction, result).fork(); + } + while (result.get() == null) { + Node<K, V> p; + if ((p = advance()) == null) { + propagateCompletion(); + break; + } + U u; + if ((u = searchFunction.apply(p.key, p.val)) != null) { + if (result.compareAndSet(null, u)) { + quietlyCompleteRoot(); + } + break; + } + } + } + } + } + + @SuppressWarnings("serial") + static final class ReduceKeysTask<K, V> + extends BulkTask<K, V, K> { + final BiFun<? super K, ? super K, ? extends K> reducer; + K result; + ReduceKeysTask<K, V> rights; + ReduceKeysTask<K, V> nextRight; + + ReduceKeysTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + ReduceKeysTask<K, V> nextRight, + BiFun<? super K, ? super K, ? extends K> reducer) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.reducer = reducer; } @Override - public void clear() { - ConcurrentHashMap.this.clear(); + public final K getRawResult() { + return result; } - @NotNull @Override - public Object[] toArray() { - Collection<K> c = new ArrayList<K>(); - for (K k : this) { - c.add(k); + public final void compute() { + final BiFun<? super K, ? super K, ? extends K> reducer; + if ((reducer = this.reducer) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + (rights = new ReduceKeysTask<K, V> + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, reducer)).fork(); + } + K r = null; + for (Node<K, V> p; (p = advance()) != null; ) { + K u = p.key; + r = r == null ? u : u == null ? r : reducer.apply(r, u); + } + result = r; + + for (CountedCompleter<?> c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") ReduceKeysTask<K, V> + t = (ReduceKeysTask<K, V>)c; + @SuppressWarnings("unchecked") ReduceKeysTask<K, V> s = t.rights; + while (s != null) { + K sr; + if ((sr = s.result) != null) { + K tr; + t.result = (tr = t.result) == null ? sr : + reducer.apply(tr, sr); + } + s = t.rights = s.nextRight; + } + } } - return c.toArray(); + } + } + + @SuppressWarnings("serial") + static final class ReduceValuesTask<K, V> + extends BulkTask<K, V, V> { + final BiFun<? super V, ? super V, ? extends V> reducer; + V result; + ReduceValuesTask<K, V> rights; + ReduceValuesTask<K, V> nextRight; + + ReduceValuesTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + ReduceValuesTask<K, V> nextRight, + BiFun<? super V, ? super V, ? extends V> reducer) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.reducer = reducer; + } + + @Override + public final V getRawResult() { + return result; } - @NotNull @Override - public <T> T[] toArray(@NotNull T[] a) { - Collection<K> c = new ArrayList<K>(); - for (K k : this) { - c.add(k); + public final void compute() { + final BiFun<? super V, ? super V, ? extends V> reducer; + if ((reducer = this.reducer) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + (rights = new ReduceValuesTask<K, V> + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, reducer)).fork(); + } + V r = null; + for (Node<K, V> p; (p = advance()) != null; ) { + V v = p.val; + r = r == null ? v : reducer.apply(r, v); + } + result = r; + for (CountedCompleter<?> c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") ReduceValuesTask<K, V> + t = (ReduceValuesTask<K, V>)c; + @SuppressWarnings("unchecked") ReduceValuesTask<K, V> s = t.rights; + while (s != null) { + V sr; + if ((sr = s.result) != null) { + V tr; + t.result = (tr = t.result) == null ? sr : + reducer.apply(tr, sr); + } + s = t.rights = s.nextRight; + } + } } - return c.toArray(a); } } - private final class Values extends AbstractCollection<V> { - @NotNull + @SuppressWarnings("serial") + static final class ReduceEntriesTask<K, V> + extends BulkTask<K, V, Map.Entry<K, V>> { + final BiFun<Map.Entry<K, V>, Map.Entry<K, V>, ? extends Map.Entry<K, V>> reducer; + Map.Entry<K, V> result; + ReduceEntriesTask<K, V> rights; + ReduceEntriesTask<K, V> nextRight; + + ReduceEntriesTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + ReduceEntriesTask<K, V> nextRight, + BiFun<Entry<K, V>, Map.Entry<K, V>, ? extends Map.Entry<K, V>> reducer) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.reducer = reducer; + } + @Override - public Iterator<V> iterator() { - return new ValueIterator(); + public final Map.Entry<K, V> getRawResult() { + return result; } @Override - public int size() { - return ConcurrentHashMap.this.size(); + public final void compute() { + final BiFun<Map.Entry<K, V>, Map.Entry<K, V>, ? extends Map.Entry<K, V>> reducer; + if ((reducer = this.reducer) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + (rights = new ReduceEntriesTask<K, V> + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, reducer)).fork(); + } + Map.Entry<K, V> r = null; + for (Node<K, V> p; (p = advance()) != null; ) { + r = r == null ? p : reducer.apply(r, p); + } + result = r; + for (CountedCompleter<?> c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") ReduceEntriesTask<K, V> + t = (ReduceEntriesTask<K, V>)c; + @SuppressWarnings("unchecked") ReduceEntriesTask<K, V> s = t.rights; + while (s != null) { + Map.Entry<K, V> sr; + if ((sr = s.result) != null) { + Entry<K, V> tr; + t.result = (tr = t.result) == null ? sr : + reducer.apply(tr, sr); + } + s = t.rights = s.nextRight; + } + } + } + } + } + + @SuppressWarnings("serial") + static final class MapReduceKeysTask<K, V, U> + extends BulkTask<K, V, U> { + final Fun<? super K, ? extends U> transformer; + final BiFun<? super U, ? super U, ? extends U> reducer; + U result; + MapReduceKeysTask<K, V, U> rights; + MapReduceKeysTask<K, V, U> nextRight; + + MapReduceKeysTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + MapReduceKeysTask<K, V, U> nextRight, + Fun<? super K, ? extends U> transformer, + BiFun<? super U, ? super U, ? extends U> reducer) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; } @Override - public boolean contains(Object o) { - return containsValue(o); + public final U getRawResult() { + return result; } @Override - public void clear() { - ConcurrentHashMap.this.clear(); + public final void compute() { + final Fun<? super K, ? extends U> transformer; + final BiFun<? super U, ? super U, ? extends U> reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + (rights = new MapReduceKeysTask<K, V, U> + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, reducer)).fork(); + } + U r = null; + for (Node<K, V> p; (p = advance()) != null; ) { + U u; + if ((u = transformer.apply(p.key)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + result = r; + for (CountedCompleter<?> c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") MapReduceKeysTask<K, V, U> + t = (MapReduceKeysTask<K, V, U>)c; + @SuppressWarnings("unchecked") MapReduceKeysTask<K, V, U> s = t.rights; + while (s != null) { + U sr; + if ((sr = s.result) != null) { + U tr; + t.result = (tr = t.result) == null ? sr : + reducer.apply(tr, sr); + } + s = t.rights = s.nextRight; + } + } + } + } + } + + @SuppressWarnings("serial") + static final class MapReduceValuesTask<K, V, U> + extends BulkTask<K, V, U> { + final Fun<? super V, ? extends U> transformer; + final BiFun<? super U, ? super U, ? extends U> reducer; + U result; + MapReduceValuesTask<K, V, U> rights; + MapReduceValuesTask<K, V, U> nextRight; + + MapReduceValuesTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + MapReduceValuesTask<K, V, U> nextRight, + Fun<? super V, ? extends U> transformer, + BiFun<? super U, ? super U, ? extends U> reducer) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; } - @NotNull @Override - public Object[] toArray() { - Collection<V> c = new ArrayList<V>(); - for (V v : this) { - c.add(v); + public final U getRawResult() { + return result; + } + + @Override + public final void compute() { + final Fun<? super V, ? extends U> transformer; + final BiFun<? super U, ? super U, ? extends U> reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + (rights = new MapReduceValuesTask<K, V, U> + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, reducer)).fork(); + } + U r = null; + for (Node<K, V> p; (p = advance()) != null; ) { + U u; + if ((u = transformer.apply(p.val)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + result = r; + for (CountedCompleter<?> c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") MapReduceValuesTask<K, V, U> + t = (MapReduceValuesTask<K, V, U>)c; + @SuppressWarnings("unchecked") MapReduceValuesTask<K, V, U> s = t.rights; + while (s != null) { + U sr; + if ((sr = s.result) != null) { + U tr; + t.result = (tr = t.result) == null ? sr : + reducer.apply(tr, sr); + } + s = t.rights = s.nextRight; + } + } } - return c.toArray(); + } + } + + @SuppressWarnings("serial") + static final class MapReduceEntriesTask<K, V, U> + extends BulkTask<K, V, U> { + final Fun<Map.Entry<K, V>, ? extends U> transformer; + final BiFun<? super U, ? super U, ? extends U> reducer; + U result; + MapReduceEntriesTask<K, V, U> rights; + MapReduceEntriesTask<K, V, U> nextRight; + + MapReduceEntriesTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + MapReduceEntriesTask<K, V, U> nextRight, + Fun<Map.Entry<K, V>, ? extends U> transformer, + BiFun<? super U, ? super U, ? extends U> reducer) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; } - @NotNull @Override - public <T> T[] toArray(@NotNull T[] a) { - Collection<V> c = new ArrayList<V>(); - for (V v : this) { - c.add(v); + public final U getRawResult() { + return result; + } + + @Override + public final void compute() { + final Fun<Map.Entry<K, V>, ? extends U> transformer; + final BiFun<? super U, ? super U, ? extends U> reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + (rights = new MapReduceEntriesTask<K, V, U> + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, reducer)).fork(); + } + U r = null; + for (Node<K, V> p; (p = advance()) != null; ) { + U u; + if ((u = transformer.apply(p)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + result = r; + for (CountedCompleter<?> c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") MapReduceEntriesTask<K, V, U> + t = (MapReduceEntriesTask<K, V, U>)c; + @SuppressWarnings("unchecked") MapReduceEntriesTask<K, V, U> s = t.rights; + while (s != null) { + U sr; + if ((sr = s.result) != null) { + U tr; + t.result = (tr = t.result) == null ? sr : + reducer.apply(tr, sr); + } + s = t.rights = s.nextRight; + } + } } - return c.toArray(a); } } - private final class EntrySet extends AbstractSet<Entry<K, V>> { - @NotNull + @SuppressWarnings("serial") + static final class MapReduceMappingsTask<K, V, U> + extends BulkTask<K, V, U> { + final BiFun<? super K, ? super V, ? extends U> transformer; + final BiFun<? super U, ? super U, ? extends U> reducer; + U result; + MapReduceMappingsTask<K, V, U> rights; + MapReduceMappingsTask<K, V, U> nextRight; + + MapReduceMappingsTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + MapReduceMappingsTask<K, V, U> nextRight, + BiFun<? super K, ? super V, ? extends U> transformer, + BiFun<? super U, ? super U, ? extends U> reducer) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.reducer = reducer; + } + @Override - public Iterator<Entry<K, V>> iterator() { - return new EntryIterator(); + public final U getRawResult() { + return result; } @Override - public boolean contains(Object o) { - if (!(o instanceof Entry)) { - return false; + public final void compute() { + final BiFun<? super K, ? super V, ? extends U> transformer; + final BiFun<? super U, ? super U, ? extends U> reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + (rights = new MapReduceMappingsTask<K, V, U> + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, reducer)).fork(); + } + U r = null; + for (Node<K, V> p; (p = advance()) != null; ) { + U u; + if ((u = transformer.apply(p.key, p.val)) != null) { + r = r == null ? u : reducer.apply(r, u); + } + } + result = r; + for (CountedCompleter<?> c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") MapReduceMappingsTask<K, V, U> + t = (MapReduceMappingsTask<K, V, U>)c; + @SuppressWarnings("unchecked") MapReduceMappingsTask<K, V, U> s = t.rights; + while (s != null) { + U sr; + if ((sr = s.result) != null) { + U tr; + t.result = (tr = t.result) == null ? sr : + reducer.apply(tr, sr); + } + s = t.rights = s.nextRight; + } + } } - Entry<K, V> e = (Entry<K, V>)o; - V v = get(e.getKey()); - return v != null && v.equals(e.getValue()); + } + } + + @SuppressWarnings("serial") + static final class MapReduceKeysToDoubleTask<K, V> + extends BulkTask<K, V, Double> { + final ObjectToDouble<? super K> transformer; + final DoubleByDoubleToDouble reducer; + final double basis; + double result; + MapReduceKeysToDoubleTask<K, V> rights; + MapReduceKeysToDoubleTask<K, V> nextRight; + + MapReduceKeysToDoubleTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + MapReduceKeysToDoubleTask<K, V> nextRight, + ObjectToDouble<? super K> transformer, + double basis, + DoubleByDoubleToDouble reducer) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; } @Override - public boolean remove(Object o) { - if (!(o instanceof Entry)) { - return false; + public final Double getRawResult() { + return result; + } + + @Override + public final void compute() { + final ObjectToDouble<? super K> transformer; + final DoubleByDoubleToDouble reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + double r = basis; + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + (rights = new MapReduceKeysToDoubleTask<K, V> + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, r, reducer)).fork(); + } + for (Node<K, V> p; (p = advance()) != null; ) { + r = reducer.apply(r, transformer.apply(p.key)); + } + result = r; + for (CountedCompleter<?> c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") MapReduceKeysToDoubleTask<K, V> + t = (MapReduceKeysToDoubleTask<K, V>)c; + @SuppressWarnings("unchecked") MapReduceKeysToDoubleTask<K, V> s = t.rights; + while (s != null) { + t.result = reducer.apply(t.result, s.result); + s = t.rights = s.nextRight; + } + } } - Entry<K, V> e = (Entry<K, V>)o; - return ConcurrentHashMap.this.remove(e.getKey(), e.getValue()); + } + } + + @SuppressWarnings("serial") + static final class MapReduceValuesToDoubleTask<K, V> + extends BulkTask<K, V, Double> { + final ObjectToDouble<? super V> transformer; + final DoubleByDoubleToDouble reducer; + final double basis; + double result; + MapReduceValuesToDoubleTask<K, V> rights; + MapReduceValuesToDoubleTask<K, V> nextRight; + + MapReduceValuesToDoubleTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + MapReduceValuesToDoubleTask<K, V> nextRight, + ObjectToDouble<? super V> transformer, + double basis, + DoubleByDoubleToDouble reducer) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; } @Override - public int size() { - return ConcurrentHashMap.this.size(); + public final Double getRawResult() { + return result; } @Override - public void clear() { - ConcurrentHashMap.this.clear(); + public final void compute() { + final ObjectToDouble<? super V> transformer; + final DoubleByDoubleToDouble reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + double r = basis; + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + (rights = new MapReduceValuesToDoubleTask<K, V> + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, r, reducer)).fork(); + } + for (Node<K, V> p; (p = advance()) != null; ) { + r = reducer.apply(r, transformer.apply(p.val)); + } + result = r; + for (CountedCompleter<?> c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") MapReduceValuesToDoubleTask<K, V> + t = (MapReduceValuesToDoubleTask<K, V>)c; + @SuppressWarnings("unchecked") MapReduceValuesToDoubleTask<K, V> s = t.rights; + while (s != null) { + t.result = reducer.apply(t.result, s.result); + s = t.rights = s.nextRight; + } + } + } + } + } + + @SuppressWarnings("serial") + static final class MapReduceEntriesToDoubleTask<K, V> + extends BulkTask<K, V, Double> { + final ObjectToDouble<Map.Entry<K, V>> transformer; + final DoubleByDoubleToDouble reducer; + final double basis; + double result; + MapReduceEntriesToDoubleTask<K, V> rights; + MapReduceEntriesToDoubleTask<K, V> nextRight; + + MapReduceEntriesToDoubleTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + MapReduceEntriesToDoubleTask<K, V> nextRight, + ObjectToDouble<Map.Entry<K, V>> transformer, + double basis, + DoubleByDoubleToDouble reducer) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + @Override + public final Double getRawResult() { + return result; } - @NotNull @Override - public Object[] toArray() { - // Since we don't ordinarily have distinct Entry objects, we - // must pack elements using exportable SimpleEntry - Collection<Entry<K, V>> c = new ArrayList<Entry<K, V>>(size()); - for (Entry<K, V> entry : this) { - c.add(new SimpleEntry(entry)); + public final void compute() { + final ObjectToDouble<Map.Entry<K, V>> transformer; + final DoubleByDoubleToDouble reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + double r = basis; + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + (rights = new MapReduceEntriesToDoubleTask<K, V> + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, r, reducer)).fork(); + } + for (Node<K, V> p; (p = advance()) != null; ) { + r = reducer.apply(r, transformer.apply(p)); + } + result = r; + for (CountedCompleter<?> c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") MapReduceEntriesToDoubleTask<K, V> + t = (MapReduceEntriesToDoubleTask<K, V>)c; + @SuppressWarnings("unchecked") MapReduceEntriesToDoubleTask<K, V> s = t.rights; + while (s != null) { + t.result = reducer.apply(t.result, s.result); + s = t.rights = s.nextRight; + } + } } - return c.toArray(); + } + } + + @SuppressWarnings("serial") + static final class MapReduceMappingsToDoubleTask<K, V> + extends BulkTask<K, V, Double> { + final ObjectByObjectToDouble<? super K, ? super V> transformer; + final DoubleByDoubleToDouble reducer; + final double basis; + double result; + MapReduceMappingsToDoubleTask<K, V> rights; + MapReduceMappingsToDoubleTask<K, V> nextRight; + + MapReduceMappingsToDoubleTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + MapReduceMappingsToDoubleTask<K, V> nextRight, + ObjectByObjectToDouble<? super K, ? super V> transformer, + double basis, + DoubleByDoubleToDouble reducer) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + @Override + public final Double getRawResult() { + return result; } - @NotNull @Override - public <T> T[] toArray(@NotNull T[] a) { - Collection<Entry<K, V>> c = new ArrayList<Entry<K, V>>(size()); - for (Entry<K, V> entry : this) { - c.add(new SimpleEntry(entry)); + public final void compute() { + final ObjectByObjectToDouble<? super K, ? super V> transformer; + final DoubleByDoubleToDouble reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + double r = basis; + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + (rights = new MapReduceMappingsToDoubleTask<K, V> + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, r, reducer)).fork(); + } + for (Node<K, V> p; (p = advance()) != null; ) { + r = reducer.apply(r, transformer.apply(p.key, p.val)); + } + result = r; + for (CountedCompleter<?> c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") MapReduceMappingsToDoubleTask<K, V> + t = (MapReduceMappingsToDoubleTask<K, V>)c; + @SuppressWarnings("unchecked") MapReduceMappingsToDoubleTask<K, V> s = t.rights; + while (s != null) { + t.result = reducer.apply(t.result, s.result); + s = t.rights = s.nextRight; + } + } } - return c.toArray(a); } } - /** - * This duplicates java.util.AbstractMap.SimpleEntry until this class - * is made accessible. - */ - final class SimpleEntry implements Entry<K, V> { - private final K key; - private V value; + @SuppressWarnings("serial") + static final class MapReduceKeysToLongTask<K, V> + extends BulkTask<K, V, Long> { + final ObjectToLong<? super K> transformer; + final LongByLongToLong reducer; + final long basis; + long result; + MapReduceKeysToLongTask<K, V> rights; + MapReduceKeysToLongTask<K, V> nextRight; + + MapReduceKeysToLongTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + MapReduceKeysToLongTask<K, V> nextRight, + ObjectToLong<? super K> transformer, + long basis, + LongByLongToLong reducer) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } - public SimpleEntry(K key, V value) { - this.key = key; - this.value = value; + @Override + public final Long getRawResult() { + return result; } - public SimpleEntry(Entry<K, V> e) { - key = e.getKey(); - value = e.getValue(); + @Override + public final void compute() { + final ObjectToLong<? super K> transformer; + final LongByLongToLong reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + long r = basis; + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + (rights = new MapReduceKeysToLongTask<K, V> + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, r, reducer)).fork(); + } + for (Node<K, V> p; (p = advance()) != null; ) { + r = reducer.apply(r, transformer.apply(p.key)); + } + result = r; + for (CountedCompleter<?> c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") MapReduceKeysToLongTask<K, V> + t = (MapReduceKeysToLongTask<K, V>)c; + @SuppressWarnings("unchecked") MapReduceKeysToLongTask<K, V> s = t.rights; + while (s != null) { + t.result = reducer.apply(t.result, s.result); + s = t.rights = s.nextRight; + } + } + } + } + } + + @SuppressWarnings("serial") + static final class MapReduceValuesToLongTask<K, V> + extends BulkTask<K, V, Long> { + final ObjectToLong<? super V> transformer; + final LongByLongToLong reducer; + final long basis; + long result; + MapReduceValuesToLongTask<K, V> rights; + MapReduceValuesToLongTask<K, V> nextRight; + + MapReduceValuesToLongTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + MapReduceValuesToLongTask<K, V> nextRight, + ObjectToLong<? super V> transformer, + long basis, + LongByLongToLong reducer) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; } @Override - public K getKey() { - return key; + public final Long getRawResult() { + return result; } @Override - public V getValue() { - return value; + public final void compute() { + final ObjectToLong<? super V> transformer; + final LongByLongToLong reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + long r = basis; + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + (rights = new MapReduceValuesToLongTask<K, V> + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, r, reducer)).fork(); + } + for (Node<K, V> p; (p = advance()) != null; ) { + r = reducer.apply(r, transformer.apply(p.val)); + } + result = r; + for (CountedCompleter<?> c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") MapReduceValuesToLongTask<K, V> + t = (MapReduceValuesToLongTask<K, V>)c; + @SuppressWarnings("unchecked") MapReduceValuesToLongTask<K, V> s = t.rights; + while (s != null) { + t.result = reducer.apply(t.result, s.result); + s = t.rights = s.nextRight; + } + } + } + } + } + + @SuppressWarnings("serial") + static final class MapReduceEntriesToLongTask<K, V> + extends BulkTask<K, V, Long> { + final ObjectToLong<Map.Entry<K, V>> transformer; + final LongByLongToLong reducer; + final long basis; + long result; + MapReduceEntriesToLongTask<K, V> rights; + MapReduceEntriesToLongTask<K, V> nextRight; + + MapReduceEntriesToLongTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + MapReduceEntriesToLongTask<K, V> nextRight, + ObjectToLong<Map.Entry<K, V>> transformer, + long basis, + LongByLongToLong reducer) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; } @Override - public V setValue(V value) { - V oldValue = this.value; - this.value = value; - return oldValue; + public final Long getRawResult() { + return result; } - public boolean equals(Object o) { - if (!(o instanceof Entry)) { - return false; + @Override + public final void compute() { + final ObjectToLong<Map.Entry<K, V>> transformer; + final LongByLongToLong reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + long r = basis; + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + (rights = new MapReduceEntriesToLongTask<K, V> + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, r, reducer)).fork(); + } + for (Node<K, V> p; (p = advance()) != null; ) { + r = reducer.apply(r, transformer.apply(p)); + } + result = r; + for (CountedCompleter<?> c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") MapReduceEntriesToLongTask<K, V> + t = (MapReduceEntriesToLongTask<K, V>)c; + @SuppressWarnings("unchecked") MapReduceEntriesToLongTask<K, V> s = t.rights; + while (s != null) { + t.result = reducer.apply(t.result, s.result); + s = t.rights = s.nextRight; + } + } } - Entry e = (Entry)o; - K o2 = (K)e.getKey(); - return (key == null ? o2 == null : myHashingStrategy.equals(key, o2)) && eq(value, e.getValue()); } + } - public int hashCode() { - return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); + @SuppressWarnings("serial") + static final class MapReduceMappingsToLongTask<K, V> + extends BulkTask<K, V, Long> { + final ObjectByObjectToLong<? super K, ? super V> transformer; + final LongByLongToLong reducer; + final long basis; + long result; + MapReduceMappingsToLongTask<K, V> rights; + MapReduceMappingsToLongTask<K, V> nextRight; + + MapReduceMappingsToLongTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + MapReduceMappingsToLongTask<K, V> nextRight, + ObjectByObjectToLong<? super K, ? super V> transformer, + long basis, + LongByLongToLong reducer) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; } - public String toString() { - return key + "=" + value; + @Override + public final Long getRawResult() { + return result; } - boolean eq(Object o1, Object o2) { - return o1 == null ? o2 == null : o1.equals(o2); + @Override + public final void compute() { + final ObjectByObjectToLong<? super K, ? super V> transformer; + final LongByLongToLong reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + long r = basis; + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + (rights = new MapReduceMappingsToLongTask<K, V> + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, r, reducer)).fork(); + } + for (Node<K, V> p; (p = advance()) != null; ) { + r = reducer.apply(r, transformer.apply(p.key, p.val)); + } + result = r; + for (CountedCompleter<?> c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") MapReduceMappingsToLongTask<K, V> + t = (MapReduceMappingsToLongTask<K, V>)c; + @SuppressWarnings("unchecked") MapReduceMappingsToLongTask<K, V> s = t.rights; + while (s != null) { + t.result = reducer.apply(t.result, s.result); + s = t.rights = s.nextRight; + } + } + } } } - /* ---------------- Serialization Support -------------- */ + @SuppressWarnings("serial") + static final class MapReduceKeysToIntTask<K, V> + extends BulkTask<K, V, Integer> { + final ObjectToInt<? super K> transformer; + final IntByIntToInt reducer; + final int basis; + int result; + MapReduceKeysToIntTask<K, V> rights; + MapReduceKeysToIntTask<K, V> nextRight; + + MapReduceKeysToIntTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + MapReduceKeysToIntTask<K, V> nextRight, + ObjectToInt<? super K> transformer, + int basis, + IntByIntToInt reducer) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } - /** - * Save the state of the <tt>ConcurrentHashMap</tt> - * instance to a stream (i.e., - * serialize it). - * - * @param s the stream - * @serialData the key (Object) and value (Object) - * for each key-value mapping, followed by a null pair. - * The key-value mappings are emitted in no particular order. - */ - private void writeObject(ObjectOutputStream s) throws IOException { - s.defaultWriteObject(); + @Override + public final Integer getRawResult() { + return result; + } - for (Segment seg : segments) { - seg.lock(); - try { - HashEntry[] tab = seg.table; - for (HashEntry tabEntry : tab) { - HashEntry<K, V> entry = (HashEntry<K, V>)tabEntry; - for (HashEntry<K, V> e = entry; e != null; e = e.next) { - s.writeObject(e.key); - s.writeObject(e.value); + @Override + public final void compute() { + final ObjectToInt<? super K> transformer; + final IntByIntToInt reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + int r = basis; + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + (rights = new MapReduceKeysToIntTask<K, V> + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, r, reducer)).fork(); + } + for (Node<K, V> p; (p = advance()) != null; ) { + r = reducer.apply(r, transformer.apply(p.key)); + } + result = r; + for (CountedCompleter<?> c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") MapReduceKeysToIntTask<K, V> + t = (MapReduceKeysToIntTask<K, V>)c; + @SuppressWarnings("unchecked") MapReduceKeysToIntTask<K, V> s = t.rights; + while (s != null) { + t.result = reducer.apply(t.result, s.result); + s = t.rights = s.nextRight; } } } - finally { - seg.unlock(); + } + } + + @SuppressWarnings("serial") + static final class MapReduceValuesToIntTask<K, V> + extends BulkTask<K, V, Integer> { + final ObjectToInt<? super V> transformer; + final IntByIntToInt reducer; + final int basis; + int result; + MapReduceValuesToIntTask<K, V> rights; + MapReduceValuesToIntTask<K, V> nextRight; + + MapReduceValuesToIntTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + MapReduceValuesToIntTask<K, V> nextRight, + ObjectToInt<? super V> transformer, + int basis, + IntByIntToInt reducer) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + @Override + public final Integer getRawResult() { + return result; + } + + @Override + public final void compute() { + final ObjectToInt<? super V> transformer; + final IntByIntToInt reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + int r = basis; + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + (rights = new MapReduceValuesToIntTask<K, V> + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, r, reducer)).fork(); + } + for (Node<K, V> p; (p = advance()) != null; ) { + r = reducer.apply(r, transformer.apply(p.val)); + } + result = r; + for (CountedCompleter<?> c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") MapReduceValuesToIntTask<K, V> + t = (MapReduceValuesToIntTask<K, V>)c; + @SuppressWarnings("unchecked") MapReduceValuesToIntTask<K, V> s = t.rights; + while (s != null) { + t.result = reducer.apply(t.result, s.result); + s = t.rights = s.nextRight; + } + } + } + } + } + + @SuppressWarnings("serial") + static final class MapReduceEntriesToIntTask<K, V> + extends BulkTask<K, V, Integer> { + final ObjectToInt<Map.Entry<K, V>> transformer; + final IntByIntToInt reducer; + final int basis; + int result; + MapReduceEntriesToIntTask<K, V> rights; + MapReduceEntriesToIntTask<K, V> nextRight; + + MapReduceEntriesToIntTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + MapReduceEntriesToIntTask<K, V> nextRight, + ObjectToInt<Map.Entry<K, V>> transformer, + int basis, + IntByIntToInt reducer) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + @Override + public final Integer getRawResult() { + return result; + } + + @Override + public final void compute() { + final ObjectToInt<Map.Entry<K, V>> transformer; + final IntByIntToInt reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + int r = basis; + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + (rights = new MapReduceEntriesToIntTask<K, V> + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, r, reducer)).fork(); + } + for (Node<K, V> p; (p = advance()) != null; ) { + r = reducer.apply(r, transformer.apply(p)); + } + result = r; + for (CountedCompleter<?> c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") MapReduceEntriesToIntTask<K, V> + t = (MapReduceEntriesToIntTask<K, V>)c; + @SuppressWarnings("unchecked") MapReduceEntriesToIntTask<K, V> s = t.rights; + while (s != null) { + t.result = reducer.apply(t.result, s.result); + s = t.rights = s.nextRight; + } + } } } - s.writeObject(null); - s.writeObject(null); + } + + @SuppressWarnings("serial") + static final class MapReduceMappingsToIntTask<K, V> + extends BulkTask<K, V, Integer> { + final ObjectByObjectToInt<? super K, ? super V> transformer; + final IntByIntToInt reducer; + final int basis; + int result; + MapReduceMappingsToIntTask<K, V> rights; + MapReduceMappingsToIntTask<K, V> nextRight; + + MapReduceMappingsToIntTask + (BulkTask<K, V, ?> p, int b, int i, int f, Node<K, V>[] t, + MapReduceMappingsToIntTask<K, V> nextRight, + ObjectByObjectToInt<? super K, ? super V> transformer, + int basis, + IntByIntToInt reducer) { + super(p, b, i, f, t); + this.nextRight = nextRight; + this.transformer = transformer; + this.basis = basis; + this.reducer = reducer; + } + + @Override + public final Integer getRawResult() { + return result; + } + + @Override + public final void compute() { + final ObjectByObjectToInt<? super K, ? super V> transformer; + final IntByIntToInt reducer; + if ((transformer = this.transformer) != null && + (reducer = this.reducer) != null) { + int r = basis; + for (int i = baseIndex, f, h; batch > 0 && + (h = (f = baseLimit) + i >>> 1) > i; ) { + addToPendingCount(1); + (rights = new MapReduceMappingsToIntTask<K, V> + (this, batch >>>= 1, baseLimit = h, f, tab, + rights, transformer, r, reducer)).fork(); + } + for (Node<K, V> p; (p = advance()) != null; ) { + r = reducer.apply(r, transformer.apply(p.key, p.val)); + } + result = r; + for (CountedCompleter<?> c = firstComplete(); c != null; c = c.nextComplete()) { + @SuppressWarnings("unchecked") MapReduceMappingsToIntTask<K, V> + t = (MapReduceMappingsToIntTask<K, V>)c; + @SuppressWarnings("unchecked") MapReduceMappingsToIntTask<K, V> s = t.rights; + while (s != null) { + t.result = reducer.apply(t.result, s.result); + s = t.rights = s.nextRight; + } + } + } + } + } + + /* ---------------- Counters -------------- */ + + // Adapted from LongAdder and Striped64. + // See their internal docs for explanation. + + // A padded cell for distributing counts + static final class CounterCell { + volatile long p0; + volatile long p1; + volatile long p2; + volatile long p3; + volatile long p4; + volatile long p5; + volatile long p6; + volatile long value; + volatile long q0; + volatile long q1; + volatile long q2; + volatile long q3; + volatile long q4; + volatile long q5; + volatile long q6; + + CounterCell(long x) { + value = x; + } } /** - * Reconstitute the <tt>ConcurrentHashMap</tt> - * instance from a stream (i.e., - * deserialize it). - * - * @param s the stream + * Holder for the thread-local hash code determining which + * CounterCell to use. The code is initialized via the + * counterHashCodeGenerator, but may be moved upon collisions. */ - private void readObject(ObjectInputStream s) - throws IOException, ClassNotFoundException { - s.defaultReadObject(); + static final class CounterHashCode { + int code; + } + + /** + * Generates initial value for per-thread CounterHashCodes. + */ + static final AtomicInteger counterHashCodeGenerator = new AtomicInteger(); - // Initialize each segment to be minimally sized, and let grow. - for (Segment segment : segments) { - segment.setTable(new HashEntry[1]); + /** + * Increment for counterHashCodeGenerator. See class ThreadLocal + * for explanation. + */ + static final int SEED_INCREMENT = 0x61c88647; + + /** + * Per-thread counter hash codes. Shared across all instances. + */ + static final ThreadLocal<CounterHashCode> threadCounterHashCode = + new ThreadLocal<CounterHashCode>(); + + + final long sumCount() { + CounterCell[] as = counterCells; + long sum = baseCount; + if (as != null) { + for (CounterCell a : as) { + if (a != null) { + sum += a.value; + } + } } + return sum; + } - // Read the keys and values, and put the mappings in the table - while (true) { - K key = (K)s.readObject(); - V value = (V)s.readObject(); - if (key == null) { - break; + // See LongAdder version for explanation + private void fullAddCount(long x, CounterHashCode hc, + boolean wasUncontended) { + int h; + if (hc == null) { + hc = new CounterHashCode(); + int s = counterHashCodeGenerator.addAndGet(SEED_INCREMENT); + h = hc.code = s == 0 ? 1 : s; // Avoid zero + threadCounterHashCode.set(hc); + } + else { + h = hc.code; + } + boolean collide = false; // True if last slot nonempty + for (; ; ) { + CounterCell[] as; + int n; + long v; + if ((as = counterCells) != null && (n = as.length) > 0) { + CounterCell a; + if ((a = as[n - 1 & h]) == null) { + if (cellsBusy == 0) { // Try to attach new Cell + CounterCell r = new CounterCell(x); // Optimistic create + if (cellsBusy == 0 && + U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + boolean created = false; + try { // Recheck under lock + CounterCell[] rs; + int m; + int j; + if ((rs = counterCells) != null && + (m = rs.length) > 0 && + rs[j = m - 1 & h] == null) { + rs[j] = r; + created = true; + } + } + finally { + cellsBusy = 0; + } + if (created) { + break; + } + continue; // Slot is now non-empty + } + } + collide = false; + } + else if (!wasUncontended) // CAS already known to fail + { + wasUncontended = true; // Continue after rehash + } + else if (U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x)) { + break; + } + else if (counterCells != as || n >= NCPU) { + collide = false; // At max size or stale + } + else if (!collide) { + collide = true; + } + else if (cellsBusy == 0 && + U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + try { + if (counterCells == as) {// Expand table unless stale + CounterCell[] rs = new CounterCell[n << 1]; + for (int i = 0; i < n; ++i) { + rs[i] = as[i]; + } + counterCells = rs; + } + } + finally { + cellsBusy = 0; + } + collide = false; + continue; // Retry with expanded table + } + h ^= h << 13; // Rehash + h ^= h >>> 17; + h ^= h << 5; } - put(key, value); + else if (cellsBusy == 0 && counterCells == as && + U.compareAndSwapInt(this, CELLSBUSY, 0, 1)) { + boolean init = false; + try { // Initialize table + if (counterCells == as) { + CounterCell[] rs = new CounterCell[2]; + rs[h & 1] = new CounterCell(x); + counterCells = rs; + init = true; + } + } + finally { + cellsBusy = 0; + } + if (init) { + break; + } + } + else if (U.compareAndSwapLong(this, BASECOUNT, v = baseCount, v + x)) { + break; // Fall back on using base + } + } + hc.code = h; // Record index for next time + } + + // Unsafe mechanics + private static final Unsafe U; + private static final long SIZECTL; + private static final long TRANSFERINDEX; + private static final long TRANSFERORIGIN; + private static final long BASECOUNT; + private static final long CELLSBUSY; + private static final long CELLVALUE; + private static final long ABASE; + private static final int ASHIFT; + + static { + try { + U = getUnsafe(); + Class<?> k = ConcurrentHashMap.class; + SIZECTL = U.objectFieldOffset + (k.getDeclaredField("sizeCtl")); + TRANSFERINDEX = U.objectFieldOffset + (k.getDeclaredField("transferIndex")); + TRANSFERORIGIN = U.objectFieldOffset + (k.getDeclaredField("transferOrigin")); + BASECOUNT = U.objectFieldOffset + (k.getDeclaredField("baseCount")); + CELLSBUSY = U.objectFieldOffset + (k.getDeclaredField("cellsBusy")); + Class<?> ck = CounterCell.class; + CELLVALUE = U.objectFieldOffset + (ck.getDeclaredField("value")); + Class<?> ak = Node[].class; + ABASE = U.arrayBaseOffset(ak); + int scale = U.arrayIndexScale(ak); + if ((scale & scale - 1) != 0) { + throw new Error("data type scale not a power of two"); + } + ASHIFT = 31 - Integer.numberOfLeadingZeros(scale); + } + catch (Exception e) { + throw new Error(e); + } + } + + /** + * Returns a sun.misc.Unsafe. Suitable for use in a 3rd party package. + * Replace with a simple call to Unsafe.getUnsafe when integrating + * into a jdk. + * + * @return a sun.misc.Unsafe + */ + private static Unsafe getUnsafe() { + try { + return Unsafe.getUnsafe(); + } + catch (SecurityException tryReflectionInstead) { + } + try { + return AccessController.doPrivileged + (new PrivilegedExceptionAction<Unsafe>() { + @Override + public Unsafe run() throws Exception { + Class<Unsafe> k = Unsafe.class; + for (Field f : k.getDeclaredFields()) { + f.setAccessible(true); + Object x = f.get(null); + if (k.isInstance(x)) { + return k.cast(x); + } + } + throw new NoSuchFieldError("the Unsafe"); + } + }); + } + catch (PrivilegedActionException e) { + throw new RuntimeException("Could not initialize intrinsics", + e.getCause()); } } + ////////////////////// IJ specific @Override public int computeHashCode(final K object) { - int h = object.hashCode(); - h += ~(h << 9); - h ^= h >>> 14; - h += h << 4; - h ^= h >>> 10; - return h; + return object == null ? 0 : object.hashCode(); } @Override @@ -1527,4 +7182,10 @@ public class ConcurrentHashMap<K, V> extends AbstractMap<K, V> implements Concur public V cacheOrGet(final K key, final V value) { return ConcurrencyUtil.cacheOrGet(this, key, value); } + + private int hash(K key) { + return spread(myHashingStrategy.computeHashCode(key)); + } + public static final float DEFAULT_LOAD_FACTOR = LOAD_FACTOR; + public static final int DEFAULT_INITIAL_CAPACITY = DEFAULT_CAPACITY; } diff --git a/platform/util/src/com/intellij/util/containers/Convertor.java b/platform/util/src/com/intellij/util/containers/Convertor.java index b51b4910024a..4020143b0f68 100644 --- a/platform/util/src/com/intellij/util/containers/Convertor.java +++ b/platform/util/src/com/intellij/util/containers/Convertor.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2009 JetBrains s.r.o. + * Copyright 2000-2013 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 @@ public interface Convertor<Src, Dst> { IntoSelf SELF = new IntoSelf(); class IntoSelf<Src> implements Convertor<Src, Src> { + @Override public Src convert(Src o) { return o; } diff --git a/platform/util/src/com/intellij/util/io/SafeFileOutputStream.java b/platform/util/src/com/intellij/util/io/SafeFileOutputStream.java index 145bc08b0a62..da71335d4d81 100644 --- a/platform/util/src/com/intellij/util/io/SafeFileOutputStream.java +++ b/platform/util/src/com/intellij/util/io/SafeFileOutputStream.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2011 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -114,8 +114,6 @@ public class SafeFileOutputStream extends OutputStream { myTargetFile, myBackDoorFile.getName())); } - final int permissions = myPreserveAttributes ? FileSystemUtil.getPermissions(myTargetFile) : -1; - final File oldFile = new File(myTargetFile.getParent(), myTargetFile.getName() + EXTENSION_OLD); try { FileUtil.rename(myTargetFile, oldFile); @@ -135,8 +133,8 @@ public class SafeFileOutputStream extends OutputStream { myTargetFile, oldFile.getName(), myBackDoorFile.getName())); } - if (permissions != -1) { - FileSystemUtil.setPermissions(myTargetFile, permissions); + if (myPreserveAttributes) { + FileSystemUtil.clonePermissions(oldFile.getPath(), myTargetFile.getPath()); } if (!FileUtil.delete(oldFile)) { diff --git a/platform/util/src/com/intellij/util/ui/UIUtil.java b/platform/util/src/com/intellij/util/ui/UIUtil.java index d0ab6777c6ca..58bd959d73a9 100644 --- a/platform/util/src/com/intellij/util/ui/UIUtil.java +++ b/platform/util/src/com/intellij/util/ui/UIUtil.java @@ -932,12 +932,12 @@ public class UIUtil { } public static Icon getTreeSelectedCollapsedIcon() { - return isUnderAquaBasedLookAndFeel() || isUnderNimbusLookAndFeel() || isUnderGTKLookAndFeel() || isUnderDarcula() + return isUnderAquaBasedLookAndFeel() || isUnderNimbusLookAndFeel() || isUnderGTKLookAndFeel() || isUnderDarcula() || isUnderIntelliJLaF() ? AllIcons.Mac.Tree_white_right_arrow : getTreeCollapsedIcon(); } public static Icon getTreeSelectedExpandedIcon() { - return isUnderAquaBasedLookAndFeel() || isUnderNimbusLookAndFeel() || isUnderGTKLookAndFeel() || isUnderDarcula() + return isUnderAquaBasedLookAndFeel() || isUnderNimbusLookAndFeel() || isUnderGTKLookAndFeel() || isUnderDarcula() || isUnderIntelliJLaF() ? AllIcons.Mac.Tree_white_down_arrow : getTreeExpandedIcon(); } @@ -1027,6 +1027,11 @@ public class UIUtil { } @SuppressWarnings({"HardCodedStringLiteral"}) + public static boolean isUnderIntelliJLaF() { + return UIManager.getLookAndFeel().getName().contains("IntelliJ"); + } + + @SuppressWarnings({"HardCodedStringLiteral"}) public static boolean isUnderGTKLookAndFeel() { return UIManager.getLookAndFeel().getName().contains("GTK"); } diff --git a/platform/util/src/com/intellij/util/ui/tree/WideSelectionTreeUI.java b/platform/util/src/com/intellij/util/ui/tree/WideSelectionTreeUI.java index 05693bdfb267..d64fb51700bc 100644 --- a/platform/util/src/com/intellij/util/ui/tree/WideSelectionTreeUI.java +++ b/platform/util/src/com/intellij/util/ui/tree/WideSelectionTreeUI.java @@ -251,7 +251,7 @@ public class WideSelectionTreeUI extends BasicTreeUI { final boolean hasBeenExpanded, final boolean isLeaf) { if (!shouldPaintLines()) return; - if (!UIUtil.isUnderAquaBasedLookAndFeel() && !UIUtil.isUnderDarcula()) { + if (!UIUtil.isUnderAquaBasedLookAndFeel() && !UIUtil.isUnderDarcula() && !UIUtil.isUnderIntelliJLaF()) { if (UIUtil.isUnderAlloyIDEALookAndFeel()) { invertLineColor = tree.getSelectionModel().isRowSelected(row) && tree.hasFocus(); } @@ -271,7 +271,7 @@ public class WideSelectionTreeUI extends BasicTreeUI { @Override protected void paintVerticalPartOfLeg(final Graphics g, final Rectangle clipBounds, final Insets insets, final TreePath path) { - if (!UIUtil.isUnderAquaBasedLookAndFeel() && !UIUtil.isUnderDarcula() && shouldPaintLines()) { + if (!UIUtil.isUnderAquaBasedLookAndFeel() && !UIUtil.isUnderDarcula() && !UIUtil.isUnderIntelliJLaF() && shouldPaintLines()) { invertLineColor = UIUtil.isUnderAlloyIDEALookAndFeel() && tree.hasFocus() && tree.getSelectionModel().isPathSelected(path); super.paintVerticalPartOfLeg(g, clipBounds, insets, path); invertLineColor = false; @@ -353,7 +353,7 @@ public class WideSelectionTreeUI extends BasicTreeUI { } } else { - if (selected && (UIUtil.isUnderAquaBasedLookAndFeel() || UIUtil.isUnderDarcula())) { + if (selected && (UIUtil.isUnderAquaBasedLookAndFeel() || UIUtil.isUnderDarcula() || UIUtil.isUnderIntelliJLaF())) { Color bg = UIUtil.getTreeSelectionBackground(tree.hasFocus()); if (myWideSelectionCondition.value(row)) { @@ -377,7 +377,7 @@ public class WideSelectionTreeUI extends BasicTreeUI { @Override public void paint(Graphics g, JComponent c) { - if (myWideSelection && !UIUtil.isUnderAquaBasedLookAndFeel() && !UIUtil.isUnderDarcula()) { + if (myWideSelection && !UIUtil.isUnderAquaBasedLookAndFeel() && !UIUtil.isUnderDarcula() && !UIUtil.isUnderIntelliJLaF()) { paintSelectedRows(g, ((JTree)c)); } if (myWideSelection) { diff --git a/platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesNio2ReadingTest.java b/platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesNio2ReadingTest.java index a732c3408060..6d00daf2412b 100644 --- a/platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesNio2ReadingTest.java +++ b/platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesNio2ReadingTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -43,7 +43,7 @@ public class FileAttributesNio2ReadingTest extends FileAttributesReadingTest { System.setProperty(FORCE_USE_NIO_2_KEY, "true"); FileSystemUtil.resetMediator(); - assertEquals("NIO2", FileSystemUtil.getMediatorName()); + assertEquals("Nio2", FileSystemUtil.getMediatorName()); } @AfterClass diff --git a/platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesReadingTest.java b/platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesReadingTest.java index 798ad4317c37..c56771bbe182 100644 --- a/platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesReadingTest.java +++ b/platform/util/testSrc/com/intellij/openapi/util/io/FileAttributesReadingTest.java @@ -19,6 +19,7 @@ import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.util.io.win32.FileInfo; import com.intellij.openapi.util.io.win32.IdeaWin32; import com.intellij.openapi.util.text.StringUtil; +import com.intellij.util.SystemProperties; import com.intellij.util.TimeoutUtil; import org.jetbrains.annotations.NotNull; import org.junit.After; @@ -151,7 +152,7 @@ public class FileAttributesReadingTest { final FileAttributes attributes = getAttributes(link); assertEquals(FileAttributes.Type.FILE, attributes.type); - assertEquals(FileAttributes.SYM_LINK, attributes.flags); + assertEquals(FileAttributes.SYM_LINK | FileAttributes.READ_ONLY, attributes.flags); assertEquals(myTestData.length, attributes.length); assertTimestampsEqual(file.lastModified(), attributes.lastModified); assertFalse(attributes.isWritable()); @@ -173,7 +174,7 @@ public class FileAttributesReadingTest { final FileAttributes attributes = getAttributes(link2); assertEquals(FileAttributes.Type.FILE, attributes.type); - assertEquals(FileAttributes.SYM_LINK, attributes.flags); + assertEquals(FileAttributes.SYM_LINK | FileAttributes.READ_ONLY, attributes.flags); assertEquals(myTestData.length, attributes.length); assertTimestampsEqual(file.lastModified(), attributes.lastModified); assertFalse(attributes.isWritable()); @@ -193,7 +194,7 @@ public class FileAttributesReadingTest { final FileAttributes attributes = getAttributes(link); assertEquals(FileAttributes.Type.DIRECTORY, attributes.type); - assertEquals(FileAttributes.SYM_LINK, attributes.flags); + assertEquals(SystemInfo.isUnix ? FileAttributes.SYM_LINK | FileAttributes.READ_ONLY : FileAttributes.SYM_LINK, attributes.flags); assertEquals(file.length(), attributes.length); assertTimestampsEqual(file.lastModified(), attributes.lastModified); if (SystemInfo.isUnix) assertFalse(attributes.isWritable()); @@ -412,6 +413,21 @@ public class FileAttributesReadingTest { assertTrue(attributes.lastModified + " not in " + t1 + ".." + t2, t1 <= attributes.lastModified && attributes.lastModified <= t2); } + @Test + public void notOwned() throws Exception { + assumeTrue(SystemInfo.isUnix); + File userHome = new File(SystemProperties.getUserHome()); + + FileAttributes homeAttributes = getAttributes(userHome); + assertTrue(homeAttributes.isDirectory()); + assertTrue(homeAttributes.isWritable()); + + FileAttributes parentAttributes = getAttributes(userHome.getParentFile()); + assertTrue(parentAttributes.isDirectory()); + assertFalse(parentAttributes.isWritable()); + } + + @NotNull private static FileAttributes getAttributes(@NotNull final File file) { return getAttributes(file, true); diff --git a/platform/util/testSrc/com/intellij/openapi/util/io/FileUtilFindFileTest.java b/platform/util/testSrc/com/intellij/openapi/util/io/FileUtilFindFileTest.java deleted file mode 100644 index 934f78a5881b..000000000000 --- a/platform/util/testSrc/com/intellij/openapi/util/io/FileUtilFindFileTest.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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.openapi.util.io; - -import com.intellij.openapi.util.text.StringUtil; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; - -import java.io.File; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -/** - * @author lene - * @since 29.03.11 - */ -public class FileUtilFindFileTest { - private static File myTempFile; - private static File myFirstFile; - private static File mySecondFile; - - @BeforeClass - public static void setUp() throws Exception { - myTempFile = FileUtil.createTempDirectory("tEF", "",false); //NON-NLS - myFirstFile = new File(myTempFile, "first"); - mySecondFile = new File(myTempFile, "second"); //NON-NLS - assertTrue(myFirstFile.createNewFile()); - assertTrue(mySecondFile.createNewFile()); - } - - @AfterClass - public static void tearDown() throws Exception { - FileUtil.delete(myTempFile); - } - - @Test - public void nonExistingFileInNonExistentDirectory() throws Exception { - String path = FileUtil.findFileInProvidedPath("123", "zero");//NON-NLS - assertTrue(StringUtil.isEmpty(path)); - } - - @Test - public void nonExistingFileInDirectory() throws Exception { - String path = FileUtil.findFileInProvidedPath(myTempFile.getAbsolutePath(), "zero");//NON-NLS - assertTrue(StringUtil.isEmpty(path)); - } - - @Test - public void nonExistingFile() throws Exception { - String path = - FileUtil.findFileInProvidedPath(myFirstFile.getAbsolutePath() + "123", myFirstFile.getName() + "123"); - assertTrue(StringUtil.isEmpty(path)); - } - - @Test - public void existingFileInDirectory() throws Exception { - String path = FileUtil.findFileInProvidedPath(myTempFile.getAbsolutePath(), "first"); - assertEquals(path, myFirstFile.getAbsolutePath()); - } - - @Test - public void existingFile() throws Exception { - String path = FileUtil.findFileInProvidedPath(myFirstFile.getAbsolutePath(), "first"); - assertEquals(path, myFirstFile.getAbsolutePath()); - } - - @Test - public void twoFilesOrderInDirectory() throws Exception { - String path = FileUtil.findFileInProvidedPath(myTempFile.getAbsolutePath(), "first", "second"); //NON-NLS - assertEquals(path, myFirstFile.getAbsolutePath()); - } - - @Test - public void twoFilesOrderInDirectory2() throws Exception { - String path = FileUtil.findFileInProvidedPath(myTempFile.getAbsolutePath(), "second", "first"); //NON-NLS - assertEquals(path, mySecondFile.getAbsolutePath()); - } - - @Test - public void twoFilesOrder() throws Exception { - String path = FileUtil.findFileInProvidedPath(myFirstFile.getAbsolutePath(), "first", "second");//NON-NLS - assertEquals(path, myFirstFile.getAbsolutePath()); - } - - @Test - public void twoFilesOrder2() throws Exception { - String path = FileUtil.findFileInProvidedPath(myFirstFile.getAbsolutePath(), "second", "first"); //NON-NLS - assertEquals(path, myFirstFile.getAbsolutePath()); - } -} diff --git a/platform/util/testSrc/com/intellij/openapi/util/io/FileUtilHeavyTest.java b/platform/util/testSrc/com/intellij/openapi/util/io/FileUtilHeavyTest.java index 9486270b0c52..0f667ad43f94 100644 --- a/platform/util/testSrc/com/intellij/openapi/util/io/FileUtilHeavyTest.java +++ b/platform/util/testSrc/com/intellij/openapi/util/io/FileUtilHeavyTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -15,10 +15,11 @@ */ package com.intellij.openapi.util.io; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.Processor; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; +import org.junit.AfterClass; +import org.junit.BeforeClass; import org.junit.Test; import java.io.File; @@ -26,69 +27,86 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; +import static org.junit.Assert.*; +import static org.junit.Assume.assumeTrue; + /** - * Created with IntelliJ IDEA. - * User: Irina.Chernushina - * Date: 11/22/12 - * Time: 11:41 AM + * @author Irina.Chernushina, lene */ public class FileUtilHeavyTest { - private File myTempDirectory; - - @Before - public void setUp() throws Exception { - myTempDirectory = FileUtil.createTempDirectory(getClass().getSimpleName() + ".", ".tmp"); + private static File myTempDirectory; + private static File myVisitorTestDirectory; + private static File myFindTestDirectory; + private static File myFindTestFirstFile; + private static File myFindTestSecondFile; + + @BeforeClass + public static void setUp() throws Exception { + myTempDirectory = FileUtil.createTempDirectory("FileUtilHeavyTest.", ".tmp"); + + myVisitorTestDirectory = IoTestUtil.createTestDir(myTempDirectory, "visitor_test_dir"); + File dir1 = IoTestUtil.createTestDir(myVisitorTestDirectory, "dir1"); + IoTestUtil.createTestFile(dir1, "1"); + IoTestUtil.createTestFile(dir1, "2"); + File dir2 = IoTestUtil.createTestDir(myVisitorTestDirectory, "dir2"); + IoTestUtil.createTestFile(dir2, "1"); + IoTestUtil.createTestFile(dir2, "2"); + File dir21 = IoTestUtil.createTestDir(dir2, "inner"); + IoTestUtil.createTestFile(dir21, "1"); + IoTestUtil.createTestFile(dir21, "2"); + + myFindTestDirectory = IoTestUtil.createTestDir(myTempDirectory, "find_file_test_dir"); + myFindTestFirstFile = IoTestUtil.createTestFile(myFindTestDirectory, "first"); + myFindTestSecondFile = IoTestUtil.createTestFile(myFindTestDirectory, "second"); } - @After - public void tearDown() throws Exception { + @AfterClass + public static void tearDown() { if (myTempDirectory != null) { FileUtil.delete(myTempDirectory); } } @Test - public void testSimpleRecursiveIteration() throws Exception { - final Tree tree = new Tree(myTempDirectory); + public void testProcessSimple() { final Map<String, Integer> result = new HashMap<String, Integer>(); - FileUtil.processFilesRecursively(myTempDirectory, new Processor<File>() { + FileUtil.processFilesRecursively(myVisitorTestDirectory, new Processor<File>() { @Override public boolean process(File file) { - final Integer integer = result.get(file.getName()); + Integer integer = result.get(file.getName()); result.put(file.getName(), integer == null ? 1 : (integer + 1)); return true; } }); - Assert.assertEquals(6, result.size()); - Assert.assertEquals(1, result.get(myTempDirectory.getName()).intValue()); - Assert.assertEquals(3, result.get("1").intValue()); - Assert.assertEquals(3, result.get("2").intValue()); - Assert.assertEquals(1, result.get("dir1").intValue()); + assertEquals(6, result.size()); + assertEquals(1, result.get(myVisitorTestDirectory.getName()).intValue()); + assertEquals(3, result.get("1").intValue()); + assertEquals(3, result.get("2").intValue()); + assertEquals(1, result.get("dir1").intValue()); } @Test - public void testStops() throws Exception { - final Tree tree = new Tree(myTempDirectory); + public void testProcessStops() { final int[] cnt = new int[]{0}; - FileUtil.processFilesRecursively(myTempDirectory, new Processor<File>() { + FileUtil.processFilesRecursively(myVisitorTestDirectory, new Processor<File>() { @Override public boolean process(File file) { - ++ cnt[0]; + ++cnt[0]; return false; } }); - Assert.assertEquals(1, cnt[0]); + + assertEquals(1, cnt[0]); } @Test - public void testDirectoryFilter() throws Exception { - final Tree tree = new Tree(myTempDirectory); + public void testProcessDirectoryFilter() { final Map<String, Integer> result = new HashMap<String, Integer>(); - FileUtil.processFilesRecursively(myTempDirectory, new Processor<File>() { + FileUtil.processFilesRecursively(myVisitorTestDirectory, new Processor<File>() { @Override public boolean process(File file) { - final Integer integer = result.get(file.getName()); + Integer integer = result.get(file.getName()); result.put(file.getName(), integer == null ? 1 : (integer + 1)); return true; } @@ -98,54 +116,115 @@ public class FileUtilHeavyTest { return ! "dir2".equals(file.getName()); } }); - Assert.assertEquals(5, result.size()); - Assert.assertEquals(1, result.get(myTempDirectory.getName()).intValue()); - Assert.assertEquals(1, result.get("1").intValue()); - Assert.assertEquals(1, result.get("2").intValue()); - Assert.assertEquals(1, result.get("dir1").intValue()); - Assert.assertEquals(1, result.get("dir2").intValue()); - Assert.assertNull(result.get("dir21")); + + assertEquals(5, result.size()); + assertEquals(1, result.get(myVisitorTestDirectory.getName()).intValue()); + assertEquals(1, result.get("1").intValue()); + assertEquals(1, result.get("2").intValue()); + assertEquals(1, result.get("dir1").intValue()); + assertEquals(1, result.get("dir2").intValue()); + assertNull(result.get("dir21")); } - private static class Tree { - private final File dir1; - private final File file11; - private final File file12; + @Test + public void nonExistingFileInNonExistentDirectory() { + String path = FileUtil.findFileInProvidedPath("123", "zero"); + assertTrue(StringUtil.isEmpty(path)); + } - private final File dir2; - private final File file21; - private final File file22; + @Test + public void nonExistingFileInDirectory() { + String path = FileUtil.findFileInProvidedPath(myFindTestDirectory.getAbsolutePath(), "zero"); + assertTrue(StringUtil.isEmpty(path)); + } - private final File dir21; - private final File file211; - private final File file212; + @Test + public void nonExistingFile() { + String path = FileUtil.findFileInProvidedPath(myFindTestFirstFile.getAbsolutePath() + "123", myFindTestFirstFile.getName() + "123"); + assertTrue(StringUtil.isEmpty(path)); + } - private Tree(final File root) throws IOException { - dir1 = new File(root, "dir1"); - dir2 = new File(root, "dir2"); - dir21 = new File(dir2, "inner"); + @Test + public void existingFileInDirectory() { + String path = FileUtil.findFileInProvidedPath(myFindTestDirectory.getAbsolutePath(), "first"); + assertEquals(path, myFindTestFirstFile.getAbsolutePath()); + } + + @Test + public void existingFile() { + String path = FileUtil.findFileInProvidedPath(myFindTestFirstFile.getAbsolutePath(), "first"); + assertEquals(path, myFindTestFirstFile.getAbsolutePath()); + } - Assert.assertTrue(dir1.mkdir()); - Assert.assertTrue(dir2.mkdir()); - Assert.assertTrue(dir21.mkdir()); + @Test + public void twoFilesOrderInDirectory() { + String path = FileUtil.findFileInProvidedPath(myFindTestDirectory.getAbsolutePath(), "first", "second"); + assertEquals(path, myFindTestFirstFile.getAbsolutePath()); + } - file11 = new File(dir1, "1"); - file12 = new File(dir1, "2"); + @Test + public void twoFilesOrderInDirectory2() { + String path = FileUtil.findFileInProvidedPath(myFindTestDirectory.getAbsolutePath(), "second", "first"); + assertEquals(path, myFindTestSecondFile.getAbsolutePath()); + } - file21 = new File(dir2, "1"); - file22 = new File(dir2, "2"); + @Test + public void twoFilesOrder() { + String path = FileUtil.findFileInProvidedPath(myFindTestFirstFile.getAbsolutePath(), "first", "second"); + assertEquals(path, myFindTestFirstFile.getAbsolutePath()); + } - file211 = new File(dir21, "1"); - file212 = new File(dir21, "2"); + @Test + public void twoFilesOrder2() { + String path = FileUtil.findFileInProvidedPath(myFindTestFirstFile.getAbsolutePath(), "second", "first"); + assertEquals(path, myFindTestFirstFile.getAbsolutePath()); + } - file11.createNewFile(); - file12.createNewFile(); + @Test + public void testRepeatableOperation() throws IOException { + abstract class CountableIOOperation implements FileUtilRt.RepeatableIOOperation<Boolean, IOException> { + private int count = 0; - file21.createNewFile(); - file22.createNewFile(); + @Override + public Boolean execute(boolean lastAttempt) throws IOException { + count++; + return stop(lastAttempt) ? true : null; + } - file211.createNewFile(); - file212.createNewFile(); + protected abstract boolean stop(boolean lastAttempt); } + + CountableIOOperation successful = new CountableIOOperation() { + @Override protected boolean stop(boolean lastAttempt) { return true; } + }; + FileUtilRt.doIOOperation(successful); + assertEquals(1, successful.count); + + CountableIOOperation failed = new CountableIOOperation() { + @Override protected boolean stop(boolean lastAttempt) { return false; } + }; + FileUtilRt.doIOOperation(failed); + assertEquals(10, failed.count); + + CountableIOOperation lastShot = new CountableIOOperation() { + @Override protected boolean stop(boolean lastAttempt) { return lastAttempt; } + }; + FileUtilRt.doIOOperation(lastShot); + assertEquals(10, lastShot.count); + } + + @Test + public void testSymlinkDeletion() throws Exception { + assumeTrue(SystemInfo.isWin7OrNewer || SystemInfo.isUnix); + + File targetDir = IoTestUtil.createTestDir(myTempDirectory, "link_del_test_1"); + IoTestUtil.createTestFile(targetDir, "file"); + File linkDir = IoTestUtil.createTestDir(myTempDirectory, "link_del_test_2"); + IoTestUtil.createTestFile(linkDir, "file"); + IoTestUtil.createSymLink(targetDir.getPath(), linkDir.getPath() + "/link"); + + assertEquals(1, targetDir.list().length); + FileUtil.delete(linkDir); + assertEquals(1, targetDir.list().length); } } diff --git a/platform/util/testSrc/com/intellij/openapi/util/io/FileUtilTest.java b/platform/util/testSrc/com/intellij/openapi/util/io/FileUtilLightTest.java index 605dc42d7733..710e0c871d4b 100644 --- a/platform/util/testSrc/com/intellij/openapi/util/io/FileUtilTest.java +++ b/platform/util/testSrc/com/intellij/openapi/util/io/FileUtilLightTest.java @@ -22,13 +22,13 @@ import com.intellij.util.ThreeState; import com.intellij.util.containers.Convertor; import org.junit.Test; -import java.io.IOException; import java.util.Arrays; import java.util.Collection; +import java.util.List; import static org.junit.Assert.*; -public class FileUtilTest { +public class FileUtilLightTest { private static final char UNIX_SEPARATOR = '/'; private static final char WINDOWS_SEPARATOR = '\\'; @@ -102,62 +102,47 @@ public class FileUtilTest { @Test public void testRemoveAncestors() throws Exception { - final String[] arr = {"/a/b/c", "/a", "/a/b", "/d/e", "/b/c", "/a/d", "/b/c/ttt", "/a/ewq.euq"}; - final String[] expectedResult = {"/a","/b/c","/d/e"}; - @SuppressWarnings("unchecked") final Collection<String> result = FileUtil.removeAncestors(Arrays.asList(arr), Convertor.SELF, PairProcessor.TRUE); - assertArrayEquals(expectedResult, ArrayUtil.toStringArray(result)); + List<String> data = Arrays.asList("/a/b/c", "/a", "/a/b", "/d/e", "/b/c", "/a/d", "/b/c/ttt", "/a/ewq.euq"); + String[] expected = {"/a","/b/c","/d/e"}; + @SuppressWarnings("unchecked") Collection<String> result = FileUtil.removeAncestors(data, Convertor.SELF, PairProcessor.TRUE); + assertArrayEquals(expected, ArrayUtil.toStringArray(result)); } @Test public void testCheckImmediateChildren() throws Exception { - final String root = "/a"; - final String[] arr = {"/a/b/c", "/a", "/a/b", "/d/e", "/b/c", "/a/d", "/a/b/c/d/e"}; - final ThreeState[] expectedResult = {ThreeState.UNSURE, ThreeState.YES, ThreeState.YES, ThreeState.NO, ThreeState.NO, ThreeState.YES, ThreeState.UNSURE}; - final ThreeState[] expectedResult2 = {ThreeState.UNSURE, ThreeState.NO, ThreeState.YES, ThreeState.NO, ThreeState.NO, ThreeState.YES, ThreeState.UNSURE}; - - for (int i = 0; i < arr.length; i++) { - String s = arr[i]; - final ThreeState state = FileUtil.isAncestorThreeState(root, s, false); - assertEquals(String.valueOf(i), expectedResult[i], state); + String root = "/a"; + String[] data = {"/a/b/c", "/a", "/a/b", "/d/e", "/b/c", "/a/d", "/a/b/c/d/e"}; + ThreeState[] expected1 = {ThreeState.UNSURE, ThreeState.YES, ThreeState.YES, ThreeState.NO, ThreeState.NO, ThreeState.YES, ThreeState.UNSURE}; + ThreeState[] expected2 = {ThreeState.UNSURE, ThreeState.NO, ThreeState.YES, ThreeState.NO, ThreeState.NO, ThreeState.YES, ThreeState.UNSURE}; + + for (int i = 0; i < data.length; i++) { + ThreeState state = FileUtil.isAncestorThreeState(root, data[i], false); + assertEquals(String.valueOf(i), expected1[i], state); } - for (int i = 0; i < arr.length; i++) { - String s = arr[i]; - final ThreeState state = FileUtil.isAncestorThreeState(root, s, true); - assertEquals(String.valueOf(i), expectedResult2[i], state); + for (int i = 0; i < data.length; i++) { + ThreeState state = FileUtil.isAncestorThreeState(root, data[i], true); + assertEquals(String.valueOf(i), expected2[i], state); } } @Test - public void testRepeatableOperation() throws Exception { - abstract class CountableIOOperation implements FileUtilRt.RepeatableIOOperation<Boolean, IOException> { - private int count = 0; - - @Override - public Boolean execute(boolean lastAttempt) throws IOException { - count++; - return stop(lastAttempt) ? true : null; - } - - protected abstract boolean stop(boolean lastAttempt); - } - - CountableIOOperation successful = new CountableIOOperation() { - @Override protected boolean stop(boolean lastAttempt) { return true; } - }; - FileUtilRt.doIOOperation(successful); - assertEquals(1, successful.count); - - CountableIOOperation failed = new CountableIOOperation() { - @Override protected boolean stop(boolean lastAttempt) { return false; } - }; - FileUtilRt.doIOOperation(failed); - assertEquals(10, failed.count); - - CountableIOOperation lastShot = new CountableIOOperation() { - @Override protected boolean stop(boolean lastAttempt) { return lastAttempt; } - }; - FileUtilRt.doIOOperation(lastShot); - assertEquals(10, lastShot.count); + public void testStartsWith() { + assertTrue(FileUtil.startsWith("/usr/local/jeka", "/usr/local/jeka")); + assertTrue(FileUtil.startsWith("/usr/local/jeka", "/usr/local/")); + assertTrue(FileUtil.startsWith("/usr/local/jeka", "/usr/")); + assertTrue(FileUtil.startsWith("/usr/local/jeka", "/usr")); + assertTrue(FileUtil.startsWith("/usr/local/jeka", "/")); + assertTrue(FileUtil.startsWith("c:/idea", "c:/")); + assertTrue(FileUtil.startsWith("c:/idea", "c:")); + assertTrue(FileUtil.startsWith("c:/idea", "")); + assertTrue(FileUtil.startsWith("c:/idea/x", "C:/IDEA", false)); + + assertFalse(FileUtil.startsWith("/usr/local/jeka", "/usr/local/jek")); + assertFalse(FileUtil.startsWith("/usr/local/jeka", "/usr/local/aaa")); + assertFalse(FileUtil.startsWith("/usr/local/jeka", "/usr/local/jeka/")); + assertFalse(FileUtil.startsWith("/usr/local/jeka", "/aaa")); + assertFalse(FileUtil.startsWith("c:/idea2", "c:/idea")); + assertFalse(FileUtil.startsWith("c:/idea_branches/i18n", "c:/idea")); } } diff --git a/platform/util/testSrc/com/intellij/util/text/StringUtilTest.java b/platform/util/testSrc/com/intellij/util/text/StringUtilTest.java index 6cc68d32d0e2..26f556e65e6f 100644 --- a/platform/util/testSrc/com/intellij/util/text/StringUtilTest.java +++ b/platform/util/testSrc/com/intellij/util/text/StringUtilTest.java @@ -83,6 +83,8 @@ public class StringUtilTest extends TestCase { } public void testNaturalCompare() { + assertEquals(1, StringUtil.naturalCompare("test011", "test10")); + assertEquals(1, StringUtil.naturalCompare("test10a", "test010")); final List<String> strings = new ArrayList<String>(Arrays.asList("Test99", "tes0", "test0", "testing", "test", "test99", "test011", "test1", "test 3", "test2", "test10a", "test10", "1.2.10.5", "1.2.9.1")); final Comparator<String> c = new Comparator<String>() { diff --git a/platform/util/util.iml b/platform/util/util.iml index ec3dc4104ea7..74255456185d 100644 --- a/platform/util/util.iml +++ b/platform/util/util.iml @@ -23,6 +23,7 @@ <orderEntry type="library" name="Guava" level="project" /> <orderEntry type="library" name="CGLIB" level="project" /> <orderEntry type="library" name="asm" level="project" /> + <orderEntry type="library" exported="" name="ForkJoin" level="project" /> </component> <component name="copyright"> <Base> diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesBrowserNode.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesBrowserNode.java index 9cc98f10ad79..7d4156873d7c 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesBrowserNode.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesBrowserNode.java @@ -155,6 +155,7 @@ public class ChangesBrowserNode<T> extends DefaultMutableTreeNode { ChangesBrowserNode child = (ChangesBrowserNode)enumeration.nextElement(); final Object value = child.getUserObject(); if (clazz.isAssignableFrom(value.getClass())) { + //noinspection unchecked changes.add((T) value); } } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesTreeList.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesTreeList.java index 39ad7fef8ade..8df7b62b159b 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesTreeList.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/changes/ui/ChangesTreeList.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2012 JetBrains s.r.o. + * Copyright 2000-2013 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. @@ -42,11 +42,14 @@ import com.intellij.ui.components.panels.NonOpaquePanel; import com.intellij.ui.treeStructure.Tree; import com.intellij.ui.treeStructure.actions.CollapseAllAction; import com.intellij.ui.treeStructure.actions.ExpandAllAction; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.Convertor; import com.intellij.util.ui.UIUtil; import com.intellij.util.ui.tree.TreeUtil; import com.intellij.util.ui.tree.WideSelectionTreeUI; +import gnu.trove.THashSet; import gnu.trove.TIntArrayList; +import gnu.trove.TIntHashSet; import org.intellij.lang.annotations.JdkConstants; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -114,6 +117,7 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP myTree.setOpaque(false); myTree.setCellRenderer(new MyTreeCellRenderer()); new TreeSpeedSearch(myTree, new Convertor<TreePath, String>() { + @Override public String convert(TreePath o) { ChangesBrowserNode node = (ChangesBrowserNode) o.getLastPathComponent(); return node.getTextPresentation(); @@ -127,6 +131,7 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP add(myTreeScrollPane = ScrollPaneFactory.createScrollPane(myTree), TREE_CARD); new ListSpeedSearch(myList) { + @Override protected String getElementText(Object element) { if (element instanceof Change) { return ChangesUtil.getFilePath((Change)element).getName(); @@ -140,6 +145,7 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP new MyToggleSelectionAction().registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0)), this); if (myShowCheckboxes) { registerKeyboardAction(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { includeSelection(); } @@ -147,6 +153,7 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP }, KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); registerKeyboardAction(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { excludeSelection(); } @@ -154,6 +161,7 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP } registerKeyboardAction(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { myDoubleClickHandler.run(); } @@ -257,6 +265,7 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP return myShowFlatten ? myList : myTree; } + @Override public Dimension getPreferredSize() { return new Dimension(400, 400); } @@ -277,6 +286,7 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP select(wasSelected); if (myList.hasFocus() || myTree.hasFocus()) { SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { requestFocus(); } @@ -285,6 +295,7 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP } + @Override public void requestFocus() { if (myShowFlatten) { myList.requestFocus(); @@ -302,12 +313,15 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP final boolean wasEmpty = myList.isEmpty(); final List<T> sortedChanges = new ArrayList<T>(changes); Collections.sort(sortedChanges, new Comparator<T>() { + @Override public int compare(final T o1, final T o2) { return TreeModelBuilder.getPathForObject(o1).getName().compareToIgnoreCase(TreeModelBuilder.getPathForObject(o2).getName()); } }); - final Set<Object> wasSelected = new HashSet<Object>(Arrays.asList(myList.getSelectedValues())); + @SuppressWarnings("deprecation") + final Set<Object> wasSelected = new THashSet<Object>(Arrays.asList(myList.getSelectedValues())); + //noinspection unchecked myList.setModel(new AbstractListModel() { @Override public int getSize() { @@ -327,6 +341,7 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP } myTree.setModel(model); if (! myAlwaysExpandList && ! wasEmpty) { + //noinspection ConstantConditions state.applyTo(myTree, (DefaultMutableTreeNode) myTree.getModel().getRoot()); final TIntArrayList indices = new TIntArrayList(); @@ -341,6 +356,7 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP } final Runnable runnable = new Runnable() { + @Override public void run() { if (myProject.isDisposed()) return; TreeUtil.expandAll(myTree); @@ -363,6 +379,7 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP while (enumeration.hasMoreElements()) { ChangesBrowserNode node = (ChangesBrowserNode)enumeration.nextElement(); + @SuppressWarnings("unchecked") final CheckboxTree.NodeState state = getNodeStatus(node); if (node != root && state == CheckboxTree.NodeState.CLEAR) { myTree.collapsePath(new TreePath(node.getPath())); @@ -372,6 +389,7 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP enumeration = root.depthFirstEnumeration(); while (enumeration.hasMoreElements()) { ChangesBrowserNode node = (ChangesBrowserNode)enumeration.nextElement(); + @SuppressWarnings("unchecked") final CheckboxTree.NodeState state = getNodeStatus(node); if (state == CheckboxTree.NodeState.FULL && node.isLeaf()) { scrollRow = myTree.getRowForPath(new TreePath(node.getPath())); @@ -471,18 +489,23 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP if (myShowFlatten) { ListModel m = myList.getModel(); int size = m.getSize(); - List result = new ArrayList(size); + List<T> result = new ArrayList<T>(size); for (int i = 0; i < size; i++) { - result.add(m.getElementAt(i)); + //noinspection unchecked + result.add((T)m.getElementAt(i)); } return result; } else { - final LinkedHashSet result = new LinkedHashSet(); + final LinkedHashSet<T> result = new LinkedHashSet<T>(); TreeUtil.traverseDepth((ChangesBrowserNode)myTree.getModel().getRoot(), new TreeUtil.Traverse() { + @Override public boolean accept(Object node) { ChangesBrowserNode changeNode = (ChangesBrowserNode)node; - if (changeNode.isLeaf()) result.addAll(changeNode.getAllChangesUnder()); + if (changeNode.isLeaf()) { + //noinspection unchecked + result.addAll(changeNode.getAllChangesUnder()); + } return true; } }); @@ -501,37 +524,33 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP @NotNull public List<T> getSelectedChanges() { if (myShowFlatten) { - final Object[] o = myList.getSelectedValues(); final List<T> changes = new ArrayList<T>(); - for (Object anO : o) { + //noinspection deprecation + for (Object anO : myList.getSelectedValues()) { + //noinspection unchecked changes.add((T)anO); } - return changes; } else { - final List<T> changes = new ArrayList<T>(); - final Set<Integer> checkSet = new HashSet<Integer>(); final TreePath[] paths = myTree.getSelectionPaths(); - if (paths != null) { + if (paths == null) { + return Collections.emptyList(); + } + else { + final List<T> changes = new ArrayList<T>(); + final TIntHashSet checkSet = new TIntHashSet(); for (TreePath path : paths) { - final ChangesBrowserNode node = (ChangesBrowserNode)path.getLastPathComponent(); - final List<T> objects = getSelectedObjects(node); - for (T object : objects) { - final int hash = object.hashCode(); - if (! checkSet.contains(hash)) { + //noinspection unchecked + List<T> list = getSelectedObjects((ChangesBrowserNode)path.getLastPathComponent()); + for (T object : list) { + if (!checkSet.add(object.hashCode()) || !changes.contains(object)) { changes.add(object); - checkSet.add(hash); - } else { - if (! changes.contains(object)) { - changes.add(object); - } } } } + return changes; } - - return changes; } } @@ -551,7 +570,10 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP } else { final TreePath path = myTree.getSelectionPath(); - if (path == null) return null; + if (path == null) { + return null; + } + //noinspection unchecked return getLeadSelectedObject((ChangesBrowserNode<T>)path.getLastPathComponent()); } } @@ -561,15 +583,13 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP if (myShowFlatten) { final int index = myList.getLeadSelectionIndex(); ListModel listModel = myList.getModel(); - if (index < 0 || index >= listModel.getSize()) return null; //noinspection unchecked - return (T)listModel.getElementAt(index); + return index < 0 || index >= listModel.getSize() ? null : (T)listModel.getElementAt(index); } else { final TreePath path = myTree.getSelectionPath(); - if (path == null) return null; - final List<T> changes = getSelectedObjects(((ChangesBrowserNode<T>)path.getLastPathComponent())); - return changes.size() > 0 ? changes.get(0) : null; + //noinspection unchecked + return path == null ? null : ContainerUtil.getFirstItem(getSelectedObjects(((ChangesBrowserNode<T>)path.getLastPathComponent()))); } } @@ -630,18 +650,20 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP public AnAction[] getTreeActions() { final ToggleShowDirectoriesAction directoriesAction = new ToggleShowDirectoriesAction(); final ExpandAllAction expandAllAction = new ExpandAllAction(myTree) { + @Override public void update(AnActionEvent e) { e.getPresentation().setVisible(!myShowFlatten); } }; final CollapseAllAction collapseAllAction = new CollapseAllAction(myTree) { + @Override public void update(AnActionEvent e) { e.getPresentation().setVisible(!myShowFlatten); } }; final AnAction[] actions = new AnAction[]{directoriesAction, expandAllAction, collapseAllAction}; directoriesAction.registerCustomShortcutSet( - new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_P, SystemInfo.isMac ? KeyEvent.META_DOWN_MASK : KeyEvent.CTRL_DOWN_MASK)), + new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_P, SystemInfo.isMac ? InputEvent.META_DOWN_MASK : InputEvent.CTRL_DOWN_MASK)), this); expandAllAction.registerCustomShortcutSet( new CustomShortcutSet(KeymapManager.getInstance().getActiveKeymap().getShortcuts(IdeActions.ACTION_EXPAND_ALL)), @@ -685,6 +707,7 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP setOpaque(false); } + @Override public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, @@ -706,9 +729,8 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP myTextRenderer.setTransparentIconBackground(true); myTextRenderer.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus); if (myShowCheckboxes) { - ChangesBrowserNode node = (ChangesBrowserNode)value; - - CheckboxTree.NodeState state = getNodeStatus(node); + @SuppressWarnings("unchecked") + CheckboxTree.NodeState state = getNodeStatus((ChangesBrowserNode)value); myCheckBox.setSelected(state != CheckboxTree.NodeState.CLEAR); myCheckBox.setEnabled(state != CheckboxTree.NodeState.PARTIAL); revalidate(); @@ -808,9 +830,11 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP add(myTextRenderer, BorderLayout.CENTER); } + @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { myTextRenderer.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); if (myShowCheckboxes) { + //noinspection SuspiciousMethodCalls myCheckbox.setSelected(myIncludedChanges.contains(value)); return this; } @@ -821,6 +845,7 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP } private class MyToggleSelectionAction extends AnAction { + @Override public void actionPerformed(AnActionEvent e) { toggleSelection(); } @@ -833,10 +858,12 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP AllIcons.Actions.GroupByPackage); } + @Override public boolean isSelected(AnActionEvent e) { return (! myProject.isDisposed()) && !PropertiesComponent.getInstance(myProject).isTrueValue(FLATTEN_OPTION_KEY); } + @Override public void setSelected(AnActionEvent e, boolean state) { PropertiesComponent.getInstance(myProject).setValue(FLATTEN_OPTION_KEY, String.valueOf(!state)); setShowFlatten(!state); @@ -848,6 +875,7 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP super("Select All", "Select all items", AllIcons.Actions.Selectall); } + @Override public void actionPerformed(final AnActionEvent e) { if (myShowFlatten) { final int count = myList.getModel().getSize(); @@ -869,7 +897,9 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP final TreeNode root = (TreeNode) treeModel.getRoot(); final List<TreePath> treeSelection = new ArrayList<TreePath>(changes.size()); TreeUtil.traverse(root, new TreeUtil.Traverse() { + @Override public boolean accept(Object node) { + @SuppressWarnings("unchecked") final T change = (T) ((DefaultMutableTreeNode) node).getUserObject(); if (changes.contains(change)) { treeSelection.add(new TreePath(((DefaultMutableTreeNode) node).getPath())); @@ -884,6 +914,7 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP final int size = model.getSize(); final List<Integer> listSelection = new ArrayList<Integer>(changes.size()); for (int i = 0; i < size; i++) { + @SuppressWarnings("unchecked") final T el = (T) model.getElementAt(i); if (changes.contains(el)) { listSelection.add(i); @@ -925,7 +956,7 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP private final int myCheckboxWidth; public MyTree(Project project, int checkboxWidth) { - super(ChangesBrowserNode.create(ChangesTreeList.this.myProject, ChangesTreeList.ROOT)); + super(ChangesBrowserNode.create(ChangesTreeList.this.myProject, ROOT)); myProject = project; myCheckboxWidth = checkboxWidth; } @@ -959,12 +990,14 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP return super.getFileColorFor(object); } + @Override public Dimension getPreferredScrollableViewportSize() { Dimension size = super.getPreferredScrollableViewportSize(); size = new Dimension(size.width + 10, size.height); return size; } + @Override protected void processMouseEvent(MouseEvent e) { if (e.getID() == MouseEvent.MOUSE_PRESSED) { if (! myTree.isEnabled()) return; @@ -981,6 +1014,7 @@ public abstract class ChangesTreeList<T> extends JPanel implements TypeSafeDataP super.processMouseEvent(e); } + @Override public int getToggleClickCount() { return -1; } diff --git a/platform/vcs-log/impl/src/META-INF/vcs-log.xml b/platform/vcs-log/impl/src/META-INF/vcs-log.xml index c686d5a21001..8058a4a919c8 100644 --- a/platform/vcs-log/impl/src/META-INF/vcs-log.xml +++ b/platform/vcs-log/impl/src/META-INF/vcs-log.xml @@ -6,6 +6,9 @@ </component> </project-components> + <extensionPoints> + <extensionPoint name="logProvider" interface="com.intellij.vcs.log.VcsLogProvider" area="IDEA_PROJECT"/> + </extensionPoints> <extensions defaultExtensionNs="com.intellij"> <applicationService serviceInterface="com.intellij.vcs.log.VcsLogObjectsFactory" serviceImplementation="com.intellij.vcs.log.impl.VcsLogObjectsFactoryImpl" /> diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/actions/handlers/XDebuggerEvaluateActionHandler.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/actions/handlers/XDebuggerEvaluateActionHandler.java index 59dfa172a349..d892acaf0f44 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/actions/handlers/XDebuggerEvaluateActionHandler.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/actions/handlers/XDebuggerEvaluateActionHandler.java @@ -22,6 +22,7 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.xdebugger.XDebugSession; import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider; import com.intellij.xdebugger.evaluation.XDebuggerEvaluator; @@ -37,6 +38,7 @@ import org.jetbrains.annotations.Nullable; * @author nik */ public class XDebuggerEvaluateActionHandler extends XDebuggerSuspendedActionHandler { + @Override protected void perform(@NotNull final XDebugSession session, final DataContext dataContext) { XDebuggerEditorsProvider editorsProvider = session.getDebugProcess().getEditorsProvider(); XStackFrame stackFrame = session.getCurrentStackFrame(); @@ -44,7 +46,6 @@ public class XDebuggerEvaluateActionHandler extends XDebuggerSuspendedActionHand final XDebuggerEvaluator evaluator = stackFrame.getEvaluator(); if (evaluator == null) return; - @Nullable Project project = CommonDataKeys.PROJECT.getData(dataContext); @Nullable Editor editor = CommonDataKeys.EDITOR.getData(dataContext); String selectedText = editor != null ? editor.getSelectionModel().getSelectedText() : null; @@ -54,7 +55,7 @@ public class XDebuggerEvaluateActionHandler extends XDebuggerSuspendedActionHand String text = selectedText; if (text == null && editor != null) { - text = getExpressionText(evaluator, project, editor); + text = getExpressionText(evaluator, CommonDataKeys.PROJECT.getData(dataContext), editor); } if (text == null) { @@ -63,10 +64,7 @@ public class XDebuggerEvaluateActionHandler extends XDebuggerSuspendedActionHand text = value.getEvaluationExpression(); } } - if (text == null) { - text = ""; - } - new XDebuggerEvaluationDialog(session, editorsProvider, evaluator, text, stackFrame.getSourcePosition()).show(); + new XDebuggerEvaluationDialog(session, editorsProvider, evaluator, StringUtil.notNullize(text), stackFrame.getSourcePosition()).show(); } @Nullable @@ -86,6 +84,7 @@ public class XDebuggerEvaluateActionHandler extends XDebuggerSuspendedActionHand return expressionInfo.second == null ? document.getText(expressionInfo.first) : expressionInfo.second; } + @Override protected boolean isEnabled(final @NotNull XDebugSession session, final DataContext dataContext) { if (!super.isEnabled(session, dataContext)) { return false; diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/evaluate/XDebuggerEvaluationDialog.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/evaluate/XDebuggerEvaluationDialog.java index a9ccf6995936..741b2878e8b2 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/evaluate/XDebuggerEvaluationDialog.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/evaluate/XDebuggerEvaluationDialog.java @@ -29,12 +29,14 @@ import com.intellij.xdebugger.XSourcePosition; import com.intellij.xdebugger.evaluation.EvaluationMode; import com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider; import com.intellij.xdebugger.evaluation.XDebuggerEvaluator; +import com.intellij.xdebugger.frame.XStackFrame; import com.intellij.xdebugger.impl.XDebugSessionImpl; import com.intellij.xdebugger.impl.actions.XDebuggerActions; import com.intellij.xdebugger.impl.ui.XDebuggerEditorBase; import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree; import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreePanel; import com.intellij.xdebugger.impl.ui.tree.nodes.EvaluatingExpressionRootNode; +import com.intellij.xdebugger.impl.ui.tree.nodes.XDebuggerTreeNode; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -51,13 +53,12 @@ public class XDebuggerEvaluationDialog extends DialogWrapper { private final JPanel myResultPanel; private final XDebuggerTreePanel myTreePanel; private EvaluationInputComponent myInputComponent; - private final XDebuggerEvaluator myEvaluator; private final XDebugSession mySession; private final XDebuggerEditorsProvider myEditorsProvider; private EvaluationMode myMode; private final XSourcePosition mySourcePosition; private final SwitchModeAction mySwitchModeAction; - private final XDebugSessionAdapter mySessionListener; + private final boolean myIsCodeFragmentEvaluationSupported; public XDebuggerEvaluationDialog(@NotNull XDebugSession session, final @NotNull XDebuggerEditorsProvider editorsProvider, @@ -72,7 +73,7 @@ public class XDebuggerEvaluationDialog extends DialogWrapper { setOKButtonText(XDebuggerBundle.message("xdebugger.button.evaluate")); setCancelButtonText(XDebuggerBundle.message("xdebugger.evaluate.dialog.close")); - mySessionListener = new XDebugSessionAdapter() { + mySession.addSessionListener(new XDebugSessionAdapter() { @Override public void sessionStopped() { SwingUtilities.invokeLater(new Runnable() { @@ -82,15 +83,13 @@ public class XDebuggerEvaluationDialog extends DialogWrapper { } }); } - }; - mySession.addSessionListener(mySessionListener, myDisposable); + }, myDisposable); myTreePanel = new XDebuggerTreePanel(session.getProject(), editorsProvider, myDisposable, sourcePosition, XDebuggerActions.EVALUATE_DIALOG_TREE_POPUP_GROUP, ((XDebugSessionImpl)session).getValueMarkers()); myResultPanel = new JPanel(new BorderLayout()); myResultPanel.add(new JLabel(XDebuggerBundle.message("xdebugger.evaluate.label.result")), BorderLayout.NORTH); myResultPanel.add(myTreePanel.getMainPanel(), BorderLayout.CENTER); - myEvaluator = evaluator; myMainPanel = new JPanel(new BorderLayout()); mySwitchModeAction = new SwitchModeAction(); @@ -109,8 +108,9 @@ public class XDebuggerEvaluationDialog extends DialogWrapper { }.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.ALT_DOWN_MASK)), getRootPane(), myDisposable); EvaluationMode mode = EvaluationMode.EXPRESSION; + myIsCodeFragmentEvaluationSupported = evaluator.isCodeFragmentEvaluationSupported(); if (text.indexOf('\n') != -1) { - if (myEvaluator.isCodeFragmentEvaluationSupported()) { + if (myIsCodeFragmentEvaluationSupported) { mode = EvaluationMode.CODE_FRAGMENT; } else { @@ -129,7 +129,7 @@ public class XDebuggerEvaluationDialog extends DialogWrapper { @NotNull @Override protected Action[] createActions() { - if (myEvaluator.isCodeFragmentEvaluationSupported()) { + if (myIsCodeFragmentEvaluationSupported) { return new Action[]{getOKAction(), mySwitchModeAction, getCancelAction()}; } return super.createActions(); @@ -186,8 +186,13 @@ public class XDebuggerEvaluationDialog extends DialogWrapper { private void evaluate() { final XDebuggerTree tree = myTreePanel.getTree(); - final EvaluatingExpressionRootNode root = new EvaluatingExpressionRootNode(this, tree); - tree.setRoot(root, false); + XDebuggerTreeNode root = tree.getRoot(); + if (root instanceof EvaluatingExpressionRootNode) { + root.clearChildren(); + } + else { + tree.setRoot(new EvaluatingExpressionRootNode(this, tree), false); + } myResultPanel.invalidate(); myInputComponent.getInputEditor().selectAll(); } @@ -206,7 +211,15 @@ public class XDebuggerEvaluationDialog extends DialogWrapper { final XDebuggerEditorBase inputEditor = myInputComponent.getInputEditor(); inputEditor.saveTextInHistory(); String expression = inputEditor.getText(); - myEvaluator.evaluate(expression, evaluationCallback, null, inputEditor.getMode()); + + XStackFrame frame = mySession.getCurrentStackFrame(); + XDebuggerEvaluator evaluator = frame == null ? null : frame.getEvaluator(); + if (evaluator == null) { + evaluationCallback.errorOccurred(XDebuggerBundle.message("xdebugger.evaluate.stack.frame.has.not.evaluator")); + } + else { + evaluator.evaluate(expression, evaluationCallback, null, inputEditor.getMode()); + } } @Override diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/DebuggerUIUtil.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/DebuggerUIUtil.java index 4a40d8a80c39..4a8011a8f808 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/DebuggerUIUtil.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/DebuggerUIUtil.java @@ -18,15 +18,21 @@ package com.intellij.xdebugger.impl.ui; import com.intellij.codeInsight.hint.HintUtil; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorFactory; import com.intellij.openapi.editor.LogicalPosition; +import com.intellij.openapi.fileTypes.FileTypes; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.popup.*; +import com.intellij.openapi.ui.popup.Balloon; +import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.ui.popup.JBPopupListener; +import com.intellij.openapi.ui.popup.LightweightWindowEvent; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.DimensionService; import com.intellij.openapi.util.Ref; import com.intellij.openapi.wm.IdeFocusManager; import com.intellij.openapi.wm.WindowManager; import com.intellij.ui.AppUIUtil; +import com.intellij.ui.EditorTextField; import com.intellij.ui.ScrollPaneFactory; import com.intellij.ui.awt.RelativePoint; import com.intellij.xdebugger.XDebuggerManager; @@ -102,12 +108,11 @@ public class DebuggerUIUtil { } public static void showValuePopup(@NotNull XFullValueEvaluator text, @NotNull MouseEvent event, @NotNull Project project) { - final JTextArea textArea = new JTextArea("Evaluating..."); + EditorTextField textArea = new EditorTextField(EditorFactory.getInstance().createDocument("Evaluating..."), project, FileTypes.PLAIN_TEXT, true); + textArea.setBackground(HintUtil.INFORMATION_COLOR); + final FullValueEvaluationCallbackImpl callback = new FullValueEvaluationCallbackImpl(textArea); text.startEvaluation(callback); - textArea.setEditable(false); - textArea.setBackground(HintUtil.INFORMATION_COLOR); - textArea.setLineWrap(false); final JScrollPane component = ScrollPaneFactory.createScrollPane(textArea); final Dimension frameSize = WindowManager.getInstance().getFrame(project).getSize(); @@ -119,7 +124,7 @@ public class DebuggerUIUtil { component.setPreferredSize(size); component.setBorder(null); - final JBPopup popup = JBPopupFactory.getInstance().createComponentPopupBuilder(component, null) + JBPopupFactory.getInstance().createComponentPopupBuilder(component, null) .setResizable(true) .setMovable(true) .setDimensionServiceKey(project, FULL_VALUE_POPUP_DIMENSION_KEY, false) @@ -131,10 +136,7 @@ public class DebuggerUIUtil { return true; } }) - .createPopup(); - final Component parentComponent = event.getComponent(); - RelativePoint point = new RelativePoint(parentComponent, new Point(event.getX()-size.width, event.getY()-size.height)); - popup.show(point); + .createPopup().show(new RelativePoint(event.getComponent(), new Point(event.getX() - size.width, event.getY() - size.height))); } public static void showXBreakpointEditorBalloon(final Project project, @@ -265,9 +267,9 @@ public class DebuggerUIUtil { private static class FullValueEvaluationCallbackImpl implements XFullValueEvaluator.XFullValueEvaluationCallback { private final AtomicBoolean myObsolete = new AtomicBoolean(false); - private final JTextArea myTextArea; + private final EditorTextField myTextArea; - public FullValueEvaluationCallbackImpl(final JTextArea textArea) { + public FullValueEvaluationCallbackImpl(final EditorTextField textArea) { myTextArea = textArea; } @@ -285,7 +287,7 @@ public class DebuggerUIUtil { if (font != null) { myTextArea.setFont(font); } - myTextArea.setCaretPosition(0); + myTextArea.getCaretModel().moveToOffset(0); } }); } diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/nodes/EvaluatingExpressionRootNode.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/nodes/EvaluatingExpressionRootNode.java index ed35a48195d5..379b07c5426a 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/nodes/EvaluatingExpressionRootNode.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/nodes/EvaluatingExpressionRootNode.java @@ -23,39 +23,70 @@ import com.intellij.xdebugger.frame.XValueChildrenList; import com.intellij.xdebugger.frame.XValueContainer; import com.intellij.xdebugger.impl.evaluate.XDebuggerEvaluationDialog; import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree; +import com.intellij.xdebugger.impl.ui.tree.XDebuggerTreeListener; import org.jetbrains.annotations.NotNull; +import java.util.List; + /** * @author nik */ public class EvaluatingExpressionRootNode extends XValueContainerNode<EvaluatingExpressionRootNode.EvaluatingResultContainer> { public EvaluatingExpressionRootNode(XDebuggerEvaluationDialog evaluationDialog, final XDebuggerTree tree) { - super(tree, null, new EvaluatingResultContainer(evaluationDialog)); + super(tree, null, new EvaluatingResultContainer(evaluationDialog, tree)); setLeaf(false); } + @Override protected MessageTreeNode createLoadingMessageNode() { return MessageTreeNode.createEvaluatingMessage(myTree, this); } - public static class EvaluatingResultContainer extends XValueContainer { + public static class EvaluatingResultContainer extends XValueContainer implements XDebuggerTreeListener { private final XDebuggerEvaluationDialog myDialog; + private XDebuggerTree myTree; - public EvaluatingResultContainer(final XDebuggerEvaluationDialog dialog) { + public EvaluatingResultContainer(final XDebuggerEvaluationDialog dialog, XDebuggerTree tree) { myDialog = dialog; + myTree = tree; } + @Override public void computeChildren(@NotNull final XCompositeNode node) { myDialog.startEvaluation(new XEvaluationCallbackBase() { + @Override public void evaluated(@NotNull final XValue result) { + myTree.addTreeListener(EvaluatingResultContainer.this); String name = UIUtil.removeMnemonic(XDebuggerBundle.message("xdebugger.evaluate.result")); node.addChildren(XValueChildrenList.singleton(name, result), true); } + @Override public void errorOccurred(@NotNull final String errorMessage) { node.setErrorMessage(errorMessage); } }); } + + @Override + public void nodeLoaded(@NotNull RestorableStateNode node, String name) { + if (node.getParent() instanceof EvaluatingExpressionRootNode) { + if (node.isLeaf()) { + myTree.removeTreeListener(this); + } + else { + // cause children computing + node.getChildCount(); + } + } + } + + @Override + public void childrenLoaded(@NotNull XDebuggerTreeNode node, @NotNull List<XValueContainerNode<?>> children, boolean last) { + if (node.getParent() instanceof EvaluatingExpressionRootNode) { + myTree.removeTreeListener(this); + myTree.expandPath(node.getPath()); + } + } } } diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/nodes/MessageTreeNode.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/nodes/MessageTreeNode.java index 8f128132e732..a70c72bbecef 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/nodes/MessageTreeNode.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/nodes/MessageTreeNode.java @@ -108,7 +108,7 @@ public class MessageTreeNode extends XDebuggerTreeNode { XDebuggerUIConstants.COLLECTING_DATA_HIGHLIGHT_ATTRIBUTES, null); } - public static MessageTreeNode createEvaluatingMessage(XDebuggerTree tree, final XDebuggerTreeNode parent) { + public static MessageTreeNode createEvaluatingMessage(XDebuggerTree tree, @Nullable XDebuggerTreeNode parent) { return new MessageTreeNode(tree, parent, XDebuggerUIConstants.EVALUATING_EXPRESSION_MESSAGE, XDebuggerUIConstants.EVALUATING_EXPRESSION_HIGHLIGHT_ATTRIBUTES, null); } diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/nodes/XValueNodeImpl.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/nodes/XValueNodeImpl.java index 5ba65e57335f..75f927ee91f1 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/nodes/XValueNodeImpl.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/ui/tree/nodes/XValueNodeImpl.java @@ -56,7 +56,7 @@ public class XValueNodeImpl extends XValueContainerNode<XValue> implements XValu private XValuePresentation myValuePresentation; //todo[nik] annotate 'name' with @NotNull - public XValueNodeImpl(XDebuggerTree tree, XDebuggerTreeNode parent, String name, @NotNull XValue value) { + public XValueNodeImpl(XDebuggerTree tree, @Nullable XDebuggerTreeNode parent, String name, @NotNull XValue value) { super(tree, parent, value); myName = name; @@ -159,6 +159,7 @@ public class XValueNodeImpl extends XValueContainerNode<XValue> implements XValu valuePresenter.renderValue(new XValueTextRendererImpl(text)); } + @Override public void markChanged() { if (myChanged) return; |