Package aQute.lib.osgi

Source Code of aQute.lib.osgi.Verifier

/* Copyright 2006 aQute SARL
* Licensed under the Apache License, Version 2.0, see http://www.apache.org/licenses/LICENSE-2.0 */
package aQute.lib.osgi;

import java.io.IOException;
import java.util.*;
import java.util.jar.*;
import java.util.regex.Pattern;

import aQute.qtokens.QuotedTokenizer;

public class Verifier extends Processor {

  Jar            dot;
  Manifest        manifest;
  Map            referred        = new HashMap();
  Map            contained        = new HashMap();
  Map            uses          = new HashMap();
  Map            mimports;
  Map            mdynimports;
  Map            mexports;
  Map            ignore          = new HashMap();                              // Packages
  // to
  // ignore

  List          bundleClassPath;
  Map            classSpace;
  boolean          r3;
  boolean          usesRequire;
  boolean          fragment;
  Attributes        main;

  final static Pattern  EENAME          = Pattern
                              .compile("CDC-1\\.0/Foundation-1\\.0"
                                  + "|OSGi/Minimum-1\\.1"
                                  + "|JRE-1\\.1"
                                  + "|J2SE-1\\.2"
                                  + "|J2SE-1\\.3"
                                  + "|J2SE-1\\.4"
                                  + "|J2SE-1\\.5"
                                  + "|PersonalJava-1\\.1"
                                  + "|PersonalJava-1\\.2"
                                  + "|CDC-1\\.0/PersonalBasis-1\\.0"
                                  + "|CDC-1\\.0/PersonalJava-1\\.0");

  final static Pattern  BUNDLEMANIFESTVERSION  = Pattern.compile("2");
  final static Pattern  SYMBOLICNAME      = Pattern
                              .compile("[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)*");

  final static String    version          = "[0-9]+(\\.[0-9]+(\\.[0-9]+(\\.[0-9A-Za-z_-]+)?)?)?";
  final static Pattern  VERSION          = Pattern.compile(version);
  final static Pattern  FILTEROP        = Pattern
                              .compile("=|<=|>=|~=");
  final static Pattern  VERSIONRANGE      = Pattern
                              .compile("((\\(|\\[)"
                                  + version
                                  + ","
                                  + version
                                  + "(\\]|\\)))|"
                                  + version);
  final static Pattern  FILE          = Pattern
                              .compile("/?[^/\"\n\r\u0000]+(/[^/\"\n\r\u0000]+)*");
  final static Pattern  WILDCARDPACKAGE      = Pattern
                              .compile("((\\p{Alnum}|_)+(\\.(\\p{Alnum}|_)+)*(\\.\\*)?)|\\*");
  final static Pattern  ISO639          = Pattern
                              .compile("[A-Z][A-Z]");

  public Verifier(Jar jar) throws Exception {
    this.dot = jar;
    this.manifest = jar.getManifest();
    if (manifest == null) {
      manifest = new Manifest();
      error("This file contains no manifest and is therefore not a bundle");
    }
    main = this.manifest.getMainAttributes();

    r3 = getHeader(Analyzer.BUNDLE_MANIFESTVERSION) == null;
    usesRequire = getHeader(Analyzer.REQUIRE_BUNDLE) != null;
    fragment = getHeader(Analyzer.FRAGMENT_HOST) != null;

    bundleClassPath = getBundleClassPath();
    mimports = parseHeader(manifest.getMainAttributes().getValue(
        Analyzer.IMPORT_PACKAGE));
    mdynimports = parseHeader(manifest.getMainAttributes().getValue(
        Analyzer.DYNAMICIMPORT_PACKAGE));
    mexports = parseHeader(manifest.getMainAttributes().getValue(
        Analyzer.EXPORT_PACKAGE));

    ignore = parseHeader(manifest.getMainAttributes().getValue(
        Analyzer.IGNORE_PACKAGE));
  }

  private List getBundleClassPath() {
    List list = new ArrayList();
    String bcp = getHeader("Bundle-Classpath");
    if (bcp == null) {
      list.add(dot);
    }
    else {
      Map entries = parseHeader(bcp);
      for (Iterator i = entries.keySet().iterator(); i.hasNext();) {
        String jarOrDir = (String) i.next();
        if (jarOrDir.equals(".")) {
          list.add(dot);
        }
        else {
          if (jarOrDir.equals("/"))
            jarOrDir = "";
          if (jarOrDir.endsWith("/")) {
            error("Bundle-Classpath directory must not end with a slash: "
                + jarOrDir);
            jarOrDir = jarOrDir.substring(0, jarOrDir.length() - 1);
          }

          Resource resource = dot.getResource(jarOrDir);
          if (resource != null) {
            try {
              Jar sub = new Jar(jarOrDir);
              EmbeddedResource.build(sub, resource
                  .openInputStream());
              if (!jarOrDir.endsWith(".jar"))
                warning("Valid JAR file on Bundle-Classpath does not have .jar extension: "
                    + jarOrDir);
              list.add(sub);
            }
            catch (Exception e) {
              error("Invalid embedded JAR file on Bundle-Classpath: "
                  + jarOrDir + ", " + e);
            }
          }
          else if (dot.getDirectories().containsKey(jarOrDir)) {
            if (r3)
              error("R3 bundles do not support directories on the Bundle-ClassPath: "
                  + jarOrDir);

            list.add(jarOrDir);
          }
          else {
            error("Cannot find a file or directory for Bundle-Classpath entry: "
                + jarOrDir);
          }
        }
      }
    }
    return list;
  }

