Skip to main content
summaryrefslogtreecommitdiffstats
blob: 13098efefed8cfd605c918c55e2bfea77e46d1de (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
680
681
682
683
684
685
686
687
688
689
690
/**********************************************************************
 * This file is part of "Object Teams Development Tooling"-Software
 * 
 * Copyright 2009, 2019 Technical University Berlin, Germany.
 * 
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 * 
 * 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.pde.validation;

import static org.eclipse.objectteams.otequinox.Constants.*;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.IMethodMappingBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.internal.corext.dom.Bindings;
import org.eclipse.objectteams.otdt.core.IMethodMapping;
import org.eclipse.objectteams.otdt.core.IRoleType;
import org.eclipse.objectteams.otdt.core.OTModelManager;
import org.eclipse.objectteams.otdt.internal.pde.ui.OTPDEUIMessages;
import org.eclipse.objectteams.otequinox.ActivationKind;
import org.eclipse.osgi.service.resolver.BundleDescription;
import org.eclipse.osgi.service.resolver.State;
import org.eclipse.osgi.util.ManifestElement;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.core.plugin.IPluginModelBase;
import org.eclipse.pde.internal.core.builders.CompilerFlags;
import org.eclipse.pde.internal.core.builders.IHeader;
import org.eclipse.pde.internal.core.builders.IncrementalErrorReporter.VirtualMarker;
import org.eclipse.pde.internal.core.builders.PDEMarkerFactory;
import org.eclipse.pde.internal.core.ibundle.IManifestHeader;
import org.eclipse.pde.internal.core.text.bundle.BundleActivationPolicyHeader;
import org.eclipse.pde.internal.core.text.bundle.BundleModel;
import org.eclipse.pde.internal.core.text.plugin.PluginAttribute;
import org.eclipse.pde.internal.ui.correction.AbstractManifestMarkerResolution;
import org.eclipse.pde.internal.ui.correction.AbstractPDEMarkerResolution;
import org.eclipse.pde.internal.ui.correction.AbstractXMLMarkerResolution;
import org.eclipse.pde.internal.ui.correction.AddExportPackageMarkerResolution;
import org.eclipse.ui.IMarkerResolution;
import org.osgi.framework.Constants;
import org.osgi.framework.namespace.PackageNamespace;
import org.osgi.resource.Capability;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import base org.eclipse.pde.internal.core.builders.BundleErrorReporter;
import base org.eclipse.pde.internal.core.builders.ExtensionsErrorReporter;
import base org.eclipse.pde.internal.core.builders.ManifestConsistencyChecker;
import base org.eclipse.pde.internal.core.builders.XMLErrorReporter;
import base org.eclipse.pde.internal.ui.correction.ResolutionGenerator;

/**
 * Enhanced validation of bundle manifests.
 * <ul>
 * <li>Check whether all bundles with aspectBindings have a proper activation policy set,<br>
 *    Provide suitable quick assist if activation policy is wrong/missing.</li>
 * </ul>
 * 
 * @author stephan
 * @since 1.2.7
 */
@SuppressWarnings("restriction")
public team class BundleValidation 
{
	/** Constant for a problem that can be resolved by adding an activation policy to the manifest. */
	static final int ADD_ACTIVATION_POLICY = 0x1801; // must not overlap with any constant in org.eclipse.pde.internal.core.builders.PDEMarkerFactory. 
	/** Constant for a problem that can be resolved by adding an activation policy to the manifest. */
	static final int ADD_PACKAGE_EXPORT = 0x1802; // must not overlap with any constant in org.eclipse.pde.internal.core.builders.PDEMarkerFactory. 
	static final int CHANGE_DOT_TO_DOLLAR = 0x1803; // must not overlap with any constant in org.eclipse.pde.internal.core.builders.PDEMarkerFactory.

	ThreadLocal<BundleCheckingContext> bundleContext = new ThreadLocal<BundleCheckingContext>();
	
	/** 
	 * Defines the context of validating one bundle. 
	 * One instance of this role exists per control flow (= per thread) 
	 * for retrieval in downstream callin bindings. 
	 */
	protected class BundleCheckingContext playedBy ManifestConsistencyChecker 
	{
		// flags set during validation of one bundle:
		protected boolean isAspectBundle = false;
		protected boolean hasTeamActivation = false;
		protected Set<String> aspectPackages = new HashSet<String>();

		/** packages containing bound base classes, which require the team to be bound to the corresponding base bundle. */
		public Map<String,List<String>> requiredBasePackagesPerTeam = new HashMap<String, List<String>>();
		
		IProject getProject() -> IProject getProject();  

		@SuppressWarnings("decapsulation")
		spanContext <- replace validateFiles;
		
		callin void spanContext() {
			BundleValidation.this.bundleContext.set(this);
			try {
				base.spanContext();
			} finally {
				// withdraw role, is for one-time use only:
				BundleValidation.this.bundleContext.set(null);
				BundleValidation.this.unregisterRole(this, BundleCheckingContext.class);
			}
		}
		protected void addRequiredBasePackage(String teamName, String baseName) {
			List<String> bases = requiredBasePackagesPerTeam.get(teamName);
			if (bases == null)
				requiredBasePackagesPerTeam.put(teamName, bases = new ArrayList<>());
			bases.add(baseName);
		}
	}
	
	/** Super-role for access to internal members. */
	protected class XMLAnalyzer playedBy XMLErrorReporter {
		@SuppressWarnings("decapsulation")
		protected String generateLocationPath(Node node, String attrName) -> String generateLocationPath(Node node, String attrName);

		@SuppressWarnings("decapsulation")
		protected IProject getFProject() -> get IProject fProject;
	}

	/**
	 * Detects aspectBindings declared in plugin.xml and records information in the current {@link BundleCheckingContext}.
	 * Directly reports erroneous use of '.' for nested team names.
	 */
	protected class ExtensionAnalyzer extends XMLAnalyzer playedBy ExtensionsErrorReporter 
			base when (BundleValidation.this.bundleContext.get() != null)
	{	
		
		@SuppressWarnings("decapsulation")
		State getState() -> get IPluginModelBase fModel
			with { result <- fModel.getBundleDescription().getContainingState() }
		
		VirtualMarker report(String message, int line, int severity, int fixId, String category)
		-> VirtualMarker report(String message, int line, int severity, int fixId, String category);

		@SuppressWarnings("decapsulation")
		int getLine(Element element) -> int getLine(Element element);
		
		@SuppressWarnings("decapsulation")
		int getLine(Element element, String attrName) -> int getLine(Element element, String attrName);

		VirtualMarker report(String message, int line, int severity, int fixId, Element element, String attrName, String category)
		<- replace VirtualMarker report(String message, int line, int severity, int fixId, Element element, String attrName, String category);

		void checkAspectBinding(Element element) <- after void validateExtension(Element element);

		protected void checkAspectBinding(Element element) 
		{
			Object pointID = element.getAttribute("point"); //$NON-NLS-1$
			if (ASPECT_BINDING_FQEXTPOINT_ID.equals(pointID)) 
			{
				BundleCheckingContext context = BundleValidation.this.bundleContext.get();
				// it's an aspect bundle
				context.isAspectBundle = true;
				
				IJavaProject jProject = JavaCore.create(context.getProject());

				boolean hasSelfAdaptation = false;
				NodeList baseNodes = element.getElementsByTagName(BASE_PLUGIN);
				for (int b=0; b<baseNodes.getLength(); b++) {
					if (SELF.equalsIgnoreCase(((Element)baseNodes.item(b)).getAttribute(ID))) {
						hasSelfAdaptation = true;
						break;
					}
				}

				Map<String,Map<String,Set<String>>> superBasePackagesByTeam;
				{
					List<IMethodMapping> mappings = new ArrayList<>();
	
					// collect binding requirements by nested teams of all bound teams:
					NodeList teamNodes = element.getElementsByTagName(TEAM);
					for (int t=0; t<teamNodes.getLength(); t++) {
						// record aspect packages:
						Object teamClass = ((Element)teamNodes.item(t)).getAttribute(CLASS);
						if (teamClass instanceof String)
							checkNestedTeams((String) teamClass, context, hasSelfAdaptation, mappings);
					}
					// collect packages with overridden base methods:
					superBasePackagesByTeam = collectOverridden(mappings);
				}
				NodeList aspectBindings = element.getChildNodes();
				int aspectCount = aspectBindings.getLength();
				for (int i = 0; i < aspectCount; i++) {
					Node aspectBinding = aspectBindings.item(i);
					// does it have elements with relevant activation?
					boolean isSelfAdaptation = false;
					boolean hasActivation = true;
					BundleDescription baseBundle = null;
					List<String> teamNames = new ArrayList<String>();

					NodeList children = aspectBinding.getChildNodes();
					int childrenCount = children.getLength();
					for (int j = 0; j < childrenCount; j++) {
						Node child = children.item(j);
						if (child instanceof Element) {
							Element childElement = (Element)child;
							String tagName = childElement.getTagName();
							if (BASE_PLUGIN.equals(tagName)) {
								String baseId = childElement.getAttribute(ID);
								if (baseId != null) {
									if (baseId.toUpperCase().equals(SELF))
										isSelfAdaptation = true; // missing bundle activation is not fatal in this case
									else
										baseBundle = checkBasePlugIn(baseId, getLine(childElement));
								}

							} else if (TEAM.equals(tagName)) {
								// analyze aspect packages:
								Element teamNode = childElement;
								Object teamClass = teamNode.getAttribute(CLASS);
								if (!(teamClass instanceof String))
									continue;
							
								String teamName = (String) teamClass;
								String actualPackage = checkActualPackage(context, teamNode, teamName);
								if (actualPackage == null)
									report(OTPDEUIMessages.Validation_MissingPackage_error, getLine(teamNode),
											CompilerFlags.ERROR, PDEMarkerFactory.NO_RESOLUTION, PDEMarkerFactory.CAT_FATAL);
								else
									context.aspectPackages.add(actualPackage);
								teamNames.add(teamName);

								// team activation?
								Object activation = teamNode.getAttribute(ACTIVATION);
								if (ActivationKind.ALL_THREADS.toString().equals(activation)) {
									hasActivation = true;
								} else if (ActivationKind.THREAD.toString().equals(activation)) {
									hasActivation = true;
								}
								NodeList superBases = teamNode.getElementsByTagName(SUPER_BASE);
								for (int k=0; k<superBases.getLength(); k++) {
									// report bad declarations & remove superBase requirements matching this declaration 
									Node grandChild = superBases.item(k);
									if (grandChild instanceof Element) {
										checkSuperBaseClass((Element) grandChild, superBasePackagesByTeam.get(teamName), jProject, baseBundle);
									}
								}
							}
						}
					}
					if (hasActivation && !isSelfAdaptation)
						context.hasTeamActivation = true;
					if (baseBundle != null) {
						// remove packages provided by this baseBundle from the list of required packages
						Set<String> providedPackages = new HashSet<String>();
						// for SELF-adaptation we don't need a package export, that's why we include those 
						// requirements from this check (see checkNestedTeams(..hasSelfAdaptation)).
						for (Capability cap : baseBundle.getCapabilities(PackageNamespace.PACKAGE_NAMESPACE))
							providedPackages.add((String) cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE));
						for (String teamName: teamNames) {
							List<String> basePackagesList = context.requiredBasePackagesPerTeam.get(teamName);
							if (basePackagesList != null) {
								Iterator<String> basePackages = basePackagesList.iterator();
								while (basePackages.hasNext()) {
									String basePackage = basePackages.next();
									if (providedPackages.contains(basePackage))
										basePackages.remove();
								}
							}
						}
					}
				}
				// complain about remaining requiredBasePackages (i.e., those for which no binding was provided)
				reportUnmatchedRequirements(element, context.requiredBasePackagesPerTeam, e->e,
											OTPDEUIMessages.Validation_MissingBindingForBasePackage_error);
				// complain about remaining undeclared super bases:
				reportUnmatchedRequirements(element, superBasePackagesByTeam,
											e->e.values().stream().flatMap(Set::stream).collect(Collectors.toSet()),
											OTPDEUIMessages.Validation_MissingSuperBasePackageDecl_error);
			}
		}

		private <T> void reportUnmatchedRequirements(Element element,
				Map<String, T> requirementsPerTeam,
				Function<T,? extends Collection<String>> extractor,
				String errorMessageTemplate)
		{
			for (Entry<String, T> entry : requirementsPerTeam.entrySet()) {
				Collection<String> requireds = extractor.apply(entry.getValue());
				if (requireds != null && !requireds.isEmpty()) {
					for (String required : requireds) {
						report(NLS.bind(errorMessageTemplate, entry.getKey(), required),
								getLine(element),
								CompilerFlags.ERROR,
								PDEMarkerFactory.NO_RESOLUTION,
								PDEMarkerFactory.CAT_FATAL);
					}
				}
			}
		}

		private Map<String, Map<String,Set<String>>> collectOverridden(List<IMethodMapping> mappings) {
			Map<String,Map<String,Set<String>>> superBasePackagesByTeam = new HashMap<>(); // inner map is package name -> class names
			try {
				// collect role types from mappings (IType, then ITypeBinding):
				Set<IType> roleTypes = new HashSet<IType>(); 
				for (IMethodMapping mapping : mappings) {
					IType type = mapping.getDeclaringType();
					if (!"java.lang.Object".equals(type.getSuperclassName())) //$NON-NLS-1$
						roleTypes.add(type);
				}
				if (roleTypes.isEmpty())
					return Collections.emptyMap();
				ASTParser parser = ASTParser.newParser(AST.JLS12);
				parser.setProject(JavaCore.create(getFProject()));
				IBinding[] bindings = parser.createBindings(roleTypes.toArray(new IType[roleTypes.size()]), null);

				// from ITypeBinding descend into IMethodMappingBinding, then IMethodBinding (base):
				for (IBinding binding : bindings) {
					if (binding instanceof ITypeBinding) {
						String teamName = ((ITypeBinding) binding).getDeclaringClass().getQualifiedName();
						Map<String,Set<String>> perTeamResult = superBasePackagesByTeam.get(teamName);
						for (IMethodMappingBinding mappingBinding : ((ITypeBinding) binding).getResolvedMethodMappings()) {
							if (mappingBinding == null) continue;
							for (IMethodBinding basemethod : mappingBinding.getBaseMethods()) {
								if (!mappingBinding.isCallin() && Flags.isPublic(basemethod.getModifiers()))
									continue; // no weaving required for callout to public
								// find overridden
								for (IMethodBinding overriddenMethod : Bindings.findOverriddenMethods(basemethod, true, false)) {									
									if (Flags.isAbstract(overriddenMethod.getModifiers()))
										continue; // no code to weave
									// remember package of declaring class
									ITypeBinding declaringClass = overriddenMethod.getDeclaringClass();
									String packageName = declaringClass.getPackage().getName();
									if (perTeamResult == null) {
										superBasePackagesByTeam.put(teamName, perTeamResult = new HashMap<>());
									}
									Set<String> classSet = perTeamResult.get(packageName);
									if (classSet == null) {
										perTeamResult.put(packageName, classSet = new HashSet<>());
									}
									classSet.add(declaringClass.getQualifiedName());
								}
							}
						}
					}
				}
			} catch (JavaModelException e) {
				// cannot analyse
			}
			return superBasePackagesByTeam;
		}

		private void checkSuperBaseClass(Element elem, Map<String,Set<String>> collectedPackages, IJavaProject jProject, BundleDescription baseBundle) {
			try {
				String packageName = null;
				String superBaseClass = elem.getAttribute(SUPER_BASE_CLASS);
				if (superBaseClass != null) {
					IType clazz = jProject.findType(superBaseClass);
					if (clazz != null) { // otherwise assume standard validation already complained
						packageName = clazz.getPackageFragment().getElementName();
						if (collectedPackages == null || collectedPackages.remove(packageName) == null) {
							report(NLS.bind(OTPDEUIMessages.Validation_UnnecessarySuperBase_warning, superBaseClass),
									getLine(elem), 
									CompilerFlags.WARNING,
									PDEMarkerFactory.NO_RESOLUTION,
									PDEMarkerFactory.CAT_OTHER);
						}
					}
				}
				if (packageName != null) {
					String bundleName = elem.getAttribute(SUPER_BASE_PLUGIN);
					BundleDescription basePlugIn = !bundleName.isEmpty() ? checkBasePlugIn(bundleName, getLine(elem))
													: baseBundle; // fall back if no explicit plugin
					if (basePlugIn != null) {
						for (Capability cap : basePlugIn.getCapabilities(PackageNamespace.PACKAGE_NAMESPACE)) {
							if (packageName.equals(cap.getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE)))
								return;
						}
						report(NLS.bind(OTPDEUIMessages.Validation_PackageNotInSuperBase_error, bundleName, packageName),
								getLine(elem), 
								CompilerFlags.ERROR,
								PDEMarkerFactory.NO_RESOLUTION,
								PDEMarkerFactory.CAT_FATAL);						
					}
				}
			} catch (JavaModelException e) {
				// cannot analyse
			}
		}

		String checkActualPackage(BundleCheckingContext context, Element teamNode, String teamName) {
			int lastDot = teamName.lastIndexOf('.');
			if (lastDot == -1)
				return null;
			String packageName = teamName.substring(0, lastDot);
			String actualPackage = getContainingPackage(context, packageName);
			if (packageName != actualPackage) {
				VirtualMarker marker = report(NLS.bind(OTPDEUIMessages.Validation_NotAPackage_error, packageName),
						   getLine(teamNode, CLASS), 
						   CompilerFlags.ERROR,
						   CHANGE_DOT_TO_DOLLAR,
						   PDEMarkerFactory.CAT_FATAL);
				if (marker != null) {
					marker.setAttribute("package", actualPackage); //$NON-NLS-1$
					marker.setAttribute("team", teamName); //$NON-NLS-1$
					marker.setAttribute(PDEMarkerFactory.MPK_LOCATION_PATH, generateLocationPath(teamNode, CLASS));
				}
			}
			return actualPackage;
		}
		String getContainingPackage(BundleCheckingContext context, String packageNameCandidate) {
			IProject project = context.getProject();
			if (project != null) {
				IJavaProject jProject = JavaCore.create(project);
				if (jProject != null) {
					try {
						IJavaElement jElement = jProject.findElement(new Path(packageNameCandidate));
						if (jElement != null && jElement.getElementType() == IJavaElement.PACKAGE_FRAGMENT)
							return packageNameCandidate;
						jElement = jProject.findType(packageNameCandidate);
						if (jElement != null) {
							IJavaElement ancestor = jElement.getAncestor(IJavaElement.PACKAGE_FRAGMENT);
							if (ancestor != null)
								return ancestor.getElementName();
						}
					} catch (JavaModelException e) {
						// cannot analyse
					}
				}
			}
			return packageNameCandidate; // be shy about reporting errors in error contexts
		}

		BundleDescription checkBasePlugIn(String symbolicName, int lineNo) {
			BundleDescription[] bundles = getState().getBundles(symbolicName);
			if (bundles.length == 0) {
				report(NLS.bind(OTPDEUIMessages.Validation_UnresolveBasePlugin_error, symbolicName),
						   lineNo, 
						   CompilerFlags.ERROR,
						   PDEMarkerFactory.NO_RESOLUTION,
						   PDEMarkerFactory.CAT_OTHER);
				return null;
			}
			return bundles[0];
		}
		
		void checkNestedTeams(String teamName, BundleCheckingContext context, boolean hasSelfAdaptation, List<IMethodMapping> mappings) {
			teamName = teamName.replace('$', '.');
			IJavaProject jPrj = JavaCore.create(getFProject());
			if (jPrj.exists()) {
				try {
					IType teamType = jPrj.findType(teamName);
					if (teamType != null) {
						for (IType member : teamType.getTypes()) {
							if (OTModelManager.isTeam(member)) {
								String nestedTeamName = member.getFullyQualifiedName('$'); // name as used in aspectBinding.basePlugin
								for (IType role : OTModelManager.getOTElement(member).getRoleTypes()) {
									IType aBase = ((IRoleType) OTModelManager.getOTElement(role)).getBaseClass();
									if (aBase != null
											&& !(hasSelfAdaptation && aBase.getJavaProject().equals(jPrj)))
										context.addRequiredBasePackage(nestedTeamName, aBase.getPackageFragment().getElementName());
								}
								checkNestedTeams(nestedTeamName, context, hasSelfAdaptation, mappings);
							} else {
								IRoleType role = (IRoleType) OTModelManager.getOTElement(member);
								for (IMethodMapping mapping : role.getMethodMappings())
									mappings.add(mapping);
							}
						}
					}
				} catch (JavaModelException e) {
					// cannot analyse
				}
			}
		}

		@SuppressWarnings("basecall")
		callin VirtualMarker report(String message, int line, int severity, int fixId, Element element, String attrName, String category) {
			if (fixId == PDEMarkerFactory.M_DISCOURAGED_CLASS) {
				if (matchElementPath(element, new String[] {ASPECT_BINDING, TEAM, SUPER_BASE}, 2))
					return null; // don't report restriction inside aspectBinding/superBase
			}
			return base.report(message, line, severity, fixId, element, attrName, category);
		}

		private boolean matchElementPath(Element cur, String[] containerTags, int idx) {
			if (idx < 0)
				return true;
			if (!containerTags[idx].equals(cur.getTagName()))
				return false;
			Node parentNode = cur.getParentNode();
			if (parentNode instanceof Element)
				return matchElementPath((Element) parentNode, containerTags, idx-1);
			return false;
		}
	}
	
	/**
	 * Validates whether activation policy is set if needed.
     * This role is only active for bundles with one or more aspect bindings.
	 */
	protected class BundleErrorReporter playedBy BundleErrorReporter 
			base when (BundleValidation.this.bundleContext.get().isAspectBundle)
	{			
		@SuppressWarnings("decapsulation")
		void addMarkerAttribute(VirtualMarker marker, String attr, String val)
			-> void addMarkerAttribute(VirtualMarker marker, String attr, String val);
		@SuppressWarnings("decapsulation")
		IHeader getHeader(String key) -> IHeader getHeader(String key);
		VirtualMarker report(String message, int line, int severity, int resolution, String category) 
			-> VirtualMarker report(String message, int line, int severity, int resolution, String category);
		
		void validateBundleActivatorPolicy() <- after void validateBundleActivatorPolicy();
		
		void validateBundleActivatorPolicy() 
		{
			IHeader header = getHeader(Constants.BUNDLE_ACTIVATIONPOLICY);
			int lineNo = 1;
			if (header != null) {
				if (Constants.ACTIVATION_LAZY.equals(header.getValue()))
					return; // OK!
				lineNo = header.getLineNumber()+1;
			}
			boolean hasTeamActivation = BundleValidation.this.bundleContext.get().hasTeamActivation;
			report(OTPDEUIMessages.Validation_MissingActivationPolicy_error, 
				   lineNo, 
				   hasTeamActivation ? CompilerFlags.ERROR : CompilerFlags.WARNING, 	// only severe if relevant team activation is requested. 
				   ADD_ACTIVATION_POLICY, 
				   PDEMarkerFactory.CAT_FATAL);
		}

		void validateExportPackages() <- after void validateExportPackages();

		void validateExportPackages() {
			Set<String> needingExport = bundleContext.get().aspectPackages;
			if (needingExport.isEmpty()) return;
			IHeader header = getHeader(Constants.EXPORT_PACKAGE);
			if (header != null) {
				ManifestElement[] elements = header.getElements();
				for (int i = 0; i < elements.length; i++)
					needingExport.remove(elements[i].getValue());
			}
			for (String unmatched : needingExport) {
				VirtualMarker marker = report(NLS.bind(OTPDEUIMessages.Validation_MissingAspectPackageExport_error, unmatched), 
						   1, 
						   CompilerFlags.ERROR, 	// can reduce severity when we have the option to add the export at runtime 
						   ADD_PACKAGE_EXPORT, 
						   PDEMarkerFactory.CAT_FATAL);
				addMarkerAttribute(marker, "package", unmatched); //$NON-NLS-1$

				IHeader aspectBundleName = getHeader(Constants.BUNDLE_SYMBOLICNAME);
				if (aspectBundleName != null && aspectBundleName.getValue() != null) {
					String bundleSymbolicName = aspectBundleName.getValue();
					int semi = bundleSymbolicName.indexOf(';');
					if (semi != -1)
						bundleSymbolicName = bundleSymbolicName.substring(0, semi); // strip of attributes/directives like ;singleton:=true
					addMarkerAttribute(marker, "packages", unmatched+";ot-aspect-host=\""+bundleSymbolicName+"\""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				}
				else {
					addMarkerAttribute(marker, "packages", unmatched); //$NON-NLS-1$
				}
			}
		}
	}

	/** Unbound role: simple rewriting of the manifest to add or correct an activation policy header. */
	protected class SetActivationPolicyResolution extends AbstractManifestMarkerResolution 
	{	
		public SetActivationPolicyResolution(int type) {
			super(type);
		}
	
		protected void createChange(BundleModel model) {
	
			IManifestHeader header = model.getBundle().getManifestHeader(Constants.BUNDLE_ACTIVATIONPOLICY);
	
			if (header != null && header instanceof BundleActivationPolicyHeader)
				((BundleActivationPolicyHeader) header).setLazyStart(true);
			else
				model.getBundle().setHeader(Constants.BUNDLE_ACTIVATIONPOLICY, Constants.ACTIVATION_LAZY);
		}
	
		public String getLabel() {
			return OTPDEUIMessages.Resolution_AddBundleActivationPolicy_label;
		}
	}
	
	/** Unbound role: simple rewriting of the manifest to add an Export-Package header. */
	protected class ExportAspectPackageResolution extends AddExportPackageMarkerResolution {
		String packageName;
		String export; // extended version with ot-aspect-host attribute
		public ExportAspectPackageResolution(IMarker marker) {
			super(marker, AbstractPDEMarkerResolution.CREATE_TYPE); //$NON-NLS-1$
			this.packageName = marker.getAttribute("package", null); //$NON-NLS-1$
			this.export = marker.getAttribute("packages", null); //$NON-NLS-1$
		}
		@Override
		public String getLabel() {
			return NLS.bind(OTPDEUIMessages.Resolution_AddAspectPackageExport_label, packageName);
		}
		@Override
		public String getDescription() {
			return NLS.bind(OTPDEUIMessages.Resolution_AddAspectPackageExport_description, packageName, export);
		}
	}

	/** Unbound role: rewrite the team@class attribute for proper usage of '$' as inner class separator. */
	protected class ChangeDotToDollarResolution extends AbstractXMLMarkerResolution {
		String packageName;
		String teamName;
		String newName;

		public ChangeDotToDollarResolution(IMarker marker) {
			super(CHANGE_DOT_TO_DOLLAR, marker);
			this.packageName = marker.getAttribute("package", null); //$NON-NLS-1$
			this.teamName = marker.getAttribute("team", null); //$NON-NLS-1$
			this.newName = packageName + '.' +teamName.substring(this.packageName.length()+1).replace('.', '$');
		}
		@Override
		public String getLabel() {
			return NLS.bind(OTPDEUIMessages.Resolution_ChangeDotToDollar_label, this.teamName);
		}
		@Override
		public String getDescription() {
			return NLS.bind(OTPDEUIMessages.Resolution_ChangeDotToDollar_description, this.teamName, this.newName);
		}

		@Override
		protected void createChange(IPluginModelBase model) {
			Object node = findNode(model);
			if (!(node instanceof PluginAttribute))
				return;
			
			PluginAttribute attr = (PluginAttribute) node;
			attr.getEnclosingElement().setXMLAttribute(attr.getName(), this.newName);
		}
	}
	
	/**
	 * Advise the base class for handling missing/incorrect activation policy 
	 * (code {@link BundleValidation#ADD_ACTIVATION_POLICY}).
	 */
	protected class ResolutionGenerator playedBy ResolutionGenerator {

		IMarkerResolution[] getResolutions(IMarker marker) <- replace IMarkerResolution[] getResolutions(IMarker marker);
		
		callin IMarkerResolution[] getResolutions(IMarker marker) {
			IMarkerResolution[] result = base.getResolutions(marker);
			if (result.length == 0) {
				int problemID = marker.getAttribute(PDEMarkerFactory.PROBLEM_ID, PDEMarkerFactory.NO_RESOLUTION);
				switch (problemID) {
					case BundleValidation.ADD_ACTIVATION_POLICY :
						return new IMarkerResolution[] {new SetActivationPolicyResolution(AbstractPDEMarkerResolution.CREATE_TYPE)};
					case BundleValidation.ADD_PACKAGE_EXPORT :
						return new IMarkerResolution[] {new ExportAspectPackageResolution(marker) };
					case BundleValidation.CHANGE_DOT_TO_DOLLAR :
						return new IMarkerResolution[] {new ChangeDotToDollarResolution(marker) };
				}
			}
			return result;
		}
	}
}

Back to the top