diff --git a/.mvn/maven.config b/.mvn/maven.config index ffe18b8db0..31f4b1ab22 100644 --- a/.mvn/maven.config +++ b/.mvn/maven.config @@ -1,4 +1,5 @@ -Pbuild-individual-bundles +-Ptck -Dcompare-version-with-baselines.skip=false -DtrimStackTrace=false --fail-at-end diff --git a/ds/org.eclipse.pde.ds.annotations/.classpath b/ds/org.eclipse.pde.ds.annotations/.classpath index 81fe078c20..3e81dc0dba 100644 --- a/ds/org.eclipse.pde.ds.annotations/.classpath +++ b/ds/org.eclipse.pde.ds.annotations/.classpath @@ -1,7 +1,17 @@ - + + + + + + + + + + + diff --git a/ds/org.eclipse.pde.ds.annotations/META-INF/MANIFEST.MF b/ds/org.eclipse.pde.ds.annotations/META-INF/MANIFEST.MF index 7d6e04c539..dcd0e4f0c4 100644 --- a/ds/org.eclipse.pde.ds.annotations/META-INF/MANIFEST.MF +++ b/ds/org.eclipse.pde.ds.annotations/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Bundle-Name Bundle-SymbolicName: org.eclipse.pde.ds.annotations;singleton:=true -Bundle-Version: 1.3.100.qualifier +Bundle-Version: 1.3.200.qualifier Bundle-Activator: org.eclipse.pde.ds.internal.annotations.Activator Bundle-Vendor: %Bundle-Vendor Require-Bundle: org.eclipse.ui;bundle-version="[3.105.0,4.0.0)", diff --git a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/AnnotationVisitor.java b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/AnnotationVisitor.java index ddb1ad1f4b..a42643c91a 100644 --- a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/AnnotationVisitor.java +++ b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/AnnotationVisitor.java @@ -35,9 +35,15 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.concurrent.CountDownLatch; +import java.util.function.Function; +import java.util.function.Supplier; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.eclipse.core.filebuffers.FileBuffers; import org.eclipse.core.filebuffers.ITextFileBuffer; @@ -59,14 +65,19 @@ import org.eclipse.jdt.core.dom.Expression; import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.IAnnotationBinding; +import org.eclipse.jdt.core.dom.IBinding; import org.eclipse.jdt.core.dom.IMemberValuePairBinding; 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.MarkerAnnotation; import org.eclipse.jdt.core.dom.MemberValuePair; import org.eclipse.jdt.core.dom.MethodDeclaration; import org.eclipse.jdt.core.dom.Modifier; import org.eclipse.jdt.core.dom.NormalAnnotation; +import org.eclipse.jdt.core.dom.SingleMemberAnnotation; +import org.eclipse.jdt.core.dom.SingleVariableDeclaration; +import org.eclipse.jdt.core.dom.StringLiteral; import org.eclipse.jdt.core.dom.TypeDeclaration; import org.eclipse.jdt.core.dom.VariableDeclarationFragment; import org.eclipse.jface.text.BadLocationException; @@ -84,6 +95,7 @@ import org.eclipse.pde.internal.core.text.IDocumentObject; import org.eclipse.pde.internal.core.text.IDocumentTextNode; import org.eclipse.pde.internal.core.text.IModelTextChangeListener; +import org.eclipse.pde.internal.ds.core.IDSBundleProperties; import org.eclipse.pde.internal.ds.core.IDSComponent; import org.eclipse.pde.internal.ds.core.IDSConstants; import org.eclipse.pde.internal.ds.core.IDSDocumentFactory; @@ -95,6 +107,7 @@ import org.eclipse.pde.internal.ds.core.IDSProvide; import org.eclipse.pde.internal.ds.core.IDSReference; import org.eclipse.pde.internal.ds.core.IDSService; +import org.eclipse.pde.internal.ds.core.IDSSingleProperty; import org.eclipse.pde.internal.ds.core.text.DSModel; import org.eclipse.pde.internal.ui.util.TextUtil; import org.eclipse.text.edits.MalformedTreeException; @@ -106,6 +119,12 @@ @SuppressWarnings("restriction") public class AnnotationVisitor extends ASTVisitor { + private static final String MAP_TYPE = Map.class.getName(); + + private static final String BUNDLE_CONTEXT = BundleContext.class.getName(); + + private static final String DEFAULT_ACTIVATE_METHOD_NAME = "activate"; + private static final String COMPONENT_CONTEXT = "org.osgi.service.component.ComponentContext"; //$NON-NLS-1$ private static final String COMPONENT_ANNOTATION = DSAnnotationCompilationParticipant.COMPONENT_ANNOTATION; @@ -118,6 +137,8 @@ public class AnnotationVisitor extends ASTVisitor { private static final String REFERENCE_ANNOTATION = "org.osgi.service.component.annotations.Reference"; //$NON-NLS-1$ + private static final String COMPONENT_PROPERTY_TYPE_ANNOTATION = "org.osgi.service.component.annotations.ComponentPropertyType"; + private static final String DESIGNATE_ANNOTATION = "org.osgi.service.metatype.annotations.Designate"; //$NON-NLS-1$ private static final Pattern PID_PATTERN = Pattern.compile("[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)*"); //$NON-NLS-1$ @@ -211,10 +232,12 @@ public boolean visit(TypeDeclaration type) { boolean isAbstract = false; boolean isNested = false; boolean noDefaultConstructor = false; + boolean hasInjectableConstructor = false; if ((isInterface = type.isInterface()) || (isAbstract = Modifier.isAbstract(type.getModifiers())) || (isNested = (!type.isPackageMemberTypeDeclaration() && !isNestedPublicStatic(type))) - || (noDefaultConstructor = !hasDefaultConstructor(type))) { + || (noDefaultConstructor = !(hasDefaultConstructor(type) + || (hasInjectableConstructor = hasInjectableConstructor(type, problemReporter))))) { // interfaces, abstract types, non-static/non-public nested types, or types with no default constructor cannot be components if (!errorLevel.isIgnore()) { if (isInterface) { @@ -224,7 +247,21 @@ public boolean visit(TypeDeclaration type) { } else if (isNested) { problemReporter.reportProblem(annotation, null, NLS.bind(Messages.AnnotationProcessor_invalidCompImplClass_notTopLevel, type.getName().getIdentifier()), type.getName().getIdentifier()); } else if (noDefaultConstructor) { - problemReporter.reportProblem(annotation, null, NLS.bind(Messages.AnnotationProcessor_invalidCompImplClass_noDefaultConstructor, type.getName().getIdentifier()), type.getName().getIdentifier()); + if (specVersion.isEqualOrHigherThan(DSAnnotationVersion.V1_4)) { + problemReporter.reportProblem(annotation, null, + NLS.bind(Messages.AnnotationProcessor_invalidCompImplClass_compatibleConstructor, + type.getName().getIdentifier()), + type.getName().getIdentifier()); + } else { + if (hasInjectableConstructor) { + // TODO we should add an error marker that offers a quickfix to upgrade the spec + // version to 1.4 + } + problemReporter.reportProblem(annotation, null, + NLS.bind(Messages.AnnotationProcessor_invalidCompImplClass_noDefaultConstructor, + type.getName().getIdentifier()), + type.getName().getIdentifier()); + } } else { problemReporter.reportProblem(annotation, null, NLS.bind(Messages.AnnotationProcessor_invalidComponentImplementationClass, type.getName().getIdentifier()), type.getName().getIdentifier()); } @@ -313,20 +350,6 @@ private boolean isNestedPublicStatic(AbstractTypeDeclaration type) { return false; } - private boolean hasDefaultConstructor(TypeDeclaration type) { - boolean hasConstructor = false; - for (MethodDeclaration method : type.getMethods()) { - if (method.isConstructor()) { - hasConstructor = true; - if (Modifier.isPublic(method.getModifiers()) && method.parameters().isEmpty()) { - return true; - } - } - } - - return !hasConstructor; - } - private void processComponent(TypeDeclaration type, ITypeBinding typeBinding, Annotation annotation, IAnnotationBinding annotationBinding) throws CoreException { // determine component name HashMap params = new HashMap<>(); @@ -546,42 +569,17 @@ private void processComponent(IDSModel model, TypeDeclaration type, ITypeBinding } } - String[] properties; - if ((value = params.get("property")) instanceof Object[]) { //$NON-NLS-1$ - Object[] elements = (Object[]) value; - ArrayList list = new ArrayList<>(elements.length); - for (Object element : elements) { - if (element instanceof String) { - list.add((String) element); - } - } - - properties = list.toArray(new String[list.size()]); - } else { - properties = new String[0]; - } - - String[] propertyFiles; - if ((value = params.get("properties")) instanceof Object[]) { //$NON-NLS-1$ - Object[] elements = (Object[]) value; - ArrayList list = new ArrayList<>(elements.length); - for (Object element : elements) { - if (element instanceof String) { - list.add((String) element); - } - } + String[] properties = collectProperties("property", params); + String[] factoryProperties = collectProperties("factoryProperty", params); - propertyFiles = list.toArray(new String[list.size()]); - validateComponentPropertyFiles(annotation, ((IType) typeBinding.getJavaElement()).getJavaProject().getProject(), propertyFiles); - } else { - propertyFiles = new String[0]; - } + String[] propertyFiles = collectPropertiesFiles("properties", typeBinding, annotation, params); + String[] factoryPropertyFiles = collectPropertiesFiles("factoryProperties", typeBinding, annotation, params); String configPolicy = null; if ((value = params.get("configurationPolicy")) instanceof IVariableBinding) { //$NON-NLS-1$ IVariableBinding configPolicyBinding = (IVariableBinding) value; configPolicy = DSEnums.getConfigurationPolicy(configPolicyBinding.getName()); - } else if (specVersion == DSAnnotationVersion.V1_3) { + } else if (DSAnnotationVersion.V1_3.isEqualOrHigherThan(specVersion)) { for (IAnnotationBinding typeAnnotation : typeBinding.getAnnotations()) { if (!DESIGNATE_ANNOTATION.equals(typeAnnotation.getAnnotationType().getQualifiedName())) { continue; @@ -605,12 +603,71 @@ private void processComponent(IDSModel model, TypeDeclaration type, ITypeBinding DSAnnotationVersion requiredVersion = DSAnnotationVersion.V1_1; + // The following changes where made between 1.0 (Compendium 4.1) and 1.1 + // (compendium 4.2) we must check these + // if we really want to support such old DS versions, currently we just use 1.0 + // as lowest version because the OSGi TCKs would fail otherwise: + // (1) Definition of the Service-Component header now uses the definition of a + // header from the module layer. It also allows a wildcards to be used in the + // last component of the path of a header entry. + // FIXME if manifest uses a wildcard we can assume that 1.1 is minimum + // (2) SCR must follow the recommendations of Property Propagation on page + // 86 and not propagate properties whose names start with ’.’ to service + // properties. + // XXX this is probably something we can't assert here + // (3) The component description now allows for a configuration policy to + // control whether component configurations are activated when + // Configuration object are present or not. + // FIXME if configuration policy is used 1.1 is the minimum + // (4) The component description now allows the names of the activate and + // deactivate methods to be specified. The signatures of the activate and + // deactivate methods are also modified. + // FIXME a custom name of the methods or the "modified signatures) need to + // trigger minimum of 1.1 + // (5) The signatures of the bind and unbind methods are modified. + // FIXME check if a "new" signature is used + // (6) The definition of accessible methods for activate, deactivate, bind and + // unbind methods is expanded to include any method accessible from the + // component implementation class. This allows private and package + // private method declared in the component implementation class to be + // used. + // FIXME if non public methods are used for bind/unbind we must require DS 1.1 + // (7) The additional signatures and additional accessibility for the activate, + // deactivate, bind and unbind methods can cause problems for compo- + // nents written to version 1.0 of this specification. The behavior in this + // specification only applies to component descriptions using the v1.1.0 + // namespace. + // This is something not controlled by the generator and don't needs further + // actions + // (8) The XML schema and namespace have been updated to v1.1.0. It now + // supports extensibility for new attributes and elements. The name + // attribute of the component element is now optional and the default + // value of this attribute is the value of the class attribute of the nested + // implementation element. The name attribute of the reference element + // is now optional and the default value of this attribute is the value of the + // interface attribute of the reference element. The Char type for the + // property element has been renamed Character to match the Java type + // name. The attributes configuration-policy, activate, deactivate and + // modified have been added to the component element. + // FIXME usage of 'Character' type in properties require 1.1 + // (9) When logging error messages, SCR must use a Log Service obtained + // using the component’s bundle context so that the resulting Log Entry is + // associated with the component’s bundle. + // This do not affect the generator + // (10) Clarified that target properties are component properties that can be + // set + // wherever component properties can be set, including configurations. + // This do not affect the generator + // (11) A component configuration can now avoid being deactivated when a + // Configuration changes by specifying the modified attribute. + // FIXME a modified method should require SCR 1.1 + String configPid = null; if ((value = params.get("configurationPid")) instanceof String) { //$NON-NLS-1$ configPid = (String) value; validateComponentConfigPID(annotation, configPid, -1); requiredVersion = DSAnnotationVersion.V1_2; - } else if (specVersion == DSAnnotationVersion.V1_3 && value instanceof Object[]) { + } else if (DSAnnotationVersion.V1_3.isEqualOrHigherThan(specVersion) && value instanceof Object[]) { Object[] configPidElems = (Object[]) value; if (configPidElems.length > 0) { LinkedHashSet configPids = new LinkedHashSet<>(configPidElems.length); @@ -657,7 +714,8 @@ private void processComponent(IDSModel model, TypeDeclaration type, ITypeBinding } String serviceScope = null; - if (specVersion == DSAnnotationVersion.V1_3 && (value = params.get("scope")) instanceof IVariableBinding) { //$NON-NLS-1$ + if (DSAnnotationVersion.V1_3.isEqualOrHigherThan(specVersion) + && (value = params.get("scope")) instanceof IVariableBinding) { //$NON-NLS-1$ IVariableBinding serviceScopeBinding = (IVariableBinding) value; serviceScope = DSEnums.getServiceScope(serviceScopeBinding.getName()); if (!errorLevel.isIgnore()) { @@ -669,7 +727,8 @@ private void processComponent(IDSModel model, TypeDeclaration type, ITypeBinding } } - if (specVersion == DSAnnotationVersion.V1_3 && serviceFactory != null && serviceScope != null && !serviceScope.equals(VALUE_SERVICE_SCOPE_DEFAULT)) { + if (DSAnnotationVersion.V1_3.isEqualOrHigherThan(specVersion) && serviceFactory != null && serviceScope != null + && !serviceScope.equals(VALUE_SERVICE_SCOPE_DEFAULT)) { // ignore servicefactory if scope specified and not <> if (!errorLevel.isIgnore() && !serviceFactory.equals(VALUE_SERVICE_SCOPE_BUNDLE.equals(serviceScope))) { problemReporter.reportProblem(annotation, "servicefactory", -1, true, errorLevel, Messages.AnnotationVisitor_invalidServiceFactory_ignored); //$NON-NLS-1$ @@ -778,27 +837,9 @@ private void processComponent(IDSModel model, TypeDeclaration type, ITypeBinding HashMap referenceNames = new HashMap<>(); IDSReference[] refElements = component.getReferences(); - HashMap refMap = new HashMap<>(refElements.length); - for (IDSReference refElement : refElements) { - String referenceName = refElement.getReferenceName(); - if (referenceName == null) { - String referenceBind = refElement.getXMLAttributeValue(ReferenceProcessor.ATTRIBUTE_REFERENCE_FIELD); - if (referenceBind != null) { - referenceName = ReferenceProcessor.getReferenceName(referenceBind); - } + HashMap refMap = buildReferenceMap(refElements); - if (referenceName == null) { - referenceName = refElement.getReferenceBind(); - if (referenceName == null) { - referenceName = refElement.getReferenceInterface(); - } - } - } - - refMap.put(referenceName, refElement); - } - - if (annotation.isNormalAnnotation() && specVersion == DSAnnotationVersion.V1_3) { + if (annotation.isNormalAnnotation() && DSAnnotationVersion.V1_3.isEqualOrHigherThan(specVersion)) { for (Object annotationValue : ((NormalAnnotation) annotation).values()) { MemberValuePair annotationMemberValuePair = (MemberValuePair) annotationValue; if (!ATTRIBUTE_COMPONENT_REFERENCE.equals(annotationMemberValuePair.getName().getIdentifier())) { @@ -848,12 +889,13 @@ private void processComponent(IDSModel model, TypeDeclaration type, ITypeBinding references.add(reference); ReferenceProcessor referenceProcessor = new ReferenceProcessor(this, specVersion, requiredVersion, errorLevel, state.getMissingUnbindMethodLevel(), problemReporter); - requiredVersion = requiredVersion.max(referenceProcessor.processReference(reference, typeBinding, referenceAnnotation, referenceAnnotationBinding, annotationParams, referenceNames)); + requiredVersion = requiredVersion.max(referenceProcessor.processReference(reference, typeBinding, + referenceAnnotation, referenceAnnotationBinding, annotationParams, referenceNames)); } } } - - if (specVersion == DSAnnotationVersion.V1_3) { + List activations = new ArrayList<>(); + if (DSAnnotationVersion.V1_3.isEqualOrHigherThan(specVersion)) { for (FieldDeclaration field : type.getFields()) { for (Object modifier : field.modifiers()) { if (!(modifier instanceof Annotation)) { @@ -871,54 +913,84 @@ private void processComponent(IDSModel model, TypeDeclaration type, ITypeBinding } String annotationName = fieldAnnotationBinding.getAnnotationType().getQualifiedName(); - if (!REFERENCE_ANNOTATION.equals(annotationName)) { - continue; - } + if (REFERENCE_ANNOTATION.equals(annotationName)) { + HashMap annotationParams = null; + // TODO do we really care about all fragments?? + for (Object fragmentElement : field.fragments()) { + VariableDeclarationFragment fragment = (VariableDeclarationFragment) fragmentElement; + IVariableBinding fieldBinding = fragment.resolveBinding(); + if (fieldBinding == null) { + if (debug.isDebugging()) { + debug.trace(String.format("Unable to resolve binding for field: %s", fragment)); //$NON-NLS-1$ + } + + continue; + } - HashMap annotationParams = null; - // TODO do we really care about all fragments?? - for (Object fragmentElement : field.fragments()) { - VariableDeclarationFragment fragment = (VariableDeclarationFragment) fragmentElement; - IVariableBinding fieldBinding = fragment.resolveBinding(); - if (fieldBinding == null) { - if (debug.isDebugging()) { - debug.trace(String.format("Unable to resolve binding for field: %s", fragment)); //$NON-NLS-1$ + if (annotationParams == null) { + annotationParams = new HashMap<>(); + for (IMemberValuePairBinding pair : fieldAnnotationBinding + .getDeclaredMemberValuePairs()) { + annotationParams.put(pair.getName(), pair.getValue()); + } } - continue; - } + String referenceName = (String) annotationParams.get("name"); //$NON-NLS-1$ + if (referenceName == null) { + referenceName = fieldBinding.getName(); + } - if (annotationParams == null) { - annotationParams = new HashMap<>(); - for (IMemberValuePairBinding pair : fieldAnnotationBinding.getDeclaredMemberValuePairs()) { - annotationParams.put(pair.getName(), pair.getValue()); + IDSReference reference = refMap.remove(referenceName); + if (reference == null) { + reference = createReference(dsFactory); } - } - String referenceName = (String) annotationParams.get("name"); //$NON-NLS-1$ - if (referenceName == null) { - referenceName = fieldBinding.getName(); - } + references.add(reference); - IDSReference reference = refMap.remove(referenceName); - if (reference == null) { - reference = createReference(dsFactory); + ReferenceProcessor referenceProcessor = new ReferenceProcessor(this, specVersion, + requiredVersion, errorLevel, state.getMissingUnbindMethodLevel(), problemReporter); + DSAnnotationVersion impliedVersion = referenceProcessor.processReference(reference, field, + field.getModifiers(), fieldBinding, + fieldAnnotation, fieldAnnotationBinding, annotationParams, referenceNames); + requiredVersion = impliedVersion.max(requiredVersion); + } + } else if (ACTIVATE_ANNOTATION.equals(annotationName)) { + for (Object fragmentElement : field.fragments()) { + VariableDeclarationFragment fragment = (VariableDeclarationFragment) fragmentElement; + IVariableBinding fieldBinding = fragment.resolveBinding(); + if (fieldBinding == null) { + if (debug.isDebugging()) { + debug.trace(String.format("Unable to resolve binding for field: %s", fragment)); //$NON-NLS-1$ + } + continue; + } + if (DSAnnotationVersion.V1_4.isEqualOrHigherThan(specVersion)) { + String fieldName = fieldBinding.getName(); + ITypeBinding binding = field.getType().resolveBinding(); + // Check if activation object and add to fields... + if (isActivationObject(binding)) { + if (Modifier.isStatic(field.getModifiers())) { + problemReporter.reportProblem(fieldAnnotation, null, + Messages.AnnotationProcessor_invalidActivate_staticField); + } else { + activations.add(new ComponentActivationAnnotation(fieldName, fieldAnnotation, + null, binding)); + } + } else { + problemReporter.reportProblem(fieldAnnotation, null, + Messages.AnnotationProcessor_invalidActivateField, fieldName); + } + requiredVersion = DSAnnotationVersion.V1_4.max(requiredVersion); + } else { + problemReporter.reportProblem(fieldAnnotation, null, + Messages.AnnotationProcessor_invalidActivate); + } } - - references.add(reference); - - ReferenceProcessor referenceProcessor = new ReferenceProcessor(this, specVersion, requiredVersion, errorLevel, state.getMissingUnbindMethodLevel(), problemReporter); - referenceProcessor.processReference(reference, field, fieldBinding, fieldAnnotation, fieldAnnotationBinding, annotationParams, referenceNames); - requiredVersion = DSAnnotationVersion.V1_3; } } } } - String activate = null; - boolean lookedForActivateMethod = false; - IMethodBinding activateMethod = null; - Annotation activateAnnotation = null; String deactivate = null; boolean lookedForDeactivateMethod = false; IMethodBinding deactivateMethod = null; @@ -926,6 +998,8 @@ private void processComponent(IDSModel model, TypeDeclaration type, ITypeBinding String modified = null; IMethodBinding modifiedMethod = null; Annotation modifiedAnnotation = null; + + for (MethodDeclaration method : type.getMethods()) { for (Object modifier : method.modifiers()) { @@ -946,29 +1020,29 @@ private void processComponent(IDSModel model, TypeDeclaration type, ITypeBinding String annotationName = methodAnnotationBinding.getAnnotationType().getQualifiedName(); if (ACTIVATE_ANNOTATION.equals(annotationName)) { - if (activate == null) { - activate = method.getName().getIdentifier(); - if (specVersion == DSAnnotationVersion.V1_3) { - activateMethod = method.resolveBinding(); - } - - activateAnnotation = methodAnnotation; - validateLifeCycleMethod(methodAnnotation, "activate", method); //$NON-NLS-1$ - } else if (!errorLevel.isIgnore()) { - problemReporter.reportProblem(methodAnnotation, null, Messages.AnnotationProcessor_duplicateActivateMethod, method.getName().getIdentifier()); - if (activateAnnotation != null) { - problemReporter.reportProblem(activateAnnotation, null, Messages.AnnotationProcessor_duplicateActivateMethod, activate); - activateAnnotation = null; - } + ComponentActivationAnnotation activation; + String activate = method.getName().getIdentifier(); + if (DSAnnotationVersion.V1_3.isEqualOrHigherThan(specVersion)) { + activation = new ComponentActivationAnnotation(activate, methodAnnotation, method, + method.resolveBinding()); + } else { + // prior to 1.3 only an 'activate' method is allowed + activation = new ComponentActivationAnnotation(activate, methodAnnotation, null, + findLifeCycleMethod(typeBinding, DEFAULT_ACTIVATE_METHOD_NAME)); + } + activations.add(activation); + if (DSAnnotationVersion.V1_4.isEqualOrHigherThan(specVersion) && method.isConstructor()) { + // will validate later... + } else { + validateLifeCycleMethod(methodAnnotation, DEFAULT_ACTIVATE_METHOD_NAME, method); // $NON-NLS-1$ } - continue; } if (DEACTIVATE_ANNOTATION.equals(annotationName)) { if (deactivate == null) { deactivate = method.getName().getIdentifier(); - if (specVersion == DSAnnotationVersion.V1_3) { + if (DSAnnotationVersion.V1_3.isEqualOrHigherThan(specVersion)) { deactivateMethod = method.resolveBinding(); } @@ -988,7 +1062,7 @@ private void processComponent(IDSModel model, TypeDeclaration type, ITypeBinding if (MODIFIED_ANNOTATION.equals(annotationName)) { if (modified == null) { modified = method.getName().getIdentifier(); - if (specVersion == DSAnnotationVersion.V1_3) { + if (DSAnnotationVersion.V1_3.isEqualOrHigherThan(specVersion)) { modifiedMethod = method.resolveBinding(); } @@ -1035,15 +1109,93 @@ private void processComponent(IDSModel model, TypeDeclaration type, ITypeBinding } } - if (activate == null) { - // only remove activate="activate" if method not found - if (!"activate".equals(component.getActivateMethod()) //$NON-NLS-1$ - || ((lookedForActivateMethod = true) - && (activateMethod = findLifeCycleMethod(typeBinding, "activate")) == null)) { //$NON-NLS-1$ - removeAttribute(component, IDSConstants.ATTRIBUTE_COMPONENT_ACTIVATE, null); + if (activations.isEmpty()) { + // lets see if we can find one... + IMethodBinding binding = findLifeCycleMethod(typeBinding, DEFAULT_ACTIVATE_METHOD_NAME); + if (binding != null) { + ComponentActivationAnnotation activation = new ComponentActivationAnnotation( + DEFAULT_ACTIVATE_METHOD_NAME, null, null, binding); + activations.add(activation); } + } + ComponentActivationAnnotation activateMethod = validateOnlyOne( activations.stream().filter(ca -> ca.isMethod()).toList()); + ComponentActivationAnnotation activateConstructor = validateOnlyOne( + activations.stream().filter(ca -> ca.isConstructor()).toList()); + // The fields are processed in lexicographical order, using String.compareTo, of + // the field names + List activateFields = activations.stream().filter(ca -> ca.isType()) + .sorted(Comparator.comparing(ComponentActivationAnnotation::activate)).toList(); + if (activateMethod == null || DEFAULT_ACTIVATE_METHOD_NAME.equals(activateMethod.activate())) { + removeAttribute(component, IDSConstants.ATTRIBUTE_COMPONENT_ACTIVATE, null); } else { - component.setActivateMethod(activate); + component.setActivateMethod(activateMethod.activate()); + } + if (activateConstructor == null || activateConstructor.parameterCount() == 0) { + removeAttribute(component, IDSConstants.ATTRIBUTE_COMPONENT_INIT, null); + } else { + component.setXMLAttribute(IDSConstants.ATTRIBUTE_COMPONENT_INIT, + Integer.toString(activateConstructor.parameterCount())); + } + if (activateFields.isEmpty()) { + removeAttribute(component, IDSConstants.ATTRIBUTE_COMPONENT_ACTIVATION_FIELDS, null); + } else { + component.setXMLAttribute(IDSConstants.ATTRIBUTE_COMPONENT_ACTIVATION_FIELDS, activateFields.stream() + .map(ComponentActivationAnnotation::activate).collect(Collectors.joining(" "))); + } + + if (DSAnnotationVersion.V1_4.isEqualOrHigherThan(specVersion) && activateConstructor != null) { + MethodDeclaration method = activateConstructor.method(); + @SuppressWarnings("unchecked") + List parameters = method.parameters(); + for (int i = 0; i < parameters.size(); i++) { + SingleVariableDeclaration parameter = parameters.get(i); + IVariableBinding variableBinding = parameter.resolveBinding(); + Optional referenceAnnotation = annotations(parameter.modifiers()) + .filter(a -> isReferenceAnnotation(a.resolveAnnotationBinding())).findFirst(); + if (referenceAnnotation.isEmpty()) { + if (isActivationObject(variableBinding.getType())) { + // That is okay! + continue; + } else { + // the spec requires @Reference annotation on non activation objects! + problemReporter.reportProblem(activateConstructor.annotation(), null, + Messages.AnnotationProcessor_invalidConstructorArgument, parameter.getName().toString(), + Integer.toString(i)); + } + } else { + Annotation constructorParameterAnnotation = referenceAnnotation.get(); + IAnnotationBinding constructorParameterAnnotationBinding = constructorParameterAnnotation + .resolveAnnotationBinding(); + if (constructorParameterAnnotationBinding == null) { + if (debug.isDebugging()) { + debug.trace(String.format("Unable to resolve binding for parameter: %s", parameter)); //$NON-NLS-1$ + } + continue; + } + Map annotationParams = new HashMap<>(); + for (IMemberValuePairBinding pair : constructorParameterAnnotationBinding + .getDeclaredMemberValuePairs()) { + annotationParams.put(pair.getName(), pair.getValue()); + } + String referenceName = (String) annotationParams.get("name"); //$NON-NLS-1$ + if (referenceName == null) { + referenceName = variableBinding.getName(); + } + IDSReference reference = refMap.remove(referenceName); + if (reference == null) { + reference = createReference(dsFactory); + } + references.add(reference); + ReferenceProcessor referenceProcessor = new ReferenceProcessor(this, specVersion, requiredVersion, + errorLevel, state.getMissingUnbindMethodLevel(), problemReporter); + referenceProcessor.processReference(reference, parameter, parameter.getModifiers(), variableBinding, + constructorParameterAnnotation, constructorParameterAnnotationBinding, annotationParams, + referenceNames); + reference.setXMLAttribute("parameter", Integer.toString(i)); + removeAttribute(reference, ReferenceProcessor.ATTRIBUTE_REFERENCE_FIELD, null); + requiredVersion = DSAnnotationVersion.V1_4.max(requiredVersion); + } + } } if (deactivate == null) { @@ -1064,42 +1216,190 @@ private void processComponent(IDSModel model, TypeDeclaration type, ITypeBinding } LinkedHashMap newPropMap = new LinkedHashMap<>(); - - if (specVersion == DSAnnotationVersion.V1_3) { + // see 112.8.3 Ordering of Generated Component Properties ... + if (DSAnnotationVersion.V1_3.isEqualOrHigherThan(specVersion)) { // collect component property types from activate, modified, and deactivate methods - if (activateMethod == null && !lookedForActivateMethod) { - activateMethod = findLifeCycleMethod(typeBinding, "activate"); //$NON-NLS-1$ - } - - if (deactivateMethod == null && !lookedForDeactivateMethod) { - deactivateMethod = findLifeCycleMethod(typeBinding, "deactivate"); //$NON-NLS-1$ - } - HashSet cptClosure = new HashSet<>(); + // 1. Properties defined through component property types used as the type of an + // activation object. + if (DSAnnotationVersion.V1_4.isEqualOrHigherThan(specVersion)) { + if (activateConstructor != null) { + // 1 a) The component property types used as parameters to the constructor. + requiredVersion = DSAnnotationVersion.V1_4.max(requiredVersion); + collectProperties(activateConstructor.binding(), dsFactory, newPropMap, cptClosure); + } + // 1 b) The component property types used as activation fields. + for (ComponentActivationAnnotation activateField : activateFields) { + requiredVersion = DSAnnotationVersion.V1_4.max(requiredVersion); + collectProperties(activateField.binding(), dsFactory, newPropMap, cptClosure); + } + } + // 1 c) The component property types used as parameters to the activate method. if (activateMethod != null) { - collectProperties(activateMethod, dsFactory, newPropMap, cptClosure); + collectProperties(activateMethod.binding(), dsFactory, newPropMap, cptClosure); } - + // 1 d) The component property types used as parameters to the modified method. if (modifiedMethod != null) { collectProperties(modifiedMethod, dsFactory, newPropMap, cptClosure); } - + // 1 e) The component property types used as parameters to the deactivate method + if (deactivateMethod == null && !lookedForDeactivateMethod) { + deactivateMethod = findLifeCycleMethod(typeBinding, "deactivate"); //$NON-NLS-1$ + } if (deactivateMethod != null) { collectProperties(deactivateMethod, dsFactory, newPropMap, cptClosure); } - if (!cptClosure.isEmpty()) { - requiredVersion = DSAnnotationVersion.V1_3; + requiredVersion = DSAnnotationVersion.V1_3.max(requiredVersion); + } + // 2) Properties defined through component property types annotating the + // component implementation class. + if (DSAnnotationVersion.V1_4.isEqualOrHigherThan(specVersion)) { + List propertyTypeAnnotations = annotations(type.modifiers()) + .filter(a -> isComponentPropertyType(a.resolveTypeBinding())).toList(); + for (Annotation propertyType : propertyTypeAnnotations) { + requiredVersion = DSAnnotationVersion.V1_4.max(requiredVersion); + collectComponentPropertyTypes(dsFactory, newPropMap, propertyType); + } } } + // 3) property element of the Component annotation. + updateProperties(model, type, annotation, value, properties, component, dsFactory::createProperty, + component.getPropertyElements(), newPropMap); + updateProperties(model, type, annotation, value, factoryProperties, component, dsFactory::createFactoryProperty, + component.getFactoryPropertyElements(), new LinkedHashMap<>()); + // 4) properties element of the Component annotation. + updatePropertyFiles(propertyFiles, component, dsFactory::createProperties, component.getPropertiesElements()); + updatePropertyFiles(factoryPropertyFiles, component, dsFactory::createFactoryProperties, + component.getFactoryPropertiesElements()); + if (factoryPropertyFiles.length > 0 || factoryProperties.length > 0) { + requiredVersion = DSAnnotationVersion.V1_4.max(requiredVersion); + } - IDSProperty[] propElements = component.getPropertyElements(); + if (references.isEmpty()) { + removeChildren(component, Arrays.asList(refElements)); + } else { + // references must be declared in ascending lexicographical order of their names + Collections.sort(references, REF_NAME_COMPARATOR); + + int firstPos; + if (refElements.length == 0) { + // insert first reference element after service element, or (if not present) last property or properties + service = component.getService(); + if (service == null) { + firstPos = Math.max(0, indexOfLastPropertyOrProperties(component)); + } else { + firstPos = component.indexOf(service) + 1; + } + } else { + firstPos = component.indexOf(refElements[0]); + } + + removeChildren(component, refMap.values()); + + addOrMoveChildren(component, references, firstPos); + } + + IDSImplementation impl = component.getImplementation(); + if (impl == null) { + impl = dsFactory.createImplementation(); + component.setImplementation(impl); + } + + impl.setClassName(implClass); + + if (DSAnnotationVersion.V1_4.isEqualOrHigherThan(specVersion)) { + if (activateConstructor != null) { + requiredVersion = DSAnnotationVersion.V1_4.max(requiredVersion); + } + // TODO using logger component also requires 1.4! + } + + String xmlns = requiredVersion.getNamespace(); + if ((value = params.get("xmlns")) instanceof String) { //$NON-NLS-1$ + xmlns = (String) value; + validateComponentXMLNS(annotation, xmlns, requiredVersion); + } + component.setNamespace(xmlns); + } + + private void collectComponentPropertyTypes(IDSDocumentFactory dsFactory, + LinkedHashMap newPropMap, Annotation propertyType) { + String fqdn = propertyType.getTypeName().getFullyQualifiedName(); + ITypeBinding propertyTypeBinding = propertyType.resolveTypeBinding(); + String prefix = getPrefix(propertyTypeBinding); + IMethodBinding[] methods = propertyTypeBinding.getDeclaredMethods(); + Map map = Arrays.stream(methods) + .map(methodBinding -> createProperty(methodBinding, prefix, dsFactory)) + .filter(Objects::nonNull).collect(Collectors.toMap(IDSProperty::getName, + Function.identity(), (a, b) -> a, LinkedHashMap::new)); + if (propertyType instanceof NormalAnnotation normal) { + @SuppressWarnings("unchecked") + List values = normal.values(); + for (MemberValuePair pair : values) { + String propName = pair.getName().getFullyQualifiedName(); + String propValue = getExpressionValue(pair.getValue()); + String key = NameGenerator.createPropertyName(propName, prefix, specVersion); + map.get(key).setPropertyValue(propValue); + } + } + if (propertyType instanceof MarkerAnnotation marker && map.isEmpty()) { + IDSProperty property = dsFactory.createProperty(); + property.setPropertyName(NameGenerator.createClassPropertyName(fqdn, prefix)); + property.setPropertyType(IDSConstants.VALUE_PROPERTY_TYPE_BOOLEAN); + property.setPropertyValue(String.valueOf(Boolean.TRUE)); + newPropMap.remove(property.getName()); // force re-insert (append) + newPropMap.put(property.getName(), property); + return; + } + if (propertyType instanceof SingleMemberAnnotation single && map.size() == 1) { + IDSProperty property = dsFactory.createProperty(); + property.setPropertyName(NameGenerator.createClassPropertyName(fqdn, prefix)); + Expression expression = single.getValue(); + property.setPropertyType(getPropertyType(expression.resolveTypeBinding())); + property.setPropertyValue(getExpressionValue(expression)); + newPropMap.remove(property.getName()); // force re-insert (append) + newPropMap.put(property.getName(), property); + return; + } + for (IDSProperty prop : map.values()) { + newPropMap.remove(prop.getName()); // force re-insert (append) + newPropMap.put(prop.getName(), prop); + } + } + + private String getExpressionValue(Expression expression) { + if (expression instanceof StringLiteral string) { + return string.getLiteralValue(); + } + return expression.toString(); + } + + private String getPrefix(ITypeBinding typeBinding) { + if (DSAnnotationVersion.V1_4.isEqualOrHigherThan(specVersion)) { + IVariableBinding[] fields = typeBinding.getDeclaredFields(); + for (IVariableBinding binding : fields) { + String name = binding.getName(); + if ("PREFIX_".equals(name)) { + if (binding.getConstantValue() instanceof String prefix) { + return prefix; + } + } + } + } + return null; + } + + private void updateProperties(IDSModel model, TypeDeclaration type, + Annotation annotation, Object value, + String[] properties, IDSComponent component, Supplier factory, T[] propElements, + LinkedHashMap newPropMap) { if (newPropMap.isEmpty() && properties.length == 0) { removeChildren(component, Arrays.asList(propElements)); } else { // build up new property elements - LinkedHashMap map = new LinkedHashMap<>(properties.length); + LinkedHashMap map = new LinkedHashMap<>(properties.length); for (int i = 0; i < properties.length; ++i) { String propertyStr = properties[i]; String[] pair = propertyStr.split("=", 2); //$NON-NLS-1$ @@ -1114,11 +1414,24 @@ private void processComponent(IDSModel model, TypeDeclaration type, ITypeBinding } String propertyValue = pair.length > 1 ? pair[1].trim() : null; + if (propertyValue != null && IDSConstants.VALUE_PROPERTY_TYPE_CHAR.equals(propertyType)) { + // according to the spec a char must be encoded as its unicode point: For + // Character types, the conversion must be handled by Integer.valueOf method, a + // Character is always represented by its Unicode value. + if (propertyValue.length() == 0 || propertyValue.length() > 1) { + problemReporter.reportProblem(annotation, "property", i, //$NON-NLS-1$ + NLS.bind(Messages.AnnotationProcessor_invalidComponentPropertyValue, type, value), + String.valueOf(value)); + } else { + char c = propertyValue.charAt(0); + propertyValue = Integer.toString(c); + } + } - IDSProperty property = map.get(propertyName); + T property = map.get(propertyName); if (property == null) { // create a new property - property = dsFactory.createProperty(); + property = factory.get(); map.put(propertyName, property); property.setPropertyName(propertyName); if (propertyType == null) { @@ -1155,18 +1468,22 @@ private void processComponent(IDSModel model, TypeDeclaration type, ITypeBinding } // reconcile against existing property elements - HashMap propMap = new HashMap<>(propElements.length); - for (IDSProperty propElement : propElements) { - propMap.put(propElement.getPropertyName(), propElement); + HashMap propMap = new HashMap<>(propElements.length); + for (T propElement : propElements) { + T put = propMap.put(propElement.getPropertyName(), propElement); + if (put != null) { + // duplicate entry + removeChildren(component, List.of(put)); + } } newPropMap.keySet().removeAll(map.keySet()); // force re-insert (append) newPropMap.putAll(map); - ArrayList propList = new ArrayList<>(newPropMap.values()); - for (ListIterator i = propList.listIterator(); i.hasNext();) { - IDSProperty newProperty = i.next(); - IDSProperty property = propMap.remove(newProperty.getPropertyName()); + ArrayList propList = new ArrayList<>(newPropMap.values()); + for (ListIterator i = propList.listIterator(); i.hasNext();) { + T newProperty = i.next(); + T property = propMap.remove(newProperty.getPropertyName()); if (property == null) { continue; } @@ -1205,32 +1522,56 @@ private void processComponent(IDSModel model, TypeDeclaration type, ITypeBinding addOrMoveChildren(component, propList, firstPos); } + } - IDSProperties[] propFileElements = component.getPropertiesElements(); + private String[] collectProperties(String key, Map params) { + Object value = params.get(key); + String[] properties; + if (value instanceof Object[]) { // $NON-NLS-1$ + Object[] elements = (Object[]) value; + ArrayList list = new ArrayList<>(elements.length); + for (Object element : elements) { + if (element instanceof String) { + list.add((String) element); + } + } + + properties = list.toArray(new String[list.size()]); + } else { + properties = new String[0]; + } + return properties; + } + + private void updatePropertyFiles(String[] propertyFiles, IDSComponent component, + Supplier factory, T[] propFileElements) { if (propertyFiles.length == 0) { removeChildren(component, Arrays.asList(propFileElements)); } else { - HashMap propFileMap = new HashMap<>(propFileElements.length); - for (IDSProperties propFileElement : propFileElements) { - propFileMap.put(propFileElement.getEntry(), propFileElement); + HashMap propFileMap = new HashMap<>(propFileElements.length); + for (T propFileElement : propFileElements) { + T put = propFileMap.put(propFileElement.getEntry(), propFileElement); + if (put != null) { + // duplicate entry! + removeChildren(component, List.of(put)); + } } - ArrayList propFileList = new ArrayList<>(propertyFiles.length); + ArrayList propFileList = new ArrayList<>(propertyFiles.length); for (String propertyFile : propertyFiles) { - IDSProperties propertiesElement = propFileMap.remove(propertyFile); + T propertiesElement = propFileMap.remove(propertyFile); if (propertiesElement == null) { - propertiesElement = dsFactory.createProperties(); + propertiesElement = factory.get(); propertiesElement.setInTheModel(false); // note: workaround for PDE bug propertiesElement.setEntry(propertyFile); } - propFileList.add(propertiesElement); } int firstPos; if (propFileElements.length == 0) { // insert first properties element after last property or (if none) first child of component - propElements = component.getPropertyElements(); + IDSProperty[] propElements = component.getPropertyElements(); firstPos = propElements.length == 0 ? 0 : component.indexOf(propElements[propElements.length - 1]) + 1; } else { firstPos = component.indexOf(propFileElements[0]); @@ -1240,46 +1581,65 @@ private void processComponent(IDSModel model, TypeDeclaration type, ITypeBinding addOrMoveChildren(component, propFileList, firstPos); } + } - if (references.isEmpty()) { - removeChildren(component, Arrays.asList(refElements)); - } else { - // references must be declared in ascending lexicographical order of their names - Collections.sort(references, REF_NAME_COMPARATOR); - - int firstPos; - if (refElements.length == 0) { - // insert first reference element after service element, or (if not present) last property or properties - service = component.getService(); - if (service == null) { - firstPos = Math.max(0, indexOfLastPropertyOrProperties(component)); - } else { - firstPos = component.indexOf(service) + 1; + private String[] collectPropertiesFiles(String key, ITypeBinding typeBinding, Annotation annotation, + Map params) { + Object value = params.get(key); + String[] propertyFiles; + if (value instanceof Object[]) { // $NON-NLS-1$ + Object[] elements = (Object[]) value; + ArrayList list = new ArrayList<>(elements.length); + for (Object element : elements) { + if (element instanceof String) { + list.add((String) element); } - } else { - firstPos = component.indexOf(refElements[0]); } - removeChildren(component, refMap.values()); - - addOrMoveChildren(component, references, firstPos); + propertyFiles = list.toArray(new String[list.size()]); + validateComponentPropertyFiles(key, annotation, + ((IType) typeBinding.getJavaElement()).getJavaProject().getProject(), propertyFiles); + } else { + propertyFiles = new String[0]; } + return propertyFiles; + } - IDSImplementation impl = component.getImplementation(); - if (impl == null) { - impl = dsFactory.createImplementation(); - component.setImplementation(impl); - } + private HashMap buildReferenceMap(IDSReference[] refElements) { + HashMap refMap = new HashMap<>(refElements.length); + for (IDSReference refElement : refElements) { + String referenceName = refElement.getReferenceName(); + if (referenceName == null) { + String referenceBind = refElement.getXMLAttributeValue(ReferenceProcessor.ATTRIBUTE_REFERENCE_FIELD); + if (referenceBind != null) { + referenceName = ReferenceProcessor.getReferenceName(referenceBind); + } - impl.setClassName(implClass); + if (referenceName == null) { + referenceName = refElement.getReferenceBind(); + if (referenceName == null) { + referenceName = refElement.getReferenceInterface(); + } + } + } - String xmlns = requiredVersion.getNamespace(); - if ((value = params.get("xmlns")) instanceof String) { //$NON-NLS-1$ - xmlns = (String) value; - validateComponentXMLNS(annotation, xmlns, requiredVersion); + refMap.put(referenceName, refElement); } + return refMap; + } - component.setNamespace(xmlns); + private ComponentActivationAnnotation validateOnlyOne(List list) { + if (list.isEmpty()) { + return null; + } + if (list.size() == 1 || errorLevel.isIgnore()) { + return list.get(0); + } + for (ComponentActivationAnnotation a : list) { + problemReporter.reportProblem(a.annotation(), null, Messages.AnnotationProcessor_duplicateActivateMethod, + a.activate()); + } + return null; } private IDSReference createReference(IDSDocumentFactory dsFactory) { @@ -1439,83 +1799,107 @@ private String normalizePropertyElemBody(String content) { return buf.toString(); } - private void collectProperties(IMethodBinding method, IDSDocumentFactory factory, Map properties, Collection visited) { - for (ITypeBinding paramTypeBinding : method.getParameterTypes()) { + private DSAnnotationVersion collectProperties(IBinding binding, IDSDocumentFactory factory, + Map properties, + Collection visited) { + DSAnnotationVersion version = DSAnnotationVersion.V1_3; + ITypeBinding[] parameterTypes; + if (binding instanceof IMethodBinding method) { + parameterTypes = method.getParameterTypes(); + } else if (binding instanceof ITypeBinding type) { + parameterTypes = new ITypeBinding[] { type }; + } else { + // unsupported binding... + return version; + } + for (ITypeBinding paramTypeBinding : parameterTypes) { if (!paramTypeBinding.isAnnotation() || !visited.add(paramTypeBinding)) { continue; } - - for (IMethodBinding methodBinding : paramTypeBinding.getDeclaredMethods()) { - if (!methodBinding.isAnnotationMember()) { + String prefix = getPrefix(paramTypeBinding); + if (prefix != null) { + version = DSAnnotationVersion.V1_4.max(version); + } + IMethodBinding[] declaredMethods = paramTypeBinding.getDeclaredMethods(); + if (DSAnnotationVersion.V1_4.isEqualOrHigherThan(specVersion)) { + if (declaredMethods.length == 0) { + // a marker annotation! Actually the spec says it is not useful to have these on + // methods but the TCK do so... + // See https://github.com/osgi/osgi/issues/640 + version = DSAnnotationVersion.V1_4.max(version); + IDSProperty property = factory.createProperty(); + property.setPropertyName(NameGenerator.createClassPropertyName(paramTypeBinding.getName(), prefix)); + property.setPropertyType(IDSConstants.VALUE_PROPERTY_TYPE_BOOLEAN); + property.setPropertyValue(String.valueOf(Boolean.TRUE)); + properties.remove(property.getName()); // force re-insert (append) + properties.put(property.getName(), property); continue; } - - Object value = methodBinding.getDefaultValue(); - if (value == null) { + if (declaredMethods.length == 1 && "value".equals(declaredMethods[0].getName())) { + // a single member annotation + version = DSAnnotationVersion.V1_4.max(version); + IDSProperty property = createProperty(declaredMethods[0], prefix, factory); + property.setPropertyName(NameGenerator.createClassPropertyName(paramTypeBinding.getName(), prefix)); + properties.remove(property.getName()); // force re-insert (append) + properties.put(property.getName(), property); continue; } - - ITypeBinding returnType = methodBinding.getReturnType(); - if (returnType.isArray() ? returnType.getElementType().isAnnotation() : returnType.isAnnotation()) { - // TODO per spec we should report error, but we may have no annotation to report it on! - continue; - } - - IDSProperty property = factory.createProperty(); - property.setPropertyName(createPropertyName(methodBinding.getName())); - property.setPropertyType(getPropertyType(returnType)); - - if (returnType.isArray()) { - StringBuilder body = new StringBuilder(); - for (Object item : ((Object[]) value)) { - String itemValue = getPropertyValue(item); - if (itemValue == null || (itemValue = itemValue.trim()).isEmpty()) { - continue; - } - - if (body.length() > 0) { - body.append(TextUtil.getDefaultLineDelimiter()); - } - - body.append(itemValue); - } - - removeAttribute(property, IDSConstants.ATTRIBUTE_PROPERTY_VALUE, null); - property.setPropertyElemBody(body.toString()); - } else { - property.setPropertyValue(getPropertyValue(value)); + } + for (IMethodBinding methodBinding : declaredMethods) { + IDSProperty property = createProperty(methodBinding, prefix, factory); + if (property != null + && (property.getPropertyElemBody() != null || property.getPropertyValue() != null)) { + properties.remove(property.getName()); // force re-insert (append) + properties.put(property.getName(), property); } - - properties.remove(property.getName()); // force re-insert (append) - properties.put(property.getName(), property); } } + return version; } - private String createPropertyName(String name) { - StringBuilder buf = new StringBuilder(name.length()); - char[] chars = name.toCharArray(); - for (int i = 0, n = chars.length; i < n; ++i) { - if (chars[i] == '$') { - if (i == n - 1 || chars[i + 1] != '$') { - continue; - } + private IDSProperty createProperty(IMethodBinding methodBinding, String prefix, IDSDocumentFactory factory) { + if (!methodBinding.isAnnotationMember()) { + return null; + } + ITypeBinding returnType = methodBinding.getReturnType(); + if (returnType.isArray() ? returnType.getElementType().isAnnotation() : returnType.isAnnotation()) { + // TODO per spec we should report error, but we may have no annotation to report + // it on! + return null; + } + Object value = methodBinding.getDefaultValue(); + String propertyName = NameGenerator.createPropertyName(methodBinding.getName(), prefix, specVersion); + String propertyType = getPropertyType(returnType); + IDSProperty property = factory.createProperty(); + property.setPropertyName(propertyName); + property.setPropertyType(propertyType); + if (value == null) { + removeAttribute(property, IDSConstants.ATTRIBUTE_PROPERTY_VALUE, null); + } else { + if (returnType.isArray()) { + StringBuilder body = new StringBuilder(); + for (Object item : ((Object[]) value)) { + String itemValue = getPropertyValue(item); + if (itemValue == null || (itemValue = itemValue.trim()).isEmpty()) { + continue; + } - i++; - } else if (chars[i] == '_') { - if (i == n - 1 || chars[i + 1] != '_') { - chars[i] = '.'; - } else { - i++; + if (body.length() > 0) { + body.append(TextUtil.getDefaultLineDelimiter()); + } + + body.append(itemValue); } + removeAttribute(property, IDSConstants.ATTRIBUTE_PROPERTY_VALUE, null); + property.setPropertyElemBody(body.toString()); + } else { + property.setPropertyValue(getPropertyValue(value)); } - - buf.append(chars[i]); } - - return buf.toString(); + return property; } + private String getPropertyType(ITypeBinding type) { if (type.isArray()) { return getPropertyType(type.getElementType()); @@ -1542,6 +1926,12 @@ private String getPropertyValue(Object value) { if (value instanceof ITypeBinding) { return ((ITypeBinding) value).getQualifiedName(); } + if (value instanceof Character character) { + // according to the spec a char must be encoded as its unicode point: For + // Character types, the conversion must be handled by Integer.valueOf method, a + // Character is always represented by its Unicode value. + return Integer.toString(character.charValue()); + } // everything else return String.valueOf(value); @@ -1601,7 +1991,7 @@ private void validateComponentProperty(Annotation annotation, String name, Strin } } - private void validateComponentPropertyFiles(Annotation annotation, IProject project, String[] files) { + private void validateComponentPropertyFiles(String key, Annotation annotation, IProject project, String[] files) { if (errorLevel.isIgnore()) { return; } @@ -1610,7 +2000,8 @@ private void validateComponentPropertyFiles(Annotation annotation, IProject proj String file = files[i]; IFile wsFile = PDEProject.getBundleRelativeFile(project, IPath.fromOSString(file)); if (!wsFile.exists()) { - problemReporter.reportProblem(annotation, "properties", i, NLS.bind(Messages.AnnotationProcessor_invalidComponentPropertyFile, file), file); //$NON-NLS-1$ + problemReporter.reportProblem(annotation, key, i, + NLS.bind(Messages.AnnotationProcessor_invalidComponentPropertyFile, file, key), file); // $NON-NLS-1$ } } } @@ -1645,6 +2036,10 @@ private void validateLifeCycleMethod(Annotation annotation, String methodName, M return; } + if (methodBinding.isConstructor()) { + problemReporter.reportProblem(annotation, methodName, + Messages.AnnotationProcessor_invalidLifecycleMethod_noMethod); + } if (Modifier.isStatic(methodBinding.getModifiers())) { problemReporter.reportProblem(annotation, methodName, Messages.AnnotationProcessor_invalidLifecycleMethod_static); @@ -1674,11 +2069,11 @@ private void validateLifeCycleMethod(Annotation annotation, String methodName, M String paramTypeName = paramTypeErasure.isMember() ? paramTypeErasure.getBinaryName() : paramTypeErasure.getQualifiedName(); boolean isDuplicate = false; - if (paramTypeBinding.isAnnotation() && specVersion == DSAnnotationVersion.V1_3) { + if (paramTypeBinding.isAnnotation() && DSAnnotationVersion.V1_3.isEqualOrHigherThan(specVersion)) { if (!annotationParams.add(paramTypeBinding)) { isDuplicate = true; } - } else if (Map.class.getName().equals(paramTypeName)) { + } else if (MAP_TYPE.equals(paramTypeName)) { if (hasMap) { isDuplicate = true; } else { @@ -1690,7 +2085,7 @@ private void validateLifeCycleMethod(Annotation annotation, String methodName, M } else { hasCompCtx = true; } - } else if (BundleContext.class.getName().equals(paramTypeName)) { + } else if (BUNDLE_CONTEXT.equals(paramTypeName)) { if (hasBundleCtx) { isDuplicate = true; } else { @@ -1728,7 +2123,8 @@ private IMethodBinding findLifeCycleMethod(ITypeBinding componentClass, String m HashSet annotationParams = new HashSet<>(1); for (ITypeBinding paramTypeBinding : paramTypeBindings) { if (paramTypeBinding.isAnnotation()) { - if (specVersion == DSAnnotationVersion.V1_3 && annotationParams.add(paramTypeBinding)) { + if (DSAnnotationVersion.V1_3.isEqualOrHigherThan(specVersion) + && annotationParams.add(paramTypeBinding)) { // component property type (multiple arguments allowed) continue; } @@ -1740,7 +2136,7 @@ private IMethodBinding findLifeCycleMethod(ITypeBinding componentClass, String m ITypeBinding paramTypeErasure = paramTypeBinding.getErasure(); String paramTypeName = paramTypeErasure.isMember() ? paramTypeErasure.getBinaryName() : paramTypeErasure.getQualifiedName(); - if (Map.class.getName().equals(paramTypeName)) { + if (MAP_TYPE.equals(paramTypeName)) { if (hasMap) { isInvalid = true; } else { @@ -1752,7 +2148,7 @@ private IMethodBinding findLifeCycleMethod(ITypeBinding componentClass, String m } else { hasCompCtx = true; } - } else if (BundleContext.class.getName().equals(paramTypeName)) { + } else if (BUNDLE_CONTEXT.equals(paramTypeName)) { if (hasBundleCtx) { isInvalid = true; } else { @@ -1782,4 +2178,91 @@ private IMethodBinding findLifeCycleMethod(ITypeBinding componentClass, String m return null; } + + /** + * An injectable constructor is one annotated with @Activate + * + * @param type + * @param problemReporter2 + * @return + */ + private static boolean hasInjectableConstructor(TypeDeclaration type, ProblemReporter problemReporter) { + for (MethodDeclaration method : type.getMethods()) { + if (method.isConstructor() + && annotations(method.modifiers()).map(Annotation::resolveAnnotationBinding) + .anyMatch(AnnotationVisitor::isActivateAnnotation)) { + return true; + } + } + return false; + } + + private static boolean hasDefaultConstructor(TypeDeclaration type) { + boolean hasConstructor = false; + for (MethodDeclaration method : type.getMethods()) { + if (method.isConstructor()) { + hasConstructor = true; + if (Modifier.isPublic(method.getModifiers()) && method.parameters().isEmpty()) { + return true; + } + } + } + + return !hasConstructor; + } + + private static Stream annotations(List modifiers) { + return modifiers.stream().filter(Annotation.class::isInstance).map(Annotation.class::cast); + } + + private static boolean isActivateAnnotation(IAnnotationBinding binding) { + return binding != null && ACTIVATE_ANNOTATION.equals(binding.getAnnotationType().getQualifiedName()); + } + + private static boolean isReferenceAnnotation(IAnnotationBinding binding) { + return binding != null && REFERENCE_ANNOTATION.equals(binding.getAnnotationType().getQualifiedName()); + } + + private static boolean isComponentPropertyType(IAnnotationBinding binding) { + return binding != null + && COMPONENT_PROPERTY_TYPE_ANNOTATION.equals(binding.getAnnotationType().getQualifiedName()); + } + + /** + * Check if the given {@link ITypeBinding} is an Activation + * Object + * + * @param param the binding to check + * @return true if this is an Activation Object, false + * otherwise + */ + private static boolean isActivationObject(ITypeBinding param) { + String binaryName = param.getErasure().getBinaryName(); + if (COMPONENT_CONTEXT.equals(binaryName) || BUNDLE_CONTEXT.equals(binaryName) || MAP_TYPE.equals(binaryName)) { + return true; + } + return param.isAnnotation(); + } + + /** + * Check if the given {@link ITypeBinding} is a Component + * Property Type + * + * @param param the binding to check + * @return true if this is a Component Property Type + * false otherwise + */ + private static boolean isComponentPropertyType(ITypeBinding param) { + if (param != null) { + IAnnotationBinding[] annotations = param.getAnnotations(); + for (IAnnotationBinding annotationAnnotation : annotations) { + if (isComponentPropertyType(annotationAnnotation)) { + return true; + } + } + } + return false; + } } \ No newline at end of file diff --git a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/ComponentActivationAnnotation.java b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/ComponentActivationAnnotation.java new file mode 100644 index 0000000000..2735e8f403 --- /dev/null +++ b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/ComponentActivationAnnotation.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2023 Christoph Läubrich and others. + * + * 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 + * + * Contributors: + * Christoph Läubrich - initial API and implementation + *******************************************************************************/ +package org.eclipse.pde.ds.internal.annotations; + +import org.eclipse.jdt.core.dom.Annotation; +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.MethodDeclaration; + +/** + * This capture the context of an @Activate annotated method or + * field binding + */ +record ComponentActivationAnnotation(String activate, Annotation annotation, MethodDeclaration method, + IBinding binding) { + + public boolean isMethod() { + if (binding instanceof IMethodBinding method) { + return !((IMethodBinding) binding).isConstructor(); + } + return false; + } + + public boolean isConstructor() { + if (binding instanceof IMethodBinding method) { + return ((IMethodBinding) binding).isConstructor(); + } + return false; + } + + public boolean isType() { + return binding instanceof ITypeBinding method; + } + + public int parameterCount() { + if (binding instanceof IMethodBinding method) { + return method.getParameterNames().length; + } + return 0; + } + +} diff --git a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/DSAnnotationPropertyPage.java b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/DSAnnotationPropertyPage.java index 498e1b0e6e..71219a38b3 100644 --- a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/DSAnnotationPropertyPage.java +++ b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/DSAnnotationPropertyPage.java @@ -15,6 +15,7 @@ package org.eclipse.pde.ds.internal.annotations; import java.util.Arrays; +import java.util.List; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ProjectScope; @@ -27,9 +28,16 @@ import org.eclipse.jdt.core.JavaCore; import org.eclipse.jface.dialogs.ControlEnableState; import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.DialogPage; import org.eclipse.jface.layout.LayoutConstants; import org.eclipse.jface.preference.IPreferencePageContainer; import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.ComboViewer; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.LabelProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.window.Window; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; @@ -74,7 +82,7 @@ public class DSAnnotationPropertyPage extends PropertyPage implements IWorkbench private Text pathText; - private Combo specVersionCombo; + private ComboViewer specVersionCombo; private Combo errorLevelCombo; @@ -232,12 +240,36 @@ public void widgetSelected(SelectionEvent e) { specVersionLabel.setText(Messages.DSAnnotationPropertyPage_specVersionLabel_text); specVersionLabel.setFont(JFaceResources.getDialogFont()); - specVersionCombo = new Combo(optionBlockControl, SWT.DROP_DOWN | SWT.READ_ONLY | SWT.BORDER); - specVersionCombo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false)); - specVersionCombo.setFont(JFaceResources.getDialogFont()); - specVersionCombo.add("1.3"); //$NON-NLS-1$ - specVersionCombo.add("1.2"); //$NON-NLS-1$ - specVersionCombo.select(0); + specVersionCombo = new ComboViewer(optionBlockControl, SWT.DROP_DOWN | SWT.READ_ONLY | SWT.BORDER); + specVersionCombo.getControl().setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false)); + specVersionCombo.getControl().setFont(JFaceResources.getDialogFont()); + specVersionCombo.setContentProvider(ArrayContentProvider.getInstance()); + specVersionCombo.setInput(List.of(DSAnnotationVersion.V1_2, DSAnnotationVersion.V1_3, DSAnnotationVersion.V1_4, + DSAnnotationVersion.V1_4)); + specVersionCombo.setSelection(new StructuredSelection(DSAnnotationVersion.V1_5)); + specVersionCombo.addSelectionChangedListener(new ISelectionChangedListener() { + + @Override + public void selectionChanged(SelectionChangedEvent event) { + DSAnnotationVersion version = (DSAnnotationVersion) event.getStructuredSelection().getFirstElement(); + if (version == DSAnnotationVersion.V1_5) { + setMessage("Specification version " + version.getSpecificationVersion() + + " is currently not fully supported.", DialogPage.WARNING); + } else { + setMessage(null); + } + + } + }); + specVersionCombo.setLabelProvider(new LabelProvider() { + @Override + public String getText(Object element) { + if (element instanceof DSAnnotationVersion version) { + return version.getSpecificationVersion(); + } + return ""; + } + }); Label errorLevelLabel = new Label(optionBlockControl, SWT.LEFT); errorLevelLabel.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); @@ -279,7 +311,7 @@ private void refreshWidgets() { boolean enableValue = prefs.getBoolean(Activator.PREF_ENABLED, false); String pathValue = prefs.get(Activator.PREF_PATH, Activator.DEFAULT_PATH); - String specVersion = prefs.get(Activator.PREF_SPEC_VERSION, DSAnnotationVersion.V1_3.name()); + String specVersion = prefs.get(Activator.PREF_SPEC_VERSION, DSAnnotationVersion.V1_4.name()); String errorLevel = prefs.get(Activator.PREF_VALIDATION_ERROR_LEVEL, ValidationErrorLevel.error.name()); String missingUnbindMethodLevel = prefs.get(Activator.PREF_MISSING_UNBIND_METHOD_ERROR_LEVEL, errorLevel); boolean generateBAPL = prefs.getBoolean(Activator.PREF_GENERATE_BAPL, true); @@ -303,10 +335,10 @@ private void refreshWidgets() { try { specVersionEnum = DSAnnotationVersion.valueOf(specVersion); } catch (IllegalArgumentException e) { - specVersionEnum = DSAnnotationVersion.V1_3; + specVersionEnum = DSAnnotationVersion.V1_4; } - specVersionCombo.select(DSAnnotationVersion.V1_3.ordinal() - specVersionEnum.ordinal()); + specVersionCombo.setSelection(new StructuredSelection(specVersionEnum)); errorLevelCombo.select(getEnumIndex(errorLevel, ValidationErrorLevel.values(), 0)); missingUnbindMethodCombo.select(getEnumIndex(missingUnbindMethodLevel, ValidationErrorLevel.values(), 0)); enableBAPLGeneration.setSelection(generateBAPL); @@ -443,9 +475,9 @@ public boolean performOk() { prefs.putBoolean(Activator.PREF_ENABLED, enableCheckbox.getSelection()); prefs.put(Activator.PREF_PATH, IPath.fromOSString(path).toString()); - DSAnnotationVersion[] versions = DSAnnotationVersion.values(); - int specVersionIndex = Math.max(Math.min(specVersionCombo.getSelectionIndex(), DSAnnotationVersion.V1_3.ordinal()), 0); - prefs.put(Activator.PREF_SPEC_VERSION, versions[DSAnnotationVersion.V1_3.ordinal() - specVersionIndex].name()); + DSAnnotationVersion version = (DSAnnotationVersion) specVersionCombo.getStructuredSelection() + .getFirstElement(); + prefs.put(Activator.PREF_SPEC_VERSION, version.name()); ValidationErrorLevel[] levels = ValidationErrorLevel.values(); int errorLevelIndex = errorLevelCombo.getSelectionIndex(); diff --git a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/DSAnnotationVersion.java b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/DSAnnotationVersion.java index bcfe1c796b..622ae8a165 100644 --- a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/DSAnnotationVersion.java +++ b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/DSAnnotationVersion.java @@ -18,15 +18,23 @@ @SuppressWarnings("restriction") public enum DSAnnotationVersion { - V1_1(IDSConstants.NAMESPACE), + V1_0("1.0", "http://www.osgi.org/xmlns/scr/v1.0.0"), - V1_2("http://www.osgi.org/xmlns/scr/v1.2.0"), //$NON-NLS-1$ + V1_1("1.1", IDSConstants.NAMESPACE), - V1_3("http://www.osgi.org/xmlns/scr/v1.3.0"); //$NON-NLS-1$ + V1_2("1.2", "http://www.osgi.org/xmlns/scr/v1.2.0"), //$NON-NLS-1$ + + V1_3("1.3", "http://www.osgi.org/xmlns/scr/v1.3.0"), //$NON-NLS-1$ + + V1_4("1.4", "http://www.osgi.org/xmlns/scr/v1.4.0"), //$NON-NLS-1$ + + V1_5("1.5", "http://www.osgi.org/xmlns/scr/v1.5.0"); //$NON-NLS-1$ private final String namespace; + private String version; - private DSAnnotationVersion(String namespace) { + private DSAnnotationVersion(String version, String namespace) { + this.version = version; this.namespace = namespace; } @@ -42,6 +50,16 @@ public DSAnnotationVersion max(DSAnnotationVersion other) { return this; } + /** + * Compares this version with another one + * + * @param other + * @return true if this version is higher or equal to this version + */ + public boolean isEqualOrHigherThan(DSAnnotationVersion other) { + return other.compareTo(this) >= 0; + } + public static DSAnnotationVersion fromNamespace(String namespace) { for (DSAnnotationVersion value : values()) { if (value.namespace.equals(namespace)) { @@ -51,4 +69,13 @@ public static DSAnnotationVersion fromNamespace(String namespace) { return null; } + + public String getSpecificationVersion() { + return version; + } + + @Override + public String toString() { + return version + " - " + namespace; + } } diff --git a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/DSEnums.java b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/DSEnums.java index abf0b4b292..f43a6c9b3d 100644 --- a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/DSEnums.java +++ b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/DSEnums.java @@ -51,6 +51,13 @@ public class DSEnums { "UPDATE", "update", // "REPLACE", "replace"); + public static final Map COLLECTION_TYPE_OPTION = Map.of( // + "SERVICE", "service", // + "REFERENCE", "reference", // + "SERVICEOBJECTS", "serviceobjects", // + "PROPERTIES", "properties", // + "TUPLE", "tuple"); + private DSEnums() { } diff --git a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/Messages.java b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/Messages.java index 8e0d9c94c4..306a5ce9ce 100644 --- a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/Messages.java +++ b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/Messages.java @@ -59,8 +59,16 @@ public class Messages extends NLS { public static String AnnotationProcessor_invalidComponentImplementationClass; + public static String AnnotationProcessor_invalidCompImplClass_compatibleConstructor; + public static String AnnotationProcessor_invalidComponentName; + public static String AnnotationProcessor_invalidActivate; + + public static String AnnotationProcessor_invalidActivateField; + + public static String AnnotationProcessor_invalidConstructorArgument; + public static String AnnotationProcessor_invalidComponentProperty_nameRequired; public static String AnnotationProcessor_invalidComponentProperty_valueRequired; @@ -73,6 +81,8 @@ public class Messages extends NLS { public static String AnnotationProcessor_invalidComponentService; + public static String AnnotationProcessor_invalidLifecycleMethod_noMethod; + public static String AnnotationProcessor_invalidLifecycleMethod_static; public static String AnnotationProcessor_invalidLifeCycleMethodParameterType; @@ -115,6 +125,8 @@ public class Messages extends NLS { public static String AnnotationProcessor_invalidReference_staticField; + public static String AnnotationProcessor_invalidActivate_staticField; + public static String AnnotationProcessor_invalidReference_serviceType; public static String AnnotationProcessor_invalidReference_serviceUnknown; diff --git a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/NameGenerator.java b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/NameGenerator.java new file mode 100644 index 0000000000..ece613610c --- /dev/null +++ b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/NameGenerator.java @@ -0,0 +1,141 @@ +/******************************************************************************* + * Copyright (c) 2023 Christoph Läubrich and others. + * + * 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 + * + * Contributors: + * Christoph Läubrich - initial API and implementation + *******************************************************************************/ +package org.eclipse.pde.ds.internal.annotations; + +public class NameGenerator { + + public static String createClassPropertyName(String name, String prefix) { + // see + // https://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.component.html#service.component-component.property.mapping + // > single-element annotation + StringBuilder buf = new StringBuilder(name.length() * 2); + if (prefix != null) { + // rule 4: If the component property type declares a PREFIX_ field whose value + // is a compile-time constant String, then the property name is prefixed with + // the value of the PREFIX_ field. + buf.append(prefix); + } + char[] chars = name.toCharArray(); + char last = 'X'; + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + if (Character.isLowerCase(last) && Character.isUpperCase(c)) { + // Rule 1: When a lower case character is followed by an upper case character, a + // full stop ('.' \u002E) is inserted between them. + buf.append('.'); + } + // Rule 2: Each upper case character is converted to lower case. + buf.append(Character.toLowerCase(c)); + // Rule 3: All other characters are unchanged. + // --> nothing to do + last = c; + } + return buf.toString(); + } + + public static String createPropertyName(String name, String prefix, DSAnnotationVersion specVersion) { + if (DSAnnotationVersion.V1_4.isEqualOrHigherThan(specVersion)) { + // See + // https://docs.osgi.org/specification/osgi.cmpn/7.0.0/service.component.html#service.component-component.property.mapping + StringBuilder sb; + if (prefix == null) { + sb = new StringBuilder(name.length()); + } else { + // Rule 4: If the component property type declares a PREFIX_ field whose value + // is a compile-time constant String, then the property name is prefixed with + // the value of the PREFIX_ field. + sb = new StringBuilder(name.length() + prefix.length()); + sb.append(prefix); + } + char[] chars = name.toCharArray(); + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + if (c == '$') { + // Rule 1: A single dollar sign ('$' \u0024) is removed unless it is followed by + if (i < chars.length - 1) { + char n = chars[i + 1]; + if (n == '_') { + // Rule 1.1: A low line ('_' \u005F) and a dollar sign in which case the three + // consecutive characters ("$_$") are converted to a single hyphen-minus ('-' + // \u002D). + if (i < chars.length - 2 && chars[i + 2] == '$') { + i = i + 2; + sb.append('-'); + } + } else if (n == '$') { + // Rule 1.2 : another dollar sign in which case the two consecutive dollar signs + // ("$$") are converted to a single dollar sign. + i = i + 1; + sb.append('$'); + } + } + continue; + } + if (c == '_') { + // Rule 2: A single low line ('_' \u005F) is converted into a full stop ('.' + // \u002E) unless is it followed by an- + // other low line in which case the two consecutive low lines ("__") are + // converted to a single low + // line. + char n = chars[i + 1]; + if (i < chars.length - 1 && n == '_') { + i = i + 1; + sb.append('_'); + } else { + sb.append('.'); + } + continue; + } + // Rule 3: All other characters are unchanged. + sb.append(chars[i]); + } + return sb.toString(); + } else { + StringBuilder sb = new StringBuilder(name.length()); + char[] chars = name.toCharArray(); + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + if (c == '$') { + // Rule 1: A single dollar sign ('$' \u0024) is removed unless it is followed by + // another dollar sign in which + // case the two consecutive dollar signs ("$$") are converted to a single dollar + // sign. + if (i < chars.length - 1 && chars[i + 1] == '$') { + i = i + 1; + sb.append('$'); + } + continue; + } + if (c == '_') { + // Rule 2: A single low line ('_' \u005F) is converted into a full stop ('.' + // \u002E) unless is it followed by an- + // other low line in which case the two consecutive low lines ("__") are + // converted to a single low + // line. + if (i < chars.length - 1 && chars[i + 1] == '_') { + i = i + 1; + sb.append('_'); + } else { + sb.append('.'); + } + continue; + } + // Rule 3: All other characters are unchanged. + sb.append(chars[i]); + } + return sb.toString(); + } + } + +} diff --git a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/ReferenceProcessor.java b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/ReferenceProcessor.java index 58fc1706b8..ec8a78f464 100644 --- a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/ReferenceProcessor.java +++ b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/ReferenceProcessor.java @@ -21,8 +21,8 @@ import java.util.Map; import org.eclipse.jdt.core.dom.AST; +import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; -import org.eclipse.jdt.core.dom.FieldDeclaration; import org.eclipse.jdt.core.dom.IAnnotationBinding; import org.eclipse.jdt.core.dom.IMethodBinding; import org.eclipse.jdt.core.dom.ITypeBinding; @@ -39,6 +39,8 @@ @SuppressWarnings("restriction") public class ReferenceProcessor { + private static final String ATTRIBUTE_COLLECTION_TYPE = "collectionType"; + private static final String COMPONENT_SERVICE_OBJECTS = "org.osgi.service.component.ComponentServiceObjects"; //$NON-NLS-1$ private static final String ATTRIBUTE_REFERENCE_POLICY_OPTION = "policy-option"; //$NON-NLS-1$ @@ -53,6 +55,8 @@ public class ReferenceProcessor { private static final String ATTRIBUTE_REFERENCE_FIELD_COLLECTION_TYPE = "field-collection-type"; //$NON-NLS-1$ + private static final String VALUE_REFERENCE_FIELD_COLLECTION_TYPE_SERVICE = "service"; //$NON-NLS-1$ + private static final String VALUE_REFERENCE_FIELD_OPTION_REPLACE = DSEnums.getFieldOption("REPLACE"); //$NON-NLS-1$ private static final String VALUE_REFERENCE_FIELD_OPTION_UPDATE = DSEnums.getFieldOption("UPDATE"); //$NON-NLS-1$ @@ -86,7 +90,7 @@ public DSAnnotationVersion processReference(IDSReference reference, MethodDeclar if ((value = params.get("service")) instanceof ITypeBinding) { //$NON-NLS-1$ serviceType = (ITypeBinding) value; if (!errorLevel.isIgnore() && argTypes.length > 0) { - if (specVersion == DSAnnotationVersion.V1_3) { + if (DSAnnotationVersion.V1_3.isEqualOrHigherThan(specVersion)) { for (ITypeBinding argType : argTypes) { if (!isValidArgumentForService(argType, serviceType)) { problemReporter.reportProblem(annotation, "service", NLS.bind(Messages.AnnotationProcessor_invalidReference_serviceType, argType.getName(), serviceType.getName()), argType.getName(), serviceType.getName()); //$NON-NLS-1$ @@ -104,7 +108,7 @@ public DSAnnotationVersion processReference(IDSReference reference, MethodDeclar } } } else if (argTypes.length > 0) { - if (specVersion == DSAnnotationVersion.V1_3) { + if (DSAnnotationVersion.V1_3.isEqualOrHigherThan(specVersion)) { serviceType = null; for (ITypeBinding argType : argTypes) { String erasure = argType.getErasure().getBinaryName(); @@ -241,7 +245,7 @@ public DSAnnotationVersion processReference(IDSReference reference, MethodDeclar } String referenceScope = null; - if (specVersion == DSAnnotationVersion.V1_3) { + if (DSAnnotationVersion.V1_3.isEqualOrHigherThan(specVersion)) { if ((value = params.get("scope")) instanceof IVariableBinding) { //$NON-NLS-1$ IVariableBinding referenceScopeBinding = (IVariableBinding) value; referenceScope = DSEnums.getReferenceScope(referenceScopeBinding.getName()); @@ -273,7 +277,7 @@ private boolean isValidArgumentForService(ITypeBinding argType, ITypeBinding ser ITypeBinding[] typeArgs; return ((ServiceReference.class.getName().equals(erasure) || COMPONENT_SERVICE_OBJECTS.equals(erasure)) && ((typeArgs = argType.getTypeArguments()).length == 0 || serviceType.isAssignmentCompatible(typeArgs[0]))) - || serviceType.isAssignmentCompatible(argType) + || isValidServiceAssignment(argType, serviceType, specVersion) || Map.class.getName().equals(erasure); } @@ -492,7 +496,11 @@ private void updateFieldAttributes( } } - public void processReference(IDSReference reference, FieldDeclaration field, IVariableBinding fieldBinding, Annotation annotation, IAnnotationBinding annotationBinding, Map params, Map names) { + public DSAnnotationVersion processReference(IDSReference reference, ASTNode field, int modifiers, + IVariableBinding fieldBinding, + Annotation annotation, IAnnotationBinding annotationBinding, Map params, + Map names) { + DSAnnotationVersion requiredVersion = DSAnnotationVersion.V1_3; String cardinality = null; Object value; if ((value = params.get("cardinality")) instanceof IVariableBinding) { //$NON-NLS-1$ @@ -522,7 +530,8 @@ public void processReference(IDSReference reference, FieldDeclaration field, IVa } ITypeBinding serviceType; - if ((value = params.get("service")) instanceof ITypeBinding) { //$NON-NLS-1$ + Object serviceProperty = params.get("service"); + if ((value = serviceProperty) instanceof ITypeBinding) { // $NON-NLS-1$ serviceType = (ITypeBinding) value; if (!errorLevel.isIgnore()) { ITypeBinding targetType = fieldType; @@ -565,8 +574,12 @@ public void processReference(IDSReference reference, FieldDeclaration field, IVa String service = serviceType == null ? null : serviceType.getBinaryName(); - String fieldCollectionType = null; - if (IDSConstants.VALUE_REFERENCE_CARDINALITY_ZERO_N.equals(cardinality) + String fieldCollectionType = DSAnnotationVersion.V1_4.isEqualOrHigherThan(specVersion) + ? getCollectionType(params) + : null; + if (fieldCollectionType != null) { + requiredVersion = requiredVersion.max(DSAnnotationVersion.V1_4); + } else if (IDSConstants.VALUE_REFERENCE_CARDINALITY_ZERO_N.equals(cardinality) || IDSConstants.VALUE_REFERENCE_CARDINALITY_ONE_N.equals(cardinality)) { if (collectionType == null) { collectionType = determineCollectionType(field.getAST(), fieldType); @@ -574,6 +587,11 @@ public void processReference(IDSReference reference, FieldDeclaration field, IVa if (collectionType.getElementType() != null) { fieldCollectionType = getFieldCollectionType(collectionType); + + } + if (fieldCollectionType == null && DSAnnotationVersion.V1_4.isEqualOrHigherThan(specVersion)) { + // TODO workaround for https://github.com/osgi/osgi/issues/641 + fieldCollectionType = "service"; } } @@ -601,7 +619,7 @@ public void processReference(IDSReference reference, FieldDeclaration field, IVa if ((value = params.get("policy")) instanceof IVariableBinding) { //$NON-NLS-1$ IVariableBinding policyBinding = (IVariableBinding) value; policy = DSEnums.getReferencePolicy(policyBinding.getName()); - } else if (Modifier.isVolatile(field.getModifiers())) { + } else if (Modifier.isVolatile(modifiers)) { policy = IDSConstants.VALUE_REFERENCE_POLICY_DYNAMIC; } @@ -629,7 +647,7 @@ public void processReference(IDSReference reference, FieldDeclaration field, IVa fieldOption = DSEnums.getFieldOption(fieldOptionBinding.getName()); if (!errorLevel.isIgnore()) { if (VALUE_REFERENCE_FIELD_OPTION_REPLACE.equals(fieldOption)) { - if (Modifier.isFinal(field.getModifiers())) { + if (Modifier.isFinal(modifiers)) { problemReporter.reportProblem(annotation, "fieldOption", Messages.AnnotationProcessor_invalidReference_fieldFinal_fieldOption, fieldOption); //$NON-NLS-1$ } } else if (VALUE_REFERENCE_FIELD_OPTION_UPDATE.equals(fieldOption)) { @@ -644,7 +662,7 @@ public void processReference(IDSReference reference, FieldDeclaration field, IVa if (IDSConstants.VALUE_REFERENCE_POLICY_DYNAMIC.equals(policy) && (IDSConstants.VALUE_REFERENCE_CARDINALITY_ZERO_N.equals(cardinality) || IDSConstants.VALUE_REFERENCE_CARDINALITY_ONE_N.equals(cardinality)) - && Modifier.isFinal(field.getModifiers())) { + && Modifier.isFinal(modifiers)) { fieldOption = VALUE_REFERENCE_FIELD_OPTION_UPDATE; } } @@ -667,6 +685,16 @@ public void processReference(IDSReference reference, FieldDeclaration field, IVa updateAttributes(reference, name, service, cardinality, policy, target, policyOption, referenceScope); updateFieldAttributes(reference, fieldName, fieldOption, fieldCollectionType); + + return requiredVersion; + } + + private String getCollectionType(Map params) { + Object object = params.get(ATTRIBUTE_COLLECTION_TYPE); + if (object instanceof IVariableBinding variable) { + return DSEnums.COLLECTION_TYPE_OPTION.get(variable.getName()); + } + return null; } private FieldCollectionTypeDescriptor determineCollectionType(AST ast, ITypeBinding type) { @@ -709,7 +737,7 @@ private boolean isValidFieldForService(ITypeBinding fieldType, ITypeBinding serv || Map.class.getName().equals(erasure) || (Map.Entry.class.getName().equals(erasure) && ((typeArgs = fieldType.getTypeArguments()).length < 2 || (Map.class.getName().equals(typeArgs[0].getErasure().getBinaryName()) && serviceType.isAssignmentCompatible(typeArgs[1])))) - || serviceType.isAssignmentCompatible(fieldType); + || isValidServiceAssignment(fieldType, serviceType, specVersion); } private ITypeBinding getFieldServiceType(AST ast, ITypeBinding type) { @@ -787,6 +815,7 @@ private String getFieldCollectionType(FieldCollectionTypeDescriptor collectionTy } else if (Map.Entry.class.getName().equals(erasure)) { fieldCollectionType = "tuple"; //$NON-NLS-1$ } + // TODO "service" return fieldCollectionType; } @@ -923,7 +952,7 @@ private void validateReferenceBindMethod(Annotation annotation, ITypeBinding ser } ITypeBinding[] argTypes = methodBinding.getParameterTypes(); - if (specVersion == DSAnnotationVersion.V1_3) { + if (DSAnnotationVersion.V1_3.isEqualOrHigherThan(specVersion)) { if (argTypes.length == 0) { problemReporter.reportProblem(annotation, null, NLS.bind(Messages.AnnotationProcessor_invalidReference_bindMethodNoArgs, serviceType == null ? Messages.AnnotationProcessor_unknownServiceTypeLabel : serviceType.getName())); } else if (serviceType != null) { @@ -931,7 +960,7 @@ private void validateReferenceBindMethod(Annotation annotation, ITypeBinding ser String erasure = argType.getErasure().getBinaryName(); if (!ServiceReference.class.getName().equals(erasure) && !COMPONENT_SERVICE_OBJECTS.equals(erasure) - && !(serviceType == null || serviceType.isAssignmentCompatible(argType)) + && !(serviceType == null || isValidServiceAssignment(argType, serviceType, specVersion)) && !Map.class.getName().equals(erasure)) { problemReporter.reportProblem(annotation, null, NLS.bind(Messages.AnnotationProcessor_invalidReference_invalidBindMethodArg, argType.getName(), serviceType == null ? Messages.AnnotationProcessor_unknownServiceTypeLabel : serviceType.getName()), argType.getName()); } @@ -992,7 +1021,7 @@ private IMethodBinding findReferenceMethod(ITypeBinding componentClass, ITypeBin && testedClass.getPackage().isEqualTo(componentClass.getPackage())))) { ITypeBinding[] paramTypes = declaredMethod.getParameterTypes(); - if (specVersion == DSAnnotationVersion.V1_3) { + if (DSAnnotationVersion.V1_3.isEqualOrHigherThan(specVersion)) { for (int i = 0; i < paramTypes.length; ++i) { ITypeBinding paramType = paramTypes[i]; int priorityOffset = i == 0 ? 10 : 0; @@ -1090,7 +1119,7 @@ private DSAnnotationVersion determineRequiredVersion( } DSAnnotationVersion requiredVersion; - if (specVersion == DSAnnotationVersion.V1_3 && + if (DSAnnotationVersion.V1_3.isEqualOrHigherThan(specVersion) && (reference.getDocumentAttribute(ATTRIBUTE_REFERENCE_SCOPE) != null || reference.getDocumentAttribute(ATTRIBUTE_REFERENCE_FIELD) != null || reference.getDocumentAttribute(ATTRIBUTE_REFERENCE_FIELD_OPTION) != null @@ -1103,7 +1132,7 @@ private DSAnnotationVersion determineRequiredVersion( requiredVersion = DSAnnotationVersion.V1_1; } - if (specVersion == DSAnnotationVersion.V1_3 && requiredVersion != DSAnnotationVersion.V1_3) { + if (DSAnnotationVersion.V1_3.isEqualOrHigherThan(specVersion) && requiredVersion != DSAnnotationVersion.V1_3) { // check if any one of the event methods *don't* have legacy-compatible signature String bind = methodParams.getBind(); IMethodBinding bindMethod = methodParams.getBindMethod(); @@ -1207,4 +1236,24 @@ public IMethodBinding getUnbindMethod() { } } + + private static boolean isValidServiceAssignment(ITypeBinding targetType, ITypeBinding serviceType, + DSAnnotationVersion specVersion) { + if (serviceType.isAssignmentCompatible(targetType)) { + // thats easy... + return true; + } + if (DSAnnotationVersion.V1_4.isEqualOrHigherThan(specVersion)) { + String serviceErasuer = serviceType.getErasure().getBinaryName(); + if ("org.osgi.service.log.LoggerFactory".equals(serviceErasuer)) { + String fieldErasure = targetType.getErasure().getBinaryName(); + if ("org.osgi.service.log.Logger".equals(fieldErasure) + || "org.osgi.service.log.FormatterLogger".equals(fieldErasure)) { + // valid per specification... + return true; + } + } + } + return false; + } } diff --git a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/messages.properties b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/messages.properties index 4297feef2a..131243e569 100644 --- a/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/messages.properties +++ b/ds/org.eclipse.pde.ds.annotations/src/org/eclipse/pde/ds/internal/annotations/messages.properties @@ -25,6 +25,7 @@ AnnotationProcessor_invalidCompImplClass_annotation=Invalid component implementa AnnotationProcessor_invalidCompImplClass_enumeration=Invalid component implementation class ''{0}'': enumeration type. AnnotationProcessor_invalidCompImplClass_interface=Invalid component implementation class ''{0}'': interface type. AnnotationProcessor_invalidCompImplClass_noDefaultConstructor=Invalid component implementation class ''{0}'': no default constructor. +AnnotationProcessor_invalidCompImplClass_compatibleConstructor=Invalid component implementation class ''{0}'': no default or @Activate annotated constructor. AnnotationProcessor_invalidCompImplClass_notPublic=Invalid component implementation class ''{0}'': not a public class. AnnotationProcessor_invalidCompImplClass_notTopLevel=Invalid component implementation class ''{0}'': not a primary class or static class nested in primary type. AnnotationProcessor_invalidComponentConfigurationPid=Invalid component configuration PID: {0} @@ -32,12 +33,16 @@ AnnotationProcessor_invalidComponentDescriptorNamespace=Invalid component descri AnnotationProcessor_invalidComponentFactoryName=Invalid component factory name: {0} AnnotationProcessor_invalidComponentImplementationClass=Invalid component implementation class: {0} AnnotationProcessor_invalidComponentName=Invalid component name: {0} +AnnotationProcessor_invalidActivate=Invalid usage of @Activate +AnnotationProcessor_invalidActivateField=Field {0} is not an Activation Object, must be either org.osgi.service.component.ComponentContext, org.osgi.framework.BundleContext, java.util.Map or a component property type +AnnotationProcessor_invalidConstructorArgument=Invalid constructor parameter '{0}' at index {1}, must be either @Reference annotated, org.osgi.service.component.ComponentContext, org.osgi.framework.BundleContext, or java.util.Map AnnotationProcessor_invalidComponentProperty_nameRequired=Invalid component property: name is required. AnnotationProcessor_invalidComponentProperty_valueRequired=Invalid component property: value is required. -AnnotationProcessor_invalidComponentPropertyFile=Component property file ''{0}'' does not exist. +AnnotationProcessor_invalidComponentPropertyFile=Component {1} file ''{0}'' does not exist. AnnotationProcessor_invalidComponentPropertyType=Invalid component property type: {0}. Supported types are String (or empty), Long, Double, Float, Integer, Byte, Character, Boolean and Short. AnnotationProcessor_invalidComponentPropertyValue=Invalid {0} value: {1} AnnotationProcessor_invalidComponentService=Component does not extend or implement type: {0} +AnnotationProcessor_invalidLifecycleMethod_noMethod=Only allowed for methods. AnnotationProcessor_invalidLifecycleMethod_static=Lifecycle method cannot be static. AnnotationProcessor_invalidLifeCycleMethodParameterType=Invalid {0} method argument type: {1}; must be org.osgi.service.component.ComponentContext, org.osgi.framework.BundleContext, or java.util.Map. AnnotationProcessor_invalidLifeCycleMethodReturnType=Invalid {0} method return type: {1}; must be void. @@ -59,6 +64,7 @@ AnnotationProcessor_invalidReference_invalidBindMethodArg=Invalid bind method ar AnnotationProcessor_invalidReference_missingRequiredParam=Missing required parameter: {0} AnnotationProcessor_invalidReference_staticBindMethod=Reference bind method cannot be static. AnnotationProcessor_invalidReference_staticField=Reference field cannot be static. +AnnotationProcessor_invalidActivate_staticField=Activate field cannot be static. AnnotationProcessor_invalidReference_serviceType=Service type {1} incompatible with argument type {0}. AnnotationProcessor_invalidReference_serviceUnknown=Unable to determine service type from method signature. Please specify service explicitly. AnnotationProcessor_invalidReference_unbindMethod=No suitable unbind method named ''{0}'' found in implementation class hierarchy. diff --git a/ds/org.eclipse.pde.ds.annotations/src_test/org/eclipse/pde/ds/annotations/NameGeneratorTest.java b/ds/org.eclipse.pde.ds.annotations/src_test/org/eclipse/pde/ds/annotations/NameGeneratorTest.java new file mode 100644 index 0000000000..892b73d766 --- /dev/null +++ b/ds/org.eclipse.pde.ds.annotations/src_test/org/eclipse/pde/ds/annotations/NameGeneratorTest.java @@ -0,0 +1,57 @@ +/******************************************************************************* + * Copyright (c) 2023 Christoph Läubrich and others. + * + * 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 + * + * Contributors: + * Christoph Läubrich - initial API and implementation + *******************************************************************************/ +package org.eclipse.pde.ds.annotations; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import org.eclipse.pde.ds.internal.annotations.DSAnnotationVersion; +import org.eclipse.pde.ds.internal.annotations.NameGenerator; +import org.junit.jupiter.api.Test; + +class NameGeneratorTest { + + @Test + void test13() { + assertEquals("myProperty143", NameGenerator.createPropertyName("myProperty143", null, DSAnnotationVersion.V1_3)); + assertEquals("new", NameGenerator.createPropertyName("$new", null, DSAnnotationVersion.V1_3)); + assertEquals("my$prop", NameGenerator.createPropertyName("my$$prop", null, DSAnnotationVersion.V1_3)); + assertEquals("dot.prop", NameGenerator.createPropertyName("dot_prop", null, DSAnnotationVersion.V1_3)); + assertEquals(".secret", NameGenerator.createPropertyName("_secret", null, DSAnnotationVersion.V1_3)); + assertEquals("another_prop", NameGenerator.createPropertyName("another__prop", null, DSAnnotationVersion.V1_3)); + assertEquals("three_.prop", NameGenerator.createPropertyName("three___prop", null, DSAnnotationVersion.V1_3)); + assertEquals("four._prop", NameGenerator.createPropertyName("four_$__prop", null, DSAnnotationVersion.V1_3)); + assertEquals("five..prop", NameGenerator.createPropertyName("five_$_prop", null, DSAnnotationVersion.V1_3)); + } + + @Test + void test14() { + assertEquals("myProperty143", NameGenerator.createPropertyName("myProperty143", null, DSAnnotationVersion.V1_4)); + assertEquals("new", NameGenerator.createPropertyName("$new", null, DSAnnotationVersion.V1_4)); + assertEquals("my$prop", NameGenerator.createPropertyName("my$$prop", null, DSAnnotationVersion.V1_4)); + assertEquals("dot.prop", NameGenerator.createPropertyName("dot_prop", null, DSAnnotationVersion.V1_4)); + assertEquals(".secret", NameGenerator.createPropertyName("_secret", null, DSAnnotationVersion.V1_4)); + assertEquals("another_prop", NameGenerator.createPropertyName("another__prop", null, DSAnnotationVersion.V1_4)); + assertEquals("three_.prop", NameGenerator.createPropertyName("three___prop", null, DSAnnotationVersion.V1_4)); + assertEquals("four._prop", NameGenerator.createPropertyName("four_$__prop", null, DSAnnotationVersion.V1_4)); + assertEquals("five..prop", NameGenerator.createPropertyName("five_$_prop", null, DSAnnotationVersion.V1_4)); + assertEquals("six-prop", NameGenerator.createPropertyName("six$_$prop", null, DSAnnotationVersion.V1_4)); + assertEquals("seven$.prop", NameGenerator.createPropertyName("seven$$_$prop", null, DSAnnotationVersion.V1_4)); + assertEquals("pre.myProperty143", + NameGenerator.createPropertyName("myProperty143", "pre.", DSAnnotationVersion.V1_4)); + assertEquals("service.ranking", NameGenerator.createClassPropertyName("ServiceRanking", null)); + assertEquals("some_name", NameGenerator.createClassPropertyName("Some_Name", null)); + assertEquals("osgi.property", NameGenerator.createClassPropertyName("OSGiProperty", null)); + } + +} diff --git a/ds/org.eclipse.pde.ds.core/plugin.xml b/ds/org.eclipse.pde.ds.core/plugin.xml index 7ac5ea9c84..e25229e127 100644 --- a/ds/org.eclipse.pde.ds.core/plugin.xml +++ b/ds/org.eclipse.pde.ds.core/plugin.xml @@ -25,10 +25,30 @@ priority="high"> + + + + + + + + + + diff --git a/ds/org.eclipse.pde.ds.core/src/org/eclipse/pde/internal/ds/core/builders/DSErrorReporter.java b/ds/org.eclipse.pde.ds.core/src/org/eclipse/pde/internal/ds/core/builders/DSErrorReporter.java index cb60d1d106..01f136d7e3 100644 --- a/ds/org.eclipse.pde.ds.core/src/org/eclipse/pde/internal/ds/core/builders/DSErrorReporter.java +++ b/ds/org.eclipse.pde.ds.core/src/org/eclipse/pde/internal/ds/core/builders/DSErrorReporter.java @@ -358,7 +358,12 @@ private void validatePropertySpecificTypeValue(String type, String value, Elemen } else { // Validate Chars if (type.equals(IDSConstants.VALUE_PROPERTY_TYPE_CHAR)) { - if (value.length() > 1) { + // The spec says: For Character types, the conversion must + // be handled by Integer.valueOf method, a Character is + // always represented by its Unicode value. + try { + Integer.valueOf(value); + } catch (NumberFormatException e) { reportPropertyTypeCastException(element, value, type); } } diff --git a/ds/org.eclipse.pde.ds.tck/.classpath b/ds/org.eclipse.pde.ds.tck/.classpath new file mode 100644 index 0000000000..66d7021dd1 --- /dev/null +++ b/ds/org.eclipse.pde.ds.tck/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/ds/org.eclipse.pde.ds.tck/.project b/ds/org.eclipse.pde.ds.tck/.project new file mode 100644 index 0000000000..3fc41e1669 --- /dev/null +++ b/ds/org.eclipse.pde.ds.tck/.project @@ -0,0 +1,33 @@ + + + org.eclipse.pde.ds.tck + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.ds.core.builder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/ds/org.eclipse.pde.ds.tck/.settings/org.eclipse.core.resources.prefs b/ds/org.eclipse.pde.ds.tck/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000000..99f26c0203 --- /dev/null +++ b/ds/org.eclipse.pde.ds.tck/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/ds/org.eclipse.pde.ds.tck/.settings/org.eclipse.jdt.core.prefs b/ds/org.eclipse.pde.ds.tck/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000000..62ef3488cc --- /dev/null +++ b/ds/org.eclipse.pde.ds.tck/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,9 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=17 diff --git a/ds/org.eclipse.pde.ds.tck/.settings/org.eclipse.m2e.core.prefs b/ds/org.eclipse.pde.ds.tck/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000000..f897a7f1cb --- /dev/null +++ b/ds/org.eclipse.pde.ds.tck/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/ds/org.eclipse.pde.ds.tck/.settings/org.eclipse.pde.ds.annotations.prefs b/ds/org.eclipse.pde.ds.tck/.settings/org.eclipse.pde.ds.annotations.prefs new file mode 100644 index 0000000000..c829ba88a6 --- /dev/null +++ b/ds/org.eclipse.pde.ds.tck/.settings/org.eclipse.pde.ds.annotations.prefs @@ -0,0 +1,7 @@ +dsVersion=V1_5 +eclipse.preferences.version=1 +enabled=true +generateBundleActivationPolicyLazy=true +path=OSGI-INF +validationErrorLevel=error +validationErrorLevel.missingImplicitUnbindMethod=error diff --git a/ds/org.eclipse.pde.ds.tck/META-INF/MANIFEST.MF b/ds/org.eclipse.pde.ds.tck/META-INF/MANIFEST.MF new file mode 100644 index 0000000000..f27a3cf57c --- /dev/null +++ b/ds/org.eclipse.pde.ds.tck/META-INF/MANIFEST.MF @@ -0,0 +1,51 @@ +Manifest-Version: 1.0 +Bundle-ManifestVersion: 2 +Bundle-Name: Component XML TCK +Bundle-SymbolicName: org.eclipse.pde.ds.tck +Bundle-Version: 1.0.0.qualifier +Import-Package: org.osgi.framework, + org.osgi.service.component, + org.osgi.service.component.propertytypes, + org.osgi.service.log +Service-Component: OSGI-INF/org.osgi.impl.bundle.component.annotations.HelloWorld10.xml, + OSGI-INF/testActivationFields.xml, + OSGI-INF/testComponentPropertyTypes.xml, + OSGI-INF/testComponentReferences.xml, + OSGI-INF/testConfigPid.xml, + OSGI-INF/testConfigPidMultiple.xml, + OSGI-INF/testConfigPolicyIgnore.xml, + OSGI-INF/testConfigPolicyOptional.xml, + OSGI-INF/testConfigPolicyRequire.xml, + OSGI-INF/testConstructorInjection.xml, + OSGI-INF/testDelayed.xml, + OSGI-INF/testDisabled.xml, + OSGI-INF/testEnabled.xml, + OSGI-INF/testFactory.xml, + OSGI-INF/testFactoryProperties.xml, + OSGI-INF/testFieldReferences.xml, + OSGI-INF/testHelloWorld11.xml, + OSGI-INF/testHelloWorld12.xml, + OSGI-INF/testHelloWorld13.xml, + OSGI-INF/testHelloWorld14.xml, + OSGI-INF/testImmediate.xml, + OSGI-INF/testLoggerComponent.xml, + OSGI-INF/testNameMapping.xml, + OSGI-INF/testNoInheritService.xml, + OSGI-INF/testNoService.xml, + OSGI-INF/testNoServiceFactory.xml, + OSGI-INF/testProperties.xml, + OSGI-INF/testPropertyOrdering.xml, + OSGI-INF/testPropertyOrdering14.xml, + OSGI-INF/testReferenceNames.xml, + OSGI-INF/testReferenceScopes.xml, + OSGI-INF/testReferenceService.xml, + OSGI-INF/testReferences.xml, + OSGI-INF/testService.xml, + OSGI-INF/testServiceBundle.xml, + OSGI-INF/testServiceFactory.xml, + OSGI-INF/testServicePrototype.xml, + OSGI-INF/testServiceSingleton.xml +Bundle-Vendor: Eclipse.org +Automatic-Module-Name: org.eclipse.pde.ds.tck +Bundle-ActivationPolicy: lazy +Bundle-RequiredExecutionEnvironment: JavaSE-17 diff --git a/ds/org.eclipse.pde.ds.tck/OSGI-INF/.gitignore b/ds/org.eclipse.pde.ds.tck/OSGI-INF/.gitignore new file mode 100644 index 0000000000..b878e882ac --- /dev/null +++ b/ds/org.eclipse.pde.ds.tck/OSGI-INF/.gitignore @@ -0,0 +1 @@ +/*.xml diff --git a/ds/org.eclipse.pde.ds.tck/OSGI-INF/vendor.properties b/ds/org.eclipse.pde.ds.tck/OSGI-INF/vendor.properties new file mode 100644 index 0000000000..e4c57dd8f5 --- /dev/null +++ b/ds/org.eclipse.pde.ds.tck/OSGI-INF/vendor.properties @@ -0,0 +1 @@ +## FIXME this is just a dummy file to account for https://github.com/osgi/osgi/issues/635 \ No newline at end of file diff --git a/ds/org.eclipse.pde.ds.tck/README.MD b/ds/org.eclipse.pde.ds.tck/README.MD new file mode 100644 index 0000000000..0550ce0e41 --- /dev/null +++ b/ds/org.eclipse.pde.ds.tck/README.MD @@ -0,0 +1,5 @@ +# OSGi Technical Compatibility Kit for DS Annotations + +This project executes the official OSGi TCK for DS annotations, it can be run with thefollwoing command from the root of the project: + +`mvn clean verify -pl :org.eclipse.pde.ds.tck,:org.eclipse.pde.ds.annotations -Ptck -am -Dtycho.localArtifacts=ignore` diff --git a/ds/org.eclipse.pde.ds.tck/build.properties b/ds/org.eclipse.pde.ds.tck/build.properties new file mode 100644 index 0000000000..c6035d8a0e --- /dev/null +++ b/ds/org.eclipse.pde.ds.tck/build.properties @@ -0,0 +1,5 @@ +source.. = tck/OSGI-INF/impl-src +output.. = target/classes +bin.includes = META-INF/,\ + .,\ + OSGI-INF/ diff --git a/ds/org.eclipse.pde.ds.tck/pom.xml b/ds/org.eclipse.pde.ds.tck/pom.xml new file mode 100644 index 0000000000..61311a0d59 --- /dev/null +++ b/ds/org.eclipse.pde.ds.tck/pom.xml @@ -0,0 +1,146 @@ + + + 4.0.0 + + org.eclipse.pde + eclipse.pde + 4.31.0-SNAPSHOT + ../../ + + org.eclipse.pde.ds.tck + 1.0.0-SNAPSHOT + eclipse-plugin + + + + + org.eclipse.tycho + target-platform-configuration + + + + + eclipse-plugin + org.osgi.service.component.annotations + [1.5, 1.6) + + + eclipse-plugin + org.eclipse.pde.ds.annotations + 0.0.0 + + + + + + + p2-installable-unit + org.eclipse.osgi.services + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + unpack-tck-sourcec + process-resources + + unpack-dependencies + + + org.osgi.test.cases.component.annotations + ${basedir}/tck + + + + + + org.eclipse.tycho + tycho-eclipse-plugin + ${tycho.version} + + + generate-xmls + + eclipse-build + + compile + + true + + false + + org.eclipse.pde.core + org.eclipse.pde.ds.annotations + + + + + + + org.eclipse.tycho + tycho-compiler-plugin + + + + default-compile + none + + + + + org.eclipse.tycho + tycho-ds-plugin + ${tycho.version} + + + + default-declarative-services + none + + + + + org.eclipse.tycho + tycho-surefire-plugin + + + execute-tck + + bnd-test + verify + + + + org.osgi.test.cases.component.annotations + + false + false + true + true + ${project.build.directory}/tck-results + + org.eclipse.pde.ds.tck + + + + + + + + + + + org.osgi + org.osgi.test.cases.component.annotations + 8.1.0 + + + + \ No newline at end of file diff --git a/ds/org.eclipse.pde.ds.tck/tck/.gitignore b/ds/org.eclipse.pde.ds.tck/tck/.gitignore new file mode 100644 index 0000000000..f59ec20aab --- /dev/null +++ b/ds/org.eclipse.pde.ds.tck/tck/.gitignore @@ -0,0 +1 @@ +* \ No newline at end of file diff --git a/ds/pom.xml b/ds/pom.xml index 9552a63499..8971bd6552 100644 --- a/ds/pom.xml +++ b/ds/pom.xml @@ -25,4 +25,13 @@ org.eclipse.pde.ds.ui org.eclipse.pde.ds.annotations + + + + tck + + org.eclipse.pde.ds.tck + + +