Author: pauls Date: Mon Jul 31 17:22:58 2017 New Revision: 1803557 URL: http://svn.apache.org/viewvc?rev=1803557&view=rev Log: Move parsing into feature-support, add Requirement/Capability matching and parsing to feature-support, and create basic check task for Req/Cap in analyser (still needs testing).
Added: sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/impl/CheckRequirementsCapabilities.java sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/ - copied from r1803556, sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/json/ sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/util/ sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/util/LambdaUtil.java sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/util/ManifestParser.java sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/util/ManifestUtil.java sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/util/PackageInfo.java sling/whiteboard/cziegeler/feature-support/src/test/java/org/apache/sling/feature/support/json/ sling/whiteboard/cziegeler/feature-support/src/test/java/org/apache/sling/feature/support/json/FeatureJSONReaderTest.java sling/whiteboard/cziegeler/feature-support/src/test/java/org/apache/sling/feature/support/json/FeatureJSONWriterTest.java sling/whiteboard/cziegeler/feature-support/src/test/java/org/apache/sling/feature/support/json/U.java sling/whiteboard/cziegeler/feature-support/src/test/resources/ sling/whiteboard/cziegeler/feature-support/src/test/resources/features/ sling/whiteboard/cziegeler/feature-support/src/test/resources/features/test.json Removed: sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/PackageInfo.java sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/impl/ManifestUtil.java sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/json/ sling/whiteboard/cziegeler/feature/src/test/java/org/apache/sling/feature/json/ sling/whiteboard/cziegeler/feature/src/test/resources/features/ Modified: sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/BundleDescriptor.java sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/Descriptor.java sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/impl/BundleDescriptorImpl.java sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/impl/CheckBundleExportsImports.java sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/impl/ContainerDescriptorImpl.java sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/main/Main.java sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/task/AnalyserTask.java sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/scanner/impl/FelixFrameworkScanner.java sling/whiteboard/cziegeler/feature-launcher/src/main/java/org/apache/sling/feature/launcher/impl/FeatureProcessor.java sling/whiteboard/cziegeler/feature-modelconverter/src/main/java/org/apache/sling/feature/modelconverter/impl/Main.java sling/whiteboard/cziegeler/feature-support/pom.xml sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/ConfigurationUtil.java sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/FeatureUtil.java sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/ApplicationJSONReader.java sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/ApplicationJSONWriter.java sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/ConfigurationJSONReader.java sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/ConfigurationJSONWriter.java sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/FeatureJSONReader.java sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/FeatureJSONWriter.java sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONConstants.java sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONReaderBase.java sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONWriterBase.java sling/whiteboard/cziegeler/feature/pom.xml sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/Capability.java sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/Requirement.java sling/whiteboard/cziegeler/feature/src/main/java/org/apache/sling/feature/process/ApplicationBuilder.java sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/Preprocessor.java sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/ProjectHelper.java sling/whiteboard/cziegeler/osgifeature-maven-plugin/src/main/java/org/apache/sling/feature/maven/mojos/AttachFeature.java Modified: sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/BundleDescriptor.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/BundleDescriptor.java?rev=1803557&r1=1803556&r2=1803557&view=diff ============================================================================== --- sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/BundleDescriptor.java (original) +++ sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/BundleDescriptor.java Mon Jul 31 17:22:58 2017 @@ -16,6 +16,8 @@ */ package org.apache.sling.feature.analyser; +import org.apache.sling.feature.support.util.PackageInfo; + /** * Information about a bundle */ Modified: sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/Descriptor.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/Descriptor.java?rev=1803557&r1=1803556&r2=1803557&view=diff ============================================================================== --- sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/Descriptor.java (original) +++ sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/Descriptor.java Mon Jul 31 17:22:58 2017 @@ -21,6 +21,7 @@ import java.util.Set; import org.apache.sling.feature.Capability; import org.apache.sling.feature.Requirement; +import org.apache.sling.feature.support.util.PackageInfo; /** * A descriptor holds information about requirements and capabilities Modified: sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/impl/BundleDescriptorImpl.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/impl/BundleDescriptorImpl.java?rev=1803557&r1=1803556&r2=1803557&view=diff ============================================================================== --- sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/impl/BundleDescriptorImpl.java (original) +++ sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/impl/BundleDescriptorImpl.java Mon Jul 31 17:22:58 2017 @@ -24,8 +24,12 @@ import java.util.Set; import java.util.jar.Manifest; import org.apache.sling.feature.Artifact; +import org.apache.sling.feature.Capability; +import org.apache.sling.feature.Requirement; import org.apache.sling.feature.analyser.BundleDescriptor; -import org.apache.sling.feature.analyser.PackageInfo; +import org.apache.sling.feature.support.util.PackageInfo; +import org.apache.sling.feature.support.util.ManifestParser; +import org.apache.sling.feature.support.util.ManifestUtil; import org.osgi.framework.Constants; /** @@ -61,6 +65,10 @@ public class BundleDescriptorImpl /** The corresponding artifact from the feature. */ private final Artifact artifact; + private final Set<Capability> capabilities = new HashSet<>(); + + private final Set<Requirement> requirements = new HashSet<>(); + public BundleDescriptorImpl(final Artifact a, final File file, final int startLevel) throws IOException { @@ -169,6 +177,16 @@ public class BundleDescriptorImpl return Collections.unmodifiableSet(this.dynamicImportedPackages); } + @Override + public Set<Capability> getCapabilities() { + return Collections.unmodifiableSet(capabilities); + } + + @Override + public Set<Requirement> getRequirements() { + return Collections.unmodifiableSet(requirements); + } + protected void analyze() throws IOException { final String name = this.manifest.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME); if ( name != null ) { @@ -186,7 +204,13 @@ public class BundleDescriptorImpl this.exportedPackages.addAll(ManifestUtil.extractExportedPackages(this.manifest)); this.importedPackages.addAll(ManifestUtil.extractImportedPackages(this.manifest)); this.dynamicImportedPackages.addAll(ManifestUtil.extractDynamicImportedPackages(this.manifest)); - + try { + ManifestParser parser = new ManifestParser(this.manifest); + this.capabilities.addAll(ManifestUtil.extractCapabilities(parser)); + this.requirements.addAll(ManifestUtil.extractRequirements(parser)); + } catch (Exception ex) { + throw new IOException(ex); + } } else { throw new IOException("Unable to get bundle symbolic name from artifact " + getArtifact().getId().toMvnId()); } Modified: sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/impl/CheckBundleExportsImports.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/impl/CheckBundleExportsImports.java?rev=1803557&r1=1803556&r2=1803557&view=diff ============================================================================== --- sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/impl/CheckBundleExportsImports.java (original) +++ sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/impl/CheckBundleExportsImports.java Mon Jul 31 17:22:58 2017 @@ -28,7 +28,7 @@ import java.util.TreeMap; import org.apache.sling.feature.Artifact; import org.apache.sling.feature.analyser.BundleDescriptor; -import org.apache.sling.feature.analyser.PackageInfo; +import org.apache.sling.feature.support.util.PackageInfo; import org.apache.sling.feature.analyser.task.AnalyserTask; import org.apache.sling.feature.analyser.task.AnalyserTaskContext; import org.osgi.framework.Version; Added: sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/impl/CheckRequirementsCapabilities.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/impl/CheckRequirementsCapabilities.java?rev=1803557&view=auto ============================================================================== --- sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/impl/CheckRequirementsCapabilities.java (added) +++ sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/impl/CheckRequirementsCapabilities.java Mon Jul 31 17:22:58 2017 @@ -0,0 +1,108 @@ +/* + * 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.impl; + +import org.apache.sling.feature.Artifact; +import org.apache.sling.feature.Capability; +import org.apache.sling.feature.Requirement; +import org.apache.sling.feature.analyser.ArtifactDescriptor; +import org.apache.sling.feature.analyser.BundleDescriptor; +import org.apache.sling.feature.analyser.task.AnalyserTask; +import org.apache.sling.feature.analyser.task.AnalyserTaskContext; +import org.apache.sling.feature.support.impl.CapabilityMatcher; +import org.osgi.framework.Constants; + +import java.util.*; +import java.util.stream.Collectors; + +public class CheckRequirementsCapabilities implements AnalyserTask { + private final String format = "Artifact %s:%s requires %s in start level %d but %s"; + + @Override + public void execute(AnalyserTaskContext ctx) throws Exception { + final SortedMap<Integer, List<ArtifactDescriptor>> artifactsMap = new TreeMap<>(); + for(final BundleDescriptor bi : ctx.getDescriptor().getBundleDescriptors()) { + List<ArtifactDescriptor> list = artifactsMap.get(bi.getBundleStartLevel()); + if ( list == null ) { + list = new ArrayList<>(); + artifactsMap.put(bi.getBundleStartLevel(), list); + } + list.add(bi); + } + + if (!ctx.getDescriptor().getArtifactDescriptors().isEmpty()) { + artifactsMap.put( + (artifactsMap.isEmpty() ? 0 : artifactsMap.lastKey()) + 1, + new ArrayList<>(ctx.getDescriptor().getArtifactDescriptors()) + ); + } + + // create a synthetic bundle info for the system bundle + final ArtifactDescriptor system = new BundleDescriptorImpl(new Artifact(ctx.getApplication().getFramework()), ctx.getDescriptor().getFrameworkDescriptor().getExportedPackages()) { + @Override + public Set<Requirement> getRequirements() { + return ctx.getDescriptor().getFrameworkDescriptor().getRequirements(); + } + + @Override + public Set<Capability> getCapabilities() { + return ctx.getDescriptor().getFrameworkDescriptor().getCapabilities(); + } + }; + + // add system artifact + final List<ArtifactDescriptor> artifacts = new ArrayList<>(); + artifacts.add(system); + + for(final Map.Entry<Integer, List<ArtifactDescriptor>> entry : artifactsMap.entrySet()) { + // first add all providing artifacts + for (final ArtifactDescriptor info : entry.getValue()) { + if (info.getCapabilities() != null) { + artifacts.add(info); + } + } + // check requiring artifacts + for (final ArtifactDescriptor info : entry.getValue()) { + if (info.getRequirements() != null) + { + for (Requirement requirement : info.getRequirements()) { + List<ArtifactDescriptor> candidates = getCandidates(artifacts, requirement); + + if (candidates.isEmpty()) { + if (!CapabilityMatcher.isOptional(requirement)) { + ctx.reportError(String.format(format, info.toString(), entry.getKey(), "no artifact is providing a matching capability in this start level.")); + } + else { + ctx.reportWarning(String.format(format, info.toString(), entry.getKey(), "while the requirement is optional no artifact is providing a matching capability in this start level.")); + } + } + else if ( candidates.size() > 1 ) { + ctx.reportWarning(String.format(format, info.toString(), entry.getKey(), "there is more than one matching capability in this start level.")); + } + } + } + } + } + } + + private List<ArtifactDescriptor> getCandidates(List<ArtifactDescriptor> artifactDescriptors, Requirement requirement) { + return artifactDescriptors.stream() + .filter(artifactDescriptor -> artifactDescriptor.getCapabilities() != null) + .filter(artifactDescriptor -> artifactDescriptor.getCapabilities().stream().anyMatch(capability -> CapabilityMatcher.matches(capability, requirement))) + .collect(Collectors.toList()); + } +} Modified: sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/impl/ContainerDescriptorImpl.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/impl/ContainerDescriptorImpl.java?rev=1803557&r1=1803556&r2=1803557&view=diff ============================================================================== --- sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/impl/ContainerDescriptorImpl.java (original) +++ sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/impl/ContainerDescriptorImpl.java Mon Jul 31 17:22:58 2017 @@ -24,7 +24,7 @@ import org.apache.sling.feature.Requirem import org.apache.sling.feature.analyser.ArtifactDescriptor; import org.apache.sling.feature.analyser.BundleDescriptor; import org.apache.sling.feature.analyser.ContainerDescriptor; -import org.apache.sling.feature.analyser.PackageInfo; +import org.apache.sling.feature.support.util.PackageInfo; /** * Information about a container (feature/application). Modified: sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/main/Main.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/main/Main.java?rev=1803557&r1=1803556&r2=1803557&view=diff ============================================================================== --- sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/main/Main.java (original) +++ sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/main/Main.java Mon Jul 31 17:22:58 2017 @@ -23,7 +23,7 @@ import java.io.IOException; import org.apache.sling.feature.Application; import org.apache.sling.feature.analyser.Analyser; import org.apache.sling.feature.analyser.Scanner; -import org.apache.sling.feature.json.ApplicationJSONReader; +import org.apache.sling.feature.support.json.ApplicationJSONReader; import org.apache.sling.feature.support.ArtifactManagerConfig; import org.apache.sling.feature.support.FeatureUtil; import org.slf4j.Logger; Modified: sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/task/AnalyserTask.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/task/AnalyserTask.java?rev=1803557&r1=1803556&r2=1803557&view=diff ============================================================================== --- sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/task/AnalyserTask.java (original) +++ sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/analyser/task/AnalyserTask.java Mon Jul 31 17:22:58 2017 @@ -23,10 +23,14 @@ package org.apache.sling.feature.analyse public interface AnalyserTask { /** A unique (short) id. */ - String getId(); + default String getId() { + return getClass().getName(); + }; /** A human readable name to identify the task. */ - String getName(); + default String getName() { + return getClass().getSimpleName(); + }; /** Execute the task. */ void execute(AnalyserTaskContext ctx) throws Exception; Modified: sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/scanner/impl/FelixFrameworkScanner.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/scanner/impl/FelixFrameworkScanner.java?rev=1803557&r1=1803556&r2=1803557&view=diff ============================================================================== --- sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/scanner/impl/FelixFrameworkScanner.java (original) +++ sling/whiteboard/cziegeler/feature-analyser/src/main/java/org/apache/sling/feature/scanner/impl/FelixFrameworkScanner.java Mon Jul 31 17:22:58 2017 @@ -31,7 +31,7 @@ import org.apache.sling.commons.osgi.Man import org.apache.sling.feature.ArtifactId; import org.apache.sling.feature.KeyValueMap; import org.apache.sling.feature.analyser.Descriptor; -import org.apache.sling.feature.analyser.PackageInfo; +import org.apache.sling.feature.support.util.PackageInfo; import org.apache.sling.feature.scanner.FrameworkScanner; import org.osgi.framework.Constants; Modified: sling/whiteboard/cziegeler/feature-launcher/src/main/java/org/apache/sling/feature/launcher/impl/FeatureProcessor.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-launcher/src/main/java/org/apache/sling/feature/launcher/impl/FeatureProcessor.java?rev=1803557&r1=1803556&r2=1803557&view=diff ============================================================================== --- sling/whiteboard/cziegeler/feature-launcher/src/main/java/org/apache/sling/feature/launcher/impl/FeatureProcessor.java (original) +++ sling/whiteboard/cziegeler/feature-launcher/src/main/java/org/apache/sling/feature/launcher/impl/FeatureProcessor.java Mon Jul 31 17:22:58 2017 @@ -28,8 +28,8 @@ import org.apache.sling.feature.Artifact import org.apache.sling.feature.Configuration; import org.apache.sling.feature.Extension; import org.apache.sling.feature.ExtensionType; -import org.apache.sling.feature.json.ApplicationJSONReader; -import org.apache.sling.feature.json.ApplicationJSONWriter; +import org.apache.sling.feature.support.json.ApplicationJSONReader; +import org.apache.sling.feature.support.json.ApplicationJSONWriter; import org.apache.sling.feature.launcher.impl.LauncherConfig.StartupMode; import org.apache.sling.feature.support.ArtifactHandler; import org.apache.sling.feature.support.ArtifactManager; Modified: sling/whiteboard/cziegeler/feature-modelconverter/src/main/java/org/apache/sling/feature/modelconverter/impl/Main.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-modelconverter/src/main/java/org/apache/sling/feature/modelconverter/impl/Main.java?rev=1803557&r1=1803556&r2=1803557&view=diff ============================================================================== --- sling/whiteboard/cziegeler/feature-modelconverter/src/main/java/org/apache/sling/feature/modelconverter/impl/Main.java (original) +++ sling/whiteboard/cziegeler/feature-modelconverter/src/main/java/org/apache/sling/feature/modelconverter/impl/Main.java Mon Jul 31 17:22:58 2017 @@ -43,8 +43,8 @@ import org.apache.sling.feature.Extensio import org.apache.sling.feature.ExtensionType; import org.apache.sling.feature.Extensions; import org.apache.sling.feature.KeyValueMap; -import org.apache.sling.feature.json.ApplicationJSONWriter; -import org.apache.sling.feature.json.FeatureJSONWriter; +import org.apache.sling.feature.support.json.ApplicationJSONWriter; +import org.apache.sling.feature.support.json.FeatureJSONWriter; import org.apache.sling.feature.support.ArtifactHandler; import org.apache.sling.feature.support.ArtifactManager; import org.apache.sling.feature.support.ArtifactManagerConfig; Modified: sling/whiteboard/cziegeler/feature-support/pom.xml URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-support/pom.xml?rev=1803557&r1=1803556&r2=1803557&view=diff ============================================================================== --- sling/whiteboard/cziegeler/feature-support/pom.xml (original) +++ sling/whiteboard/cziegeler/feature-support/pom.xml Mon Jul 31 17:22:58 2017 @@ -65,6 +65,32 @@ <scope>provided</scope> </dependency> <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.commons.osgi</artifactId> + <version>2.4.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.converter</artifactId> + <version>0.1.0-SNAPSHOT</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.configurator</artifactId> + <version>0.0.1-SNAPSHOT</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.geronimo.specs</groupId> + <artifactId>geronimo-json_1.0_spec</artifactId> + <version>1.0-alpha-1</version> + <scope>provided</scope> + </dependency> + + <!-- Testing --> + <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> </dependency> @@ -74,5 +100,11 @@ <version>2.8.9</version> <scope>test</scope> </dependency> + <dependency> + <groupId>org.apache.johnzon</groupId> + <artifactId>johnzon-core</artifactId> + <version>1.0.0</version> + <scope>test</scope> + </dependency> </dependencies> </project> Modified: sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/ConfigurationUtil.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/ConfigurationUtil.java?rev=1803557&r1=1803556&r2=1803557&view=diff ============================================================================== --- sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/ConfigurationUtil.java (original) +++ sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/ConfigurationUtil.java Mon Jul 31 17:22:58 2017 @@ -26,7 +26,7 @@ import java.util.jar.Manifest; import java.util.zip.ZipEntry; import org.apache.sling.feature.Configurations; -import org.apache.sling.feature.json.ConfigurationJSONWriter; +import org.apache.sling.feature.support.json.ConfigurationJSONWriter; import org.osgi.framework.Constants; Modified: sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/FeatureUtil.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/FeatureUtil.java?rev=1803557&r1=1803556&r2=1803557&view=diff ============================================================================== --- sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/FeatureUtil.java (original) +++ sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/FeatureUtil.java Mon Jul 31 17:22:58 2017 @@ -28,7 +28,7 @@ import java.util.List; import org.apache.sling.feature.Application; import org.apache.sling.feature.ArtifactId; import org.apache.sling.feature.Feature; -import org.apache.sling.feature.json.FeatureJSONReader; +import org.apache.sling.feature.support.json.FeatureJSONReader; import org.apache.sling.feature.process.ApplicationBuilder; import org.apache.sling.feature.process.FeatureProvider; Modified: sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/ApplicationJSONReader.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/ApplicationJSONReader.java?rev=1803557&r1=1803556&r2=1803557&view=diff ============================================================================== --- sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/ApplicationJSONReader.java (original) +++ sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/ApplicationJSONReader.java Mon Jul 31 17:22:58 2017 @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.sling.feature.json; +package org.apache.sling.feature.support.json; import java.io.IOException; import java.io.Reader; Modified: sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/ApplicationJSONWriter.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/ApplicationJSONWriter.java?rev=1803557&r1=1803556&r2=1803557&view=diff ============================================================================== --- sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/ApplicationJSONWriter.java (original) +++ sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/ApplicationJSONWriter.java Mon Jul 31 17:22:58 2017 @@ -14,7 +14,7 @@ * License for the specific language governing permissions and limitations under * the License. */ -package org.apache.sling.feature.json; +package org.apache.sling.feature.support.json; import java.io.IOException; import java.io.Writer; Modified: sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/ConfigurationJSONReader.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/ConfigurationJSONReader.java?rev=1803557&r1=1803556&r2=1803557&view=diff ============================================================================== --- sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/ConfigurationJSONReader.java (original) +++ sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/ConfigurationJSONReader.java Mon Jul 31 17:22:58 2017 @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.sling.feature.json; +package org.apache.sling.feature.support.json; import java.io.IOException; import java.io.Reader; Modified: sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/ConfigurationJSONWriter.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/ConfigurationJSONWriter.java?rev=1803557&r1=1803556&r2=1803557&view=diff ============================================================================== --- sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/ConfigurationJSONWriter.java (original) +++ sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/ConfigurationJSONWriter.java Mon Jul 31 17:22:58 2017 @@ -14,7 +14,7 @@ * License for the specific language governing permissions and limitations under * the License. */ -package org.apache.sling.feature.json; +package org.apache.sling.feature.support.json; import java.io.IOException; import java.io.Writer; Modified: sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/FeatureJSONReader.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/FeatureJSONReader.java?rev=1803557&r1=1803556&r2=1803557&view=diff ============================================================================== --- sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/FeatureJSONReader.java (original) +++ sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/FeatureJSONReader.java Mon Jul 31 17:22:58 2017 @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.sling.feature.json; +package org.apache.sling.feature.support.json; import java.io.IOException; import java.io.Reader; @@ -32,6 +32,7 @@ import org.apache.sling.feature.Capabili import org.apache.sling.feature.Feature; import org.apache.sling.feature.Include; import org.apache.sling.feature.Requirement; +import org.apache.sling.feature.support.util.ManifestUtil; /** * This class offers a method to read a {@code Feature} using a {@code Reader} instance. @@ -283,18 +284,14 @@ public class FeatureJSONReader extends J checkType("Requirement attributes", obj.get(JSONConstants.REQCAP_ATTRIBUTES), Map.class); @SuppressWarnings("unchecked") final Map<String, Object> attrs = (Map<String, Object>)obj.get(JSONConstants.REQCAP_ATTRIBUTES); - for(final Map.Entry<String, Object> entry : attrs.entrySet()) { - r.getAttributes().put(entry.getKey(), entry.getValue().toString()); - } + attrs.forEach((key, value) -> ManifestUtil.unmarshallAttribute(key, value, r.getAttributes()::put)); } if ( obj.containsKey(JSONConstants.REQCAP_DIRECTIVES) ) { checkType("Requirement directives", obj.get(JSONConstants.REQCAP_DIRECTIVES), Map.class); @SuppressWarnings("unchecked") final Map<String, Object> dirs = (Map<String, Object>)obj.get(JSONConstants.REQCAP_DIRECTIVES); - for(final Map.Entry<String, Object> entry : dirs.entrySet()) { - r.getDirectives().put(entry.getKey(), entry.getValue().toString()); - } + dirs.forEach((key, value) -> ManifestUtil.unmarshallDirective(key, value, r.getDirectives()::put)); } } } @@ -324,18 +321,14 @@ public class FeatureJSONReader extends J checkType("Capability attributes", obj.get(JSONConstants.REQCAP_ATTRIBUTES), Map.class); @SuppressWarnings("unchecked") final Map<String, Object> attrs = (Map<String, Object>)obj.get(JSONConstants.REQCAP_ATTRIBUTES); - for(final Map.Entry<String, Object> entry : attrs.entrySet()) { - c.getAttributes().put(entry.getKey(), entry.getValue().toString()); - } + attrs.forEach((key, value) -> ManifestUtil.unmarshallAttribute(key, value, c.getAttributes()::put)); } if ( obj.containsKey(JSONConstants.REQCAP_DIRECTIVES) ) { checkType("Capability directives", obj.get(JSONConstants.REQCAP_DIRECTIVES), Map.class); @SuppressWarnings("unchecked") - final Map<String, Object> dirs = (Map<String, Object>)obj.get(JSONConstants.REQCAP_DIRECTIVES); - for(final Map.Entry<String, Object> entry : dirs.entrySet()) { - c.getDirectives().put(entry.getKey(), entry.getValue().toString()); - } + final Map<String, Object> dirs = (Map<String, Object>) obj.get(JSONConstants.REQCAP_DIRECTIVES); + dirs.forEach((key, value) -> ManifestUtil.unmarshallDirective(key, value, c.getDirectives()::put)); } } } Modified: sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/FeatureJSONWriter.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/FeatureJSONWriter.java?rev=1803557&r1=1803556&r2=1803557&view=diff ============================================================================== --- sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/FeatureJSONWriter.java (original) +++ sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/FeatureJSONWriter.java Mon Jul 31 17:22:58 2017 @@ -14,12 +14,13 @@ * License for the specific language governing permissions and limitations under * the License. */ -package org.apache.sling.feature.json; +package org.apache.sling.feature.support.json; import java.io.IOException; import java.io.Writer; import java.util.List; import java.util.Map; +import java.util.function.BiFunction; import javax.json.Json; import javax.json.stream.JsonGenerator; @@ -31,6 +32,7 @@ import org.apache.sling.feature.Configur import org.apache.sling.feature.Feature; import org.apache.sling.feature.Include; import org.apache.sling.feature.Requirement; +import org.apache.sling.feature.support.util.ManifestUtil; /** @@ -152,16 +154,12 @@ public class FeatureJSONWriter extends J w.write(JSONConstants.REQCAP_NAMESPACE, req.getNamespace()); if ( !req.getAttributes().isEmpty() ) { w.writeStartObject(JSONConstants.REQCAP_ATTRIBUTES); - for(final Map.Entry<String, String> entry : req.getAttributes()) { - w.write(entry.getKey(), entry.getValue()); - } + req.getAttributes().forEach((key, value) -> ManifestUtil.marshallAttribute(key, value, w::write)); w.writeEnd(); } if ( !req.getDirectives().isEmpty() ) { w.writeStartObject(JSONConstants.REQCAP_DIRECTIVES); - for(final Map.Entry<String, String> entry : req.getDirectives()) { - w.write(entry.getKey(), entry.getValue()); - } + req.getDirectives().forEach((key, value) -> ManifestUtil.marshallDirective(key, value, w::write)); w.writeEnd(); } w.writeEnd(); @@ -178,16 +176,12 @@ public class FeatureJSONWriter extends J w.write(JSONConstants.REQCAP_NAMESPACE, cap.getNamespace()); if ( !cap.getAttributes().isEmpty() ) { w.writeStartObject(JSONConstants.REQCAP_ATTRIBUTES); - for(final Map.Entry<String, String> entry : cap.getAttributes()) { - w.write(entry.getKey(), entry.getValue()); - } + cap.getAttributes().forEach((key, value) -> ManifestUtil.marshallAttribute(key, value, w::write)); w.writeEnd(); } if ( !cap.getDirectives().isEmpty() ) { w.writeStartObject(JSONConstants.REQCAP_DIRECTIVES); - for(final Map.Entry<String, String> entry : cap.getDirectives()) { - w.write(entry.getKey(), entry.getValue()); - } + cap.getDirectives().forEach((key, value) -> ManifestUtil.marshallDirective(key, value, w::write)); w.writeEnd(); } w.writeEnd(); Modified: sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONConstants.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONConstants.java?rev=1803557&r1=1803556&r2=1803557&view=diff ============================================================================== --- sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONConstants.java (original) +++ sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONConstants.java Mon Jul 31 17:22:58 2017 @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.sling.feature.json; +package org.apache.sling.feature.support.json; import java.util.Arrays; import java.util.List; Modified: sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONReaderBase.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONReaderBase.java?rev=1803557&r1=1803556&r2=1803557&view=diff ============================================================================== --- sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONReaderBase.java (original) +++ sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONReaderBase.java Mon Jul 31 17:22:58 2017 @@ -14,7 +14,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.sling.feature.json; +package org.apache.sling.feature.support.json; import java.io.IOException; import java.io.Reader; Modified: sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONWriterBase.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONWriterBase.java?rev=1803557&r1=1803556&r2=1803557&view=diff ============================================================================== --- sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONWriterBase.java (original) +++ sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/json/JSONWriterBase.java Mon Jul 31 17:22:58 2017 @@ -14,7 +14,7 @@ * License for the specific language governing permissions and limitations under * the License. */ -package org.apache.sling.feature.json; +package org.apache.sling.feature.support.json; import java.io.StringReader; import java.lang.reflect.Array; Added: sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/util/LambdaUtil.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/util/LambdaUtil.java?rev=1803557&view=auto ============================================================================== --- sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/util/LambdaUtil.java (added) +++ sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/util/LambdaUtil.java Mon Jul 31 17:22:58 2017 @@ -0,0 +1,99 @@ +/* + * 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.support.util; + +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; + + +public final class LambdaUtil { + + @FunctionalInterface + public interface Consumer_WithExceptions<T, E extends Exception> { + void accept(T t) throws E; + } + + @FunctionalInterface + public interface BiConsumer_WithExceptions<T, U, E extends Exception> { + void accept(T t, U u) throws E; + } + + @FunctionalInterface + public interface Function_WithExceptions<T, R, E extends Exception> { + R apply(T t) throws E; + } + + @FunctionalInterface + public interface Supplier_WithExceptions<T, E extends Exception> { + T get() throws E; + } + + @FunctionalInterface + public interface Runnable_WithExceptions<E extends Exception> { + void run() throws E; + } + + public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) { + return t -> { + try { consumer.accept(t); } + catch (Exception exception) { throwAsUnchecked(exception); } + }; + } + + public static <T, U, E extends Exception> BiConsumer<T, U> rethrowBiConsumer(BiConsumer_WithExceptions<T, U, E> biConsumer) { + return (t, u) -> { + try { biConsumer.accept(t, u); } + catch (Exception exception) { throwAsUnchecked(exception); } + }; + } + + public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) { + return t -> { + try { return function.apply(t); } + catch (Exception exception) { throwAsUnchecked(exception); return null; } + }; + } + + public static <T, E extends Exception> Supplier<T> rethrowSupplier(Supplier_WithExceptions<T, E> function) { + return () -> { + try { return function.get(); } + catch (Exception exception) { throwAsUnchecked(exception); return null; } + }; + } + + public static void uncheck(Runnable_WithExceptions t) + { + try { t.run(); } + catch (Exception exception) { throwAsUnchecked(exception); } + } + + public static <R, E extends Exception> R uncheck(Supplier_WithExceptions<R, E> supplier) + { + try { return supplier.get(); } + catch (Exception exception) { throwAsUnchecked(exception); return null; } + } + + public static <T, R, E extends Exception> R uncheck(Function_WithExceptions<T, R, E> function, T t) { + try { return function.apply(t); } + catch (Exception exception) { throwAsUnchecked(exception); return null; } + } + + @SuppressWarnings ("unchecked") + private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E { throw (E)exception; } +} Added: sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/util/ManifestParser.java URL: http://svn.apache.org/viewvc/sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/util/ManifestParser.java?rev=1803557&view=auto ============================================================================== --- sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/util/ManifestParser.java (added) +++ sling/whiteboard/cziegeler/feature-support/src/main/java/org/apache/sling/feature/support/util/ManifestParser.java Mon Jul 31 17:22:58 2017 @@ -0,0 +1,1038 @@ +/* + * 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.support.util; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.jar.Manifest; + +import org.apache.sling.feature.Capability; +import org.apache.sling.feature.Requirement; +import org.apache.sling.feature.support.impl.SimpleFilter; +import org.apache.sling.feature.support.impl.VersionRange; +import org.osgi.framework.BundleException; +import org.osgi.framework.Constants; +import org.osgi.framework.Version; +import org.osgi.framework.namespace.BundleNamespace; +import org.osgi.framework.namespace.ExecutionEnvironmentNamespace; +import org.osgi.framework.namespace.IdentityNamespace; +import org.osgi.framework.wiring.BundleRevision; + +public class ManifestParser +{ + private static final String BUNDLE_LICENSE_HEADER = "Bundle-License"; // No constant defined by OSGi... + + private final Manifest m_headerMap; + private volatile String m_bundleSymbolicName; + private volatile Version m_bundleVersion; + private volatile List<Capability> m_capabilities; + private volatile List<Requirement> m_requirements; + + public ManifestParser(Manifest m) + throws BundleException + { + m_headerMap = m; + + // Verify that only manifest version 2 is specified. + String manifestVersion = getManifestVersion(m_headerMap); + if ((manifestVersion != null) && !manifestVersion.equals("2")) + { + throw new BundleException( + "Unknown 'Bundle-ManifestVersion' value: " + manifestVersion); + } + + List<Capability> capList = new ArrayList<>(); + + // + // Parse bundle version. + // + + m_bundleVersion = Version.emptyVersion; + if (m_headerMap.getMainAttributes().getValue(Constants.BUNDLE_VERSION) != null) + { + try + { + m_bundleVersion = Version.parseVersion(m_headerMap.getMainAttributes().getValue(Constants.BUNDLE_VERSION)); + } + catch (RuntimeException ex) + { + // R4 bundle versions must parse, R3 bundle version may not. + if (getManifestVersion().equals("2")) + { + throw ex; + } + m_bundleVersion = Version.emptyVersion; + } + } + + // + // Parse bundle symbolic name. + // + + Capability bundleCap = parseBundleSymbolicName(m_headerMap); + if (bundleCap != null) + { + m_bundleSymbolicName = (String) + bundleCap.getAttributes().get(BundleRevision.BUNDLE_NAMESPACE); + + // Add a bundle capability and a host capability to all + // non-fragment bundles. A host capability is the same + // as a require capability, but with a different capability + // namespace. Bundle capabilities resolve required-bundle + // dependencies, while host capabilities resolve fragment-host + // dependencies. + if (m_headerMap.getMainAttributes().getValue(Constants.FRAGMENT_HOST) == null) + { + // All non-fragment bundles have host capabilities. + capList.add(bundleCap); + // A non-fragment bundle can choose to not have a host capability. + String attachment = + (String) bundleCap.getDirectives().get(Constants.FRAGMENT_ATTACHMENT_DIRECTIVE); + attachment = (attachment == null) + ? Constants.FRAGMENT_ATTACHMENT_RESOLVETIME + : attachment; + if (!attachment.equalsIgnoreCase(Constants.FRAGMENT_ATTACHMENT_NEVER)) + { + Map<String, Object> hostAttrs = + new HashMap<String, Object>(bundleCap.getAttributes()); + Object value = hostAttrs.remove(BundleRevision.BUNDLE_NAMESPACE); + hostAttrs.put(BundleRevision.HOST_NAMESPACE, value); + Capability cap = new Capability(BundleRevision.HOST_NAMESPACE); + cap.getAttributes().putAll(hostAttrs); + cap.getDirectives().putAll(bundleCap.getDirectives()); + capList.add(cap); + } + } + + // + // Add the osgi.identity capability. + // + capList.add(addIdentityCapability(m_headerMap, bundleCap)); + } + + // Verify that bundle symbolic name is specified. + if (getManifestVersion().equals("2") && (m_bundleSymbolicName == null)) + { + throw new BundleException( + "R4 bundle manifests must include bundle symbolic name."); + } + + // + // Parse Fragment-Host. + // + + List<Requirement> hostReqs = parseFragmentHost(m_headerMap); + + // + // Parse Require-Bundle + // + + List<ParsedHeaderClause> rbClauses = + parseStandardHeader(m_headerMap.getMainAttributes().getValue(Constants.REQUIRE_BUNDLE)); + rbClauses = normalizeRequireClauses(rbClauses, getManifestVersion()); + List<Requirement> rbReqs = convertRequires(rbClauses); + + + // + // Parse Require-Capability. + // + + List<ParsedHeaderClause> requireClauses = + parseStandardHeader(m_headerMap.getMainAttributes().getValue(Constants.REQUIRE_CAPABILITY)); + List<Requirement> requireReqs = convertRequireCapabilities(normalizeCapabilityClauses( requireClauses, getManifestVersion())); + + // + // Parse Provide-Capability. + // + + List<ParsedHeaderClause> provideClauses = + parseStandardHeader(m_headerMap.getMainAttributes().getValue(Constants.PROVIDE_CAPABILITY)); + + List<Capability> provideCaps = convertProvideCapabilities(normalizeCapabilityClauses(provideClauses, getManifestVersion())); + + // Combine all requirements. + m_requirements = new ArrayList<>(); + m_requirements.addAll(hostReqs); + m_requirements.addAll(rbReqs); + m_requirements.addAll(requireReqs); + + // Combine all capabilities. + m_capabilities = new ArrayList<>(); + m_capabilities.addAll(capList); + m_capabilities.addAll(provideCaps); + } + + + private static List<Requirement> convertRequireCapabilities( + List<ParsedHeaderClause> clauses) + throws BundleException + { + // Now convert generic header clauses into requirements. + List<Requirement> reqList = new ArrayList<>(); + for (ParsedHeaderClause clause : clauses) + { + for (String path : clause.m_paths) + { + if (path.startsWith("osgi.wiring.")) + { + throw new BundleException("Manifest cannot use Require-Capability for '" + + path + + "' namespace."); + } + + Requirement req = new Requirement(path); + req.getAttributes().putAll(clause.m_attrs); + req.getDirectives().putAll(clause.m_dirs); + // Create requirement and add to requirement list. + reqList.add(req); + } + } + + return reqList; + } + + private static List<ParsedHeaderClause> normalizeCapabilityClauses( + List<ParsedHeaderClause> clauses, String mv) + throws BundleException + { + + if (!mv.equals("2") && !clauses.isEmpty()) + { + // Should we error here if we are not an R4 bundle? + } + + // Convert attributes into specified types. + for (ParsedHeaderClause clause : clauses) + { + for (Entry<String, String> entry : clause.m_types.entrySet()) + { + String type = entry.getValue(); + if (!type.equals("String")) + { + if (type.equals("Double")) + { + clause.m_attrs.put( + entry.getKey(), + new Double(clause.m_attrs.get(entry.getKey()).toString().trim())); + } + else if (type.equals("Version")) + { + clause.m_attrs.put( + entry.getKey(), + new Version(clause.m_attrs.get(entry.getKey()).toString().trim())); + } + else if (type.equals("Long")) + { + clause.m_attrs.put( + entry.getKey(), + new Long(clause.m_attrs.get(entry.getKey()).toString().trim())); + } + else if (type.startsWith("List")) + { + int startIdx = type.indexOf('<'); + int endIdx = type.indexOf('>'); + if (((startIdx > 0) && (endIdx <= startIdx)) + || ((startIdx < 0) && (endIdx > 0))) + { + throw new BundleException( + "Invalid Provide-Capability attribute list type for '" + + entry.getKey() + + "' : " + + type); + } + + String listType = "String"; + if (endIdx > startIdx) + { + listType = type.substring(startIdx + 1, endIdx).trim(); + } + + List<String> tokens = parseDelimitedString( + clause.m_attrs.get(entry.getKey()).toString(), ",", false); + List<Object> values = new ArrayList<Object>(tokens.size()); + for (String token : tokens) + { + if (listType.equals("String")) + { + values.add(token); + } + else if (listType.equals("Double")) + { + values.add(new Double(token.trim())); + } + else if (listType.equals("Version")) + { + values.add(new Version(token.trim())); + } + else if (listType.equals("Long")) + { + values.add(new Long(token.trim())); + } + else + { + throw new BundleException( + "Unknown Provide-Capability attribute list type for '" + + entry.getKey() + + "' : " + + type); + } + } + clause.m_attrs.put( + entry.getKey(), + values); + } + else + { + throw new BundleException( + "Unknown Provide-Capability attribute type for '" + + entry.getKey() + + "' : " + + type); + } + } + } + } + + return clauses; + } + + private static List<Capability> convertProvideCapabilities( + List<ParsedHeaderClause> clauses) + throws BundleException + { + List<Capability> capList = new ArrayList<Capability>(); + for (ParsedHeaderClause clause : clauses) + { + for (String path : clause.m_paths) + { + if (path.startsWith("osgi.wiring.")) + { + throw new BundleException("Manifest cannot use Provide-Capability for '" + + path + + "' namespace."); + } + + Capability capability = new Capability(path); + capability.getAttributes().putAll(clause.m_attrs); + capability.getDirectives().putAll(clause.m_dirs); + // Create package capability and add to capability list. + capList.add(capability); + } + } + + return capList; + } + + + public String getManifestVersion() + { + String manifestVersion = getManifestVersion(m_headerMap); + return (manifestVersion == null) ? "1" : manifestVersion; + } + + private static String getManifestVersion(Manifest headerMap) + { + String manifestVersion = headerMap.getMainAttributes().getValue(Constants.BUNDLE_MANIFESTVERSION); + return (manifestVersion == null) ? null : manifestVersion.trim(); + } + + public String getSymbolicName() + { + return m_bundleSymbolicName; + } + + public Version getBundleVersion() + { + return m_bundleVersion; + } + + public List<Capability> getCapabilities() + { + return m_capabilities; + } + + public List<Requirement> getRequirements() + { + return m_requirements; + } + + + static class ParsedHeaderClause + { + public final List<String> m_paths; + public final Map<String, String> m_dirs; + public final Map<String, Object> m_attrs; + public final Map<String, String> m_types; + + public ParsedHeaderClause( + List<String> paths, Map<String, String> dirs, Map<String, Object> attrs, + Map<String, String> types) + { + m_paths = paths; + m_dirs = dirs; + m_attrs = attrs; + m_types = types; + } + } + + private static Capability parseBundleSymbolicName( + Manifest headerMap) + throws BundleException + { + List<ParsedHeaderClause> clauses = parseStandardHeader(headerMap.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME)); + if (clauses.size() > 0) + { + if (clauses.size() > 1) + { + throw new BundleException( + "Cannot have multiple symbolic names: " + + headerMap.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME)); + } + else if (clauses.get(0).m_paths.size() > 1) + { + throw new BundleException( + "Cannot have multiple symbolic names: " + + headerMap.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME)); + } + + // Get bundle version. + Version bundleVersion = Version.emptyVersion; + if (headerMap.getMainAttributes().getValue(Constants.BUNDLE_VERSION) != null) + { + try + { + bundleVersion = Version.parseVersion( + headerMap.getMainAttributes().getValue(Constants.BUNDLE_VERSION)); + } + catch (RuntimeException ex) + { + // R4 bundle versions must parse, R3 bundle version may not. + String mv = getManifestVersion(headerMap); + if (mv != null) + { + throw ex; + } + bundleVersion = Version.emptyVersion; + } + } + + // Create a require capability and return it. + String symName = clauses.get(0).m_paths.get(0); + clauses.get(0).m_attrs.put(BundleRevision.BUNDLE_NAMESPACE, symName); + clauses.get(0).m_attrs.put(Constants.BUNDLE_VERSION_ATTRIBUTE, bundleVersion); + Capability cap = new Capability(BundleRevision.BUNDLE_NAMESPACE); + cap.getAttributes().putAll(clauses.get(0).m_attrs); + cap.getAttributes().putAll(clauses.get(0).m_dirs); + } + + return null; + } + + private static Capability addIdentityCapability(Manifest headerMap, Capability bundleCap) + { + Map<String, Object> attrs = new HashMap<String, Object>(); + + attrs.put(IdentityNamespace.IDENTITY_NAMESPACE, + bundleCap.getAttributes().get(BundleNamespace.BUNDLE_NAMESPACE)); + attrs.put(IdentityNamespace.CAPABILITY_TYPE_ATTRIBUTE, + headerMap.getMainAttributes().getValue(Constants.FRAGMENT_HOST) == null + ? IdentityNamespace.TYPE_BUNDLE + : IdentityNamespace.TYPE_FRAGMENT); + attrs.put(IdentityNamespace.CAPABILITY_VERSION_ATTRIBUTE, + bundleCap.getAttributes().get(Constants.BUNDLE_VERSION_ATTRIBUTE)); + + if (headerMap.getMainAttributes().getValue(Constants.BUNDLE_COPYRIGHT) != null) + { + attrs.put(IdentityNamespace.CAPABILITY_COPYRIGHT_ATTRIBUTE, + headerMap.getMainAttributes().getValue(Constants.BUNDLE_COPYRIGHT)); + } + + if (headerMap.getMainAttributes().getValue(Constants.BUNDLE_DESCRIPTION) != null) + { + attrs.put(IdentityNamespace.CAPABILITY_DESCRIPTION_ATTRIBUTE, + headerMap.getMainAttributes().getValue(Constants.BUNDLE_DESCRIPTION)); + } + if (headerMap.getMainAttributes().getValue(Constants.BUNDLE_DOCURL) != null) + { + attrs.put(IdentityNamespace.CAPABILITY_DOCUMENTATION_ATTRIBUTE, + headerMap.getMainAttributes().getValue(Constants.BUNDLE_DOCURL)); + } + if (headerMap.getMainAttributes().getValue(BUNDLE_LICENSE_HEADER) != null) + { + attrs.put(IdentityNamespace.CAPABILITY_LICENSE_ATTRIBUTE, + headerMap.getMainAttributes().getValue(BUNDLE_LICENSE_HEADER)); + } + + Map<String, String> dirs; + if (bundleCap.getDirectives().get(Constants.SINGLETON_DIRECTIVE) != null) + { + dirs = Collections.singletonMap(IdentityNamespace.CAPABILITY_SINGLETON_DIRECTIVE, + (String) bundleCap.getDirectives().get(Constants.SINGLETON_DIRECTIVE)); + } + else + { + dirs = Collections.emptyMap(); + } + Capability cap = new Capability(IdentityNamespace.IDENTITY_NAMESPACE); + cap.getAttributes().putAll(attrs); + cap.getDirectives().putAll(dirs); + return cap; + } + + private static List<Requirement> parseFragmentHost( + Manifest headerMap) + throws BundleException + { + List<Requirement> reqs = new ArrayList<>(); + + String mv = getManifestVersion(headerMap); + if ((mv != null) && mv.equals("2")) + { + List<ParsedHeaderClause> clauses = parseStandardHeader( + headerMap.getMainAttributes().getValue(Constants.FRAGMENT_HOST)); + if (clauses.size() > 0) + { + // Make sure that only one fragment host symbolic name is specified. + if (clauses.size() > 1) + { + throw new BundleException( + "Fragments cannot have multiple hosts: " + + headerMap.getMainAttributes().getValue(Constants.FRAGMENT_HOST)); + } + else if (clauses.get(0).m_paths.size() > 1) + { + throw new BundleException( + "Fragments cannot have multiple hosts: " + + headerMap.getMainAttributes().getValue(Constants.FRAGMENT_HOST)); + } + + // If the bundle-version attribute is specified, then convert + // it to the proper type. + Object value = clauses.get(0).m_attrs.get(Constants.BUNDLE_VERSION_ATTRIBUTE); + value = (value == null) ? "0.0.0" : value; + if (value != null) + { + clauses.get(0).m_attrs.put( + Constants.BUNDLE_VERSION_ATTRIBUTE, + VersionRange.parse(value.toString())); + } + + // Note that we use a linked hash map here to ensure the + // host symbolic name is first, which will make indexing + // more efficient. +// TODO: OSGi R4.3 - This is ordering is kind of hacky. + // Prepend the host symbolic name to the map of attributes. + Map<String, Object> attrs = clauses.get(0).m_attrs; + Map<String, Object> newAttrs = new LinkedHashMap<String, Object>(attrs.size() + 1); + // We want this first from an indexing perspective. + newAttrs.put( + BundleRevision.HOST_NAMESPACE, + clauses.get(0).m_paths.get(0)); + newAttrs.putAll(attrs); + // But we need to put it again to make sure it wasn't overwritten. + newAttrs.put( + BundleRevision.HOST_NAMESPACE, + clauses.get(0).m_paths.get(0)); + + // Create filter now so we can inject filter directive. + SimpleFilter sf = SimpleFilter.convert(newAttrs); + + // Inject filter directive. +// TODO: OSGi R4.3 - Can we insert this on demand somehow? + Map<String, String> dirs = clauses.get(0).m_dirs; + Map<String, String> newDirs = new HashMap<String, String>(dirs.size() + 1); + newDirs.putAll(dirs); + newDirs.put( + Constants.FILTER_DIRECTIVE, + sf.toString()); + + Requirement req = new Requirement(BundleRevision.HOST_NAMESPACE); + req.getAttributes().putAll(newAttrs); + req.getDirectives().putAll(newDirs); + reqs.add(req); + } + } + else if (headerMap.getMainAttributes().getValue(Constants.FRAGMENT_HOST) != null) + { + String s = headerMap.getMainAttributes().getValue(Constants.BUNDLE_SYMBOLICNAME); + s = (s == null) ? headerMap.getMainAttributes().getValue(Constants.BUNDLE_NAME) : s; + s = (s == null) ? headerMap.toString() : s; + } + + return reqs; + } + + + private static List<Requirement> parseBreeHeader(String header) + { + List<String> filters = new ArrayList<String>(); + for (String entry : parseDelimitedString(header, ",")) + { + List<String> names = parseDelimitedString(entry, "/"); + List<String> left = parseDelimitedString(names.get(0), "-"); + + String lName = left.get(0); + Version lVer; + try + { + lVer = Version.parseVersion(left.get(1)); + } + catch (Exception ex) + { + // Version doesn't parse. Make it part of the name. + lName = names.get(0); + lVer = null; + } + + String rName = null; + Version rVer = null; + if (names.size() > 1) + { + List<String> right = parseDelimitedString(names.get(1), "-"); + rName = right.get(0); + try + { + rVer = Version.parseVersion(right.get(1)); + } + catch (Exception ex) + { + rName = names.get(1); + rVer = null; + } + } + + String versionClause; + if (lVer != null) + { + if ((rVer != null) && (!rVer.equals(lVer))) + { + // Both versions are defined, but different. Make each of them part of the name + lName = names.get(0); + rName = names.get(1); + versionClause = null; + } + else + { + versionClause = getBreeVersionClause(lVer); + } + } + else + { + versionClause = getBreeVersionClause(rVer); + } + + if ("J2SE".equals(lName)) + { + // J2SE is not used in the Capability variant of BREE, use JavaSE here + // This can only happen with the lName part... + lName = "JavaSE"; + } + + String nameClause; + if (rName != null) + nameClause = "(" + ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE + "=" + lName + "/" + rName + ")"; + else + nameClause = "(" + ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE + "=" + lName + ")"; + + String filter; + if (versionClause != null) + filter = "(&" + nameClause + versionClause + ")"; + else + filter = nameClause; + + filters.add(filter); + } + + if (filters.size() == 0) + { + return Collections.emptyList(); + } + else + { + String reqFilter; + if (filters.size() == 1) + { + reqFilter = filters.get(0); + } + else + { + // If there are more BREE filters, we need to or them together + StringBuilder sb = new StringBuilder("(|"); + for (String f : filters) + { + sb.append(f); + } + sb.append(")"); + reqFilter = sb.toString(); + } + + SimpleFilter sf = SimpleFilter.parse(reqFilter); + Requirement req = new Requirement(ExecutionEnvironmentNamespace.EXECUTION_ENVIRONMENT_NAMESPACE); + req.getDirectives().put(ExecutionEnvironmentNamespace.REQUIREMENT_FILTER_DIRECTIVE, reqFilter); + return Collections.<Requirement>singletonList(req); + } + } + + private static String getBreeVersionClause(Version ver) { + if (ver == null) + return null; + + return "(" + ExecutionEnvironmentNamespace.CAPABILITY_VERSION_ATTRIBUTE + "=" + ver + ")"; + } + + private static List<ParsedHeaderClause> normalizeRequireClauses(List<ParsedHeaderClause> clauses, String mv) + { + // R3 bundles cannot require other bundles. + if (!mv.equals("2")) + { + clauses.clear(); + } + else + { + // Convert bundle version attribute to VersionRange type. + for (ParsedHeaderClause clause : clauses) + { + Object value = clause.m_attrs.get(Constants.BUNDLE_VERSION_ATTRIBUTE); + if (value != null) + { + clause.m_attrs.put( + Constants.BUNDLE_VERSION_ATTRIBUTE, + VersionRange.parse(value.toString())); + } + } + } + + return clauses; + } + + private static List<Requirement> convertRequires( + List<ParsedHeaderClause> clauses) + { + List<Requirement> reqList = new ArrayList<>(); + for (ParsedHeaderClause clause : clauses) + { + for (String path : clause.m_paths) + { + // Prepend the bundle symbolic name to the array of attributes. + Map<String, Object> attrs = clause.m_attrs; + // Note that we use a linked hash map here to ensure the + // symbolic name attribute is first, which will make indexing + // more efficient. +// TODO: OSGi R4.3 - This is ordering is kind of hacky. + // Prepend the symbolic name to the array of attributes. + Map<String, Object> newAttrs = new LinkedHashMap<String, Object>(attrs.size() + 1); + // We want this first from an indexing perspective. + newAttrs.put( + BundleRevision.BUNDLE_NAMESPACE, + path); + newAttrs.putAll(attrs); + // But we need to put it again to make sure it wasn't overwritten. + newAttrs.put( + BundleRevision.BUNDLE_NAMESPACE, + path); + + // Create filter now so we can inject filter directive. + SimpleFilter sf = SimpleFilter.convert(newAttrs); + + // Inject filter directive. +// TODO: OSGi R4.3 - Can we insert this on demand somehow? + Map<String, String> dirs = clause.m_dirs; + Map<String, String> newDirs = new HashMap<String, String>(dirs.size() + 1); + newDirs.putAll(dirs); + newDirs.put( + Constants.FILTER_DIRECTIVE, + sf.toString()); + + Requirement req = new Requirement(BundleRevision.BUNDLE_NAMESPACE); + req.getAttributes().putAll(newAttrs); + req.getDirectives().putAll(newDirs); + reqList.add(req); + } + } + + return reqList; + } + + private static final char EOF = (char) -1; + + private static char charAt(int pos, String headers, int length) + { + if (pos >= length) + { + return EOF; + } + return headers.charAt(pos); + } + + private static final int CLAUSE_START = 0; + private static final int PARAMETER_START = 1; + private static final int KEY = 2; + private static final int DIRECTIVE_OR_TYPEDATTRIBUTE = 4; + private static final int ARGUMENT = 8; + private static final int VALUE = 16; + + @SuppressWarnings({ "unchecked", "rawtypes" }) + private static List<ParsedHeaderClause> parseStandardHeader(String header) + { + List<ParsedHeaderClause> clauses = new ArrayList<ParsedHeaderClause>(); + if (header == null) + { + return clauses; + } + ParsedHeaderClause clause = null; + String key = null; + Map targetMap = null; + int state = CLAUSE_START; + int currentPosition = 0; + int startPosition = 0; + int length = header.length(); + boolean quoted = false; + boolean escaped = false; + + char currentChar = EOF; + do + { + currentChar = charAt(currentPosition, header, length); + switch (state) + { + case CLAUSE_START: + clause = new ParsedHeaderClause( + new ArrayList<>(), + new HashMap<>(), + new HashMap<>(), + new HashMap<>()); + clauses.add(clause); + state = PARAMETER_START; + case PARAMETER_START: + startPosition = currentPosition; + state = KEY; + case KEY: + switch (currentChar) + { + case ':': + case '=': + key = header.substring(startPosition, currentPosition).trim(); + startPosition = currentPosition + 1; + targetMap = clause.m_attrs; + state = currentChar == ':' ? DIRECTIVE_OR_TYPEDATTRIBUTE : ARGUMENT; + break; + case EOF: + case ',': + case ';': + clause.m_paths.add(header.substring(startPosition, currentPosition).trim()); + state = currentChar == ',' ? CLAUSE_START : PARAMETER_START; + break; + default: + break; + } + currentPosition++; + break; + case DIRECTIVE_OR_TYPEDATTRIBUTE: + switch(currentChar) + { + case '=': + if (startPosition != currentPosition) + { + clause.m_types.put(key, header.substring(startPosition, currentPosition).trim()); + } + else + { + targetMap = clause.m_dirs; + } + state = ARGUMENT; + startPosition = currentPosition + 1; + break; + default: + break; + } + currentPosition++; + break; + case ARGUMENT: + if (currentChar == '\"') + { + quoted = true; + currentPosition++; + } + else + { + quoted = false; + } + if (!Character.isWhitespace(currentChar)) { + state = VALUE; + } + else { + currentPosition++; + } + break; + case VALUE: + if (escaped) + { + escaped = false; + } + else + { + if (currentChar == '\\' ) + { + escaped = true; + } + else if (quoted && currentChar == '\"') + { + quoted = false; + } + else if (!quoted) + { + String value = null; + switch(currentChar) + { + case EOF: + case ';': + case ',': + value = header.substring(startPosition, currentPosition).trim(); + if (value.startsWith("\"") && value.endsWith("\"")) + { + value = value.substring(1, value.length() - 1); + } + if (targetMap.put(key, value) != null) + { + throw new IllegalArgumentException( + "Duplicate '" + key + "' in: " + header); + } + state = currentChar == ';' ? PARAMETER_START : CLAUSE_START; + break; + default: + break; + } + } + } + currentPosition++; + break; + default: + break; + } + } while ( currentChar != EOF); + + if (state > PARAMETER_START) + { + throw new IllegalArgumentException("Unable to parse header: " + header); + } + return clauses; + } + + public static List<String> parseDelimitedString(String value, String delim) + { + return parseDelimitedString(value, delim, true); + } + + /** + * Parses delimited string and returns an array containing the tokens. This + * parser obeys quotes, so the delimiter character will be ignored if it is + * inside of a quote. This method assumes that the quote character is not + * included in the set of delimiter characters. + * @param value the delimited string to parse. + * @param delim the characters delimiting the tokens. + * @return a list of string or an empty list if there are none. + **/ + public static List<String> parseDelimitedString(String value, String delim, boolean trim) + { + if (value == null) + { + value = ""; + } + + List<String> list = new ArrayList<String>(); + + int CHAR = 1; + int DELIMITER = 2; + int STARTQUOTE = 4; + int ENDQUOTE = 8; + + StringBuffer sb = new StringBuffer(); + + int expecting = (CHAR | DELIMITER | STARTQUOTE); + + boolean isEscaped = false; + for (int i = 0; i < value.length(); i++) + { + char c = value.charAt(i); + + boolean isDelimiter = (delim.indexOf(c) >= 0); + + if (!isEscaped && (c == '\\')) + { + isEscaped = true; + continue; + } + + if (isEscaped) + { + sb.append(c); + } + else if (isDelimiter && ((expecting & DELIMITER) > 0)) + { + if (trim) + { + list.add(sb.toString().trim()); + } + else + { + list.add(sb.toString()); + } + sb.delete(0, sb.length()); + expecting = (CHAR | DELIMITER | STARTQUOTE); + } + else if ((c == '"') && ((expecting & STARTQUOTE) > 0)) + { + sb.append(c); + expecting = CHAR | ENDQUOTE; + } + else if ((c == '"') && ((expecting & ENDQUOTE) > 0)) + { + sb.append(c); + expecting = (CHAR | STARTQUOTE | DELIMITER); + } + else if ((expecting & CHAR) > 0) + { + sb.append(c); + } + else + { + throw new IllegalArgumentException("Invalid delimited string: " + value); + } + + isEscaped = false; + } + + if (sb.length() > 0) + { + if (trim) + { + list.add(sb.toString().trim()); + } + else + { + list.add(sb.toString()); + } + } + + return list; + } +}