  /*
   * Bundle-NativeCode ::= nativecode ( ',' nativecode )* ( ’,’ optional) ?
   * nativecode ::= path ( ';' path )* // See 1.4.2 ( ';' parameter )+
   * optional ::= ’*’
   */
  public void verifyNative() {
    String nc = getHeader("Bundle-NativeCode");
    doNative(nc);
  }
 
  public void doNative(String nc) {
    if (nc != null) {
      QuotedTokenizer qt = new QuotedTokenizer(nc, ",;=", false);
      char del;
      do {
        do {
          String name = qt.nextToken();
          del = qt.getSeparator();
          if (del == ';') {
            if (!dot.exists(name)) {
              error("Native library not found in JAR: " + name);
            }
          }
          else {
            String value = qt.nextToken();
            String key = name.toLowerCase();
            if (key.equals("osname")) {
              // ...
            }
            else if (key.equals("osversion")) {
              // verify version range
              verify(value, VERSIONRANGE);
            }
            else if (key.equals("lanuage")) {
              verify(value, ISO639);
            }
            else if (key.equals("processor")) {
              // verify(value, PROCESSORS);
            }
            else if (key.equals("selection-filter")) {
              // verify syntax filter
              verifyFilter(value, 0);
            }
            else {
              warning("Unknown attribute in native code: " + name
                  + "=" + value);
            }
            del = qt.getSeparator();
          }
        } while (del == ';');
      } while (del == ',');
    }
  }

  private void verifyActivator() {
    String bactivator = getHeader("Bundle-Activator");
    if (bactivator != null) {
      Clazz cl = loadClass(bactivator);
      if (cl == null) {
        error("Bundle-Activator not found on the bundle class path or imports: "
            + bactivator);
      }
    }
  }

  private Clazz loadClass(String className) {
    String path = className.replace('.', '/') + ".class";
    return (Clazz) classSpace.get(path);
  }

  private void verifyComponent() {
    String serviceComponent = getHeader("Service-Component");
    if (serviceComponent != null) {
      Map map = parseHeader(serviceComponent);
      for (Iterator i = map.keySet().iterator(); i.hasNext();) {
        String component = (String) i.next();
        if (!dot.exists(component)) {
          error("Service-Component entry can not be located in JAR: "
              + component);
        }
        else {
          // validate component ...
        }
      }
    }
  }

  public void info() {
    System.out.println("Refers                           : " + referred);
    System.out.println("Contains                         : " + contained);
    System.out.println("Manifest Imports                 : " + mimports);
    System.out.println("Manifest Exports                 : " + mexports);
  }

  /**
   * Invalid exports are exports mentioned in the manifest but not found on
   * the classpath. This can be calculated with: exports - contains.
   */
  private void verifyInvalidExports() {
    Set invalidExport = new HashSet(mexports.keySet());
    invalidExport.removeAll(contained.keySet());
    if (!invalidExport.isEmpty())
      error("Exporting packages that are not on the Bundle-Classpath"
          + bundleClassPath + ": " + invalidExport);
  }

  /**
   * Invalid imports are imports that we never refer to. They can be
   * calculated by removing the refered packages from the imported packages.
   * This leaves packages that the manifest imported but that we never use.
   */
  private void verifyInvalidImports() {
    Set invalidImport = new TreeSet(mimports.keySet());
    invalidImport.removeAll(referred.keySet());
    if (!invalidImport.isEmpty())
      warning("Importing packages that are never refered to by any class on the Bundle-Classpath"
          + bundleClassPath + ": " + invalidImport);
  }

