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 }