001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *   http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing,
013     * software distributed under the License is distributed on an
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015     * KIND, either express or implied.  See the License for the
016     * specific language governing permissions and limitations
017     * under the License.
018     */
019    package org.apache.felix.framework.util.manifestparser;
020    
021    import java.util.*;
022    
023    import org.apache.felix.framework.Logger;
024    import org.apache.felix.framework.util.FelixConstants;
025    import org.apache.felix.framework.util.VersionRange;
026    import org.apache.felix.moduleloader.ICapability;
027    import org.apache.felix.moduleloader.IModule;
028    import org.apache.felix.moduleloader.IRequirement;
029    import org.osgi.framework.*;
030    
031    public class ManifestParser
032    {
033        private final Logger m_logger;
034        private final Map m_configMap;
035        private final Map m_headerMap;
036        private volatile int m_activationPolicy = IModule.EAGER_ACTIVATION;
037        private volatile String m_activationIncludeDir;
038        private volatile String m_activationExcludeDir;
039        private volatile boolean m_isExtension = false;
040        private volatile String m_bundleSymbolicName;
041        private volatile Version m_bundleVersion;
042        private volatile ICapability[] m_capabilities;
043        private volatile IRequirement[] m_requirements;
044        private volatile IRequirement[] m_dynamicRequirements;
045        private volatile R4LibraryClause[] m_libraryHeaders;
046        private volatile boolean m_libraryHeadersOptional = false;
047    
048        public ManifestParser(Logger logger, Map configMap, IModule owner, Map headerMap)
049            throws BundleException
050        {
051            m_logger = logger;
052            m_configMap = configMap;
053            m_headerMap = headerMap;
054    
055            // Verify that only manifest version 2 is specified.
056            String manifestVersion = getManifestVersion(m_headerMap);
057            if ((manifestVersion != null) && !manifestVersion.equals("2"))
058            {
059                throw new BundleException(
060                    "Unknown 'Bundle-ManifestVersion' value: " + manifestVersion);
061            }
062    
063            // Create lists to hold capabilities and requirements.
064            List capList = new ArrayList();
065            List reqList = new ArrayList();
066    
067            //
068            // Parse bundle version.
069            //
070    
071            m_bundleVersion = Version.emptyVersion;
072            if (headerMap.get(Constants.BUNDLE_VERSION) != null)
073            {
074                try
075                {
076                    m_bundleVersion = Version.parseVersion((String) headerMap.get(Constants.BUNDLE_VERSION));
077                }
078                catch (RuntimeException ex)
079                {
080                    // R4 bundle versions must parse, R3 bundle version may not.
081                    if (getManifestVersion().equals("2"))
082                    {
083                        throw ex;
084                    }
085                    m_bundleVersion = Version.emptyVersion;
086                }
087            }
088    
089            //
090            // Parse bundle symbolic name.
091            //
092    
093            ICapability moduleCap = parseBundleSymbolicName(owner, m_headerMap);
094            if (moduleCap != null)
095            {
096                m_bundleSymbolicName = (String)
097                    moduleCap.getProperties().get(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE);
098    
099                // Add a module capability and a host capability to all
100                // non-fragment bundles. A host capability is the same
101                // as a module capability, but with a different capability
102                // namespace. Module capabilities resolve required-bundle
103                // dependencies, while host capabilities resolve fragment-host
104                // dependencies.
105                if (headerMap.get(Constants.FRAGMENT_HOST) == null)
106                {
107                    capList.add(moduleCap);
108                    capList.add(new Capability(
109                        owner, ICapability.HOST_NAMESPACE, null,
110                        ((Capability) moduleCap).getAttributes()));
111                }
112            }
113    
114            //
115            // Parse Export-Package.
116            //
117    
118            // Get exported packages from bundle manifest.
119            ICapability[] exportCaps = parseExportHeader(
120                owner, (String) headerMap.get(Constants.EXPORT_PACKAGE));
121    
122            // Verify that "java.*" packages are not exported.
123            for (int capIdx = 0; capIdx < exportCaps.length; capIdx++)
124            {
125                // Verify that the named package has not already been declared.
126                String pkgName = (String)
127                    exportCaps[capIdx].getProperties().get(ICapability.PACKAGE_PROPERTY);
128                // Verify that java.* packages are not exported.
129                if (pkgName.startsWith("java."))
130                {
131                    throw new BundleException(
132                        "Exporting java.* packages not allowed: " + pkgName);
133                }
134                capList.add(exportCaps[capIdx]);
135            }
136    
137            // Create an array of all capabilities.
138            m_capabilities = (ICapability[]) capList.toArray(new ICapability[capList.size()]);
139    
140            //
141            // Parse Fragment-Host.
142            //
143    
144            IRequirement req = parseFragmentHost(m_logger, m_headerMap);
145            if (req != null)
146            {
147                reqList.add(req);
148            }
149    
150            //
151            // Parse Require-Bundle
152            //
153    
154            IRequirement[] bundleReq = parseRequireBundleHeader(
155                (String) headerMap.get(Constants.REQUIRE_BUNDLE));
156            for (int reqIdx = 0; reqIdx < bundleReq.length; reqIdx++)
157            {
158                reqList.add(bundleReq[reqIdx]);
159            }
160    
161            //
162            // Parse Import-Package.
163            //
164    
165            // Get import packages from bundle manifest.
166            IRequirement[] importReqs = parseImportHeader(
167                (String) headerMap.get(Constants.IMPORT_PACKAGE));
168    
169            // Verify there are no duplicate import declarations.
170            Set dupeSet = new HashSet();
171            for (int reqIdx = 0; reqIdx < importReqs.length; reqIdx++)
172            {
173                // Verify that the named package has not already been declared.
174                String pkgName = ((Requirement) importReqs[reqIdx]).getTargetName();
175                if (!dupeSet.contains(pkgName))
176                {
177                    // Verify that java.* packages are not imported.
178                    if (pkgName.startsWith("java."))
179                    {
180                        throw new BundleException(
181                            "Importing java.* packages not allowed: " + pkgName);
182                    }
183                    dupeSet.add(pkgName);
184                }
185                else
186                {
187                    throw new BundleException("Duplicate import - " + pkgName);
188                }
189                // If it has not already been imported, then add it to the list
190                // of requirements.
191                reqList.add(importReqs[reqIdx]);
192            }
193    
194            // Create an array of all requirements.
195            m_requirements = (IRequirement[]) reqList.toArray(new IRequirement[reqList.size()]);
196    
197            //
198            // Parse DynamicImport-Package.
199            //
200    
201            // Get dynamic import packages from bundle manifest.
202            m_dynamicRequirements = parseImportHeader(
203                (String) headerMap.get(Constants.DYNAMICIMPORT_PACKAGE));
204    
205            // Dynamic imports can have duplicates, so just check for import
206            // of java.*.
207            for (int reqIdx = 0; reqIdx < m_dynamicRequirements.length; reqIdx++)
208            {
209                // Verify that java.* packages are not imported.
210                String pkgName = ((Requirement) m_dynamicRequirements[reqIdx]).getTargetName();
211                if (pkgName.startsWith("java."))
212                {
213                    throw new BundleException(
214                        "Dynamically importing java.* packages not allowed: " + pkgName);
215                }
216                else if (!pkgName.equals("*") && pkgName.endsWith("*") && !pkgName.endsWith(".*"))
217                {
218                    throw new BundleException(
219                        "Partial package name wild carding is not allowed: " + pkgName);
220                }
221            }
222    
223            //
224            // Parse Bundle-NativeCode.
225            //
226    
227            // Get native library entry names for module library sources.
228            m_libraryHeaders =
229                parseLibraryStrings(
230                    m_logger,
231                    parseDelimitedString((String) m_headerMap.get(Constants.BUNDLE_NATIVECODE), ","));
232    
233            // Check to see if there was an optional native library clause, which is
234            // represented by a null library header; if so, record it and remove it.
235            if ((m_libraryHeaders.length > 0) &&
236                (m_libraryHeaders[m_libraryHeaders.length - 1].getLibraryEntries() == null))
237            {
238                m_libraryHeadersOptional = true;
239                R4LibraryClause[] tmp = new R4LibraryClause[m_libraryHeaders.length - 1];
240                System.arraycopy(m_libraryHeaders, 0, tmp, 0, m_libraryHeaders.length - 1);
241                m_libraryHeaders = tmp;
242            }
243    
244            //
245            // Parse activation policy.
246            //
247    
248            // This sets m_activationPolicy, m_includedPolicyClasses, and
249            // m_excludedPolicyClasses.
250            parseActivationPolicy(headerMap);
251    
252            // Do final checks and normalization of manifest.
253            if (getManifestVersion().equals("2"))
254            {
255                checkAndNormalizeR4();
256            }
257            else
258            {
259                checkAndNormalizeR3();
260            }
261        }
262    
263        public String getManifestVersion()
264        {
265            String manifestVersion = getManifestVersion(m_headerMap);
266            return (manifestVersion == null) ? "1" : manifestVersion;
267        }
268    
269        private static String getManifestVersion(Map headerMap)
270        {
271            String manifestVersion = (String) headerMap.get(Constants.BUNDLE_MANIFESTVERSION);
272            return (manifestVersion == null) ? null : manifestVersion.trim();
273        }
274    
275        public int getActivationPolicy()
276        {
277            return m_activationPolicy;
278        }
279    
280        public String getActivationIncludeDirective()
281        {
282            return m_activationIncludeDir;
283        }
284    
285        public String getActivationExcludeDirective()
286        {
287            return m_activationExcludeDir;
288        }
289    
290        public boolean isExtension()
291        {
292            return m_isExtension;
293        }
294    
295        public String getSymbolicName()
296        {
297            return m_bundleSymbolicName;
298        }
299    
300        public Version getBundleVersion()
301        {
302            return m_bundleVersion;
303        }
304    
305        public ICapability[] getCapabilities()
306        {
307            return m_capabilities;
308        }
309    
310        public IRequirement[] getRequirements()
311        {
312            return m_requirements;
313        }
314    
315        public IRequirement[] getDynamicRequirements()
316        {
317            return m_dynamicRequirements;
318        }
319    
320        public R4LibraryClause[] getLibraryClauses()
321        {
322            return m_libraryHeaders;
323        }
324    
325        /**
326         * <p>
327         * This method returns the selected native library metadata from
328         * the manifest. The information is not the raw metadata from the
329         * manifest, but is the native library clause selected according
330         * to the OSGi native library clause selection policy. The metadata
331         * returned by this method will be attached directly to a module and
332         * used for finding its native libraries at run time. To inspect the
333         * raw native library metadata refer to <tt>getLibraryClauses()</tt>.
334         * </p>
335         * <p>
336         * This method returns one of three values:
337         * </p>
338         * <ul>
339         * <li><tt>null</tt> - if the are no native libraries for this module;
340         *     this may also indicate the native libraries are optional and
341         *     did not match the current platform.</li>
342         * <li>Zero-length <tt>R4Library</tt> array - if no matching native library
343         *     clause was found; this bundle should not resolve.</li>
344         * <li>Nonzero-length <tt>R4Library</tt> array - the native libraries
345         *     associated with the matching native library clause.</li>
346         * </ul>
347         *
348         * @return <tt>null</tt> if there are no native libraries, a zero-length
349         *         array if no libraries matched, or an array of selected libraries.
350        **/
351        public R4Library[] getLibraries()
352        {
353            R4Library[] libs = null;
354            try
355            {
356                R4LibraryClause clause = getSelectedLibraryClause();
357                if (clause != null)
358                {
359                    String[] entries = clause.getLibraryEntries();
360                    libs = new R4Library[entries.length];
361                    int current = 0;
362                    for (int i = 0; i < libs.length; i++)
363                    {
364                        String name = getName(entries[i]);
365                        boolean found = false;
366                        for (int j = 0; !found && (j < current); j++)
367                        {
368                            found = getName(entries[j]).equals(name);
369                        }
370                        if (!found)
371                        {
372                            libs[current++] = new R4Library(
373                                clause.getLibraryEntries()[i],
374                                clause.getOSNames(), clause.getProcessors(), clause.getOSVersions(),
375                                clause.getLanguages(), clause.getSelectionFilter());
376                        }
377                    }
378                    if (current < libs.length)
379                    {
380                        R4Library[] tmp = new R4Library[current];
381                        System.arraycopy(libs, 0, tmp, 0, current);
382                        libs = tmp;
383                    }
384                }
385            }
386            catch (Exception ex)
387            {
388                libs = new R4Library[0];
389            }
390            return libs;
391        }
392    
393        private String getName(String path)
394        {
395            int idx = path.lastIndexOf('/');
396            if (idx > -1)
397            {
398                return path.substring(idx);
399            }
400            return path;
401        }
402    
403        private R4LibraryClause getSelectedLibraryClause() throws BundleException
404        {
405            if ((m_libraryHeaders != null) && (m_libraryHeaders.length > 0))
406            {
407                List clauseList = new ArrayList();
408    
409                // Search for matching native clauses.
410                for (int i = 0; i < m_libraryHeaders.length; i++)
411                {
412                    if (m_libraryHeaders[i].match(m_configMap))
413                    {
414                        clauseList.add(m_libraryHeaders[i]);
415                    }
416                }
417    
418                // Select the matching native clause.
419                int selected = 0;
420                if (clauseList.size() == 0)
421                {
422                    // If optional clause exists, no error thrown.
423                    if (m_libraryHeadersOptional)
424                    {
425                        return null;
426                    }
427                    else
428                    {
429                        throw new BundleException("Unable to select a native library clause.");
430                    }
431                }
432                else if (clauseList.size() == 1)
433                {
434                    selected = 0;
435                }
436                else if (clauseList.size() > 1)
437                {
438                    selected = firstSortedClause(clauseList);
439                }
440                return ((R4LibraryClause) clauseList.get(selected));
441            }
442    
443            return null;
444        }
445    
446        private int firstSortedClause(List clauseList)
447        {
448            ArrayList indexList = new ArrayList();
449            ArrayList selection = new ArrayList();
450    
451            // Init index list
452            for (int i = 0; i < clauseList.size(); i++)
453            {
454                indexList.add("" + i);
455            }
456    
457            // Select clause with 'osversion' range declared
458            // and get back the max floor of 'osversion' ranges.
459            Version osVersionRangeMaxFloor = new Version(0, 0, 0);
460            for (int i = 0; i < indexList.size(); i++)
461            {
462                int index = Integer.parseInt(indexList.get(i).toString());
463                String[] osversions = ((R4LibraryClause) clauseList.get(index)).getOSVersions();
464                if (osversions != null)
465                {
466                    selection.add("" + indexList.get(i));
467                }
468                for (int k = 0; (osversions != null) && (k < osversions.length); k++)
469                {
470                    VersionRange range = VersionRange.parse(osversions[k]);
471                    if ((range.getLow()).compareTo(osVersionRangeMaxFloor) >= 0)
472                    {
473                        osVersionRangeMaxFloor = range.getLow();
474                    }
475                }
476            }
477    
478            if (selection.size() == 1)
479            {
480                return Integer.parseInt(selection.get(0).toString());
481            }
482            else if (selection.size() > 1)
483            {
484                // Keep only selected clauses with an 'osversion'
485                // equal to the max floor of 'osversion' ranges.
486                indexList = selection;
487                selection = new ArrayList();
488                for (int i = 0; i < indexList.size(); i++)
489                {
490                    int index = Integer.parseInt(indexList.get(i).toString());
491                    String[] osversions = ((R4LibraryClause) clauseList.get(index)).getOSVersions();
492                    for (int k = 0; k < osversions.length; k++)
493                    {
494                        VersionRange range = VersionRange.parse(osversions[k]);
495                        if ((range.getLow()).compareTo(osVersionRangeMaxFloor) >= 0)
496                        {
497                            selection.add("" + indexList.get(i));
498                        }
499                    }
500                }
501            }
502    
503            if (selection.size() == 0)
504            {
505                // Re-init index list.
506                selection.clear();
507                indexList.clear();
508                for (int i = 0; i < clauseList.size(); i++)
509                {
510                    indexList.add("" + i);
511                }
512            }
513            else if (selection.size() == 1)
514            {
515                return Integer.parseInt(selection.get(0).toString());
516            }
517            else
518            {
519                indexList = selection;
520                selection.clear();
521            }
522    
523            // Keep only clauses with 'language' declared.
524            for (int i = 0; i < indexList.size(); i++)
525            {
526                int index = Integer.parseInt(indexList.get(i).toString());
527                if (((R4LibraryClause) clauseList.get(index)).getLanguages() != null)
528                {
529                    selection.add("" + indexList.get(i));
530                }
531            }
532    
533            // Return the first sorted clause
534            if (selection.size() == 0)
535            {
536                return 0;
537            }
538            else
539            {
540                return Integer.parseInt(selection.get(0).toString());
541            }
542        }
543    
544        private void checkAndNormalizeR3() throws BundleException
545        {
546            // Check to make sure that R3 bundles have only specified
547            // the 'specification-version' attribute and no directives
548            // on their exports; ignore all unknown attributes.
549            for (int capIdx = 0;
550                (m_capabilities != null) && (capIdx < m_capabilities.length);
551                capIdx++)
552            {
553                if (m_capabilities[capIdx].getNamespace().equals(ICapability.PACKAGE_NAMESPACE))
554                {
555                    // R3 bundles cannot have directives on their exports.
556                    if (((Capability) m_capabilities[capIdx]).getDirectives().length != 0)
557                    {
558                        throw new BundleException("R3 exports cannot contain directives.");
559                    }
560    
561                    // Remove and ignore all attributes other than version.
562                    // NOTE: This is checking for "version" rather than "specification-version"
563                    // because the package class normalizes to "version" to avoid having
564                    // future special cases. This could be changed if more strict behavior
565                    // is required.
566                    if (((Capability) m_capabilities[capIdx]).getAttributes() != null)
567                    {
568                        // R3 package capabilities should only have name and
569                        // version attributes.
570                        R4Attribute pkgName = null;
571                        R4Attribute pkgVersion = new R4Attribute(ICapability.VERSION_PROPERTY, Version.emptyVersion, false);
572                        for (int attrIdx = 0;
573                            attrIdx < ((Capability) m_capabilities[capIdx]).getAttributes().length;
574                            attrIdx++)
575                        {
576                            if (((Capability) m_capabilities[capIdx]).getAttributes()[attrIdx]
577                                .getName().equals(ICapability.PACKAGE_PROPERTY))
578                            {
579                                pkgName = ((Capability) m_capabilities[capIdx]).getAttributes()[attrIdx];
580                            }
581                            else if (((Capability) m_capabilities[capIdx]).getAttributes()[attrIdx]
582                                .getName().equals(ICapability.VERSION_PROPERTY))
583                            {
584                                pkgVersion = ((Capability) m_capabilities[capIdx]).getAttributes()[attrIdx];
585                            }
586                            else
587                            {
588                                m_logger.log(Logger.LOG_WARNING,
589                                    "Unknown R3 export attribute: "
590                                        + ((Capability) m_capabilities[capIdx]).getAttributes()[attrIdx].getName());
591                            }
592                        }
593    
594                        // Recreate the export to remove any other attributes
595                        // and add version if missing.
596                        m_capabilities[capIdx] = new Capability(
597                            m_capabilities[capIdx].getModule(),
598                            ICapability.PACKAGE_NAMESPACE,
599                            null,
600                            new R4Attribute[] { pkgName, pkgVersion } );
601                    }
602                }
603            }
604    
605            // Check to make sure that R3 bundles have only specified
606            // the 'specification-version' attribute and no directives
607            // on their imports; ignore all unknown attributes.
608            for (int reqIdx = 0; (m_requirements != null) && (reqIdx < m_requirements.length); reqIdx++)
609            {
610                if (m_requirements[reqIdx].getNamespace().equals(ICapability.PACKAGE_NAMESPACE))
611                {
612                    // R3 bundles cannot have directives on their imports.
613                    if (((Requirement) m_requirements[reqIdx]).getDirectives().length != 0)
614                    {
615                        throw new BundleException("R3 imports cannot contain directives.");
616                    }
617    
618                    // Remove and ignore all attributes other than version.
619                    // NOTE: This is checking for "version" rather than "specification-version"
620                    // because the package class normalizes to "version" to avoid having
621                    // future special cases. This could be changed if more strict behavior
622                    // is required.
623                    if (((Requirement) m_requirements[reqIdx]).getAttributes() != null)
624                    {
625                        // R3 package requirements should only have name and
626                        // version attributes.
627                        R4Attribute pkgName = null;
628                        R4Attribute pkgVersion =
629                            new R4Attribute(ICapability.VERSION_PROPERTY,
630                                new VersionRange(Version.emptyVersion, true, null, true), false);
631                        for (int attrIdx = 0;
632                            attrIdx < ((Requirement) m_requirements[reqIdx]).getAttributes().length;
633                            attrIdx++)
634                        {
635                            if (((Requirement) m_requirements[reqIdx]).getAttributes()[attrIdx]
636                                .getName().equals(ICapability.PACKAGE_PROPERTY))
637                            {
638                                pkgName = ((Requirement) m_requirements[reqIdx]).getAttributes()[attrIdx];
639                            }
640                            else if (((Requirement) m_requirements[reqIdx]).getAttributes()[attrIdx]
641                              .getName().equals(ICapability.VERSION_PROPERTY))
642                            {
643                                pkgVersion = ((Requirement) m_requirements[reqIdx]).getAttributes()[attrIdx];
644                            }
645                            else
646                            {
647                                m_logger.log(Logger.LOG_WARNING,
648                                    "Unknown R3 import attribute: "
649                                        + ((Requirement) m_requirements[reqIdx]).getAttributes()[attrIdx].getName());
650                            }
651                        }
652    
653                        // Recreate the import to remove any other attributes
654                        // and add version if missing.
655                        m_requirements[reqIdx] = new Requirement(
656                            ICapability.PACKAGE_NAMESPACE,
657                            null,
658                            new R4Attribute[] { pkgName, pkgVersion });
659                    }
660                }
661            }
662    
663            // Since all R3 exports imply an import, add a corresponding
664            // requirement for each existing export capability. Do not
665            // duplicate imports.
666            Map map =  new HashMap();
667            // Add existing imports.
668            for (int i = 0; i < m_requirements.length; i++)
669            {
670                if (m_requirements[i].getNamespace().equals(ICapability.PACKAGE_NAMESPACE))
671                {
672                    map.put(
673                        ((Requirement) m_requirements[i]).getTargetName(),
674                        m_requirements[i]);
675                }
676            }
677            // Add import requirement for each export capability.
678            for (int i = 0; i < m_capabilities.length; i++)
679            {
680                if (m_capabilities[i].getNamespace().equals(ICapability.PACKAGE_NAMESPACE) &&
681                    (map.get(m_capabilities[i].getProperties().get(ICapability.PACKAGE_PROPERTY)) == null))
682                {
683                    // Convert Version to VersionRange.
684                    R4Attribute[] attrs = (R4Attribute[]) ((Capability) m_capabilities[i]).getAttributes().clone();
685                    for (int attrIdx = 0; (attrs != null) && (attrIdx < attrs.length); attrIdx++)
686                    {
687                        if (attrs[attrIdx].getName().equals(Constants.VERSION_ATTRIBUTE))
688                        {
689                            attrs[attrIdx] = new R4Attribute(
690                                attrs[attrIdx].getName(),
691                                VersionRange.parse(attrs[attrIdx].getValue().toString()),
692                                attrs[attrIdx].isMandatory());
693                        }
694                    }
695    
696                    map.put(
697                        m_capabilities[i].getProperties().get(ICapability.PACKAGE_PROPERTY),
698                        new Requirement(ICapability.PACKAGE_NAMESPACE, null, attrs));
699                }
700            }
701            m_requirements =
702                (IRequirement[]) map.values().toArray(new IRequirement[map.size()]);
703    
704            // Add a "uses" directive onto each export of R3 bundles
705            // that references every other import (which will include
706            // exports, since export implies import); this is
707            // necessary since R3 bundles assumed a single class space,
708            // but R4 allows for multiple class spaces.
709            String usesValue = "";
710            for (int i = 0; (m_requirements != null) && (i < m_requirements.length); i++)
711            {
712                if (m_requirements[i].getNamespace().equals(ICapability.PACKAGE_NAMESPACE))
713                {
714                    usesValue = usesValue
715                        + ((usesValue.length() > 0) ? "," : "")
716                        + ((Requirement) m_requirements[i]).getTargetName();
717                }
718            }
719            R4Directive uses = new R4Directive(
720                Constants.USES_DIRECTIVE, usesValue);
721            for (int i = 0; (m_capabilities != null) && (i < m_capabilities.length); i++)
722            {
723                if (m_capabilities[i].getNamespace().equals(ICapability.PACKAGE_NAMESPACE))
724                {
725                    m_capabilities[i] = new Capability(
726                        m_capabilities[i].getModule(),
727                        ICapability.PACKAGE_NAMESPACE,
728                        new R4Directive[] { uses },
729                        ((Capability) m_capabilities[i]).getAttributes());
730                }
731            }
732    
733            // Check to make sure that R3 bundles have no attributes or
734            // directives on their dynamic imports.
735            for (int i = 0;
736                (m_dynamicRequirements != null) && (i < m_dynamicRequirements.length);
737                i++)
738            {
739                if (((Requirement) m_dynamicRequirements[i]).getDirectives().length != 0)
740                {
741                    throw new BundleException("R3 dynamic imports cannot contain directives.");
742                }
743                if (((Requirement) m_dynamicRequirements[i]).getAttributes().length != 0)
744                {
745    //                throw new BundleException("R3 dynamic imports cannot contain attributes.");
746                }
747            }
748        }
749    
750        private void checkAndNormalizeR4() throws BundleException
751        {
752            // Verify that bundle symbolic name is specified.
753            if (m_bundleSymbolicName == null)
754            {
755                throw new BundleException("R4 bundle manifests must include bundle symbolic name.");
756            }
757    
758            m_capabilities = checkAndNormalizeR4Exports(
759                m_capabilities, m_bundleSymbolicName, m_bundleVersion);
760    
761            R4Directive extension = parseExtensionBundleHeader((String)
762                m_headerMap.get(Constants.FRAGMENT_HOST));
763    
764            if (extension != null)
765            {
766                if (!(Constants.EXTENSION_FRAMEWORK.equals(extension.getValue()) || 
767                    Constants.EXTENSION_BOOTCLASSPATH.equals(extension.getValue())))
768                {
769                    throw new BundleException(
770                        "Extension bundle must have either 'extension:=framework' or 'extension:=bootclasspath'");
771                }
772                checkExtensionBundle();
773                m_isExtension = true;
774            }
775        }
776    
777        private static ICapability[] checkAndNormalizeR4Exports(
778            ICapability[] caps, String bsn, Version bv)
779            throws BundleException
780        {
781            // Verify that the exports do not specify bundle symbolic name
782            // or bundle version.
783            for (int i = 0; (caps != null) && (i < caps.length); i++)
784            {
785                if (caps[i].getNamespace().equals(ICapability.PACKAGE_NAMESPACE))
786                {
787                    R4Attribute[] attrs = ((Capability) caps[i]).getAttributes();
788                    for (int attrIdx = 0; attrIdx < attrs.length; attrIdx++)
789                    {
790                        // Find symbolic name and version attribute, if present.
791                        if (attrs[attrIdx].getName().equals(Constants.BUNDLE_VERSION_ATTRIBUTE) ||
792                            attrs[attrIdx].getName().equals(Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE))
793                        {
794                            throw new BundleException(
795                                "Exports must not specify bundle symbolic name or bundle version.");
796                        }
797                    }
798    
799                    // Now that we know that there are no bundle symbolic name and version
800                    // attributes, add them since the spec says they are there implicitly.
801                    R4Attribute[] newAttrs = new R4Attribute[attrs.length + 2];
802                    System.arraycopy(attrs, 0, newAttrs, 0, attrs.length);
803                    newAttrs[attrs.length] = new R4Attribute(
804                        Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE, bsn, false);
805                    newAttrs[attrs.length + 1] = new R4Attribute(
806                        Constants.BUNDLE_VERSION_ATTRIBUTE, bv, false);
807                    caps[i] = new Capability(
808                        caps[i].getModule(),
809                        ICapability.PACKAGE_NAMESPACE,
810                        ((Capability) caps[i]).getDirectives(),
811                        newAttrs);
812                }
813            }
814    
815            return caps;
816        }
817    
818        private void checkExtensionBundle() throws BundleException
819        {
820            if (m_headerMap.containsKey(Constants.IMPORT_PACKAGE) ||
821                m_headerMap.containsKey(Constants.REQUIRE_BUNDLE) ||
822                m_headerMap.containsKey(Constants.BUNDLE_NATIVECODE) ||
823                m_headerMap.containsKey(Constants.DYNAMICIMPORT_PACKAGE) ||
824                m_headerMap.containsKey(Constants.BUNDLE_ACTIVATOR))
825            {
826                throw new BundleException("Invalid extension bundle manifest");
827            }
828        }
829    
830        private static ICapability parseBundleSymbolicName(IModule owner, Map headerMap)
831            throws BundleException
832        {
833            Object[][][] clauses = parseStandardHeader(
834                (String) headerMap.get(Constants.BUNDLE_SYMBOLICNAME));
835            if (clauses.length > 0)
836            {
837                if (clauses.length > 1)
838                {
839                    throw new BundleException(
840                        "Cannot have multiple symbolic names: "
841                            + headerMap.get(Constants.BUNDLE_SYMBOLICNAME));
842                }
843                else if (clauses[0][CLAUSE_PATHS_INDEX].length > 1)
844                {
845                    throw new BundleException(
846                        "Cannot have multiple symbolic names: "
847                            + headerMap.get(Constants.BUNDLE_SYMBOLICNAME));
848                }
849    
850                // Get bundle version.
851                Version bundleVersion = Version.emptyVersion;
852                if (headerMap.get(Constants.BUNDLE_VERSION) != null)
853                {
854                    try
855                    {
856                        bundleVersion = Version.parseVersion(
857                            (String) headerMap.get(Constants.BUNDLE_VERSION));
858                    }
859                    catch (RuntimeException ex)
860                    {
861                        // R4 bundle versions must parse, R3 bundle version may not.
862                        String mv = getManifestVersion(headerMap);
863                        if (mv != null)
864                        {
865                            throw ex;
866                        }
867                        bundleVersion = Version.emptyVersion;
868                    }
869                }
870    
871                // Create a module capability and return it.
872                String symName = (String) clauses[0][CLAUSE_PATHS_INDEX][0];
873                R4Attribute[] attrs = new R4Attribute[2];
874                attrs[0] = new R4Attribute(
875                    Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE, symName, false);
876                attrs[1] = new R4Attribute(
877                    Constants.BUNDLE_VERSION_ATTRIBUTE, bundleVersion, false);
878                return new Capability(
879                    owner,
880                    ICapability.MODULE_NAMESPACE,
881                    (R4Directive[]) clauses[0][CLAUSE_DIRECTIVES_INDEX],
882                    attrs);
883            }
884    
885            return null;
886        }
887    
888        private static IRequirement parseFragmentHost(Logger logger, Map headerMap)
889            throws BundleException
890        {
891            IRequirement req = null;
892    
893            String mv = getManifestVersion(headerMap);
894            if ((mv != null) && mv.equals("2"))
895            {
896                Object[][][] clauses = parseStandardHeader(
897                    (String) headerMap.get(Constants.FRAGMENT_HOST));
898                if (clauses.length > 0)
899                {
900                    // Make sure that only one fragment host symbolic name is specified.
901                    if (clauses.length > 1)
902                    {
903                        throw new BundleException(
904                            "Fragments cannot have multiple hosts: "
905                                + headerMap.get(Constants.FRAGMENT_HOST));
906                    }
907                    else if (clauses[0][CLAUSE_PATHS_INDEX].length > 1)
908                    {
909                        throw new BundleException(
910                            "Fragments cannot have multiple hosts: "
911                                + headerMap.get(Constants.FRAGMENT_HOST));
912                    }
913    
914                    // If the bundle version matching attribute is specified, then
915                    // convert it to the proper type.
916                    for (int attrIdx = 0;
917                        attrIdx < clauses[0][CLAUSE_ATTRIBUTES_INDEX].length;
918                        attrIdx++)
919                    {
920                        R4Attribute attr = (R4Attribute) clauses[0][CLAUSE_ATTRIBUTES_INDEX][attrIdx];
921                        if (attr.getName().equals(Constants.BUNDLE_VERSION_ATTRIBUTE))
922                        {
923                            clauses[0][CLAUSE_ATTRIBUTES_INDEX][attrIdx] =
924                                new R4Attribute(
925                                    Constants.BUNDLE_VERSION_ATTRIBUTE,
926                                    VersionRange.parse(attr.getValue().toString()),
927                                    attr.isMandatory());
928                        }
929                    }
930    
931                    // Prepend the host symbolic name to the array of attributes.
932                    R4Attribute[] attrs = (R4Attribute[]) clauses[0][CLAUSE_ATTRIBUTES_INDEX];
933                    R4Attribute[] newAttrs = new R4Attribute[attrs.length + 1];
934                    newAttrs[0] = new R4Attribute(
935                        Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE,
936                        clauses[0][CLAUSE_PATHS_INDEX][0], false);
937                    System.arraycopy(attrs, 0, newAttrs, 1, attrs.length);
938    
939                    req = new Requirement(ICapability.HOST_NAMESPACE,
940                        (R4Directive[]) clauses[0][CLAUSE_DIRECTIVES_INDEX],
941                        newAttrs);
942                }
943            }
944            else
945            {
946                logger.log(Logger.LOG_WARNING, "Only R4 bundles can be fragments.");
947            }
948    
949            return req;
950        }
951    
952        public static ICapability[] parseExportHeader(
953            IModule owner, String header, String bsn, Version bv)
954            throws BundleException
955        {
956            ICapability[] caps = parseExportHeader(owner, header);
957            try
958            {
959                caps = checkAndNormalizeR4Exports(caps, bsn, bv);
960            }
961            catch (BundleException ex)
962            {
963                caps = null;
964            }
965            return caps;
966        }
967    
968        private static ICapability[] parseExportHeader(IModule owner, String header)
969        {
970            Object[][][] clauses = parseStandardHeader(header);
971    
972    // TODO: FRAMEWORK - Perhaps verification/normalization should be completely
973    // separated from parsing, since verification/normalization may vary.
974    
975            // If both version and specification-version attributes are specified,
976            // then verify that the values are equal.
977            Map attrMap = new HashMap();
978            for (int clauseIdx = 0; clauseIdx < clauses.length; clauseIdx++)
979            {
980                // Put attributes for current clause in a map for easy lookup.
981                attrMap.clear();
982                for (int attrIdx = 0;
983                    attrIdx < clauses[clauseIdx][CLAUSE_ATTRIBUTES_INDEX].length;
984                    attrIdx++)
985                {
986                    R4Attribute attr = (R4Attribute) clauses[clauseIdx][CLAUSE_ATTRIBUTES_INDEX][attrIdx];
987                    attrMap.put(attr.getName(), attr);
988                }
989    
990                // Check for "version" and "specification-version" attributes
991                // and verify they are the same if both are specified.
992                R4Attribute v = (R4Attribute) attrMap.get(Constants.VERSION_ATTRIBUTE);
993                R4Attribute sv = (R4Attribute) attrMap.get(Constants.PACKAGE_SPECIFICATION_VERSION);
994                if ((v != null) && (sv != null))
995                {
996                    // Verify they are equal.
997                    if (!((String) v.getValue()).trim().equals(((String) sv.getValue()).trim()))
998                    {
999                        throw new IllegalArgumentException(
1000                            "Both version and specificat-version are specified, but they are not equal.");
1001                    }
1002                }
1003    
1004                // Always add the default version if not specified.
1005                if ((v == null) && (sv == null))
1006                {
1007                    v = new R4Attribute(
1008                        Constants.VERSION_ATTRIBUTE, Version.emptyVersion, false);
1009                }
1010    
1011                // Ensure that only the "version" attribute is used and convert
1012                // it to the appropriate type.
1013                if ((v != null) || (sv != null))
1014                {
1015                    // Convert version attribute to type Version.
1016                    attrMap.remove(Constants.PACKAGE_SPECIFICATION_VERSION);
1017                    v = (v == null) ? sv : v;
1018                    attrMap.put(Constants.VERSION_ATTRIBUTE,
1019                        new R4Attribute(
1020                            Constants.VERSION_ATTRIBUTE,
1021                            Version.parseVersion(v.getValue().toString()),
1022                            v.isMandatory()));
1023    
1024                    // Re-copy the attribute array since it has changed.
1025                    clauses[clauseIdx][CLAUSE_ATTRIBUTES_INDEX] =
1026                        attrMap.values().toArray(new R4Attribute[attrMap.size()]);
1027                }
1028            }
1029    
1030            // Now convert generic header clauses into capabilities.
1031            List capList = new ArrayList();
1032            for (int clauseIdx = 0; clauseIdx < clauses.length; clauseIdx++)
1033            {
1034                for (int pathIdx = 0;
1035                    pathIdx < clauses[clauseIdx][CLAUSE_PATHS_INDEX].length;
1036                    pathIdx++)
1037                {
1038                    // Make sure a package name was specified.
1039                    if (((String) clauses[clauseIdx][CLAUSE_PATHS_INDEX][pathIdx]).length() == 0)
1040                    {
1041                        throw new IllegalArgumentException(
1042                            "An empty package name was specified: " + header);
1043                    }
1044                    // Prepend the package name to the array of attributes.
1045                    R4Attribute[] attrs = (R4Attribute[]) clauses[clauseIdx][CLAUSE_ATTRIBUTES_INDEX];
1046                    R4Attribute[] newAttrs = new R4Attribute[attrs.length + 1];
1047                    newAttrs[0] = new R4Attribute(
1048                        ICapability.PACKAGE_PROPERTY,
1049                        clauses[clauseIdx][CLAUSE_PATHS_INDEX][pathIdx], false);
1050                    System.arraycopy(attrs, 0, newAttrs, 1, attrs.length);
1051    
1052                    // Create package capability and add to capability list.
1053                    capList.add(
1054                        new Capability(
1055                            owner,
1056                            ICapability.PACKAGE_NAMESPACE,
1057                            (R4Directive[]) clauses[clauseIdx][CLAUSE_DIRECTIVES_INDEX],
1058                            newAttrs));
1059                }
1060            }
1061    
1062            return (ICapability[]) capList.toArray(new ICapability[capList.size()]);
1063        }
1064    
1065        private static IRequirement[] parseImportHeader(String header)
1066        {
1067            Object[][][] clauses = parseStandardHeader(header);
1068    
1069    // TODO: FRAMEWORK - Perhaps verification/normalization should be completely
1070    // separated from parsing, since verification/normalization may vary.
1071    
1072            // Verify that the values are equals if the package specifies
1073            // both version and specification-version attributes.
1074            Map attrMap = new HashMap();
1075            for (int clauseIdx = 0; clauseIdx < clauses.length; clauseIdx++)
1076            {
1077                // Put attributes for current clause in a map for easy lookup.
1078                attrMap.clear();
1079                for (int attrIdx = 0;
1080                    attrIdx < clauses[clauseIdx][CLAUSE_ATTRIBUTES_INDEX].length;
1081                    attrIdx++)
1082                {
1083                    R4Attribute attr = (R4Attribute) clauses[clauseIdx][CLAUSE_ATTRIBUTES_INDEX][attrIdx];
1084                    attrMap.put(attr.getName(), attr);
1085                }
1086    
1087                // Check for "version" and "specification-version" attributes
1088                // and verify they are the same if both are specified.
1089                R4Attribute v = (R4Attribute) attrMap.get(Constants.VERSION_ATTRIBUTE);
1090                R4Attribute sv = (R4Attribute) attrMap.get(Constants.PACKAGE_SPECIFICATION_VERSION);
1091                if ((v != null) && (sv != null))
1092                {
1093                    // Verify they are equal.
1094                    if (!((String) v.getValue()).trim().equals(((String) sv.getValue()).trim()))
1095                    {
1096                        throw new IllegalArgumentException(
1097                            "Both version and specificat-version are specified, but they are not equal.");
1098                    }
1099                }
1100    
1101                // Ensure that only the "version" attribute is used and convert
1102                // it to the VersionRange type.
1103                if ((v != null) || (sv != null))
1104                {
1105                    attrMap.remove(Constants.PACKAGE_SPECIFICATION_VERSION);
1106                    v = (v == null) ? sv : v;
1107                    attrMap.put(Constants.VERSION_ATTRIBUTE,
1108                        new R4Attribute(
1109                            Constants.VERSION_ATTRIBUTE,
1110                            VersionRange.parse(v.getValue().toString()),
1111                            v.isMandatory()));
1112                }
1113    
1114                // If bundle version is specified, then convert its type to VersionRange.
1115                v = (R4Attribute) attrMap.get(Constants.BUNDLE_VERSION_ATTRIBUTE);
1116                if (v != null)
1117                {
1118                    attrMap.put(Constants.BUNDLE_VERSION_ATTRIBUTE,
1119                        new R4Attribute(
1120                            Constants.BUNDLE_VERSION_ATTRIBUTE,
1121                            VersionRange.parse(v.getValue().toString()),
1122                            v.isMandatory()));
1123                }
1124    
1125                // Re-copy the attribute array in case it has changed.
1126                clauses[clauseIdx][CLAUSE_ATTRIBUTES_INDEX] =
1127                    attrMap.values().toArray(new R4Attribute[attrMap.size()]);
1128            }
1129    
1130            // Now convert generic header clauses into requirements.
1131            List reqList = new ArrayList();
1132            for (int clauseIdx = 0; clauseIdx < clauses.length; clauseIdx++)
1133            {
1134                for (int pathIdx = 0;
1135                    pathIdx < clauses[clauseIdx][CLAUSE_PATHS_INDEX].length;
1136                    pathIdx++)
1137                {
1138                    // Make sure a package name was specified.
1139                    if (((String) clauses[clauseIdx][CLAUSE_PATHS_INDEX][pathIdx]).length() == 0)
1140                    {
1141                        throw new IllegalArgumentException(
1142                            "An empty package name was specified: " + header);
1143                    }
1144                    // Prepend the package name to the array of attributes.
1145                    R4Attribute[] attrs = (R4Attribute[]) clauses[clauseIdx][CLAUSE_ATTRIBUTES_INDEX];
1146                    R4Attribute[] newAttrs = new R4Attribute[attrs.length + 1];
1147                    newAttrs[0] = new R4Attribute(
1148                        ICapability.PACKAGE_PROPERTY,
1149                        clauses[clauseIdx][CLAUSE_PATHS_INDEX][pathIdx], false);
1150                    System.arraycopy(attrs, 0, newAttrs, 1, attrs.length);
1151    
1152                    // Create package requirement and add to requirement list.
1153                    reqList.add(
1154                        new Requirement(
1155                            ICapability.PACKAGE_NAMESPACE,
1156                            (R4Directive[]) clauses[clauseIdx][CLAUSE_DIRECTIVES_INDEX],
1157                            newAttrs));
1158                }
1159            }
1160    
1161            return (IRequirement[]) reqList.toArray(new IRequirement[reqList.size()]);
1162        }
1163    
1164        private static IRequirement[] parseRequireBundleHeader(String header)
1165        {
1166            Object[][][] clauses = parseStandardHeader(header);
1167    
1168    // TODO: FRAMEWORK - Perhaps verification/normalization should be completely
1169    // separated from parsing, since verification/normalization may vary.
1170    
1171            // Convert bundle version attribute to VersionRange type.
1172            for (int clauseIdx = 0; clauseIdx < clauses.length; clauseIdx++)
1173            {
1174                for (int attrIdx = 0;
1175                    attrIdx < clauses[clauseIdx][CLAUSE_ATTRIBUTES_INDEX].length;
1176                    attrIdx++)
1177                {
1178                    R4Attribute attr = (R4Attribute) clauses[clauseIdx][CLAUSE_ATTRIBUTES_INDEX][attrIdx];
1179                    if (attr.getName().equals(Constants.BUNDLE_VERSION_ATTRIBUTE))
1180                    {
1181                        clauses[clauseIdx][CLAUSE_ATTRIBUTES_INDEX][attrIdx] =
1182                            new R4Attribute(
1183                                Constants.BUNDLE_VERSION_ATTRIBUTE,
1184                                VersionRange.parse(attr.getValue().toString()),
1185                                attr.isMandatory());
1186                    }
1187                }
1188            }
1189    
1190            // Now convert generic header clauses into requirements.
1191            List reqList = new ArrayList();
1192            for (int clauseIdx = 0; clauseIdx < clauses.length; clauseIdx++)
1193            {
1194                for (int pathIdx = 0;
1195                    pathIdx < clauses[clauseIdx][CLAUSE_PATHS_INDEX].length;
1196                    pathIdx++)
1197                {
1198                    // Prepend the symbolic name to the array of attributes.
1199                    R4Attribute[] attrs = (R4Attribute[]) clauses[clauseIdx][CLAUSE_ATTRIBUTES_INDEX];
1200                    R4Attribute[] newAttrs = new R4Attribute[attrs.length + 1];
1201                    newAttrs[0] = new R4Attribute(
1202                        Constants.BUNDLE_SYMBOLICNAME_ATTRIBUTE,
1203                        clauses[clauseIdx][CLAUSE_PATHS_INDEX][pathIdx], false);
1204                    System.arraycopy(attrs, 0, newAttrs, 1, attrs.length);
1205    
1206                    // Create package requirement and add to requirement list.
1207                    reqList.add(
1208                        new Requirement(
1209                            ICapability.MODULE_NAMESPACE,
1210                            (R4Directive[]) clauses[clauseIdx][CLAUSE_DIRECTIVES_INDEX],
1211                            newAttrs));
1212                }
1213            }
1214    
1215            return (IRequirement[]) reqList.toArray(new IRequirement[reqList.size()]);
1216        }
1217    
1218        public static R4Directive parseExtensionBundleHeader(String header)
1219            throws BundleException
1220        {
1221            Object[][][] clauses = parseStandardHeader(header);
1222    
1223            R4Directive result = null;
1224    
1225            if (clauses.length == 1)
1226            {
1227                // See if there is the "extension" directive.
1228                for (int i = 0;
1229                    (result == null) && (i < clauses[0][CLAUSE_DIRECTIVES_INDEX].length);
1230                    i++)
1231                {
1232                    if (Constants.EXTENSION_DIRECTIVE.equals(((R4Directive)
1233                        clauses[0][CLAUSE_DIRECTIVES_INDEX][i]).getName()))
1234                    {
1235                        // If the extension directive is specified, make sure
1236                        // the target is the system bundle.
1237                        if (FelixConstants.SYSTEM_BUNDLE_SYMBOLICNAME.equals(clauses[0][CLAUSE_PATHS_INDEX][0]) ||
1238                            Constants.SYSTEM_BUNDLE_SYMBOLICNAME.equals(clauses[0][CLAUSE_PATHS_INDEX][0]))
1239                        {
1240                            result = (R4Directive) clauses[0][CLAUSE_DIRECTIVES_INDEX][i];
1241                        }
1242                        else
1243                        {
1244                            throw new BundleException(
1245                                "Only the system bundle can have extension bundles.");
1246                        }
1247                    }
1248                }
1249            }
1250    
1251            return result;
1252        }
1253    
1254        private void parseActivationPolicy(Map headerMap)
1255        {
1256            m_activationPolicy = IModule.EAGER_ACTIVATION;
1257    
1258            Object[][][] clauses = parseStandardHeader(
1259                (String) headerMap.get(Constants.BUNDLE_ACTIVATIONPOLICY));
1260    
1261            if (clauses.length > 0)
1262            {
1263                // Just look for a "path" matching the lazy policy, ignore
1264                // everything else.
1265                for (int i = 0;
1266                    i < clauses[0][CLAUSE_PATHS_INDEX].length;
1267                    i++)
1268                {
1269                    if (clauses[0][CLAUSE_PATHS_INDEX][i].equals(Constants.ACTIVATION_LAZY))
1270                    {
1271                        m_activationPolicy = IModule.LAZY_ACTIVATION;
1272                        for (int j = 0; j < clauses[0][CLAUSE_DIRECTIVES_INDEX].length; j++)
1273                        {
1274                            R4Directive dir = (R4Directive) clauses[0][CLAUSE_DIRECTIVES_INDEX][j];
1275                            if (dir.getName().equalsIgnoreCase(Constants.INCLUDE_DIRECTIVE))
1276                            {
1277                                m_activationIncludeDir = dir.getValue();
1278                            }
1279                            else if (dir.getName().equalsIgnoreCase(Constants.EXCLUDE_DIRECTIVE))
1280                            {
1281                                m_activationExcludeDir = dir.getValue();
1282                            }
1283                        }
1284                        break;
1285                    }
1286                }
1287            }
1288        }
1289    
1290        public static final int CLAUSE_PATHS_INDEX = 0;
1291        public static final int CLAUSE_DIRECTIVES_INDEX = 1;
1292        public static final int CLAUSE_ATTRIBUTES_INDEX = 2;
1293    
1294        // Like this: path; path; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2,
1295        //            path; path; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2
1296        private static Object[][][] parseStandardHeader(String header)
1297        {
1298            Object[][][] clauses = null;
1299    
1300            if (header != null)
1301            {
1302                if (header.length() == 0)
1303                {
1304                    throw new IllegalArgumentException(
1305                        "A header cannot be an empty string.");
1306                }
1307    
1308                String[] clauseStrings = parseDelimitedString(
1309                    header, FelixConstants.CLASS_PATH_SEPARATOR);
1310    
1311                List completeList = new ArrayList();
1312                for (int i = 0; (clauseStrings != null) && (i < clauseStrings.length); i++)
1313                {
1314                    completeList.add(parseStandardHeaderClause(clauseStrings[i]));
1315                }
1316                clauses = (Object[][][]) completeList.toArray(new Object[completeList.size()][][]);
1317            }
1318    
1319            return (clauses == null) ? new Object[0][][] : clauses;
1320        }
1321    
1322        // Like this: path; path; dir1:=dirval1; dir2:=dirval2; attr1=attrval1; attr2=attrval2
1323        private static Object[][] parseStandardHeaderClause(String clauseString)
1324            throws IllegalArgumentException
1325        {
1326            // Break string into semi-colon delimited pieces.
1327            String[] pieces = parseDelimitedString(
1328                clauseString, FelixConstants.PACKAGE_SEPARATOR);
1329    
1330            // Count the number of different paths; paths
1331            // will not have an '=' in their string. This assumes
1332            // that paths come first, before directives and
1333            // attributes.
1334            int pathCount = 0;
1335            for (int pieceIdx = 0; pieceIdx < pieces.length; pieceIdx++)
1336            {
1337                if (pieces[pieceIdx].indexOf('=') >= 0)
1338                {
1339                    break;
1340                }
1341                pathCount++;
1342            }
1343    
1344            // Error if no paths were specified.
1345            if (pathCount == 0)
1346            {
1347                throw new IllegalArgumentException(
1348                    "No paths specified in header: " + clauseString);
1349            }
1350    
1351            // Create an array of paths.
1352            String[] paths = new String[pathCount];
1353            System.arraycopy(pieces, 0, paths, 0, pathCount);
1354    
1355            // Parse the directives/attributes.
1356            Map dirsMap = new HashMap();
1357            Map attrsMap = new HashMap();
1358            int idx = -1;
1359            String sep = null;
1360            for (int pieceIdx = pathCount; pieceIdx < pieces.length; pieceIdx++)
1361            {
1362                // Check if it is a directive.
1363                if ((idx = pieces[pieceIdx].indexOf(FelixConstants.DIRECTIVE_SEPARATOR)) >= 0)
1364                {
1365                    sep = FelixConstants.DIRECTIVE_SEPARATOR;
1366                }
1367                // Check if it is an attribute.
1368                else if ((idx = pieces[pieceIdx].indexOf(FelixConstants.ATTRIBUTE_SEPARATOR)) >= 0)
1369                {
1370                    sep = FelixConstants.ATTRIBUTE_SEPARATOR;
1371                }
1372                // It is an error.
1373                else
1374                {
1375                    throw new IllegalArgumentException("Not a directive/attribute: " + clauseString);
1376                }
1377    
1378                String key = pieces[pieceIdx].substring(0, idx).trim();
1379                String value = pieces[pieceIdx].substring(idx + sep.length()).trim();
1380    
1381                // Remove quotes, if value is quoted.
1382                if (value.startsWith("\"") && value.endsWith("\""))
1383                {
1384                    value = value.substring(1, value.length() - 1);
1385                }
1386    
1387                // Save the directive/attribute in the appropriate array.
1388                if (sep.equals(FelixConstants.DIRECTIVE_SEPARATOR))
1389                {
1390                    // Check for duplicates.
1391                    if (dirsMap.get(key) != null)
1392                    {
1393                        throw new IllegalArgumentException(
1394                            "Duplicate directive: " + key);
1395                    }
1396                    dirsMap.put(key, new R4Directive(key, value));
1397                }
1398                else
1399                {
1400                    // Check for duplicates.
1401                    if (attrsMap.get(key) != null)
1402                    {
1403                        throw new IllegalArgumentException(
1404                            "Duplicate attribute: " + key);
1405                    }
1406                    attrsMap.put(key, new R4Attribute(key, value, false));
1407                }
1408            }
1409    
1410            // Create directive array.
1411            R4Directive[] dirs = (R4Directive[])
1412                dirsMap.values().toArray(new R4Directive[dirsMap.size()]);
1413    
1414            // Create attribute array.
1415            R4Attribute[] attrs = (R4Attribute[])
1416                attrsMap.values().toArray(new R4Attribute[attrsMap.size()]);
1417    
1418            // Create an array to hold the parsed paths, directives, and attributes.
1419            Object[][] clause = new Object[3][];
1420            clause[CLAUSE_PATHS_INDEX] = paths;
1421            clause[CLAUSE_DIRECTIVES_INDEX] = dirs;
1422            clause[CLAUSE_ATTRIBUTES_INDEX] = attrs;
1423    
1424            return clause;
1425        }
1426    
1427        /**
1428         * Parses delimited string and returns an array containing the tokens. This
1429         * parser obeys quotes, so the delimiter character will be ignored if it is
1430         * inside of a quote. This method assumes that the quote character is not
1431         * included in the set of delimiter characters.
1432         * @param value the delimited string to parse.
1433         * @param delim the characters delimiting the tokens.
1434         * @return an array of string tokens or null if there were no tokens.
1435        **/
1436        public static String[] parseDelimitedString(String value, String delim)
1437        {
1438            if (value == null)
1439            {
1440               value = "";
1441            }
1442    
1443            List list = new ArrayList();
1444    
1445            int CHAR = 1;
1446            int DELIMITER = 2;
1447            int STARTQUOTE = 4;
1448            int ENDQUOTE = 8;
1449    
1450            StringBuffer sb = new StringBuffer();
1451    
1452            int expecting = (CHAR | DELIMITER | STARTQUOTE);
1453    
1454            for (int i = 0; i < value.length(); i++)
1455            {
1456                char c = value.charAt(i);
1457    
1458                boolean isDelimiter = (delim.indexOf(c) >= 0);
1459                boolean isQuote = (c == '"');
1460    
1461                if (isDelimiter && ((expecting & DELIMITER) > 0))
1462                {
1463                    list.add(sb.toString().trim());
1464                    sb.delete(0, sb.length());
1465                    expecting = (CHAR | DELIMITER | STARTQUOTE);
1466                }
1467                else if (isQuote && ((expecting & STARTQUOTE) > 0))
1468                {
1469                    sb.append(c);
1470                    expecting = CHAR | ENDQUOTE;
1471                }
1472                else if (isQuote && ((expecting & ENDQUOTE) > 0))
1473                {
1474                    sb.append(c);
1475                    expecting = (CHAR | STARTQUOTE | DELIMITER);
1476                }
1477                else if ((expecting & CHAR) > 0)
1478                {
1479                    sb.append(c);
1480                }
1481                else
1482                {
1483                    throw new IllegalArgumentException("Invalid delimited string: " + value);
1484                }
1485            }
1486    
1487            if (sb.length() > 0)
1488            {
1489                list.add(sb.toString().trim());
1490            }
1491    
1492            return (String[]) list.toArray(new String[list.size()]);
1493        }
1494    
1495        /**
1496         * Parses native code manifest headers.
1497         * @param libStrs an array of native library manifest header
1498         *        strings from the bundle manifest.
1499         * @return an array of <tt>LibraryInfo</tt> objects for the
1500         *         passed in strings.
1501        **/
1502        private static R4LibraryClause[] parseLibraryStrings(Logger logger, String[] libStrs)
1503            throws IllegalArgumentException
1504        {
1505            if (libStrs == null)
1506            {
1507                return new R4LibraryClause[0];
1508            }
1509    
1510            List libList = new ArrayList();
1511    
1512            for (int i = 0; i < libStrs.length; i++)
1513            {
1514                R4LibraryClause clause = R4LibraryClause.parse(logger, libStrs[i]);
1515                libList.add(clause);
1516            }
1517    
1518            return (R4LibraryClause[]) libList.toArray(new R4LibraryClause[libList.size()]);
1519        }
1520    }