  /**
   * Check for unresolved imports. These are referals that are not imported by
   * the manifest and that are not part of our bundle classpath. The are
   * calculated by removing all the imported packages and contained from the
   * refered packages.
   */
  private void verifyUnresolvedReferences() {
    Set unresolvedReferences = new TreeSet(referred.keySet());
    unresolvedReferences.removeAll(mimports.keySet());
    unresolvedReferences.removeAll(contained.keySet());

    // Remove any java.** packages.
    for (Iterator p = unresolvedReferences.iterator(); p.hasNext();) {
      String pack = (String) p.next();
      if (pack.startsWith("java.") || ignore.containsKey(pack))
        p.remove();
      else {
        // Remove any dynamic imports
        if ( isDynamicImport(pack) )
          p.remove();
      }
    }

    if (!unresolvedReferences.isEmpty()) {
      // Now we want to know the
      // classes that are the culprits
      Set culprits = new HashSet();
      for (Iterator i = classSpace.values().iterator(); i.hasNext();) {
        Clazz clazz = (Clazz) i.next();
        if (hasOverlap(unresolvedReferences, clazz.imports.keySet()))
          culprits.add(clazz.getPath());
      }

      error("Unresolved references to " + unresolvedReferences
          + " by class(es) on the Bundle-Classpath" + bundleClassPath
          + ": " + culprits);
    }
  }

  /**
   * @param p
   * @param pack
   */
  private boolean isDynamicImport(String pack) {
    for (Iterator dimp = mdynimports.keySet().iterator(); dimp
        .hasNext();) {
      String pattern = (String) dimp.next();
      // Wildcard?
      if (pattern.equals("*"))
        return true; // All packages can be dynamically imported

      if (pattern.endsWith(".*")) {
        pattern = pattern.substring(0, pattern.length() - 2);
        if (pack.startsWith(pattern)
            && (pack.length() == pattern.length() || pack
                .charAt(pattern.length()) == '.'))
          return true;
      }
      else {
        if (pack.equals(pattern))
          return true;
      }
    }
    return false;
  }

  private boolean hasOverlap(Set a, Set b) {
    for (Iterator i = a.iterator(); i.hasNext();) {
      if (b.contains(i.next()))
        return true;
    }
    return false;
  }

  public void verify() throws IOException {
    classSpace = analyzeBundleClasspath(
        dot,
        parseHeader(getHeader(Analyzer.BUNDLE_CLASSPATH)),
        contained,
        referred,
        uses);
    verifyManifestFirst();
    verifyActivator();
    verifyComponent();
    verifyNative();
    verifyInvalidExports();
    verifyInvalidImports();
    verifyUnresolvedReferences();
    verifySymbolicName();
    verifyListHeader("Bundle-RequiredExecutionEnvironment", EENAME, false);
    verifyHeader("Bundle-ManifestVersion", BUNDLEMANIFESTVERSION, false);
    verifyHeader("Bundle-Version", VERSION, true);
    verifyListHeader("Bundle-Classpath", FILE, false);
    verifyDynamicImportPackage();
    if (usesRequire) {
      if (!errors.isEmpty()) {
        warnings
            .add(
                0,
                "Bundle uses Require Bundle, this can generate false errors because then not enough information is available without the required bundles");
      }
    }
  }

  /**
   * <pre>
   *         DynamicImport-Package ::= dynamic-description
   *             ( ',' dynamic-description )*
   *            
   *         dynamic-description::= wildcard-names ( ';' parameter )*
   *         wildcard-names ::= wildcard-name ( ';' wildcard-name )*
   *         wildcard-name ::= package-name
   *                        | ( package-name '.*' ) // See 1.4.2
   *                        | '*'
   * </pre>
   */
  private void verifyDynamicImportPackage() {
    verifyListHeader("DynamicImport-Package", WILDCARDPACKAGE, true);
    String dynamicImportPackage = getHeader("DynamicImport-Package");
    if (dynamicImportPackage == null)
      return;

    Map map = parseHeader(dynamicImportPackage);
    for (Iterator i = map.keySet().iterator(); i.hasNext();) {
      String name = (String) i.next();
      name = name.trim();
      if (!verify(name, WILDCARDPACKAGE))
        error("DynamicImport-Package header contains an invalid package name: "
            + name);

      Map sub = (Map) map.get(name);
      if (r3 && sub.size() != 0) {
        error("DynamicPackage-Import has attributes on import: "
            + name
            + ". This is however, an <=R3 bundle and attributes on this header were introduced in R4. ");
      }
    }
  }

  private void verifyManifestFirst() {
    if (!dot.manifestFirst) {
      errors
          .add("Invalid JAR stream: Manifest should come first to be compatible with JarInputStream, it was not");
    }
  }

  private void verifySymbolicName() {
    Map bsn = parseHeader(getHeader("Bundle-SymbolicName"));
    if (!bsn.isEmpty()) {
      if (bsn.size() > 1)
        errors.add("More than one BSN specified " + bsn);

      String name = (String) bsn.keySet().iterator().next();
      if (!SYMBOLICNAME.matcher(name).matches()) {
        errors.add("Symbolic Name has invalid format: " + name);
      }
    }
  }

