This is an automated email from the ASF dual-hosted git repository.

simonetripodi pushed a commit to branch SLING-7925
in repository 
https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-analyser.git

commit 38eb881e5c684dad724102ce0f6730e6da5b063a
Author: Simo Tripodi <[email protected]>
AuthorDate: Sat Sep 15 10:35:59 2018 +0200

    SLING-7925 - Donate new AnalyzerTask which is able to validate
    APIs/Regions
    
    initial checkin
---
 .../analyser/task/impl/CheckApiRegions.java        | 222 +++++++++++++++++++++
 .../scanner/impl/ApiRegionsExtensionScanner.java   |  56 ++++++
 ...apache.sling.feature.analyser.task.AnalyserTask |   2 +-
 ...ache.sling.feature.scanner.spi.ExtensionScanner |   2 +-
 4 files changed, 280 insertions(+), 2 deletions(-)

diff --git 
a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckApiRegions.java
 
b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckApiRegions.java
new file mode 100644
index 0000000..6ef2b89
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckApiRegions.java
@@ -0,0 +1,222 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.feature.analyser.task.impl;
+
+import java.io.StringReader;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Formatter;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.json.Json;
+import javax.json.stream.JsonParser;
+import javax.json.stream.JsonParser.Event;
+
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.Extensions;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.analyser.task.AnalyserTask;
+import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
+import org.apache.sling.feature.scanner.BundleDescriptor;
+import org.apache.sling.feature.scanner.FeatureDescriptor;
+import org.apache.sling.feature.scanner.PackageInfo;
+
+public class CheckApiRegions implements AnalyserTask {
+
+    private static final String API_REGIONS_KEY = "api-regions";
+
+    private static final String NAME_KEY = "name";
+
+    private static final String EXPORTS_KEY = "exports";
+
+    @Override
+    public String getId() {
+        return API_REGIONS_KEY;
+    }
+
+    @Override
+    public String getName() {
+        return "Api Regions analyser task";
+    }
+
+    @Override
+    public void execute(AnalyserTaskContext ctx) throws Exception {
+        Feature feature = ctx.getFeature();
+
+        // extract and check the api-regions
+
+        Extensions extensions = feature.getExtensions();
+        Extension apiRegionsExtension = extensions.getByName(API_REGIONS_KEY);
+        if (apiRegionsExtension == null) {
+            // no need to be analyzed
+            return;
+        }
+
+        String jsonRepresentation = apiRegionsExtension.getJSON();
+        if (jsonRepresentation == null || jsonRepresentation.isEmpty()) {
+            // no need to be analyzed
+            return;
+        }
+
+        // read the api-regions and create a Sieve data structure for checks
+
+        ApiRegions apiRegions = fromJson(jsonRepresentation);
+
+        // then, for each bundle, get the Export-Package and process the 
packages
+
+        FeatureDescriptor featureDescriptor = ctx.getFeatureDescriptor();
+        for (BundleDescriptor bundleDescriptor : 
featureDescriptor.getBundleDescriptors()) {
+            for (PackageInfo packageInfo : 
bundleDescriptor.getExportedPackages()) {
+                String exportedPackage = packageInfo.getName();
+                // use the Sieve technique: remove bundle exported packages 
from the api-regions
+                apiRegions.remove(exportedPackage);
+            }
+        }
+
+        // final evaluation: if the Sieve is not empty, not all declared 
packages are exported by bundles of the same feature
+        if (!apiRegions.isEmpty()) {
+            // track a single error for each region
+            for (String region : apiRegions.getRegions()) {
+                Set<String> apis = apiRegions.getApis(region);
+                if (!apis.isEmpty()) {
+                    Formatter formatter = new Formatter();
+                    formatter.format("Region '%s' defined in feature '%s' 
declares %s package%s which %s not exported by any bundle:%n",
+                                     region,
+                                     feature.getId(),
+                                     apis.size(),
+                                     getExtension(apis, "", "s"),
+                                     getExtension(apis, "is", "are"));
+                    apis.forEach(api -> formatter.format(" * %s%n", api));
+
+                    ctx.reportError(formatter.toString());
+
+                    formatter.close();
+                }
+            }
+        }
+    }
+
+    // utility methods
+
+    private static <T> String getExtension(Collection<T> collection, String 
singular, String plural) {
+        return collection.size() > 1 ? plural : singular;
+    }
+
+    private static ApiRegions fromJson(String jsonRepresentation) {
+        ApiRegions apiRegions = new ApiRegions();
+
+        // pointers
+        Event event;
+        String region = null;
+        Collection<String> apis = null;
+
+        JsonParser parser = Json.createParser(new 
StringReader(jsonRepresentation));
+        while (parser.hasNext()) {
+            event = parser.next();
+            if (Event.KEY_NAME == event) {
+                switch (parser.getString()) {
+                    case NAME_KEY:
+                        parser.next();
+                        region = parser.getString();
+                        break;
+
+                    case EXPORTS_KEY:
+                        apis = new LinkedList<>();
+
+                        // start array
+                        parser.next();
+
+                        while (parser.hasNext() && Event.VALUE_STRING == 
parser.next()) {
+                            String api = parser.getString();
+                            // skip comments
+                            if ('#' != api.charAt(0)) {
+                                apis.add(api);
+                            }
+                        }
+
+                        break;
+
+                    default:
+                        break;
+                }
+            } else if (Event.END_OBJECT == event) {
+                if (region != null && apis != null) {
+                    apiRegions.add(region, apis);
+                }
+
+                region = null;
+                apis = null;
+            }
+        }
+
+        return apiRegions;
+    }
+
+    // the Sieve data structure to check exported packages
+
+    private static final class ApiRegions {
+
+        // class members
+
+        private final Map<String, Set<String>> apis = new TreeMap<>();
+
+        // ctor
+
+        protected ApiRegions() {
+            // it should not be directly instantiated outside this package
+        }
+
+        // methods
+
+        public void add(String region, Collection<String> exportedApis) {
+            apis.computeIfAbsent(region, k -> new 
TreeSet<>()).addAll(exportedApis);
+        }
+
+        public Iterable<String> getRegions() {
+            return apis.keySet();
+        }
+
+        public Set<String> getApis(String region) {
+            return apis.computeIfAbsent(region, k -> Collections.emptySet());
+        }
+
+        public void remove(String packageName) {
+            apis.values().forEach(apis -> apis.remove(packageName));
+        }
+
+        public boolean isEmpty() {
+            for (Set<String> packages : apis.values()) {
+                if (!packages.isEmpty()) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
+        public String toString() {
+            return apis.toString().replace(',', '\n');
+        }
+
+    }
+
+
+}
diff --git 
a/src/main/java/org/apache/sling/feature/scanner/impl/ApiRegionsExtensionScanner.java
 
b/src/main/java/org/apache/sling/feature/scanner/impl/ApiRegionsExtensionScanner.java
new file mode 100644
index 0000000..0aaa9b5
--- /dev/null
+++ 
b/src/main/java/org/apache/sling/feature/scanner/impl/ApiRegionsExtensionScanner.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sling.feature.scanner.impl;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.io.ArtifactManager;
+import org.apache.sling.feature.scanner.BundleDescriptor;
+import org.apache.sling.feature.scanner.ContainerDescriptor;
+import org.apache.sling.feature.scanner.FeatureDescriptor;
+import org.apache.sling.feature.scanner.spi.ExtensionScanner;
+
+public class ApiRegionsExtensionScanner implements ExtensionScanner {
+
+    @Override
+    public String getId() {
+        return "api-regions";
+    }
+
+    @Override
+    public String getName() {
+        return "Api Regions extention scanner";
+    }
+
+    @Override
+    public ContainerDescriptor scan(Feature feature, Extension extension, 
ArtifactManager manager) throws IOException {
+        FeatureDescriptor featureDescriptor = new 
FeatureDescriptorImpl(feature);
+
+        for (Artifact artifact : feature.getBundles()) {
+            File file = 
manager.getArtifactHandler(artifact.getId().toMvnUrl()).getFile();
+            BundleDescriptor bundleDescriptor = new 
BundleDescriptorImpl(artifact, file, artifact.getStartOrder());
+            featureDescriptor.getBundleDescriptors().add(bundleDescriptor);
+        }
+
+        return featureDescriptor;
+    }
+
+}
diff --git 
a/src/main/resources/META-INF/services/org.apache.sling.feature.analyser.task.AnalyserTask
 
b/src/main/resources/META-INF/services/org.apache.sling.feature.analyser.task.AnalyserTask
index dabd8fb..7f58e8d 100644
--- 
a/src/main/resources/META-INF/services/org.apache.sling.feature.analyser.task.AnalyserTask
+++ 
b/src/main/resources/META-INF/services/org.apache.sling.feature.analyser.task.AnalyserTask
@@ -2,4 +2,4 @@ 
org.apache.sling.feature.analyser.task.impl.CheckBundleExportsImports
 org.apache.sling.feature.analyser.task.impl.CheckBundlesForInitialContent
 org.apache.sling.feature.analyser.task.impl.CheckBundlesForResources
 org.apache.sling.feature.analyser.task.impl.CheckRequirementsCapabilities
-
+org.apache.sling.feature.analyser.task.impl.CheckApiRegions
diff --git 
a/src/main/resources/META-INF/services/org.apache.sling.feature.scanner.spi.ExtensionScanner
 
b/src/main/resources/META-INF/services/org.apache.sling.feature.scanner.spi.ExtensionScanner
index 643c7cf..528834d 100644
--- 
a/src/main/resources/META-INF/services/org.apache.sling.feature.scanner.spi.ExtensionScanner
+++ 
b/src/main/resources/META-INF/services/org.apache.sling.feature.scanner.spi.ExtensionScanner
@@ -1,3 +1,3 @@
 org.apache.sling.feature.scanner.impl.ContentPackagesExtensionScanner
 org.apache.sling.feature.scanner.impl.RepoInitScanner
-
+org.apache.sling.feature.scanner.impl.ApiRegionsExtensionScanner

Reply via email to