FELIX-3794 : Use DS specification version per component
FELIX-3815 : Maven SCR Plugin does not correctly set specVersion to 1.1 if bind method with two parameters are used (OSGi Comp 4.2 $112.3.1)

git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1437411 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/scrplugin/generator/src/main/java/org/apache/felix/scrplugin/SCRDescriptorGenerator.java b/scrplugin/generator/src/main/java/org/apache/felix/scrplugin/SCRDescriptorGenerator.java
index 17e3823..dfa5e99 100644
--- a/scrplugin/generator/src/main/java/org/apache/felix/scrplugin/SCRDescriptorGenerator.java
+++ b/scrplugin/generator/src/main/java/org/apache/felix/scrplugin/SCRDescriptorGenerator.java
@@ -162,6 +162,7 @@
                                 desc.getSource());
             } else {
                 final ComponentContainer container = this.createComponent(desc, iLog);
+
                 if (container.getComponentDescription().getSpecVersion() != null) {
                     if ( specVersion == null ) {
                         specVersion = container.getComponentDescription().getSpecVersion();
@@ -178,6 +179,12 @@
                             logger.debug("Setting used spec version to " + specVersion);
                         }
                     }
+                } else {
+                    if ( this.options.getSpecVersion() != null ) {
+                        container.getComponentDescription().setSpecVersion(options.getSpecVersion());
+                    } else {
+                        container.getComponentDescription().setSpecVersion(SpecVersion.VERSION_1_0);
+                    }
                 }
                 processedContainers.add(container);
             }
@@ -190,64 +197,16 @@
         this.logger.debug("Generating descriptor for spec version: " + specVersion);
         options.setSpecVersion(specVersion);
 