  /**
   * <pre>
   *        filter ::= ’(’ filter-comp ’)’
   *        filter-comp ::= and | or | not | operation
   *        and ::= ’&amp;’ filter-list
   *        or ::= ’|’ filter-list
   *        not ::= ’!’ filter
   *        filter-list ::= filter | filter filter-list
   *        operation ::= simple | present | substring
   *        simple ::= attr filter-type value
   *        filter-type ::= equal | approx | greater | less
   *        equal ::= ’=’
   *        approx ::= ’&tilde;=’
   *        greater ::= ’&gt;=’
   *        less ::= ’&lt;=’
   *        present ::= attr ’=*’
   *        substring ::= attr ’=’ initial any final
   *        inital ::= () | value
   *        any ::= ’*’ star-value
   *        star-value ::= () | value ’*’ star-value
   *        final ::= () | value
   *        value ::= &lt;see text&gt;
   * </pre>
   *
   * @param expr
   * @param index
   * @return
   */

  int verifyFilter(String expr, int index) {
    try {
      while (Character.isWhitespace(expr.charAt(index)))
        index++;

      if (expr.charAt(index) != '(')
        throw new IllegalArgumentException(
            "Filter mismatch: expected ( at position " + index
                + " : " + expr);

      while (Character.isWhitespace(expr.charAt(index)))
        index++;

      switch (expr.charAt(index)) {
        case '!' :
        case '&' :
        case '|' :
          return verifyFilterSubExpression(expr, index) + 1;

        default :
          return verifyFilterOperation(expr, index) + 1;
      }
    }
    catch (IndexOutOfBoundsException e) {
      throw new IllegalArgumentException(
          "Filter mismatch: early EOF from " + index);
    }
  }

  private int verifyFilterOperation(String expr, int index) {
    StringBuffer sb = new StringBuffer();
    while ("=><~()".indexOf(expr.charAt(index)) < 0) {
      sb.append(expr.charAt(index++));
    }
    String attr = sb.toString().trim();
    if (attr.length() == 0)
      throw new IllegalArgumentException(
          "Filter mismatch: attr at index " + index + " is 0");
    sb = new StringBuffer();
    while ("=><~".indexOf(expr.charAt(index)) >= 0) {
      sb.append(expr.charAt(index++));
    }
    String operator = sb.toString();
    if (!verify(operator, FILTEROP))
      throw new IllegalArgumentException(
          "Filter error, illegal operator " + operator + " at index "
              + index);

    sb = new StringBuffer();
    while (")".indexOf(expr.charAt(index)) < 0) {
      switch (expr.charAt(index)) {
        case '\\' :
          if (expr.charAt(index + 1) == '*'
              || expr.charAt(index + 1) == ')')
            index++;
          else
            throw new IllegalArgumentException(
                "Filter error, illegal use of backslash at index "
                    + index
                    + ". Backslash may only be used before * or (");
      }
      sb.append(expr.charAt(index++));
    }
    return index;
  }

  private int verifyFilterSubExpression(String expr, int index) {
    do {
      index = verifyFilter(expr, index + 1);
      while (Character.isWhitespace(expr.charAt(index)))
        index++;
      if (expr.charAt(index) != ')')
        throw new IllegalArgumentException(
            "Filter mismatch: expected ) at position " + index
                + " : " + expr);
      index++;
    } while (expr.charAt(index) == '(');
    return index;
  }

  private String getHeader(String string) {
    return main.getValue(string);
  }

  private boolean verifyHeader(String name, Pattern regex, boolean error) {
    String value = manifest.getMainAttributes().getValue(name);
    if (value == null)
      return false;

    QuotedTokenizer st = new QuotedTokenizer(value.trim(), ",");
    for (Iterator i = st.getTokenSet().iterator(); i.hasNext();) {
      if (!verify((String) i.next(), regex)) {
        (error ? errors : warnings).add("Invalid value for " + name
            + ", " + value + " does not match " + regex.pattern());
      }
    }
    return true;
  }

  private boolean verify(String value, Pattern regex) {
    return regex.matcher(value).matches();
  }

  private boolean verifyListHeader(String name, Pattern regex, boolean error) {
    String value = manifest.getMainAttributes().getValue(name);
    if (value == null)
      return false;

    QuotedTokenizer st = new QuotedTokenizer(value.trim(), ",");
    for (Iterator i = st.getTokenSet().iterator(); i.hasNext();) {
      if (!regex.matcher((String) i.next()).matches()) {
        (error ? errors : warnings).add("Invalid value for " + name
            + ", " + value + " does not match " + regex.pattern());
      }
    }
    return true;
  }

}
TOP

Related Classes of aQute.lib.osgi.Verifier

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.