Community
Participate
Working Groups
Build 2.1 Once a shared working copy is created, it would be used in place of its original element accross the Java model. E.g. asking for the children of a pkg fragment would return a wrapper on each compilation unit: this wrapper would query the compilation unit if no shared working copy exists, it would use the shared working copy otherwise.
Posted on newsgroup Are there any client using shared working copies outside JDT/UI ? These working copies are meant to be reused in multiple spots (e.g. several editors in different windows) and thus provide some notion of sharing. This mechanism allows all editors pointing at the same file to be updated simultaneously when edits occur, since the underlying working copy and associated buffer is the same. Moving towards 3.0, shared working copies seem to be still hard to manage, and bug report http://bugs.eclipse.org/bugs/show_bug.cgi?id=36888 expresses the need to make these 'special' working copies being edited more transparent. A typical situation causing grief is the package explorer view, which needs to switch a child element from a compilation unit to its corresponding shared working copy, as soon as it gets open in an editor. These items aren't identical, and what they'd like instead is to have a unique handle onto the unit, which would be smart enough to understand it becomes edited and thus would switch to a working copy like behavior from thereon. Currently, shared working copies are associated with a buffer factory, which is contributed by clients who care about these working copies. This mechanism allows to have each client manage its own set of shared working copies. If moving away from these, and rather considering some smarter and more transparent compilation units instead, then naturally we wonder whether we should preserve supporting multiple clients or not. If all clients are only interested in sharing the edited unit contents anyway, the multiple clients story is just unnecessary flexibility which will likely cause major API changes so as to provide the client viewpoint information when necessary. Please answer the PR directly, so that the discussion gets archived.
One thing to remember is that the buffer implementation for an edited unit is still customizable. This allows the client to reuse its underlying buffer implementation and just adapt it to our APIs. This should be still preserved, and if loosing the multi-client approach, this would mean than once starting editing a unit, the first client who contributed a buffer implementation is winning. If we believe there is only one client anyway, this may not be an issue. If we want to enable multiple clients, then each client can contribute its own buffer implementation, but all accesses need to mention explicitely which client is doing the work, since there would be no privileged default client. i.e. all operations which can return IType elements would need to specify the client to consider so as to transparently reach the edited type. This would likely clutter lots of APIs.
My suggestion is to eliminate the two kind of element showing up in UI by introducing a new adapting Compilation Unit. -Viewers currently have to listen to deltas to know when to replace elements -Actions have to be aware that elements retrieved in selectionChanged can be different to the elements available in 'run' (they have to retrieve the elements again). When making modifications they must make sure that either an editor is opened on the element (so the modification is done on a working copy) or have to create and (when done) save a working copy. The approach I want to suggest tries to be as non-breaking as possible. We introduce a new adapting compilation unit which hides the two worlds (original elements and shared working copy elements). All viewers now only show these elements. The suggestion is to not change the control of the life-cycle of the shared working copy: Creating and saving it is still controlled by the Java editor, or, by the action. ICompilationUnit.getAdaptingCompilationUnit(IBufferManager) : ICompilationUnit returns a IComilationUnit that is from now on the compilation unit shown by viewers. This adapting compilation unit can be requested at any time, regardless if the original element has a shared working copy or not. The adapting compilation unit and its children internally switch the element infos between original and shared working copy, depending on the creation/release of the shared working copy. - If no shared working copy exists: Adapting compilation unit internally points to the buffer of the original - If a shared working copy exists: Adapting compilation unit internally points to the buffer of the shared working copy A Java element delta is offered for this adapting compilation unit that is equal to the shared working copy delta when a shared working copy exists or the original delta when no shared working copy exists. The advantage of this solution is that viewers can now show the adapting working copy instead of the original and don't have to be aware of editor creations/close. They now only have to listen to one delta. When calling a create method (e.g. createMethod) on a adapting element - If no shared working copy exists: Modification is done on the original (includes a save, resource modification) - If a shared working copy exists: Modification is done on the shared working copy (no save) The switch from original to working copy is still performed through a call of ICompilationUnit.getSharedWorkingCopy (called on the original) or on IWorkingCopy.release() (called on the shared working copy) Original elements and shared working copy elements still are available and have the same role in the system. Views written for 2.0, tracking the switching from original to working copy still can do so. For compatibility reasons, Actions have to be ready for getting original or shared working copies, but can convert them once ('toAdaptingElement') and then stick to them. An adapting working copy is parameterized with the BufferManager. There should be no memory overhead with the adapted elements: - Only new handles are created. No new element infos - If all views are rewritten for the new elements, we even reduce the number of created handles as we have now only one world instead of two.
Suggestion b.) is a bit more breaking. The idea is to merge the adapting compilation unit suggested in comment #3 into the existing 'shared working copy': A shared working copy has a reference count which is increased when getSharedWorkingCopy is called and decreased on 'destroy'. When the counter is 0, the shared working copy is destroyed: it does not exist anymore and all accesses to its buffer or members throw a JavaModelException (viewer have to switch to originals). The suggestion is to allow a reference count of 0. A shared working copy can be requested even if the reference count is 0. In this case the shared working copy is equal to the original and uses the buffer/infos of the original (...adapts to the original...) As soon as the reference count goes from 0 to 1, all its infos are flushed and the new buffer is used. When the count changes from 1 to 0, again all infos are flushed. This new behavior only applies to the working copies marked as 'shared' working copy. The delta created for the shared working copy has to be augmented. Having this change, all views can now directly show the 'shared working copy' and only have to listen to the shared working copy delta. There is no memory overhead; no buffers/new infos are created until an editor is created.
I agree with the fact that a separate layer (adapting units in Martin's comment) allow to still reach the original elements. However, these require a new kind of elements, and APIs for them as well. There would not be much transparency, except that the handle onto these entities would be stable. But, in order to reach these, new APIs have to be defined so as to pass along the IBufferFactory, which identifies the client. Also, if nobody else is using shared working copies, I'd rather discard them alltogether, instead of maintaining yet another story which nobody uses.
We would do the following: - define IEditableCompilationUnit (prefer this to adapting unit) - change IWorkingCopy to subclass ICompilationUnit (instead of the opposite currently) - have IEditableCompilationUnit implements IWorkingCopy and have the characteristics of a shared working copy (as of today) which would map to a compilation unit contents when nobody is editing it. We are trying to measure the API breakage.
Entered separate bug 36876 for working copy hierarchy issue.
Current thinking is to name those new unit handles IWorkingCopyAdapter (since they adapt units to working copies transparently). Then in order to switch it to a working copy (when starting editing it), an API would be used: #becomeWorkingCopy(). From thereon, it would naturally behave as a working copy (#destroy, #commit). How does this sound to clients ?
Working copy hierarchy issue is bug 36987.
Sounds good to me. Does the EditableCompilationUnit still contain a counter like the shared working copies do? I think they should otherwise client code becomes quite tricky as the client needs to remember who created the EditableCompilationUnit.
The presented interface sounds good to me as well (given Martin's comments). Thinking about the IEditableCompilationUnit I would now propose that manipulating an original working copy should be forbidden (if the breaking is not to big). This would mean a client must always create a IEditableCompilationUnit, apply the changes to this object and then commit them back to the original. What do you think ?
Yes, an IWorkingCopyAdapter would have a counter: calling becomeWorkingCopy() would increment it, and calling destroy() would decrement it. Note we prefer to call it IWorkingCopyAdapter (instead of IEditableCompilationUnit) as this doesn't imply that the client is a UI client.
Dirk, I'm not sure I understand the advantage of your proposal.
Cleaner model (we are working this way anyway as far as I know). And it might give us some advantages when doing more stuff in parallel. Then we know that the original CU is "particial" read-only (except for the case of a commit of an IWorkingCopyAdapter).
To do stuff in parallel (I guess you meant that you want to create several IWorkingCopyAdapaters on a given compilation unit), do you agree that you need to pass some sort of client ID to all the Java model operations so that the right IWorkingCopyAdapter would be returned by the Java model operation? I'm thinking about refactoring here: you first need to search in the working copies corresponding to open editors (client ID for the search operation would be "Java editor"), then you need to create new working copies that contain the potential changes and resolve in this new working copies (client ID for the resolve operation would be "Java refactoring").
To Dirk's last comment: maybe we could add notion of read-only-ness for working copy adapters which aren't in working copy mode. As said Jerome, last thinking also surfaces the notion of an IWorkingCopyClient, denoting the one who requested this working copy creation, and may care about it later on (same idea as the buffer factory in the past). The client would still be used to find the buffer to use, and could be asked other questions like a clientID which we could use to serialize such handles (mementos). In order to step further, we want to also support the fact that operations which perform resolution against other units, have a way to indicate that they prefer to use their working copies in place of underlying units. This would allow for instance reconciling against other editor contents, whithout requiring to save buffer contents. This desire has been expressed in various other bug reports, and this means that we would need to pass along a reference onto the client in quite a number of APIs. One more thing. We anticipate that some clients may want to be aware of each other, e.g. refactoring client may want to provide working copies based on edited units (as opposed to forcing editor contents to be saved before proceeding). We would thus allow some client composition to be achieved through some delegation, so that the editor buffer can be reused by refactoring automatically. Is this need any real, or just extra unnecessary complexity ? (this wouldn't be another API breakage, just extra flexibility). Last, regular working copies would also benefit from this client approach. WC adapters would only have the transparency mode as an extra behavior.
Let's just have one dedicated BufferManager, describing the IWorkingCopyAdapaters world. A UI less text infrastructure plugin (resources.text) should define this buffer manager. The client IDs could come into the game when creating working copies of a IWorkingCopyAdapaters.
What would the BufferManager API look like?
The notion of buffer manager suggests some caching strategy. A client sounds more generic, and can be requested to create a buffer (which we can cache later on, as we do currently). We may ask it a few more questions down the road than just the buffer (its mementoID for instance). I also think it should be an abstract type, so that it can later be augmented in term of APIs in a non-breaking way.
Regarding comment #16: this is a real need, but another solution would be (this is how it works right now): 1. collect the set of effected compilation units using IWorkingCopyAdapters. 2. for precondition checking we create a new set of working copies form the IWorkingCopyAdapters from step 1. We change those working copies and do the analysis. 3. apply the changes to the IWorkingCopyAdapters if 2. was successful. So from refactoring prespective there is no need for a client id in the IWorkingCopyAdapter world.
From bug 36232, I understand you want to take the IWorkingCopyAdapters into account during the analysis in step 2. To do so, you would need to pass all the IWorkingCopyAdapters to the resolve operation, but a simpler solution would be to pass the client ID (that we also call IWorkingCopyClient) to the resolve operation, don't you think?
Jerome, you are right. A client id would make life easier in the refactoring world. Currently we manage the "virtual world" by our own.
Just some thought. Given most APIs would need to be revisited to pass an extra argument denoting the client (or working copies instead). Wouldn't it be simpler to set the default client at a convenient location, and then use this one from thereon ? A convenient location for defining this would be JavaModel batch operation.
Even further thinking... We could unify compilation units and working copies into one entity. Pretty much our working copy adapter would be promoted as true compilation units. Some global API would allow clients to check-in/out, and from thereon all our APIs would answer results specific to this implicit client. e.g. when JDT/UI starts up, it would register its editor client, and subsequent use of our APIs would transparently reflect the editor contents. Other clients would not be able to tell the difference in between an edited unit and a unit on filesystem (except if using #isWorkingCopy()). We could imagine to allow client layers. For instance, refactoring would usually want to perform its modification starting from editor contents. Thus, it could register a refactoring client which would provide this new layer. The UI client would still be alive underneath. Then, when refactoring creates its working copies, they would be associated with the buffer contents implicitly obtained from the enclosing client (UI). Multiple clients would still be supported, and could be nested in each other. Consequently, separate working copies would not longer be necessary. Regular units would intrinsically support their functionalities once becoming such. In term of API, this means less changes than previous approaches, except that IWorkingCopy would disappear alltogether.
So the new proposal is as follow: 1. IWorkingCopy disapears. Clients will manipulate ICompilationUnits only. 2. An ICompilationUnit handle is still implicetly defined by its path, but also by a CompilationUnitOwner. So 2 ICompilationUnits on the same IResource are equal only if their owners are equal. 3. A run operation (tentatively on the abstract class CompilationUnitOwner) will define which is the current owner. During this run operation, created ICompilationUnit handles (e.g. through IPackageFragment.getCompilationUnits()) will have the current owner. This run operation will also allow owner nesting: the ICompilationUnit handle will be created with the inner most owner. 4. For a given owner, an ICompilationUnit can have 2 modes: a file mode and a working copy mode. In file mode (mode by default), operations on the ICompilationUnit handle use a default buffer populated from the underlying IFile. In working copy mode, operations on the ICompilationUnit handle use a buffer created by the owner. This replaces IBufferFactory.createBuffer(...). 5. An ICompilationUnit in file mode becomes an ICompilationUnit in working copy mode using the becomeWorkingCopy() protocol. It switches back to file mode using the discardWorkingCopy() protocol. An internal counter is maintained so that the same number of calls to discardWorkingCopy() as to becomeWorkingCopy() must be done before the ICompilationUnit switches back to file mode. 6. In working copy mode only, commitWorkingCopy() allows to save the content of the buffer to the underlying IFile. 7. In addition to the createBuffer(...) protocol, a CompilationUnitOwner also provides protocols to pass in an IProblemRequestor used to report problems in working copy mode and a owner ID used to create a memento of the ICompilationUnit. 8. If no client is specified (i.e. the ICompilationUnit has been created outside the CompilationUnitOwner run operation), a default CompilationUnitOwner will be used and attempting to switch the ICompilationUnit in working copy mode will be refused. 9. As several clients may create ICompilationUnit handles on the same IFile but with different owners, they will need to pass in the owner they're interested in when registring for Java element changes. Thus the IJavaElementDelta will contain ICompilationUnit handles for a given owner. 10. The following protocols that were on IWorkingCopy will be moved to ICompilationUnit: - findElements(IJavaElement) - findPrimaryType() - getOriginal(IJavaElement) - getOriginalElement() - isBasedOn(IResource) - isWorkingCopy() - reconcile() - reconcile(boolean, IProgressMonitor) - restore() Thus all protocols having to do with the creation of working copies (getWorkingCopy(...), getSharedWorkingCopy(...), etc.)will disapear. becomeWorkingCopy() will play their role. destroy() will be replaced by discardWorkingCopy() 11. The protocols for creating a hierarchy (e.g. IType.newTypeHierarchy(IWorkingCopy[], ...) or for searching (e.g. SearchEngine(IWorkingCopy[]) will disapear. Clients would use a CompilationUnitOwner run operation instead. We expect to first deprecate IWorkingCopy and all protocols described above to make the transition easier. Comments are welcome.
Not yet addressed, but on the list: 12- some mechanism for delegating amongst owners should be proposed. For instance, refactoring may be interested in getting UI (editor) client buffer contents somewhat transparently. 13- we should no longer support editing directly compilation units, but only working copies (some JDOM operations currently allow modifying units in file mode).
Created attachment 4874 [details] code example To understand what point 3 would mean for the client code I tried to create an example content provider as well as an example action: If I understood right, all accesses to the Java model have to be done in a runnable: So whenever Java Core needs the current CompilationUnitOwner it can find it in the call stack. I assumed that a runnable is required for all accesses to the Java model, not just packageFragment.getCompilationUnit()
Problems to be mentioned: - It's hard to pass return values and thrown exceptions out of a runnable - What happens if an JDT action gets a ICompilation that is not from us? What if it gets a mixed selection? - What happens if you access a ICompilation of an other owner (in your own runnable) - All JDT UI code has to be rewritten, including client code that wants to work with 'our' compilation units - Everybody who wants to work in 'our' world has to get the JDTCompilationUnitOwner. If it is defined in JDT-UI, all clients need to know JDT-UI
> I assumed that a runnable is required for all accesses to the Java model, > not just packageFragment.getCompilationUnit() You need the runnable only when creating a handle for a compilation unit (or inside a compilation unit). Handles for IPackageFragment, IJavaProject, etc don't need to be in a runnable. > It's hard to pass return values and thrown exceptions out of a runnable The only other alternative would be to pass the CompilationUnitOwner to all Java model APIs that may create a ICompilationUnit handle. That would be too many. > What happens if an JDT action gets a ICompilation that is not from us? What > if it gets a mixed selection? Same as would happen if it got an IWorkingCopy that was not from you. > What happens if you access a ICompilation of an other owner (in your own > runnable) It would behave normally (as if you would access an IWorkingCopy that you didn't create) > All JDT UI code has to be rewritten, including client code that wants to work > with 'our' compilation units What do you mean by 'rewritten'? > Everybody who wants to work in 'our' world has to get the > JDTCompilationUnitOwner. If it is defined in JDT-UI, all clients need to know > JDT-UI Right
The proposal suggests not to code any working copy / owner information into the handle. Currently markers do not allow objects other than Boolean, String and Integer to be stored in them therefore we store the handle identifier (IJavaElement.getHandleIdentifier()) in the marker. Currently when accessing the marker later we have ugly code which tries to figure out whether the handle identifier belongs to a (still existing) working copy or to a compilation unit. Am I right that such code will still be necessary with you proposal?
Funny, we just talked about it. The idea would be that you would call JavaCore.create(String) in a runnable on your CompilationUnitOwner. The resulting ICompilationUnit handle would have your CompilationUnitOwner. It would then know if it is in working copy mode or in file mode.
I think the example code shows that forcing the client to do most accesses to JavaModel in a runnable are very clumbsy. Of course I agree with Jerome than adding a CompilationUnitOwner to most of the JavaModel API is scary too. So here is another proposal, based on the latest, tring to simplify the model. 8*. If no client is specified (i.e. the ICompilationUnit has been created outside the CompilationUnitOwner run operation), a default CompilationUnitOwner is used which describes the world as seen by the JDT editors and views. Working copy mode is supported. This Primary CompilationUnitOwner is created and owned by JDT-Core on plugin startup using a text-buffer infrastructure offered by UI-independend plugin org.eclipse.platform.text (to be implemented). 12*. Client created CompilationUnitOwners are based on a parent CompilationUnitOwner (e.g. Primary CompilationUnitOwner). Analog to point 4; they are either in 'parent' mode (content is the same as the parent content) or in working copy mode. A commit does change the content of the parent. 14 (new). Client created CompilationUnitOwners only have the life span of a runnable: When the runnable terminates, all compilation units that are in working copy mode (=not commited) are discarded, no buffers are kept. While in a runnable it is not possible to modify ComilationUnits of other owners except the parent by committing a own compilation unit. The idea of point 8*. is that it is not realistic that there will be 2 or more primary worlds, e.g. 2 editors of the same file with different contents. Contributed tools and actions want to work on the most current JDT world as in the editor. As such contributions can be headless (UI-independend) the conclusion is that a core plugin has to provide such a primary CompilationUnitOwner. To limit a client CompilationUnitOwner to a life span of a runnable (14) is intended to simplify the model. It avoids problems like: - What happens if there is a change in a parent compilation unit (can not happen because parent compilation units can not be changed while a runnable is active). - Multiple worlds existing at the same time: e.g 2 viewers show same compilation unit but of different worlds: Do actions have to be aware of multiple worlds? (can not happen except if a client runnable would explicitly call one of our actions)
Based on our current thinking (not yet taking Martin's comment #32 into account), I posted a version of org.eclipse.jdt.core with the new APIs. You can find it at: http://dev.eclipse.org/viewcvs/index.cgi/%7Echeckout%7E/jdt-core- home/patches/org.eclipse.jdt.core_bug36888_3.0.0.zip
> 8*. ... If I undertand you correctly, the buffer creation mechanism is contributed to jdt.core through an extension point. What if another plugin contributes to this extension point? If it is loaded before jdt.ui, then the world would not be the one jdt.ui expects.
No, not contributed. Defined by jdt.core or a plugin required by jdt.core. Something like org.eclipse.text would offer the infrastructure (IDocument, DocumentProvider) and care about annotations ect. so you would have something like JavaCore.getPrimaryBufferManager().
Is this platform.text plugin on the plan, or is this just hypothetical?
it exists for a while ;-)
The notion of default cu owner is tied with the filesystem. i.e. through the default owner, you can never see working copies. What you suggest is that it tolerates #becomeWorkingCopy, using a global custom buffer factory defined in PLATFORM/TEXT. However, from thereon, nobody else can access filesystem units, until the editor closes and saves its changes. I can see this being useful for JDT/UI, but other clients may not be interested by editor contents and would be burnt by this approach. The new working copy APIs should not be reserved to other clients than JDT/UI, where JDT/UI has a privileged way to register its own customizations. Either we support no other client or all of them. The remaining options are: 1- make the cu owner explicit, and add basically one argument in all/most of our existing APIs. We are not keen on this one. 2- support multiple implicit cu owners, as we described before. We still need to remove unnecessary pain for client code. 3- units have built-in support for one working copy (likely the one opened in editor). Further shareable working copies can be created, but these aren't transparent to clients (no free delta, no free navigation). In particular, operations requiring to consider these working copies over the original units will have a way to express this explicitely.
Martin, I looked at org.eclipse.text and I don't see how we can have the same behavior as DocumentAdapter without some UI knowledge. Would it be possible for you to do the first implementation of createBuffer (...) and getProblemRequestor() so that we can integrate it after all?
Discussed with Kai (text team): The DocumentProvider is not yet moved down to a UI independend plugin. Best is probably to start with an own buffer manager using your own buffers and later change it to documents and the document provider when text is ready. (of course until then jdt ui is broken). Another interesting thought is if this wouldn't be a good moment to eliminate IBuffer in favor of IDocument... (of course this is easier said than done)
Does all this mean that getHandleIdentifier() for an ICompilationUnit will not always return the same thing but has some owner part to it? If so, this is a problem for me since I use them as keys in persistent storage to associate metrics with java elements. Frank
Frank, no the format of getHandleIdentifier() will not be changed. It is only when you create the ICompilationUnit from it (using JavaCore.create(String)) that you would pass a owner if you didn't want the handle to have the primary owner.
After discussing this issue on jdt-core-dev, the following solution was decided: 1. IWorkingCopy disapears. Clients will manipulate ICompilationUnits only. 2. An ICompilationUnit handle is still implicetly defined by its path, but also by a WorkingCopyOwner. So 2 ICompilationUnits on the same IResource are equal only if their owners are equal. 3. The working copy owner also defines how the buffer of a working copy is created (see WorkingCopyOwner.createBuffer(...)). 4. There exists a default working copy owner in the Java model called the primary owner. Compilation units that have this owner are called primary compilation units. They can switch back and forth between the original mode and the working mode using becomWorkingCopy(...) and discardWorkingCopy(...). 5. Clients that want to use different working copies than the primary compilation units in working copy mode can define a WorkingCopyOwner. 6. Java model operations that create compilation unit handles were augmented to include a working copy owner. Clients can pass their working copy owner along with these Java model operations. The operations are then run in the context of the working copies of this owner. 7. Working copies are now all implicitely shared and can be retrieved using their working copy owner. See build notes for more details.
Verified.