Skip to main content
summaryrefslogtreecommitdiffstats
blob: 705d847ea191dfeaa30f9378bf6709a74332ce37 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
/**********************************************************************
 * This file is part of "Object Teams Development Tooling"-Software
 * 
 * Copyright 2006, 2007 Technical University Berlin, Germany.
 * 
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * $Id$
 * 
 * Please visit http://www.eclipse.org/objectteams for updates and contact.
 * 
 * Contributors:
 * Technical University Berlin - Initial API and implementation
 **********************************************************************/
package org.eclipse.objectteams.otdt.internal.ui.assist;

import static org.eclipse.objectteams.otdt.ui.ImageConstants.CALLINMETHOD_IMG;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AbstractMethodMappingDeclaration;
import org.eclipse.jdt.core.dom.CallinMappingDeclaration;
import org.eclipse.jdt.core.dom.CalloutMappingDeclaration;
import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.LiftingType;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.MethodMappingElement;
import org.eclipse.jdt.core.dom.MethodSpec;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.RoleTypeDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ITrackedNodePosition;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.jdt.internal.corext.fix.CleanUpConstants;
import org.eclipse.jdt.internal.corext.fix.FixMessages;
import org.eclipse.jdt.internal.corext.fix.IProposableFix;
import org.eclipse.jdt.internal.corext.fix.LinkedProposalModel;
import org.eclipse.jdt.internal.corext.fix.LinkedProposalPositionGroup;
import org.eclipse.jdt.internal.ui.JavaPluginImages;
import org.eclipse.jdt.internal.ui.fix.Java50CleanUp;
import org.eclipse.jdt.internal.ui.text.correction.ASTResolving;
import org.eclipse.jdt.internal.ui.text.correction.CorrectionMessages;
import org.eclipse.jdt.internal.ui.text.correction.proposals.AddImportCorrectionProposal;
import org.eclipse.jdt.internal.ui.text.correction.proposals.FixCorrectionProposal;
import org.eclipse.jdt.ui.cleanup.CleanUpOptions;
import org.eclipse.jdt.ui.text.java.IInvocationContext;
import org.eclipse.jdt.ui.text.java.IProblemLocation;
import org.eclipse.jdt.ui.text.java.correction.ASTRewriteCorrectionProposal;
import org.eclipse.jdt.ui.text.java.correction.CUCorrectionProposal;
import org.eclipse.jdt.ui.text.java.correction.ICommandAccess;
import org.eclipse.jface.viewers.IDecoration;
import org.eclipse.objectteams.otdt.core.IOTType;
import org.eclipse.objectteams.otdt.core.IRoleType;
import org.eclipse.objectteams.otdt.core.OTModelManager;
import org.eclipse.objectteams.otdt.core.compiler.ConfigHelper;
import org.eclipse.objectteams.otdt.core.compiler.IOTConstants;
import org.eclipse.objectteams.otdt.internal.ui.text.correction.MappingProposalSubProcessor;
import org.eclipse.objectteams.otdt.internal.ui.text.correction.TypeProposalSubProcessor;
import org.eclipse.objectteams.otdt.internal.ui.util.Images;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.graphics.Image;

import base org.eclipse.jdt.internal.corext.fix.Java50Fix;
import base org.eclipse.jdt.internal.corext.util.JdtFlags;
import base org.eclipse.jdt.internal.ui.text.correction.JavaCorrectionProcessor;
import base org.eclipse.jdt.internal.ui.text.correction.ModifierCorrectionSubProcessor;
import base org.eclipse.jdt.internal.ui.text.correction.QuickFixProcessor;
import base org.eclipse.jdt.internal.ui.text.correction.SuppressWarningsSubProcessor;
import base org.eclipse.jdt.internal.ui.text.correction.UnresolvedElementsSubProcessor;
import base org.eclipse.jdt.internal.ui.text.correction.SuppressWarningsSubProcessor.SuppressWarningsProposal;
import base org.eclipse.jdt.internal.ui.text.correction.proposals.AbstractMethodCorrectionProposal;
import base org.eclipse.jdt.internal.ui.text.correction.proposals.NewMethodCorrectionProposal;