-        final DescriptionContainer module = new DescriptionContainer(this.options);
-
         // before we can validate we should check the references for bind/unbind method
         // in order to create them if possible
         if ( this.options.isGenerateAccessors() ) {
-
-            for (final ComponentContainer container : processedContainers) {
-                for (final ReferenceDescription ref : container.getReferences().values()) {
-                    // if this is a field with a single cardinality,
-                    // we look for the bind/unbind methods
-                    // and create them if they are not availabe
-                    if (ref.getStrategy() != ReferenceStrategy.LOOKUP && ref.getField() != null
-                        && ref.getField().getDeclaringClass().getName().equals(container.getClassDescription().getDescribedClass().getName())
-                        && (ref.getCardinality() == ReferenceCardinality.OPTIONAL_UNARY || ref.getCardinality() == ReferenceCardinality.MANDATORY_UNARY)) {
-
-                        final String bindValue = ref.getBind();
-                        final String unbindValue = ref.getUnbind();
-                        final String name = ref.getName();
-                        final String type = ref.getInterfaceName();
-
-                        boolean createBind = false;
-                        boolean createUnbind = false;
-
-                        // Only create method if no bind name has been specified
-                        if (bindValue == null && Validator.findMethod(this.project, this.options, container.getClassDescription(), ref, "bind") == null) {
-                            // create bind method
-                            createBind = true;
-                        }
-                        if (unbindValue == null && Validator.findMethod(this.project, this.options, container.getClassDescription(), ref, "unbind") == null) {
-                            // create unbind method
-                            createUnbind = true;
-                        }
-                        if (createBind || createUnbind) {
-                            // logging
-                            if ( createBind && createUnbind ) {
-                                this.logger.debug("Generating bind and unbind method for " + name + " in " + container.getClassDescription().getDescribedClass().getName());
-                            } else if ( createBind ) {
-                                this.logger.debug("Generating bind method for " + name + " in " + container.getClassDescription().getDescribedClass().getName());
-                            } else {
-                                this.logger.debug("Generating unbind method for " + name + " in " + container.getClassDescription().getDescribedClass().getName());
-
-                            }
-                            ClassModifier.addMethods(container.getClassDescription().getDescribedClass().getName(),
-                                            name,
-                                            ref.getField().getName(),
-                                            type,
-                                            createBind,
-                                            createUnbind,
-                                            this.project.getClassLoader(),
-                                            this.project.getClassesDirectory(),
-                                            this.logger);
-                        }
-                    }
-                }
+            for(final ComponentContainer container : processedContainers) {
+                this.generateMethods(container);
             }
         }
 
         // now validate
+        final DescriptionContainer module = new DescriptionContainer(this.options);
         for (final ComponentContainer container : processedContainers) {
             final int errorCount = iLog.getNumberOfErrors();
 
@@ -274,6 +233,55 @@
         return result;
     }
 
+    private void generateMethods(final ComponentContainer container) throws SCRDescriptorException {
+        for (final ReferenceDescription ref : container.getReferences().values()) {
+            // if this is a field with a single cardinality,
+            // we look for the bind/unbind methods
+            // and create them if they are not availabe
+            if (ref.getStrategy() != ReferenceStrategy.LOOKUP && ref.getField() != null
+                && ref.getField().getDeclaringClass().getName().equals(container.getClassDescription().getDescribedClass().getName())
+                && (ref.getCardinality() == ReferenceCardinality.OPTIONAL_UNARY || ref.getCardinality() == ReferenceCardinality.MANDATORY_UNARY)) {
+
+                final String bindValue = ref.getBind();
+                final String unbindValue = ref.getUnbind();
+                final String name = ref.getName();
+                final String type = ref.getInterfaceName();
+
+                boolean createBind = false;
+                boolean createUnbind = false;
+
+                // Only create method if no bind name has been specified
+                if (bindValue == null && Validator.findMethod(this.project, this.options, container.getClassDescription(), ref, "bind") == null) {
+                    // create bind method
+                    createBind = true;
+                }
+                if (unbindValue == null && Validator.findMethod(this.project, this.options, container.getClassDescription(), ref, "unbind") == null) {
+                    // create unbind method
+                    createUnbind = true;
+                }
+                if (createBind || createUnbind) {
+                    // logging
+                    if ( createBind && createUnbind ) {
+                        this.logger.debug("Generating bind and unbind method for " + name + " in " + container.getClassDescription().getDescribedClass().getName());
+                    } else if ( createBind ) {
+                        this.logger.debug("Generating bind method for " + name + " in " + container.getClassDescription().getDescribedClass().getName());
+                    } else {
+                        this.logger.debug("Generating unbind method for " + name + " in " + container.getClassDescription().getDescribedClass().getName());
+
+                    }
+                    ClassModifier.addMethods(container.getClassDescription().getDescribedClass().getName(),
+                                    name,
+                                    ref.getField().getName(),
+                                    type,
+                                    createBind,
+                                    createUnbind,
+                                    this.project.getClassLoader(),
+                                    this.project.getClassesDirectory(),
+                                    this.logger);
+                }
+            }
+        }
+    }
     /**
      * Create the SCR objects based on the descriptions
      */
@@ -281,6 +289,8 @@
                     final IssueLog iLog) {
         final ComponentDescription componentDesc = desc.getDescription(ComponentDescription.class);
 
+        final SpecVersion intitialComponentSpecVersion = componentDesc.getSpecVersion();
+
         // configuration pid in 1.2
         if ( componentDesc.getConfigurationPid() != null && !componentDesc.getConfigurationPid().equals(componentDesc.getName())) {
             componentDesc.setSpecVersion(SpecVersion.VERSION_1_2);
@@ -388,6 +398,12 @@
             container.getProperties().put(org.osgi.framework.Constants.SERVICE_PID, pid);
         }
 
+        // check if component has spec version configured but requires a higher one
+        if ( intitialComponentSpecVersion != null && componentDesc.getSpecVersion().ordinal() > intitialComponentSpecVersion.ordinal() ) {
+            iLog.addError("Component " + container + " requires spec version " + container.getComponentDescription().getSpecVersion().name()
+                    + " but component is configured to use version " + intitialComponentSpecVersion.name(),
+                    desc.getSource());
+        }
         return container;
     }
 
