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 }