/**
 * This team class extends the quickfix functionality of the jdt.ui for OT/J elements.
 * Currently supported:
 * <ul>
 * <li>inserting @SuppressWarnings for callin & callout bindings 
 *     ({@link OTQuickFixes.SuppressWarningsAdaptor},{@link OTQuickFixes.SuppressWarningsProposal}).</li>
 * <li>Proposing imports for role types is intercepted to propose an anchored type instead
 *     {@link OTQuickFixes.UnresolvedElementsSubProcessor}).</li>
 * <li>Adapt JavaCorrectionProcessor to cope with entries into the compiler without
 *     a Config configured ({@link OTQuickFixes.JavaCorrectionProcessor}).</li>
 * <li>Create new methods from an unresolved method spec ({@link OTQuickFixes.NewMethodCompletionProposal}).</li>
 * <li>Modifier corrections for role methods and ctors ({@link OTQuickFixes.ModifierCorrectionSubProcessor}).</li>
 * </ul>
 * 
 * @author stephan
 */
@SuppressWarnings("restriction")
public team class OTQuickFixes  {
	
	private static OTQuickFixes instance;
	
	public OTQuickFixes() {
		instance= this;
	}
	public static OTQuickFixes instance() {
		return instance;
	}
	
	/**
	 * Filter null proposals (we might have suppressed some proposals by nullification)
	 */
	protected class QuickFixProcessor playedBy QuickFixProcessor 
	{
		void filterNulls(Collection<ICommandAccess> proposals)
		<-  after void process(IInvocationContext context, IProblemLocation problem, Collection<ICommandAccess> proposals)
			with { proposals <- proposals }

		void filterNulls(Collection<ICommandAccess> proposals) {
			if ((proposals instanceof List)) {
				List<ICommandAccess> list = (List<ICommandAccess>)proposals;
				for (int i=list.size()-1; i>=0; i--)
					if (list.get(i) == null)
						list.remove(i);
			}
		}
	}

	/** 
	 * This role adapts the processor responsible for generating SuppressWarnings-proposals. 
	 */
	protected class SuppressWarningsAdaptor playedBy SuppressWarningsSubProcessor
	{
		@SuppressWarnings("basecall")  
		static callin int addSuppressWarningsProposal(ICompilationUnit cu, 
													   ASTNode node, 
													   String warningToken, 
													   int relevance, 
													   Collection<ICommandAccess> proposals) 
		{
			// adding one case block to the front of the original method:
			ChildListPropertyDescriptor property= null;
			String name;
			Object baseElement;
			switch (node.getNodeType()) {
			case ASTNode.CALLIN_MAPPING_DECLARATION:
				property= CallinMappingDeclaration.MODIFIERS2_PROPERTY;
				baseElement = ((CallinMappingDeclaration) node).getBaseMappingElements().get(0);
				name = ((CallinMappingDeclaration) node).getRoleMappingElement().getName().getIdentifier();
				name += "<-"; //$NON-NLS-1$
				name += ((MethodSpec)baseElement).getName().getIdentifier();
				break;			
			case ASTNode.CALLOUT_MAPPING_DECLARATION:
				property= CalloutMappingDeclaration.MODIFIERS2_PROPERTY;
				baseElement = ((CalloutMappingDeclaration) node).getBaseMappingElement();
				name = ((CalloutMappingDeclaration) node).getRoleMappingElement().getName().getIdentifier();
				name += "->"; //$NON-NLS-1$
				name += ((MethodMappingElement)baseElement).getName().getIdentifier();
				break;			
			case ASTNode.ROLE_TYPE_DECLARATION:
				property= RoleTypeDeclaration.MODIFIERS2_PROPERTY;
				name= ((RoleTypeDeclaration) node).getName().getIdentifier();
				break;
			default: 
				// other cases are already handled by the original method.
				return base.addSuppressWarningsProposal(cu, node, warningToken, relevance, proposals);
			}
			String label= NLS.bind(CorrectionMessages.SuppressWarningsSubProcessor_suppress_warnings_label, 
								   warningToken, 
								   name);
			
			// instantiate a role wrapping an invisible class 
			// and immediately lower the object using a public base type:
			ASTRewriteCorrectionProposal proposal=
					new SuppressWarningsProposal(warningToken, label, cu, node, property, relevance);

			proposals.add(proposal);
			return 0; // not affecting a local variable
		}
		
		@SuppressWarnings("decapsulation") // base-side arg "proposals" is not generic 
		addSuppressWarningsProposal <- replace addSuppressWarningsProposalIfPossible; 
	}
	
	/**
	 * The sole purpose of this role class is providing access to an invisible base class.
	 */	
	@SuppressWarnings("decapsulation") // base class and its constructor
	protected class SuppressWarningsProposal playedBy SuppressWarningsProposal
	{
		public SuppressWarningsProposal(String warningToken, String label, ICompilationUnit cu, ASTNode node, ChildListPropertyDescriptor property, int relevance) 
		{
			base(warningToken, label, cu, node, property, relevance);
		}
	}

	/**
	 * Prevent proposing an import for a role type (which would be illegal!), 
	 * but instead delegate to TypeProposalSubProcessor.changeTypeToAnchored().
	 * 
	 * Prevent proposing a cast for the faked $fakethis$ name.
	 */
	protected class UnresolvedElementsSubProcessor playedBy UnresolvedElementsSubProcessor 
	{
		static callin CUCorrectionProposal createTypeRefChangeProposal(ICompilationUnit cu, String fullName, Name node, int relevance, int maxProposals)
		{
			CUCorrectionProposal proposal = base.createTypeRefChangeProposal(cu, fullName, node, relevance, maxProposals);
			if (proposal instanceof AddImportCorrectionProposal) 
			{
				// if importRewrite suppressed a base import in a role file, don't propose empty changes, but redirect to the team:
				ImportRewrite importRewrite = ((AddImportCorrectionProposal) proposal).getImportRewrite();
				if (importRewrite != null && !importRewrite.myHasRecordedChanges())
					proposal = TypeProposalSubProcessor.createImportInRoFisTeamProposal(cu, fullName, node, relevance, maxProposals);
				
				// we cannot import roles, check if it is a role:
				try {
					IType type = cu.getJavaProject().findType(fullName);
					if (type != null) {
						IOTType otType = OTModelManager.getOTElement(type);
						if (otType != null && otType.isRole()) {
							// but an anchored role type needs no import: propose to add an anchor:
							return TypeProposalSubProcessor.changeTypeToAnchored(cu, fullName, node, ((IRoleType)otType).getTeam().getFullyQualifiedName());
						}
					}
					// import for the role side in DeclaredLifting?
					ASTNode typeRef = node.getParent();
					if (typeRef instanceof Type && typeRef.getLocationInParent() == LiftingType.ROLE_TYPE_PROPERTY) {
						return null;
					}
				} catch (JavaModelException jme) {
					// ignore
				}
			}
			return proposal;
		}
		@SuppressWarnings("decapsulation")
		createTypeRefChangeProposal <- replace createTypeRefChangeProposal;
		
		@SuppressWarnings("decapsulation") // Collection
		void addMissingCastParentsProposal(ICompilationUnit cu, MethodInvocation invocationNode) 
			<- replace void addMissingCastParentsProposal(ICompilationUnit cu, MethodInvocation invocationNode, Collection<ICommandAccess> proposals);

		@SuppressWarnings("basecall")
		static callin void addMissingCastParentsProposal(ICompilationUnit cu, MethodInvocation invocationNode) {
			Expression sender = invocationNode.getExpression();
			if (sender != null && sender.getNodeType() == ASTNode.SIMPLE_NAME) {
				if (((SimpleName)sender).getIdentifier().equals(MappingProposalSubProcessor.FAKETHIS))
					return;
				if (((SimpleName)sender).getIdentifier().startsWith(IOTConstants.OT_DOLLAR))
					return;
			}
			base.addMissingCastParentsProposal(cu, invocationNode);
		}
		
	}

	
	/** 
	 * When collecting proposals prepare Config to have at least a stub config.
	 */
	protected class JavaCorrectionProcessor playedBy JavaCorrectionProcessor {
		callin static IStatus guardDependencies() {
			boolean hasConfig = ConfigHelper.checkCreateStubConfig(OTQuickFixes.this);
			try {
				return base.guardDependencies();
			} finally {
				if (hasConfig)
					ConfigHelper.removeConfig(OTQuickFixes.this);
			}
		}
		guardDependencies <- replace collectProposals;
	}
	
	/** Declare a callin binding to a private method. Real stuff happens in sub-role. */
	@SuppressWarnings("abstractrelevantrole")
	protected abstract class AbstractMethodCompletionProposal playedBy AbstractMethodCorrectionProposal 
	{
		@SuppressWarnings("hidden-lifting-problem") // abstract role could potentially throw LiftingFailedException
		void updateRewrite(ASTRewrite rewrite) <- before MethodDeclaration getStub(ASTRewrite rewrite, ASTNode t)
			base when (OTQuickFixes.this.hasRole(base, AbstractMethodCompletionProposal.class));
		abstract void updateRewrite(ASTRewrite rewrite);
	}
	
	/** 
	 * This role helps the MappingProposalSubProcessor for unresolved method specs.
	 * Its behavior is initiated by calling {@link OTQuickFixes#registerNewMethodCorrectionProposal}.
	 */
	protected team class NewMethodCompletionProposal
		extends AbstractMethodCompletionProposal
		playedBy NewMethodCorrectionProposal
		base when (OTQuickFixes.this.hasRole(base, NewMethodCompletionProposal.class)) // on invitation only
	{
		// === Imports using CALLOUT: ===
		void setImage(Image img)		              				-> void setImage(Image img);
		
		@SuppressWarnings("decapsulation")
		protected String getKEY_TYPE()                				-> get String KEY_TYPE;

		@SuppressWarnings("decapsulation")
		protected void setFArguments(List<Expression> fArguments) 	-> set List<Expression> fArguments;

		ImportRewrite getImportRewrite()                           	-> ImportRewrite getImportRewrite();
		ImportRewrite createImportRewrite(CompilationUnit astRoot) 	-> ImportRewrite createImportRewrite(CompilationUnit astRoot);

		@SuppressWarnings("decapsulation")
		LinkedProposalModel getLinkedProposalModel() 			   	-> LinkedProposalModel getLinkedProposalModel();

		void addLinkedPosition(ITrackedNodePosition position, boolean isFirst, String groupID)
		-> void addLinkedPosition(ITrackedNodePosition position, boolean isFirst, String groupID);

		// === Basic State: ===
		/** Actual types etc. from the method spec. */
		protected Type[] parameterTypes;
		protected Type returnType;
		protected boolean needCallinModifier;
		protected boolean needStaticModifier;
		
		@SuppressWarnings("basecall")
		callin Type substituteParameterType(AST ast, Expression elem, String key) 
		{
			final String prefix = "arg_type_";  //$NON-NLS-1$
			if (this.parameterTypes != null && key.startsWith(prefix)) {
				int idx= Integer.parseInt(key.substring(prefix.length()));
				if (idx < this.parameterTypes.length)
					return (Type) ASTNode.copySubtree(ast, this.parameterTypes[idx]);
			}
			return base.substituteParameterType(ast, elem, key);
		}
		@SuppressWarnings("decapsulation")
		substituteParameterType <- replace evaluateParameterType;
		
		@SuppressWarnings("basecall")
		callin Type getMethodReturnType (ASTRewrite rewrite) throws CoreException {
			if (this.returnType != null) {
				Type newTypeNode = (Type) ASTNode.copySubtree(rewrite.getAST(), this.returnType);
				addLinkedPosition(rewrite.track(newTypeNode), false, getKEY_TYPE());
				return newTypeNode;
			}
			return base.getMethodReturnType(rewrite);
		}
		getMethodReturnType <- replace getNewMethodType; 
		
		callin int evaluateModifiers(ASTNode targetTypeDecl) {
			int result= base.evaluateModifiers(targetTypeDecl);
			if (this.needCallinModifier) {
				result &= ~(Modifier.PRIVATE|Modifier.PROTECTED|Modifier.PUBLIC);
				result |= Modifier.OT_CALLIN;
			}
			if (this.needStaticModifier) 
				result |= Modifier.STATIC;
			return result;
		}
		@SuppressWarnings("decapsulation")
		int evaluateModifiers(ASTNode targetTypeDecl) 
		<- replace int evaluateModifiers(ASTNode targetTypeDecl);
		
		@Override
		void updateRewrite(ASTRewrite rewrite) {
			if (this.needCallinModifier)
				rewrite.setToOTJ();
		}
		
		protected void adjustModifiers(CallinMappingDeclaration callinMapping, boolean isRoleSide) {
			if (isRoleSide && callinMapping.getCallinModifier()==Modifier.OT_REPLACE_CALLIN) {
				this.needCallinModifier= true;
				Image baseImage = JavaPluginImages.get(JavaPluginImages.IMG_MISC_DEFAULT);
				setImage(Images.decorateImage(baseImage, CALLINMETHOD_IMG, IDecoration.TOP_RIGHT));
			}
			if (callinMapping.isStatic())
				this.needStaticModifier= true;
		}

		// ==== Handle lifting/lowering when inferring method signatures across a method mapping: ====

		/** data class for alternative type proposals to be added as linked proposals, once the rewrite is ready. */
		protected class TypeProposalsMemento {
			protected Type originalType;
			protected List<Type> types = new ArrayList<Type>();
			protected String positionGroupID;
			protected TypeProposalsMemento(String positionGroupID, Type originalType) {
				this.positionGroupID = positionGroupID;
				this.originalType = originalType;
			}
		}
		List<TypeProposalsMemento> pendingLinkedLiftings = new ArrayList<TypeProposalsMemento>();

		/**
		 * Propose a type node either directly from typeBinding, 
		 * or considering a lifting/lowering translation if appropriate. 
		 * If translation is indeed proposed, schedule a TypeProposalsMemento
		 * for adding linked proposals once the rewrite is ready.
		 * @param imports     use this for creating type nodes from type bindings
		 * @param ast		  the AST we are generating against
		 * @param typeBinding type binding of AST node to investigate
		 * @param groupKey 	  key of the linked position group
		 * @param roles       available roles in the enclosing team
		 * @param isRoleSide  are we inferring a role method signature?
		 * @return a suitable type node or null
		 */
		protected Type proposeType(ImportRewrite imports, AST ast, ITypeBinding typeBinding, String groupKey, ITypeBinding[] roles, boolean isRoleSide) 
		{
			if (!isRoleSide && typeBinding.isRole()) {
				// if trying to pass one of our roles to the base unconditionally apply lowering
				for (int i = 0; i < roles.length; i++) {
					if (typeBinding.getErasure().equals(roles[i].getErasure())) {
						typeBinding = typeBinding.getBaseClass();
						if (typeBinding == null)
							return null; // cannot lower unbound role
						break;
					}
				}
			}
			Type newType = imports.addImport(typeBinding, ast);
			if (isRoleSide) {
				newType = checkLifting(imports, ast, typeBinding, newType, groupKey, roles);
			}
			return newType;
		}

		/**
		 * check whether a given type is played by a role from a given array and replace the base type with the given role.
		 * However, actual creation of the linked proposal has to be deferred until the rewrite is created by our base.
		 */
		Type checkLifting(ImportRewrite imports, AST ast, ITypeBinding typeBinding, Type originalType, String groupKey, ITypeBinding[] roles)
		{
			Type roleType = null;
			TypeProposalsMemento memento = null;
			for (ITypeBinding roleBinding : roles) {
				if (roleBinding.isSynthRoleIfc()) continue; // synth ifcs would otherwise cause dupes
				if (typeBinding.equals(roleBinding.getBaseClass())) {
					// found
					if (memento == null) {
						memento = new TypeProposalsMemento(groupKey, originalType);
						this.pendingLinkedLiftings.add(memento);
					}
					Type candidateRoleType = imports.addImport(roleBinding, ast);
					memento.types.add(candidateRoleType);
					if (roleType == null) // prefer the first found role type as the primary proposal
						roleType = candidateRoleType;
				}
			}
			if (roleType != null)
				return roleType; // replace with translated type
			return originalType; // unchanged
		}

		void addPendingLiftingProposals(ASTRewrite rewrite) <- after ASTRewrite getRewrite()
			with { rewrite <- result }
		// as the rewrite has been created, add linked proposals now:
		void addPendingLiftingProposals(ASTRewrite rewrite) {
			for (TypeProposalsMemento memento : this.pendingLinkedLiftings) {
				ITrackedNodePosition typePos= rewrite.track(memento.originalType);
				addLinkedPosition(typePos, true, memento.positionGroupID);
				LinkedProposalPositionGroup group=
						getLinkedProposalModel().getPositionGroup(memento.positionGroupID, true);
				for (Type type : memento.types)				
					group.addProposal(type.toString(), null, 13); // TODO: relevance
				group.addProposal(memento.originalType.toString(), null, 13);
			}
		}
	}
	
	/** Register a new method completion proposal with a method spec,
	 *  in order to use the method spec's signature for constructing the new method's signature.
	 * @param spec     the unresolved method spec 
	 * @param proposal a new completion proposal to be adapted
	 */
	@SuppressWarnings("rawtypes")
	public void registerNewMethodCorrectionProposal(MethodSpec spec,
				NewMethodCorrectionProposal as NewMethodCompletionProposal proposal) 
	{
		if (spec.hasSignature()) {
			List parameters= spec.parameters();
			proposal.parameterTypes= new Type[parameters.size()];
			for (int i=0; i<parameters.size(); i++) {
				SingleVariableDeclaration arg= (SingleVariableDeclaration)parameters.get(i);
				proposal.parameterTypes[i]= arg.getType();
			}
			proposal.returnType= spec.getReturnType2();
		} else {
			// create signature from resolved other (base/role) element:
			// Bug 329988 -  Quickfix method generation on missing replace callin method generates wrong method
			StructuralPropertyDescriptor locationInParent = spec.getLocationInParent();
			if (locationInParent == CallinMappingDeclaration.ROLE_MAPPING_ELEMENT_PROPERTY) {
				List baseSpecs = ((CallinMappingDeclaration)spec.getParent()).getBaseMappingElements();
				if (baseSpecs.size() == 1) {
					inferMethodSignature(proposal, spec, ((MethodSpec)baseSpecs.get(0)).resolveBinding(), true);
				}
			} else if (   locationInParent == CallinMappingDeclaration.BASE_MAPPING_ELEMENTS_PROPERTY
					   || locationInParent == CalloutMappingDeclaration.BASE_MAPPING_ELEMENT_PROPERTY)
			{
				MethodSpec roleSpec = (MethodSpec) ((AbstractMethodMappingDeclaration)spec.getParent()).getRoleMappingElement();
				inferMethodSignature(proposal, spec, roleSpec.resolveBinding(), false);
			}
		}
		ASTNode mapping= spec.getParent();
		if (mapping instanceof CallinMappingDeclaration) {
			CallinMappingDeclaration callinDecl = (CallinMappingDeclaration)mapping;
			proposal.adjustModifiers(callinDecl, spec == callinDecl.getRoleMappingElement());
		}
	}
	/** 
	 * A method spec has no signature, infer the signature for a missing method
	 * from the resolved method at the other side of the method mapping.
	 */
	private void inferMethodSignature(NewMethodCompletionProposal proposal, MethodSpec spec, IMethodBinding resolvedMethod, boolean isRoleSide) {
		AST ast = spec.getAST();
		ImportRewrite imports = proposal.getImportRewrite();
		if (imports == null)
			imports = proposal.createImportRewrite((CompilationUnit) ASTResolving.findAncestor(spec, ASTNode.COMPILATION_UNIT));

		ITypeBinding roleTypeBinding = ((RoleTypeDeclaration) ASTResolving.findAncestor(spec, ASTNode.ROLE_TYPE_DECLARATION)).resolveBinding();
		ITypeBinding[] roles= roleTypeBinding.getDeclaringClass().getDeclaredTypes();

		// preset return type and parameter types from the resolved method:
		
		ITypeBinding returnType = resolvedMethod.getReturnType();
		proposal.returnType = proposal.proposeType(imports, ast, returnType, NewMethodCompletionProposal.getKEY_TYPE(), roles, isRoleSide);
		
		ITypeBinding[] parameterTypes = resolvedMethod.getParameterTypes();
		proposal.parameterTypes = new Type[parameterTypes.length];
		List<Expression> arguments = new ArrayList<Expression>(parameterTypes.length);
		for (int i = 0; i < parameterTypes.length; i++) {
			proposal.parameterTypes[i] = proposal.proposeType(imports, ast, parameterTypes[i], "arg_type_"+i, roles, isRoleSide); //$NON-NLS-1$
			arguments.add(ast.newConditionalExpression());  // dummy expr that will be filtered out in StubUtility.getVariableNameSuggestions()
		}
		proposal.setFArguments(arguments); // ensure we will loop properly in NewMethodCorrectionProposal.addNewParameters(ASTRewrite, List, List)
	}
	
	/** 
	 * This flag lets clients control whether modifiers should unconditionally be set to <code>public</code>.
	 * Note that this static variable is not thread-safe. 
	 */
	public static boolean publicRequested= false;
	/**
	 * Adapt proposals for non-accessible references using a {@link QuickFixCoreAdaptor}.
	 * Here: adjust the needed visibility for role members.
	 */
	protected class ModifierCorrectionSubProcessor playedBy ModifierCorrectionSubProcessor {

		cflow <- replace addNonAccessibleReferenceProposal;

		static callin void cflow() throws CoreException {
			within (new QuickFixCoreAdaptor())
				base.cflow();
		}

		@SuppressWarnings("decapsulation")
		getNeededVisibility <- replace  getNeededVisibility;

		@SuppressWarnings("basecall")
		callin static int getNeededVisibility(ASTNode currNode, ITypeBinding targetType) {
			if (publicRequested) return Modifier.PUBLIC;
			int vis = base.getNeededVisibility(currNode, targetType);
			if (vis == 0 && targetType.isRole()) {
				// is targetType in scope from currNode? TODO(SH): this search is not exact.
				ITypeBinding currentEnclosing= Bindings.getBindingOfParentType(currNode);
				while (currentEnclosing != null) {
					ITypeBinding targetEnclosing = targetType.getDeclaringClass();
					while (targetEnclosing != null) {
						if (targetEnclosing.equals(currentEnclosing))
							return Modifier.PROTECTED;
						targetEnclosing = targetEnclosing.getDeclaringClass();
					}
					currentEnclosing = currentEnclosing.getDeclaringClass();
				}
				// accessing from the outside, requires public:
				return Modifier.PUBLIC;
			}
			return vis;
		}
		protectedRun <- replace addAbstractMethodProposals;

		@SuppressWarnings("basecall")
		static callin void protectedRun() {
			try {
				base.protectedRun();
			} catch (ClassCastException cce) {
				// this one is excepted, base method may try to located abstract method but finds RoleTypeDeclaration
			}
		}
	}
	
	/** Interpret some flags with knowledge of OT/J: */
	protected class FlagAdaptation playedBy JdtFlags {

		@SuppressWarnings("decapsulation")
		boolean isInterfaceOrAnnotationMember(IBinding binding) 
			<- replace boolean isInterfaceOrAnnotationMember(IBinding binding);

		static callin boolean isInterfaceOrAnnotationMember(IBinding binding) {
			boolean result = base.isInterfaceOrAnnotationMember(binding);
			if (result) { // double check for member of synth ifc:
				ITypeBinding declaringType= null;
				if (binding instanceof IVariableBinding) {
					declaringType= ((IVariableBinding) binding).getDeclaringClass();
				} else if (binding instanceof IMethodBinding) {
					declaringType= ((IMethodBinding) binding).getDeclaringClass();
				} else if (binding instanceof ITypeBinding) {
					declaringType= ((ITypeBinding) binding).getDeclaringClass();
				}
				if (declaringType != null && declaringType.isSynthRoleIfc())
					return false; // result was false alarm ;-)
			}
			return result;
		}
		
	}
	
	/**
	 * Quickfixes for things that are new in Java 5, here: annotations.
	 * <ul>
	 * <li>Support @Override annotation for role classes, too.</li>
	 * </ul>
	 * @since 1.2.8
	 */
	protected class Java50Fix playedBy Java50Fix {

		@SuppressWarnings("decapsulation")
		protected Java50Fix createFix(CompilationUnit compilationUnit, IProblemLocation problem, String annotation, String label) 
			   -> Java50Fix createFix(CompilationUnit compilationUnit, IProblemLocation problem, String annotation, String label);

		@SuppressWarnings("decapsulation")
		ASTNode getDeclaringNode(ASTNode selectedNode) <- replace ASTNode getDeclaringNode(ASTNode selectedNode);

		/** Also expect RoleTypeDeclaration. */
		static callin ASTNode getDeclaringNode(ASTNode selectedNode) {
			ASTNode result = base.getDeclaringNode(selectedNode);
			if (result != null)
				return result;
			// similar to fragment in base method:
			if (selectedNode instanceof SimpleName) {
				StructuralPropertyDescriptor locationInParent= selectedNode.getLocationInParent();
				if (locationInParent == RoleTypeDeclaration.NAME_PROPERTY)
					return selectedNode.getParent();
			}
			return null;
		}		
	}
	
	/**
	 * Offer proposal from ModifierCorrectionSubProcessor in a version adapted for role classes instead of methods. 
	 * @param context   completion context to pass through
	 * @param problem   the problem that triggered this assist
	 * @param proposals list of proposals to which the new proposal should be added.
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public void addOverrideAnnotationProposal(IInvocationContext context, IProblemLocation problem, Collection proposals) {
		// avoid calling Java50Fix.createAddOverrideAnnotationFix, which expects a specific problemID
		IProposableFix fix= Java50Fix.createFix(context.getASTRoot(), problem, "Override", FixMessages.Java50Fix_AddOverride_description); //$NON-NLS-1$
		// original from ModifierCorrectionSubProcessor:
		if (fix != null) {
			Image image= JavaPluginImages.get(JavaPluginImages.IMG_CORRECTION_CHANGE);
			Map options= new Hashtable();
			options.put(CleanUpConstants.ADD_MISSING_ANNOTATIONS, CleanUpOptions.TRUE);
			options.put(CleanUpConstants.ADD_MISSING_ANNOTATIONS_OVERRIDE, CleanUpOptions.TRUE);
			FixCorrectionProposal proposal= new FixCorrectionProposal(fix, new Java50CleanUp(options), 5, image, context);
			proposals.add(proposal);
		}
	}
}

Back to the top