@@ -546,6 +562,7 @@
 
     /**
      * Process reference directives
+     * @throws SCRDescriptorException
      */
     private void processReferences(final ClassDescription current,
                     final ComponentContainer component) {
@@ -562,6 +579,25 @@
             }
 
             this.testReference(current, component.getReferences(), rd, component.getClassDescription() == current);
+
+            // check for method signature
+            try {
+                final Validator.MethodResult bindMethod = Validator.findMethod(this.project, this.options, current, rd,
+                        rd.getBind() == null ? "bind" : rd.getBind());
+                if ( bindMethod != null ) {
+                    component.getComponentDescription().setSpecVersion(bindMethod.requiredSpecVersion);
+                }
+
+                final Validator.MethodResult unbindMethod = Validator.findMethod(this.project, this.options, current, rd,
+                        rd.getUnbind() == null ? "unbind" : rd.getUnbind());
+                if ( unbindMethod != null ) {
+                    component.getComponentDescription().setSpecVersion(unbindMethod.requiredSpecVersion);
+                }
+
+            } catch (final SCRDescriptorException sde) {
+                // this happens only if a class not found exception occurs, so we can ignore this at this point!
+            }
+
         }
     }
 
diff --git a/scrplugin/generator/src/main/java/org/apache/felix/scrplugin/helper/Validator.java b/scrplugin/generator/src/main/java/org/apache/felix/scrplugin/helper/Validator.java
index 5267b23..1eff278 100644
--- a/scrplugin/generator/src/main/java/org/apache/felix/scrplugin/helper/Validator.java
+++ b/scrplugin/generator/src/main/java/org/apache/felix/scrplugin/helper/Validator.java
@@ -496,8 +496,8 @@
 
     private String validateMethod(final ReferenceDescription ref, final String methodName, final boolean componentIsAbstract)
     throws SCRDescriptorException {
-        final Method method = findMethod(this.project, this.options, this.container.getClassDescription(), ref, methodName);
-        if (method == null) {
+        final MethodResult result = findMethod(this.project, this.options, this.container.getClassDescription(), ref, methodName);
+        if (result == null) {
             if (!componentIsAbstract) {
                 this.logError(ref,
                                 "Missing method " + methodName + " for reference "
@@ -508,14 +508,19 @@
 
         // method needs to be protected for 1.0
         if (this.options.getSpecVersion() == SpecVersion.VERSION_1_0) {
-            if (Modifier.isPublic(method.getModifiers())) {
-                this.logWarn(ref, "Method " + method.getName() + " should be declared protected");
-            } else if (!Modifier.isProtected(method.getModifiers())) {
-                this.logError(ref, "Method " + method.getName() + " has wrong qualifier, public or protected required");
+            if (Modifier.isPublic(result.method.getModifiers())) {
+                this.logWarn(ref, "Method " + result.method.getName() + " should be declared protected");
+            } else if (!Modifier.isProtected(result.method.getModifiers())) {
+                this.logError(ref, "Method " + result.method.getName() + " has wrong qualifier, public or protected required");
                 return null;
             }
         }
-        return method.getName();
+
+        if (this.options.getSpecVersion().ordinal() < result.requiredSpecVersion.ordinal() ) {
+            this.logError(ref, "Method declaration for '" + result.method.getName() + "' requires version "
+                    + result.requiredSpecVersion + " or newer");
+        }
+        return result.method.getName();
     }
 
     private static final String TYPE_SERVICE_REFERENCE = "org.osgi.framework.ServiceReference";
@@ -535,12 +540,22 @@
         return null;
     }
 
-    public static Method findMethod(final Project project,
+    public static final class MethodResult {
+        public Method method;
+        public SpecVersion requiredSpecVersion;
+    }
+
+    /**
+     * Find the method and the required spec version
+     * @throws SCRDescriptorException If the class can't be found
+     */
+    public static MethodResult findMethod(final Project project,
                     final Options options,
                     final ClassDescription cd,
                     final ReferenceDescription ref,
                     final String methodName)
     throws SCRDescriptorException {
+        SpecVersion requiredVersion = SpecVersion.VERSION_1_0;
         try {
             final Class<?>[] sig = new Class<?>[] { project.getClassLoader().loadClass(TYPE_SERVICE_REFERENCE) };
             final Class<?>[] sig2 = new Class<?>[] { project.getClassLoader().loadClass(ref.getInterfaceName()) };
@@ -551,8 +566,9 @@
             Method method = getMethod(cd, realMethodName, sig);
             if (method == null) {
                 method = getMethod(cd, realMethodName, sig2);
-                if (method == null && options.getSpecVersion().ordinal() >= SpecVersion.VERSION_1_1.ordinal() ) {
+                if (method == null && (options.getSpecVersion() == null || options.getSpecVersion().ordinal() >= SpecVersion.VERSION_1_1.ordinal()) ) {
                     method = getMethod(cd, realMethodName, sig3);
+                    requiredVersion = SpecVersion.VERSION_1_1;
                 }
             }
 
@@ -572,8 +588,9 @@
             }
             if (method == null) {
                 method = getMethod(cd, realMethodName, sig2);
-                if (method == null && options.getSpecVersion().ordinal() >= SpecVersion.VERSION_1_1.ordinal() ) {
+                if (method == null && (options.getSpecVersion() == null || options.getSpecVersion().ordinal() >= SpecVersion.VERSION_1_1.ordinal()) ) {
                     method = getMethod(cd, realMethodName, sig3);
+                    requiredVersion = SpecVersion.VERSION_1_1;
                 }
             }
 
@@ -585,12 +602,17 @@
             }
             if (method == null) {
                 method = getMethod(cd, realMethodName, sig2);
-                if (method == null && options.getSpecVersion().ordinal() >= SpecVersion.VERSION_1_1.ordinal() ) {
+                if (method == null && (options.getSpecVersion() == null || options.getSpecVersion().ordinal() >= SpecVersion.VERSION_1_1.ordinal()) ) {
                     method = getMethod(cd, realMethodName, sig3);
+                    requiredVersion = SpecVersion.VERSION_1_1;
                 }
             }
 
-            return method;
+            final MethodResult result = new MethodResult();
+            result.method = method;
+            result.requiredSpecVersion = requiredVersion;
+
+            return result;
         } catch (final ClassNotFoundException cnfe) {
             throw new SCRDescriptorException("Unable to load class!", cnfe);
         }
diff --git a/scrplugin/generator/src/main/java/org/apache/felix/scrplugin/xml/ComponentDescriptorIO.java b/scrplugin/generator/src/main/java/org/apache/felix/scrplugin/xml/ComponentDescriptorIO.java
index 1ae4d3a..155398c 100644
--- a/scrplugin/generator/src/main/java/org/apache/felix/scrplugin/xml/ComponentDescriptorIO.java
+++ b/scrplugin/generator/src/main/java/org/apache/felix/scrplugin/xml/ComponentDescriptorIO.java
@@ -764,7 +764,9 @@
 
         final List<String> fileNames = new ArrayList<String>();
         if ( options.isGenerateSeparateDescriptors() ) {
+            final SpecVersion globalVersion = module.getOptions().getSpecVersion();
             for(final ComponentContainer component : components ) {
+                module.getOptions().setSpecVersion(component.getComponentDescription().getSpecVersion());
                 final File file = new File(descriptorDir, component.getClassDescription().getDescribedClass().getName() + ".xml");
                 try {
                     ComponentDescriptorIO.generateXML(module, Collections.singletonList(component), file, logger);
@@ -777,6 +779,7 @@
                 }
                 fileNames.add(PARENT_NAME + '/' + file.getName());
             }
+            module.getOptions().setSpecVersion(globalVersion);
         } else {
             if (descriptorFile == null) {
                 throw new SCRDescriptorFailureException("Descriptor file name must not be empty.");