Beispiel #1
1
  public String _range(String args[]) {
    verifyCommand(args, _rangeHelp, _rangePattern, 2, 3);
    Version version = null;
    if (args.length >= 3) version = new Version(args[2]);
    else {
      String v = domain.getProperty("@");
      if (v == null) return null;
      version = new Version(v);
    }
    String spec = args[1];

    Matcher m = RANGE_MASK.matcher(spec);
    m.matches();
    String floor = m.group(1);
    String floorMask = m.group(2);
    String ceilingMask = m.group(3);
    String ceiling = m.group(4);

    String left = version(version, floorMask);
    String right = version(version, ceilingMask);
    StringBuilder sb = new StringBuilder();
    sb.append(floor);
    sb.append(left);
    sb.append(",");
    sb.append(right);
    sb.append(ceiling);

    String s = sb.toString();
    VersionRange vr = new VersionRange(s);
    if (!(vr.includes(vr.getHigh()) || vr.includes(vr.getLow()))) {
      domain.error(
          "${range} macro created an invalid range %s from %s and mask %s", s, version, spec);
    }
    return sb.toString();
  }
Beispiel #2
0
  String filter(String[] args, boolean include) {
    verifyCommand(args, String.format(_filterHelp, args[0]), null, 3, 3);

    Collection<String> list = new ArrayList<String>(Processor.split(args[1]));
    Pattern pattern = Pattern.compile(args[2]);

    for (Iterator<String> i = list.iterator(); i.hasNext(); ) {
      if (pattern.matcher(i.next()).matches() == include) i.remove();
    }
    return Processor.join(list);
  }
Beispiel #3
0
  private void copy(File workspaceDir, InputStream in, Pattern glob, boolean overwrite)
      throws Exception {

    Jar jar = new Jar("dot", in);
    try {
      for (Entry<String, Resource> e : jar.getResources().entrySet()) {

        String path = e.getKey();
        bnd.trace("path %s", path);

        if (glob != null && !glob.matcher(path).matches()) continue;

        Resource r = e.getValue();
        File dest = Processor.getFile(workspaceDir, path);
        if (overwrite
            || !dest.isFile()
            || dest.lastModified() < r.lastModified()
            || r.lastModified() <= 0) {

          bnd.trace("copy %s to %s", path, dest);

          File dp = dest.getParentFile();
          if (!dp.exists() && !dp.mkdirs()) {
            throw new IOException("Could not create directory " + dp);
          }

          IO.copy(r.openInputStream(), dest);
        }
      }
    } finally {
      jar.close();
    }
  }
Beispiel #4
0
  private String doCommands(String key, Link source) {
    String[] args = commands.split(key);
    if (args == null || args.length == 0) return null;

    for (int i = 0; i < args.length; i++)
      if (args[i].indexOf('\\') >= 0) args[i] = args[i].replaceAll("\\\\;", ";");

    if (args[0].startsWith("^")) {
      String varname = args[0].substring(1).trim();

      Processor parent = source.start.getParent();
      if (parent != null) return parent.getProperty(varname);
      return null;
    }

    Processor rover = domain;
    while (rover != null) {
      String result = doCommand(rover, args[0], args);
      if (result != null) return result;

      rover = rover.getParent();
    }

    for (int i = 0; targets != null && i < targets.length; i++) {
      String result = doCommand(targets[i], args[0], args);
      if (result != null) return result;
    }

    return doCommand(this, args[0], args);
  }
 public String getArtifactIdFromCoord(String coord) {
   Matcher m = COORD_P.matcher(coord);
   if (m.matches()) {
     return m.group(2);
   } else {
     return null;
   }
 }
  public String verify(JarFile jar, String... algorithms) throws IOException {
    if (algorithms == null || algorithms.length == 0) algorithms = new String[] {"MD5", "SHA"};
    else if (algorithms.length == 1 && algorithms[0].equals("-")) return null;

    try {
      Manifest m = jar.getManifest();
      if (m.getEntries().isEmpty()) return "No name sections";

      for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements(); ) {
        JarEntry je = e.nextElement();
        if (MANIFEST_ENTRY.matcher(je.getName()).matches()) continue;

        Attributes nameSection = m.getAttributes(je.getName());
        if (nameSection == null) return "No name section for " + je.getName();

        for (String algorithm : algorithms) {
          try {
            MessageDigest md = MessageDigest.getInstance(algorithm);
            String expected = nameSection.getValue(algorithm + "-Digest");
            if (expected != null) {
              byte digest[] = Base64.decodeBase64(expected);
              copy(jar.getInputStream(je), md);
              if (!Arrays.equals(digest, md.digest()))
                return "Invalid digest for "
                    + je.getName()
                    + ", "
                    + expected
                    + " != "
                    + Base64.encodeBase64(md.digest());
            } else reporter.error("could not find digest for " + algorithm + "-Digest");
          } catch (NoSuchAlgorithmException nsae) {
            return "Missing digest algorithm " + algorithm;
          }
        }
      }
    } catch (Exception e) {
      return "Failed to verify due to exception: " + e.getMessage();
    }
    return null;
  }
Beispiel #7
0
  private void doManifest(
      Jar jar, String[] digestNames, MessageDigest[] algorithms, OutputStream out)
      throws Exception {

    for (Map.Entry<String, Resource> entry : jar.getResources().entrySet()) {
      String name = entry.getKey();
      if (!METAINFDIR.matcher(name).matches()) {
        out.write("\r\n".getBytes("UTF-8"));
        out.write("Name: ".getBytes("UTF-8"));
        out.write(name.getBytes("UTF-8"));
        out.write("\r\n".getBytes("UTF-8"));

        digest(algorithms, entry.getValue());
        for (int a = 0; a < algorithms.length; a++) {
          if (algorithms[a] != null) {
            byte[] digest = algorithms[a].digest();
            String header = digestNames[a] + "-Digest: " + new Base64(digest) + "\r\n";
            out.write(header.getBytes("UTF-8"));
          }
        }
      }
    }
  }
Beispiel #8
0
  public void _workspace(WorkspaceOptions opts) throws Exception {
    File base = bnd.getBase();

    String name = opts._arguments().get(0);

    File workspaceDir = Processor.getFile(base, name);
    name = workspaceDir.getName();
    base = workspaceDir.getParentFile();

    if (base == null) {
      bnd.error(
          "You cannot create a workspace in the root (%s). The parent of a workspace %n"
              + "must be a valid directory. Recommended is to dedicate a directory to %n"
              + "all (or related) workspaces.",
          workspaceDir);
      return;
    }

    if (!opts.anyname() && !Verifier.isBsn(name)) {
      bnd.error(
          "You specified a workspace name that does not follow the recommended pattern "
              + "(it should be like a Bundle Symbolic name). It is a bit pedantic but it "
              + "really helps hwne you get many workspaces. If you insist on this name, use the -a/--anyname option.");
      return;
    }

    Workspace ws = bnd.getWorkspace((File) null);

    if (ws != null && ws.isValid()) {
      bnd.error(
          "You are currently in a workspace already (%s) in %s. You can only create a new workspace outside an existing workspace",
          ws, base);
      return;
    }

    File eclipseDir = workspaceDir;
    workspaceDir.mkdirs();

    if (!opts.single()) workspaceDir = new File(workspaceDir, "scm");

    workspaceDir.mkdirs();

    if (!base.isDirectory()) {
      bnd.error("Could not create directory for the bnd workspace %s", base);
    } else if (!eclipseDir.isDirectory()) {
      bnd.error("Could not create directory for the Eclipse workspace %s", eclipseDir);
    }

    if (!workspaceDir.isDirectory()) {
      bnd.error("Could not create the workspace directory %s", workspaceDir);
      return;
    }

    if (!opts.update() && !opts.force() && workspaceDir.list().length > 0) {
      bnd.error(
          "The workspace directory %s is not empty, specify -u/--update to update or -f/--force to replace",
          workspaceDir);
    }

    InputStream in = getClass().getResourceAsStream("/templates/enroute.zip");
    if (in == null) {
      bnd.error("Cannot find template in this jar %s", "/templates/enroute.zip");
      return;
    }

    Pattern glob = Pattern.compile("[^/]+|cnf/.*|\\...+/.*");

    copy(workspaceDir, in, glob, opts.force());

    File readme = new File(workspaceDir, "README.md");
    if (readme.isFile()) IO.copy(readme, bnd.out);

    bnd.out.printf(
        "%nWorkspace %s created.%n%n" //
            + " Start Eclipse:%n" //
            + "   1) Select the Eclipse workspace %s%n" //
            + "   2) Package Explorer context menu: Import/General/Existing Projects from %s%n"
            + "%n"
            + "", //
        workspaceDir.getName(), eclipseDir, workspaceDir);
  }
Beispiel #9
0
/**
 * Provide a macro processor. This processor can replace variables in strings based on a properties
 * and a domain. The domain can implement functions that start with a "_" and take args[], the names
 * of these functions are available as functions in the macro processor (without the _). Macros can
 * nest to any depth but may not contain loops. Add POSIX macros: ${#parameter} String length.
 * ${parameter%word} Remove smallest suffix pattern. ${parameter%%word} Remove largest suffix
 * pattern. ${parameter#word} Remove smallest prefix pattern. ${parameter##word} Remove largest
 * prefix pattern.
 */
public class Macro {
  Processor domain;
  Object targets[];
  boolean flattening;

  public Macro(Processor domain, Object... targets) {
    this.domain = domain;
    this.targets = targets;
    if (targets != null) {
      for (Object o : targets) {
        assert o != null;
      }
    }
  }

  public String process(String line, Processor source) {
    return process(line, new Link(source, null, line));
  }

  String process(String line, Link link) {
    StringBuilder sb = new StringBuilder();
    process(line, 0, '\u0000', '\u0000', sb, link);
    return sb.toString();
  }

  int process(CharSequence org, int index, char begin, char end, StringBuilder result, Link link) {
    StringBuilder line = new StringBuilder(org);
    int nesting = 1;

    StringBuilder variable = new StringBuilder();
    outer:
    while (index < line.length()) {
      char c1 = line.charAt(index++);
      if (c1 == end) {
        if (--nesting == 0) {
          result.append(replace(variable.toString(), link));
          return index;
        }
      } else if (c1 == begin) nesting++;
      else if (c1 == '\\' && index < line.length() - 1 && line.charAt(index) == '$') {
        // remove the escape backslash and interpret the dollar
        // as a
        // literal
        index++;
        variable.append('$');
        continue outer;
      } else if (c1 == '$' && index < line.length() - 2) {
        char c2 = line.charAt(index);
        char terminator = getTerminator(c2);
        if (terminator != 0) {
          index = process(line, index + 1, c2, terminator, variable, link);
          continue outer;
        }
      } else if (c1 == '.' && index < line.length() && line.charAt(index) == '/') {
        // Found the sequence ./
        if (index == 1 || Character.isWhitespace(line.charAt(index - 2))) {
          // make sure it is preceded by whitespace or starts at begin
          index++;
          variable.append(domain.getBase().getAbsolutePath());
          variable.append('/');
          continue outer;
        }
      }
      variable.append(c1);
    }
    result.append(variable);
    return index;
  }

  public static char getTerminator(char c) {
    switch (c) {
      case '(':
        return ')';
      case '[':
        return ']';
      case '{':
        return '}';
      case '<':
        return '>';
      case '\u00ab': // Guillemet double << >>
        return '\u00bb';
      case '\u2039': // Guillemet single
        return '\u203a';
    }
    return 0;
  }

  protected String replace(String key, Link link) {
    if (link != null && link.contains(key)) return "${infinite:" + link.toString() + "}";

    if (key != null) {
      key = key.trim();
      if (key.length() > 0) {
        Processor source = domain;
        String value = null;

        if (key.indexOf(';') < 0) {
          Instruction ins = new Instruction(key);
          if (!ins.isLiteral()) {
            SortedList<String> sortedList = SortedList.fromIterator(domain.iterator());
            StringBuilder sb = new StringBuilder();
            String del = "";
            for (String k : sortedList) {
              if (ins.matches(k)) {
                String v = replace(k, new Link(source, link, key));
                if (v != null) {
                  sb.append(del);
                  del = ",";
                  sb.append(v);
                }
              }
            }
            return sb.toString();
          }
        }
        while (value == null && source != null) {
          value = source.getProperties().getProperty(key);
          source = source.getParent();
        }

        if (value != null) return process(value, new Link(source, link, key));

        value = doCommands(key, link);
        if (value != null) return process(value, new Link(source, link, key));

        if (key != null && key.trim().length() > 0) {
          value = System.getProperty(key);
          if (value != null) return value;
        }
        if (!flattening && !key.equals("@"))
          domain.warning("No translation found for macro: " + key);
      } else {
        domain.warning("Found empty macro key");
      }
    } else {
      domain.warning("Found null macro key");
    }
    return "${" + key + "}";
  }

  /**
   * Parse the key as a command. A command consist of parameters separated by ':'.
   *
   * @param key
   * @return
   */
  static Pattern commands = Pattern.compile("(?<!\\\\);");

  private String doCommands(String key, Link source) {
    String[] args = commands.split(key);
    if (args == null || args.length == 0) return null;

    for (int i = 0; i < args.length; i++)
      if (args[i].indexOf('\\') >= 0) args[i] = args[i].replaceAll("\\\\;", ";");

    if (args[0].startsWith("^")) {
      String varname = args[0].substring(1).trim();

      Processor parent = source.start.getParent();
      if (parent != null) return parent.getProperty(varname);
      return null;
    }

    Processor rover = domain;
    while (rover != null) {
      String result = doCommand(rover, args[0], args);
      if (result != null) return result;

      rover = rover.getParent();
    }

    for (int i = 0; targets != null && i < targets.length; i++) {
      String result = doCommand(targets[i], args[0], args);
      if (result != null) return result;
    }

    return doCommand(this, args[0], args);
  }

  private String doCommand(Object target, String method, String[] args) {
    if (target == null) ; // System.err.println("Huh? Target should never be null " +
    // domain);
    else {
      String cname = "_" + method.replaceAll("-", "_");
      try {
        Method m = target.getClass().getMethod(cname, new Class[] {String[].class});
        return (String) m.invoke(target, new Object[] {args});
      } catch (NoSuchMethodException e) {
        // Ignore
      } catch (InvocationTargetException e) {
        if (e.getCause() instanceof IllegalArgumentException) {
          domain.error(
              "%s, for cmd: %s, arguments; %s",
              e.getCause().getMessage(), method, Arrays.toString(args));
        } else {
          domain.warning("Exception in replace: %s", e.getCause());
          e.getCause().printStackTrace();
        }
      } catch (Exception e) {
        domain.warning("Exception in replace: " + e + " method=" + method);
        e.printStackTrace();
      }
    }
    return null;
  }

  /**
   * Return a unique list where the duplicates are removed.
   *
   * @param args
   * @return
   */
  static String _uniqHelp = "${uniq;<list> ...}";

  public String _uniq(String args[]) {
    verifyCommand(args, _uniqHelp, null, 1, Integer.MAX_VALUE);
    Set<String> set = new LinkedHashSet<String>();
    for (int i = 1; i < args.length; i++) {
      Processor.split(args[i], set);
    }
    return Processor.join(set, ",");
  }

  public String _pathseparator(@SuppressWarnings("unused") String args[]) {
    return File.pathSeparator;
  }

  public String _separator(@SuppressWarnings("unused") String args[]) {
    return File.separator;
  }

  public String _filter(String args[]) {
    return filter(args, false);
  }

  public String _filterout(String args[]) {
    return filter(args, true);
  }

  static String _filterHelp = "${%s;<list>;<regex>}";

  String filter(String[] args, boolean include) {
    verifyCommand(args, String.format(_filterHelp, args[0]), null, 3, 3);

    Collection<String> list = new ArrayList<String>(Processor.split(args[1]));
    Pattern pattern = Pattern.compile(args[2]);

    for (Iterator<String> i = list.iterator(); i.hasNext(); ) {
      if (pattern.matcher(i.next()).matches() == include) i.remove();
    }
    return Processor.join(list);
  }

  static String _sortHelp = "${sort;<list>...}";

  public String _sort(String args[]) {
    verifyCommand(args, _sortHelp, null, 2, Integer.MAX_VALUE);

    List<String> result = new ArrayList<String>();
    for (int i = 1; i < args.length; i++) {
      Processor.split(args[i], result);
    }
    Collections.sort(result);
    return Processor.join(result);
  }

  static String _joinHelp = "${join;<list>...}";

  public String _join(String args[]) {

    verifyCommand(args, _joinHelp, null, 1, Integer.MAX_VALUE);

    List<String> result = new ArrayList<String>();
    for (int i = 1; i < args.length; i++) {
      Processor.split(args[i], result);
    }
    return Processor.join(result);
  }

  static String _ifHelp = "${if;<condition>;<iftrue> [;<iffalse>] }";

  public String _if(String args[]) {
    verifyCommand(args, _ifHelp, null, 3, 4);
    String condition = args[1].trim();
    if (!condition.equalsIgnoreCase("false")) if (condition.length() != 0) return args[2];

    if (args.length > 3) return args[3];
    return "";
  }

  public String _now(@SuppressWarnings("unused") String args[]) {
    return new Date().toString();
  }

  public static final String _fmodifiedHelp =
      "${fmodified;<list of filenames>...}, return latest modification date";

  public String _fmodified(String args[]) throws Exception {
    verifyCommand(args, _fmodifiedHelp, null, 2, Integer.MAX_VALUE);

    long time = 0;
    Collection<String> names = new ArrayList<String>();
    for (int i = 1; i < args.length; i++) {
      Processor.split(args[i], names);
    }
    for (String name : names) {
      File f = new File(name);
      if (f.exists() && f.lastModified() > time) time = f.lastModified();
    }
    return "" + time;
  }

  public String _long2date(String args[]) {
    try {
      return new Date(Long.parseLong(args[1])).toString();
    } catch (Exception e) {
      e.printStackTrace();
    }
    return "not a valid long";
  }

  public String _literal(String args[]) {
    if (args.length != 2)
      throw new RuntimeException("Need a value for the ${literal;<value>} macro");
    return "${" + args[1] + "}";
  }

  public String _def(String args[]) {
    if (args.length != 2) throw new RuntimeException("Need a value for the ${def;<value>} macro");

    return domain.getProperty(args[1], "");
  }

  /**
   * replace ; <list> ; regex ; replace
   *
   * @param args
   * @return
   */
  public String _replace(String args[]) {
    if (args.length != 4) {
      domain.warning("Invalid nr of arguments to replace " + Arrays.asList(args));
      return null;
    }

    String list[] = args[1].split("\\s*,\\s*");
    StringBuilder sb = new StringBuilder();
    String del = "";
    for (int i = 0; i < list.length; i++) {
      String element = list[i].trim();
      if (!element.equals("")) {
        sb.append(del);
        sb.append(element.replaceAll(args[2], args[3]));
        del = ", ";
      }
    }

    return sb.toString();
  }

  public String _warning(String args[]) {
    for (int i = 1; i < args.length; i++) {
      domain.warning(process(args[i]));
    }
    return "";
  }

  public String _error(String args[]) {
    for (int i = 1; i < args.length; i++) {
      domain.error(process(args[i]));
    }
    return "";
  }

  /**
   * toclassname ; <path>.class ( , <path>.class ) *
   *
   * @param args
   * @return
   */
  static String _toclassnameHelp =
      "${classname;<list of class names>}, convert class paths to FQN class names ";

  public String _toclassname(String args[]) {
    verifyCommand(args, _toclassnameHelp, null, 2, 2);
    Collection<String> paths = Processor.split(args[1]);

    List<String> names = new ArrayList<String>(paths.size());
    for (String path : paths) {
      if (path.endsWith(".class")) {
        String name = path.substring(0, path.length() - 6).replace('/', '.');
        names.add(name);
      } else if (path.endsWith(".java")) {
        String name = path.substring(0, path.length() - 5).replace('/', '.');
        names.add(name);
      } else {
        domain.warning(
            "in toclassname, "
                + args[1]
                + " is not a class path because it does not end in .class");
      }
    }
    return Processor.join(names, ",");
  }

  /**
   * toclassname ; <path>.class ( , <path>.class ) *
   *
   * @param args
   * @return
   */
  static String _toclasspathHelp =
      "${toclasspath;<list>[;boolean]}, convert a list of class names to paths";

  public String _toclasspath(String args[]) {
    verifyCommand(args, _toclasspathHelp, null, 2, 3);
    boolean cl = true;
    if (args.length > 2) cl = Boolean.valueOf(args[2]);

    Collection<String> names = Processor.split(args[1]);
    Collection<String> paths = new ArrayList<String>(names.size());
    for (String name : names) {
      String path = name.replace('.', '/') + (cl ? ".class" : "");
      paths.add(path);
    }
    return Processor.join(paths, ",");
  }

  public String _dir(String args[]) {
    if (args.length < 2) {
      domain.warning("Need at least one file name for ${dir;...}");
      return null;
    }
    String del = "";
    StringBuilder sb = new StringBuilder();
    for (int i = 1; i < args.length; i++) {
      File f = domain.getFile(args[i]);
      if (f.exists() && f.getParentFile().exists()) {
        sb.append(del);
        sb.append(f.getParentFile().getAbsolutePath());
        del = ",";
      }
    }
    return sb.toString();
  }

  public String _basename(String args[]) {
    if (args.length < 2) {
      domain.warning("Need at least one file name for ${basename;...}");
      return null;
    }
    String del = "";
    StringBuilder sb = new StringBuilder();
    for (int i = 1; i < args.length; i++) {
      File f = domain.getFile(args[i]);
      if (f.exists() && f.getParentFile().exists()) {
        sb.append(del);
        sb.append(f.getName());
        del = ",";
      }
    }
    return sb.toString();
  }

  public String _isfile(String args[]) {
    if (args.length < 2) {
      domain.warning("Need at least one file name for ${isfile;...}");
      return null;
    }
    boolean isfile = true;
    for (int i = 1; i < args.length; i++) {
      File f = new File(args[i]).getAbsoluteFile();
      isfile &= f.isFile();
    }
    return isfile ? "true" : "false";
  }

  public String _isdir(String args[]) {
    if (args.length < 2) {
      domain.warning("Need at least one file name for ${isdir;...}");
      return null;
    }
    boolean isdir = true;
    for (int i = 1; i < args.length; i++) {
      File f = new File(args[i]).getAbsoluteFile();
      isdir &= f.isDirectory();
    }
    return isdir ? "true" : "false";
  }

  public String _tstamp(String args[]) {
    String format = "yyyyMMddHHmm";
    long now = System.currentTimeMillis();
    TimeZone tz = TimeZone.getTimeZone("UTC");

    if (args.length > 1) {
      format = args[1];
    }
    if (args.length > 2) {
      tz = TimeZone.getTimeZone(args[2]);
    }
    if (args.length > 3) {
      now = Long.parseLong(args[3]);
    }
    if (args.length > 4) {
      domain.warning("Too many arguments for tstamp: " + Arrays.toString(args));
    }

    SimpleDateFormat sdf = new SimpleDateFormat(format);
    sdf.setTimeZone(tz);

    return sdf.format(new Date(now));
  }

  /**
   * Wildcard a directory. The lists can contain Instruction that are matched against the given
   * directory ${lsr;<dir>;<list>(;<list>)*} ${lsa;<dir>;<list>(;<list>)*}
   *
   * @author aqute
   */
  public String _lsr(String args[]) {
    return ls(args, true);
  }

  public String _lsa(String args[]) {
    return ls(args, false);
  }

  String ls(String args[], boolean relative) {
    if (args.length < 2)
      throw new IllegalArgumentException(
          "the ${ls} macro must at least have a directory as parameter");

    File dir = domain.getFile(args[1]);
    if (!dir.isAbsolute())
      throw new IllegalArgumentException(
          "the ${ls} macro directory parameter is not absolute: " + dir);

    if (!dir.exists())
      throw new IllegalArgumentException(
          "the ${ls} macro directory parameter does not exist: " + dir);

    if (!dir.isDirectory())
      throw new IllegalArgumentException(
          "the ${ls} macro directory parameter points to a file instead of a directory: " + dir);

    Collection<File> files = new ArrayList<File>(new SortedList<File>(dir.listFiles()));

    for (int i = 2; i < args.length; i++) {
      Instructions filters = new Instructions(args[i]);
      files = filters.select(files, true);
    }

    List<String> result = new ArrayList<String>();
    for (File file : files) result.add(relative ? file.getName() : file.getAbsolutePath());

    return Processor.join(result, ",");
  }

  public String _currenttime(@SuppressWarnings("unused") String args[]) {
    return Long.toString(System.currentTimeMillis());
  }

  /**
   * Modify a version to set a version policy. Thed policy is a mask that is mapped to a version.
   *
   * <pre>
   * +           increment
   * -           decrement
   * =           maintain
   * &tilde;           discard
   *
   * ==+      = maintain major, minor, increment micro, discard qualifier
   * &tilde;&tilde;&tilde;=     = just get the qualifier
   * version=&quot;[${version;==;${@}},${version;=+;${@}})&quot;
   * </pre>
   *
   * @param args
   * @return
   */
  static final String MASK_STRING = "[\\-+=~0123456789]{0,3}[=~]?";

  static final Pattern MASK = Pattern.compile(MASK_STRING);
  static final String _versionHelp =
      "${version;<mask>;<version>}, modify a version\n"
          + "<mask> ::= [ M [ M [ M [ MQ ]]]\n"
          + "M ::= '+' | '-' | MQ\n"
          + "MQ ::= '~' | '='";
  static final Pattern _versionPattern[] = new Pattern[] {null, null, MASK, Verifier.VERSION};

  public String _version(String args[]) {
    verifyCommand(args, _versionHelp, null, 2, 3);

    String mask = args[1];

    Version version = null;
    if (args.length >= 3) version = new Version(args[2]);

    return version(version, mask);
  }

  String version(Version version, String mask) {
    if (version == null) {
      String v = domain.getProperty("@");
      if (v == null) {
        domain.error(
            "No version specified for ${version} or ${range} and no implicit version ${@} either, mask=%s",
            mask);
        v = "0";
      }
      version = new Version(v);
    }

    StringBuilder sb = new StringBuilder();
    String del = "";

    for (int i = 0; i < mask.length(); i++) {
      char c = mask.charAt(i);
      String result = null;
      if (c != '~') {
        if (i == 3) {
          result = version.getQualifier();
        } else if (Character.isDigit(c)) {
          // Handle masks like +00, =+0
          result = String.valueOf(c);
        } else {
          int x = version.get(i);
          switch (c) {
            case '+':
              x++;
              break;
            case '-':
              x--;
              break;
            case '=':
              break;
          }
          result = Integer.toString(x);
        }
        if (result != null) {
          sb.append(del);
          del = ".";
          sb.append(result);
        }
      }
    }
    return sb.toString();
  }

  /**
   * Schortcut for version policy
   *
   * <pre>
   * -provide-policy : ${policy;[==,=+)}
   * -consume-policy : ${policy;[==,+)}
   * </pre>
   *
   * @param args
   * @return
   */
  static Pattern RANGE_MASK =
      Pattern.compile("(\\[|\\()(" + MASK_STRING + "),(" + MASK_STRING + ")(\\]|\\))");

  static String _rangeHelp =
      "${range;<mask>[;<version>]}, range for version, if version not specified lookyp ${@}\n"
          + "<mask> ::= [ M [ M [ M [ MQ ]]]\n"
          + "M ::= '+' | '-' | MQ\n"
          + "MQ ::= '~' | '='";
  static Pattern _rangePattern[] = new Pattern[] {null, RANGE_MASK};

  public String _range(String args[]) {
    verifyCommand(args, _rangeHelp, _rangePattern, 2, 3);
    Version version = null;
    if (args.length >= 3) version = new Version(args[2]);
    else {
      String v = domain.getProperty("@");
      if (v == null) return null;
      version = new Version(v);
    }
    String spec = args[1];

    Matcher m = RANGE_MASK.matcher(spec);
    m.matches();
    String floor = m.group(1);
    String floorMask = m.group(2);
    String ceilingMask = m.group(3);
    String ceiling = m.group(4);

    String left = version(version, floorMask);
    String right = version(version, ceilingMask);
    StringBuilder sb = new StringBuilder();
    sb.append(floor);
    sb.append(left);
    sb.append(",");
    sb.append(right);
    sb.append(ceiling);

    String s = sb.toString();
    VersionRange vr = new VersionRange(s);
    if (!(vr.includes(vr.getHigh()) || vr.includes(vr.getLow()))) {
      domain.error(
          "${range} macro created an invalid range %s from %s and mask %s", s, version, spec);
    }
    return sb.toString();
  }

  /**
   * System command. Execute a command and insert the result.
   *
   * @param args
   * @param help
   * @param patterns
   * @param low
   * @param high
   */
  public String system_internal(boolean allowFail, String args[]) throws Exception {
    verifyCommand(
        args,
        "${"
            + (allowFail ? "system-allow-fail" : "system")
            + ";<command>[;<in>]}, execute a system command",
        null,
        2,
        3);
    String command = args[1];
    String input = null;

    if (args.length > 2) {
      input = args[2];
    }

    Process process = Runtime.getRuntime().exec(command, null, domain.getBase());
    if (input != null) {
      process.getOutputStream().write(input.getBytes("UTF-8"));
    }
    process.getOutputStream().close();

    String s = IO.collect(process.getInputStream(), "UTF-8");
    int exitValue = process.waitFor();
    if (exitValue != 0) return exitValue + "";

    if (!allowFail && (exitValue != 0)) {
      domain.error("System command " + command + " failed with " + exitValue);
    }
    return s.trim();
  }

  public String _system(String args[]) throws Exception {
    return system_internal(false, args);
  }

  public String _system_allow_fail(String args[]) throws Exception {
    String result = "";
    try {
      result = system_internal(true, args);
    } catch (Throwable t) {
      /* ignore */
    }
    return result;
  }

  public String _env(String args[]) {
    verifyCommand(args, "${env;<name>}, get the environmet variable", null, 2, 2);

    try {
      return System.getenv(args[1]);
    } catch (Throwable t) {
      return null;
    }
  }

  /**
   * Get the contents of a file.
   *
   * @param in
   * @return
   * @throws IOException
   */
  public String _cat(String args[]) throws IOException {
    verifyCommand(args, "${cat;<in>}, get the content of a file", null, 2, 2);
    File f = domain.getFile(args[1]);
    if (f.isFile()) {
      return IO.collect(f);
    } else if (f.isDirectory()) {
      return Arrays.toString(f.list());
    } else {
      try {
        URL url = new URL(args[1]);
        return IO.collect(url, "UTF-8");
      } catch (MalformedURLException mfue) {
        // Ignore here
      }
      return null;
    }
  }

  public static void verifyCommand(
      String args[],
      @SuppressWarnings("unused") String help,
      Pattern[] patterns,
      int low,
      int high) {
    String message = "";
    if (args.length > high) {
      message = "too many arguments";
    } else if (args.length < low) {
      message = "too few arguments";
    } else {
      for (int i = 0; patterns != null && i < patterns.length && i < args.length; i++) {
        if (patterns[i] != null) {
          Matcher m = patterns[i].matcher(args[i]);
          if (!m.matches())
            message +=
                String.format(
                    "Argument %s (%s) does not match %s%n", i, args[i], patterns[i].pattern());
        }
      }
    }
    if (message.length() != 0) {
      StringBuilder sb = new StringBuilder();
      String del = "${";
      for (String arg : args) {
        sb.append(del);
        sb.append(arg);
        del = ";";
      }
      sb.append("}, is not understood. ");
      sb.append(message);
      throw new IllegalArgumentException(sb.toString());
    }
  }

  // Helper class to track expansion of variables
  // on the stack.
  static class Link {
    Link previous;
    String key;
    Processor start;

    public Link(Processor start, Link previous, String key) {
      this.previous = previous;
      this.key = key;
      this.start = start;
    }

    public boolean contains(String key) {
      if (this.key.equals(key)) return true;

      if (previous == null) return false;

      return previous.contains(key);
    }

    @Override
    public String toString() {
      StringBuilder sb = new StringBuilder();
      String del = "[";
      for (Link r = this; r != null; r = r.previous) {
        sb.append(del);
        sb.append(r.key);
        del = ",";
      }
      sb.append("]");
      return sb.toString();
    }
  }

  /**
   * Take all the properties and translate them to actual values. This method takes the set
   * properties and traverse them over all entries, including the default properties for that
   * properties. The values no longer contain macros.
   *
   * @return A new Properties with the flattened values
   */
  public Properties getFlattenedProperties() {
    // Some macros only work in a lower processor, so we
    // do not report unknown macros while flattening
    flattening = true;
    try {
      Properties flattened = new Properties();
      Properties source = domain.getProperties();
      for (Enumeration<?> e = source.propertyNames(); e.hasMoreElements(); ) {
        String key = (String) e.nextElement();
        if (!key.startsWith("_"))
          if (key.startsWith("-")) flattened.put(key, source.getProperty(key));
          else flattened.put(key, process(source.getProperty(key)));
      }
      return flattened;
    } finally {
      flattening = false;
    }
  }

  public static final String _fileHelp =
      "${file;<base>;<paths>...}, create correct OS dependent path";

  public String _osfile(String args[]) {
    verifyCommand(args, _fileHelp, null, 3, 3);
    File base = new File(args[1]);
    File f = Processor.getFile(base, args[2]);
    return f.getAbsolutePath();
  }

  public String _path(String args[]) {
    List<String> list = new ArrayList<String>();
    for (int i = 1; i < args.length; i++) {
      list.addAll(Processor.split(args[i]));
    }
    return Processor.join(list, File.pathSeparator);
  }

  public static Properties getParent(Properties p) {
    try {
      Field f = Properties.class.getDeclaredField("defaults");
      f.setAccessible(true);
      return (Properties) f.get(p);
    } catch (Exception e) {
      Field[] fields = Properties.class.getFields();
      System.err.println(Arrays.toString(fields));
      return null;
    }
  }

  public String process(String line) {
    return process(line, domain);
  }
}
  public String what(String key, boolean oneliner) throws Exception {
    byte[] sha;

    Matcher m = SHA_P.matcher(key);
    if (m.matches()) {
      sha = Hex.toByteArray(key);
    } else {
      m = URL_P.matcher(key);
      if (m.matches()) {
        URL url = new URL(key);
        sha = SHA1.digest(url.openStream()).digest();
      } else {
        File jarfile = new File(key);
        if (!jarfile.exists()) {
          reporter.error("File does not exist: %s", jarfile.getCanonicalPath());
        }
        sha = SHA1.digest(jarfile).digest();
      }
    }
    reporter.trace("sha %s", Hex.toHexString(sha));
    Revision revision = library.getRevision(sha);
    if (revision == null) {
      return null;
    }

    StringBuilder sb = new StringBuilder();
    Formatter f = new Formatter(sb);
    Justif justif = new Justif(120, 20, 70, 20, 75);
    DateFormat dateFormat = DateFormat.getDateInstance();

    try {
      if (oneliner) {
        f.format("%20s %s%n", Hex.toHexString(revision._id), createCoord(revision));
      } else {
        f.format("Artifact: %s%n", revision.artifactId);
        if (revision.organization != null && revision.organization.name != null) {
          f.format(" (%s)", revision.organization.name);
        }
        f.format("%n");
        f.format("Coordinates\t0: %s%n", createCoord(revision));
        f.format("Created\t0: %s%n", dateFormat.format(new Date(revision.created)));
        f.format("Size\t0: %d%n", revision.size);
        f.format("Sha\t0: %s%n", Hex.toHexString(revision._id));
        f.format("URL\t0: %s%n", createJpmLink(revision));
        f.format("%n");
        f.format("%s%n", revision.description);
        f.format("%n");
        f.format("Dependencies\t0:%n");
        boolean flag = false;
        Iterable<RevisionRef> closure = library.getClosure(revision._id, true);
        for (RevisionRef dep : closure) {
          f.format(
              " - %s \t2- %s \t3- %s%n",
              dep.name, createCoord(dep), dateFormat.format(new Date(dep.created)));
          flag = true;
        }
        if (!flag) {
          f.format("     None%n");
        }
        f.format("%n");
      }
      f.flush();
      justif.wrap(sb);
      return sb.toString();
    } finally {
      f.close();
    }
  }
/**
 * JPM is the Java package manager. It manages a local repository in the user global directory
 * and/or a global directory. This class is the main entry point for the command line. This program
 * maintains a repository, a list of installed commands, and a list of installed service. It
 * provides the commands to changes these resources. All information is kept in a platform specific
 * area. However, the layout of this area is standardized.
 *
 * <pre>
 * 	platform/
 *      check									check for write access
 *      repo/                              		repository
 *        &lt;bsn&gt;/                     		bsn directory
 *          &lt;bsn&gt;-&lt;version&gt;.jar		jar file
 *      service/                               All service
 *        &lt;service&gt;/                      A service
 *        	data								Service data (JSON)
 *          wdir/								Working dir
 *          lock								Lock file (if running, contains port)
 *      commands/
 *        &lt;command&gt;						Command data (JSON)
 * </pre>
 *
 * For each service, the platform must also have a user writable directory used for working dir,
 * lock, and logging.
 *
 * <pre>
 *   platform var/
 *       wdir/									Working dir
 *       lock									Lock file (exists only when running, contains UDP port)
 * </pre>
 */
public class JustAnotherPackageManager {
  private static final String SERVICE_JAR_FILE = "service.jar";
  public static final String SERVICE = "service";
  public static final String COMMANDS = "commands";
  public static final String LOCK = "lock";
  private static final String JPM_CACHE_LOCAL = "jpm.cache.local";
  private static final String JPM_CACHE_GLOBAL = "jpm.cache.global";
  static final String PERMISSION_ERROR =
      "No write acces, might require administrator or root privileges (sudo in *nix)";
  static JSONCodec codec = new JSONCodec();
  static Pattern BSN_P =
      Pattern.compile(
          "([-a-z0-9_]+(?:\\.[-a-z0-9_]+)+)(?:@([0-9]+(?:\\.[0-9]+(?:\\.[0-9]+(?:\\.[-_a-z0-9]+)?)?)?))?",
          Pattern.CASE_INSENSITIVE);
  static Pattern COORD_P =
      Pattern.compile(
          "([-a-z0-9_.]+):([-a-z0-9_.]+)(?::([-a-z0-9_.]+))?(?:@([-a-z0-9._]+))?",
          Pattern.CASE_INSENSITIVE);
  static Pattern URL_P = Pattern.compile("([a-z]{3,6}:/.*)", Pattern.CASE_INSENSITIVE);
  static Pattern CMD_P = Pattern.compile("([a-z_][a-z\\d_]*)", Pattern.CASE_INSENSITIVE);
  static Pattern SHA_P = Pattern.compile("(?:sha:)?([a-fA-F0-9]{40,40})", Pattern.CASE_INSENSITIVE);
  static Executor executor;

  final File homeDir;
  final File binDir;
  final File repoDir;
  final File commandDir;
  final File serviceDir;
  final File service;
  final Platform platform;
  final Reporter reporter;

  JpmRepo library;
  final List<Service> startedByDaemon = new ArrayList<Service>();
  boolean localInstall = false;
  private URLClient host;
  private boolean underTest;

  /**
   * Constructor
   *
   * @throws IOException
   */
  public JustAnotherPackageManager(Reporter reporter, Platform platform, File homeDir, File binDir)
      throws IOException {
    this.platform = platform;
    this.reporter = reporter;
    this.homeDir = homeDir;
    if (!homeDir.exists() && !homeDir.mkdirs())
      throw new IllegalArgumentException("Could not create directory " + homeDir);

    repoDir = IO.getFile(homeDir, "repo");
    if (!repoDir.exists() && !repoDir.mkdirs())
      throw new IllegalArgumentException("Could not create directory " + repoDir);

    commandDir = new File(homeDir, COMMANDS);
    serviceDir = new File(homeDir, SERVICE);
    commandDir.mkdir();
    serviceDir.mkdir();
    service = new File(repoDir, SERVICE_JAR_FILE);
    if (!service.isFile()) init();

    this.binDir = binDir;
    if (!binDir.exists() && !binDir.mkdirs())
      throw new IllegalArgumentException("Could not create bin directory " + binDir);
  }

  public String getArtifactIdFromCoord(String coord) {
    Matcher m = COORD_P.matcher(coord);
    if (m.matches()) {
      return m.group(2);
    } else {
      return null;
    }
  }

  public boolean hasAccess() {
    assert (binDir != null);
    assert (homeDir != null);

    return binDir.canWrite() && homeDir.canWrite();
  }

  public File getHomeDir() {
    return homeDir;
  }

  public File getRepoDir() {
    return repoDir;
  }

  public File getBinDir() {
    return binDir;
  }

  public List<ServiceData> getServices() throws Exception {
    return getServices(serviceDir);
  }

  public List<ServiceData> getServices(File serviceDir) throws Exception {
    List<ServiceData> result = new ArrayList<ServiceData>();

    if (!serviceDir.exists()) {
      return result;
    }

    for (File sdir : serviceDir.listFiles()) {
      File dataFile = new File(sdir, "data");
      ServiceData data = getData(ServiceData.class, dataFile);
      result.add(data);
    }
    return result;
  }

  public List<CommandData> getCommands() throws Exception {
    return getCommands(commandDir);
  }

  public List<CommandData> getCommands(File commandDir) throws Exception {
    List<CommandData> result = new ArrayList<CommandData>();

    if (!commandDir.exists()) {
      return result;
    }

    for (File f : commandDir.listFiles()) {
      CommandData data = getData(CommandData.class, f);
      if (data != null) result.add(data);
    }
    return result;
  }

  public CommandData getCommand(String name) throws Exception {
    File f = new File(commandDir, name);
    if (!f.isFile()) return null;

    return getData(CommandData.class, f);
  }

  /**
   * Garbage collect repository
   *
   * @throws Exception
   */
  public void gc() throws Exception {
    HashSet<byte[]> deps = new HashSet<byte[]>();

    // deps.add(SERVICE_JAR_FILE);

    for (File cmd : commandDir.listFiles()) {
      CommandData data = getData(CommandData.class, cmd);
      addDependencies(deps, data);
    }

    for (File service : serviceDir.listFiles()) {
      File dataFile = new File(service, "data");
      ServiceData data = getData(ServiceData.class, dataFile);
      addDependencies(deps, data);
    }

    int count = 0;
    for (File f : repoDir.listFiles()) {
      String name = f.getName();
      if (!deps.contains(name)) {
        if (!name.endsWith(".json")
            || !deps.contains(name.substring(0, name.length() - ".json".length()))) { // Remove
          // json
          // files
          // only
          // if
          // the
          // bin
          // is
          // going
          // as
          // well
          f.delete();
          count++;
        } else {

        }
      }
    }
    System.out.format("Garbage collection done (%d file(s) removed)%n", count);
  }

  private void addDependencies(HashSet<byte[]> deps, CommandData data) {
    for (byte[] dep : data.dependencies) {
      deps.add(dep);
    }
    for (byte[] dep : data.runbundles) {
      deps.add(dep);
    }
  }

  public void deinit(Appendable out, boolean force) throws Exception {
    Settings settings = new Settings(platform.getConfigFile());

    if (!force) {
      Justif justify = new Justif(80, 40);
      StringBuilder sb = new StringBuilder();
      Formatter f = new Formatter(sb);

      try {
        String list = listFiles(platform.getGlobal());
        if (list != null) {
          f.format("In global default environment:%n");
          f.format(list);
        }

        list = listFiles(platform.getLocal());
        if (list != null) {
          f.format("In local default environment:%n");
          f.format(list);
        }

        if (settings.containsKey(JPM_CACHE_GLOBAL)) {
          list = listFiles(IO.getFile(settings.get(JPM_CACHE_GLOBAL)));
          if (list != null) {
            f.format("In global configured environment:%n");
            f.format(list);
          }
        }

        if (settings.containsKey(JPM_CACHE_LOCAL)) {
          list = listFiles(IO.getFile(settings.get(JPM_CACHE_LOCAL)));
          if (list != null) {
            f.format("In local configured environment:%n");
            f.format(list);
          }
        }

        list = listSupportFiles();
        if (list != null) {
          f.format("jpm support files:%n");
          f.format(list);
        }

        f.format("%n%n");

        f.format(
            "All files listed above will be deleted if deinit is run with the force flag set"
                + " (\"jpm deinit -f\" or \"jpm deinit --force\"%n%n");
        f.flush();

        justify.wrap(sb);
        out.append(sb.toString());
      } finally {
        f.close();
      }
    } else { // i.e. if(force)
      int count = 0;
      File[] caches = {platform.getGlobal(), platform.getLocal(), null, null};
      if (settings.containsKey(JPM_CACHE_LOCAL)) {
        caches[2] = IO.getFile(settings.get(JPM_CACHE_LOCAL));
      }
      if (settings.containsKey(JPM_CACHE_GLOBAL)) {
        caches[3] = IO.getFile(settings.get(JPM_CACHE_GLOBAL));
      }
      ArrayList<File> toDelete = new ArrayList<File>();

      for (File cache : caches) {
        if (cache == null || !cache.exists()) {
          continue;
        }
        listFiles(cache, toDelete);
        if (toDelete.size() > count) {
          count = toDelete.size();
          if (!cache.canWrite()) {
            reporter.error(PERMISSION_ERROR + " (" + cache + ")");
            return;
          }
          toDelete.add(cache);
        }
      }
      listSupportFiles(toDelete);

      for (File f : toDelete) {
        if (f.exists() && !f.canWrite()) {
          reporter.error(PERMISSION_ERROR + " (" + f + ")");
        }
      }
      if (reporter.getErrors().size() > 0) {
        return;
      }

      for (File f : toDelete) {
        if (f.exists()) {
          IO.deleteWithException(f);
        }
      }
    }
  }

  // Adapter to list without planning to delete
  private String listFiles(final File cache) throws Exception {
    return listFiles(cache, null);
  }

  private String listFiles(final File cache, List<File> toDelete) throws Exception {
    boolean stopServices = false;
    if (toDelete == null) {
      toDelete = new ArrayList<File>();
    } else {
      stopServices = true;
    }
    int count = 0;
    Formatter f = new Formatter();

    f.format(" - Cache:%n    * %s%n", cache.getCanonicalPath());
    f.format(" - Commands:%n");
    for (CommandData cdata : getCommands(new File(cache, COMMANDS))) {
      f.format("    * %s \t0 handle for \"%s\"%n", cdata.bin, cdata.name);
      toDelete.add(new File(cdata.bin));
      count++;
    }
    f.format(" - Services:%n");
    for (ServiceData sdata : getServices(new File(cache, SERVICE))) {
      if (sdata != null) {
        f.format("    * %s \t0 service directory for \"%s\"%n", sdata.sdir, sdata.name);
        toDelete.add(new File(sdata.sdir));
        File initd = platform.getInitd(sdata);
        if (initd != null && initd.exists()) {
          f.format("    * %s \t0 init.d file for \"%s\"%n", initd.getCanonicalPath(), sdata.name);
          toDelete.add(initd);
        }
        if (stopServices) {
          Service s = getService(sdata);
          try {
            s.stop();
          } catch (Exception e) {
          }
        }
        count++;
      }
    }
    f.format("%n");

    String result = (count > 0) ? f.toString() : null;
    f.close();
    return result;
  }

  private String listSupportFiles() throws Exception { // Adapter to list
    // without planning
    // to delete
    return listSupportFiles(null);
  }

  private String listSupportFiles(List<File> toDelete) throws Exception {
    Formatter f = new Formatter();
    try {
      if (toDelete == null) {
        toDelete = new ArrayList<File>();
      }
      int precount = toDelete.size();
      File confFile = IO.getFile(platform.getConfigFile()).getCanonicalFile();
      if (confFile.exists()) {
        f.format("    * %s \t0 Config file%n", confFile);
        toDelete.add(confFile);
      }

      String result = (toDelete.size() > precount) ? f.toString() : null;
      return result;
    } finally {
      f.close();
    }
  }

  /**
   * @param data
   * @param target
   * @throws Exception
   * @throws IOException
   */
  public String createService(ServiceData data) throws Exception, IOException {

    File sdir = new File(serviceDir, data.name);
    if (!sdir.exists() && !sdir.mkdirs()) {
      throw new IOException("Could not create directory " + data.sdir);
    }
    data.sdir = sdir.getAbsolutePath();

    File lock = new File(data.sdir, LOCK);
    data.lock = lock.getAbsolutePath();

    if (data.work == null) data.work = new File(data.sdir, "work").getAbsolutePath();
    if (data.user == null) data.user = platform.user();

    if (data.user == null) data.user = "******";

    new File(data.work).mkdir();

    if (data.log == null) data.log = new File(data.sdir, "log").getAbsolutePath();

    // TODO
    // if (Data.validate(data) != null)
    // return "Invalid service data: " + Data.validate(data);

    if (service == null)
      throw new RuntimeException(
          "Missing biz.aQute.jpm.service in repo, should have been installed by init, try reiniting");

    data.serviceLib = service.getAbsolutePath();

    platform.chown(data.user, true, new File(data.sdir));

    String s = platform.createService(data, null, false);
    if (s == null) storeData(new File(data.sdir, "data"), data);
    return s;
  }

  /**
   * @param data
   * @param target
   * @throws Exception
   * @throws IOException
   */
  public String createCommand(CommandData data, boolean force) throws Exception, IOException {

    // TODO
    // if (Data.validate(data) != null)
    // return "Invalid command data: " + Data.validate(data);

    Map<String, String> map = null;
    if (data.trace) {
      map = new HashMap<String, String>();
      map.put("java.security.manager", "aQute.jpm.service.TraceSecurityManager");
      reporter.trace("tracing");
    }
    String s = platform.createCommand(data, map, force, service.getAbsolutePath());
    if (s == null) storeData(new File(commandDir, data.name), data);
    return s;
  }

  public void deleteCommand(String name) throws Exception {
    CommandData cmd = getCommand(name);
    if (cmd == null) throw new IllegalArgumentException("No such command " + name);

    platform.deleteCommand(cmd);
    File tobedel = new File(commandDir, name);
    IO.deleteWithException(tobedel);
  }

  public Service getService(String serviceName) throws Exception {
    File base = new File(serviceDir, serviceName);
    return getService(base);
  }

  public Service getService(ServiceData sdata) throws Exception {
    return getService(new File(sdata.sdir));
  }

  private Service getService(File base) throws Exception {
    File dataFile = new File(base, "data");
    if (!dataFile.isFile()) return null;

    ServiceData data = getData(ServiceData.class, dataFile);
    return new Service(this, data);
  }

  /**
   * Verify that the jar file is correct. This also verifies ok when there are no checksums or.
   *
   * @throws IOException
   */
  static Pattern MANIFEST_ENTRY = Pattern.compile("(META-INF/[^/]+)|(.*/)");

  public String verify(JarFile jar, String... algorithms) throws IOException {
    if (algorithms == null || algorithms.length == 0) algorithms = new String[] {"MD5", "SHA"};
    else if (algorithms.length == 1 && algorithms[0].equals("-")) return null;

    try {
      Manifest m = jar.getManifest();
      if (m.getEntries().isEmpty()) return "No name sections";

      for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements(); ) {
        JarEntry je = e.nextElement();
        if (MANIFEST_ENTRY.matcher(je.getName()).matches()) continue;

        Attributes nameSection = m.getAttributes(je.getName());
        if (nameSection == null) return "No name section for " + je.getName();

        for (String algorithm : algorithms) {
          try {
            MessageDigest md = MessageDigest.getInstance(algorithm);
            String expected = nameSection.getValue(algorithm + "-Digest");
            if (expected != null) {
              byte digest[] = Base64.decodeBase64(expected);
              copy(jar.getInputStream(je), md);
              if (!Arrays.equals(digest, md.digest()))
                return "Invalid digest for "
                    + je.getName()
                    + ", "
                    + expected
                    + " != "
                    + Base64.encodeBase64(md.digest());
            } else reporter.error("could not find digest for " + algorithm + "-Digest");
          } catch (NoSuchAlgorithmException nsae) {
            return "Missing digest algorithm " + algorithm;
          }
        }
      }
    } catch (Exception e) {
      return "Failed to verify due to exception: " + e.getMessage();
    }
    return null;
  }

  /**
   * @param clazz
   * @param dataFile
   * @return
   * @throws Exception
   */
  private <T> T getData(Class<T> clazz, File dataFile) throws Exception {
    try {
      return codec.dec().from(dataFile).get(clazz);
    } catch (Exception e) {
      // e.printStackTrace();
      // System.out.println("Cannot read data file "+dataFile+": " +
      // IO.collect(dataFile));
      return null;
    }
  }

  private void storeData(File dataFile, Object o) throws Exception {
    codec.enc().to(dataFile).put(o);
  }

  /**
   * This is called when JPM runs in the background to start jobs
   *
   * @throws Exception
   */
  public void daemon() throws Exception {
    Runtime.getRuntime()
        .addShutdownHook(
            new Thread("Daemon shutdown") {
              public void run() {

                for (Service service : startedByDaemon) {
                  try {
                    reporter.error("Stopping " + service);
                    service.stop();
                    reporter.error("Stopped " + service);
                  } catch (Exception e) {
                    // Ignore
                  }
                }
              }
            });
    List<ServiceData> services = getServices();
    Map<String, ServiceData> map = new HashMap<String, ServiceData>();
    for (ServiceData d : services) {
      map.put(d.name, d);
    }
    List<ServiceData> start = new ArrayList<ServiceData>();
    Set<ServiceData> set = new HashSet<ServiceData>();
    for (ServiceData sd : services) {
      checkStartup(map, start, sd, set);
    }

    if (start.isEmpty()) reporter.warning("No services to start");

    for (ServiceData sd : start) {
      try {
        Service service = getService(sd.name);
        reporter.trace("Starting " + service);
        String result = service.start();
        if (result != null) reporter.error("Started error " + result);
        else startedByDaemon.add(service);
        reporter.trace("Started " + service);
      } catch (Exception e) {
        reporter.error("Cannot start daemon %s, due to %s", sd.name, e);
      }
    }

    while (true) {
      for (Service sd : startedByDaemon) {
        try {
          if (!sd.isRunning()) {
            reporter.error("Starting due to failure " + sd);
            String result = sd.start();
            if (result != null) reporter.error("Started error " + result);
          }
        } catch (Exception e) {
          reporter.error("Cannot start daemon %s, due to %s", sd, e);
        }
      }
      Thread.sleep(10000);
    }
  }

  private void checkStartup(
      Map<String, ServiceData> map,
      List<ServiceData> start,
      ServiceData sd,
      Set<ServiceData> cyclic) {
    if (sd.after.isEmpty() || start.contains(sd)) return;

    if (cyclic.contains(sd)) {
      reporter.error("Cyclic dependency for " + sd.name);
      return;
    }

    cyclic.add(sd);

    for (String dependsOn : sd.after) {
      if (dependsOn.equals("boot")) continue;

      ServiceData deps = map.get(dependsOn);
      if (deps == null) {
        reporter.error("No such service " + dependsOn + " but " + sd.name + " depends on it");
      } else {
        checkStartup(map, start, deps, cyclic);
      }
    }
    start.add(sd);
  }

  public void register(boolean user) throws Exception {
    platform.installDaemon(user);
  }

  public ArtifactData putAsync(final URI uri) {
    final ArtifactData data = new ArtifactData();
    data.busy = true;
    Runnable r =
        new Runnable() {

          public void run() {
            try {
              put(uri, data);
            } catch (Throwable e) {
              e.printStackTrace();
              data.error = e.toString();
            } finally {
              reporter.trace("done downloading %s", uri);
              data.done();
            }
          }
        };
    getExecutor().execute(r);
    return data;
  }

  public ArtifactData put(final URI uri) throws Exception {
    final ArtifactData data = new ArtifactData();
    put(uri, data);
    return data;
  }

  void put(final URI uri, ArtifactData data) throws Exception {
    reporter.trace("put %s %s", uri, data);
    File tmp = createTempFile(repoDir, "mtp", ".whatever");
    tmp.deleteOnExit();
    try {
      copy(uri.toURL(), tmp);
      byte[] sha = SHA1.digest(tmp).digest();
      reporter.trace("SHA %s %s", uri, Hex.toHexString(sha));
      ArtifactData existing = get(sha);
      if (existing != null) {
        reporter.trace("existing");
        xcopy(existing, data);
        return;
      }
      File meta = new File(repoDir, Hex.toHexString(sha) + ".json");
      File file = new File(repoDir, Hex.toHexString(sha));
      rename(tmp, file);
      reporter.trace("file %s", file);
      data.file = file.getAbsolutePath();
      data.sha = sha;
      data.busy = false;
      CommandData cmddata = parseCommandData(data);
      if (cmddata.bsn != null) {
        data.name = cmddata.bsn + "-" + cmddata.version;
      } else data.name = Strings.display(cmddata.title, cmddata.bsn, cmddata.name, uri);
      codec.enc().to(meta).put(data);
      reporter.trace("TD = " + data);
    } finally {
      tmp.delete();
      reporter.trace("puted %s %s", uri, data);
    }
  }

  public ArtifactData get(byte[] sha) throws Exception {
    String name = Hex.toHexString(sha);
    File data = IO.getFile(repoDir, name + ".json");
    reporter.trace("artifact data file %s", data);
    if (data.isFile()) { // Bin + metadata
      ArtifactData artifact = codec.dec().from(data).get(ArtifactData.class);
      artifact.file = IO.getFile(repoDir, name).getAbsolutePath();
      return artifact;
    }
    File bin = IO.getFile(repoDir, name);
    if (bin.exists()) { // Only bin
      ArtifactData artifact = new ArtifactData();
      artifact.file = bin.getAbsolutePath();
      artifact.sha = sha;
      return artifact;
    }

    return null;
  }

  public List<Revision> filter(Collection<Revision> list, EnumSet<Library.Phase> phases) {
    List<Revision> filtered = new ArrayList<Library.Revision>();
    for (Revision r : list) if (phases.contains(r.phase)) filtered.add(r);

    return filtered;
  }

  public Map<String, Revision> latest(Collection<Revision> list) {
    Map<String, Revision> programs = new HashMap<String, Library.Revision>();

    for (Revision r : list) {
      String coordinates = r.groupId + ":" + r.artifactId;
      if (r.classifier != null) coordinates += ":" + r.classifier;

      if (r.groupId.equals(Library.SHA_GROUP)) continue;

      Revision current = programs.get(coordinates);
      if (current == null) programs.put(coordinates, r);
      else {
        // who is better?
        if (compare(r, current) >= 0) programs.put(coordinates, r);
      }
    }
    return programs;
  }

  private int compare(Revision a, Revision b) {
    if (Arrays.equals(a._id, b._id)) return 0;

    Version va = getVersion(a);
    Version vb = getVersion(b);
    int n = va.compareTo(vb);
    if (n != 0) return n;

    if (a.created != b.created) return a.created > b.created ? 1 : -1;

    for (int i = 0; i < a._id.length; i++)
      if (a._id[i] != b._id[i]) return a._id[i] > b._id[i] ? 1 : -1;

    return 0;
  }

  private Version getVersion(Revision a) {
    if (a.qualifier != null) return new Version(a.baseline + "." + a.qualifier);
    return new Version(a.baseline);
  }

  public String getCoordinates(Revision r) {
    StringBuilder sb = new StringBuilder(r.groupId).append(":").append(r.artifactId);
    if (r.classifier != null) sb.append(":").append(r.classifier);
    sb.append("@").append(r.version);

    return sb.toString();
  }

  public String getCoordinates(RevisionRef r) {
    StringBuilder sb = new StringBuilder(r.groupId).append(":").append(r.artifactId).append(":");
    if (r.classifier != null) sb.append(r.classifier).append("@");
    sb.append(r.version);

    return sb.toString();
  }

  public ArtifactData getCandidate(String key) throws Exception {
    ArtifactData data = getCandidateAsync(key);
    if (data != null) {
      data.sync();
    }
    return data;
  }

  public ArtifactData getCandidateAsync(String arg) throws Exception {
    reporter.trace("coordinate %s", arg);
    if (isUrl(arg))
      try {
        ArtifactData data = putAsync(new URI(arg));
        data.local = true;
        return data;
      } catch (Exception e) {
        reporter.trace("hmm, not a valid url %s, will try the server", arg);
      }

    Coordinate c = new Coordinate(arg);

    if (c.isSha()) {
      ArtifactData r = get(c.getSha());
      if (r != null) return r;
    }

    Revision revision = library.getRevisionByCoordinate(c);
    if (revision == null) return null;

    reporter.trace("revision %s", Hex.toHexString(revision._id));

    ArtifactData ad = get(revision._id);
    if (ad != null) {
      reporter.trace("found in cache");
      return ad;
    }

    URI url = revision.urls.iterator().next();
    ArtifactData artifactData = putAsync(url);
    artifactData.coordinate = c;
    return artifactData;
  }

  public static Executor getExecutor() {
    if (executor == null) executor = Executors.newFixedThreadPool(4);
    return executor;
  }

  public static void setExecutor(Executor executor) {
    JustAnotherPackageManager.executor = executor;
  }

  public void setLibrary(URI url) throws Exception {
    if (url == null) url = new URI("http://repo.jpm4j.org/");

    this.host = new URLClient(url.toString());
    host.setReporter(reporter);
    library = JSONRPCProxy.createRPC(JpmRepo.class, host, "jpm");
  }

  public void close() {
    if (executor != null && executor instanceof ExecutorService)
      ((ExecutorService) executor).shutdown();
  }

  public void init() throws IOException {
    URL s = getClass().getClassLoader().getResource(SERVICE_JAR_FILE);
    if (s == null)
      if (underTest) return;
      else throw new Error("No " + SERVICE_JAR_FILE + " resource in jar");
    service.getParentFile().mkdirs();
    IO.copy(s, service);
  }

  public Platform getPlatform() {
    return platform;
  }

  /**
   * Copy from the copy method in StructUtil. Did not want to drag that code in. maybe this actually
   * should go to struct.
   *
   * @param from
   * @param to
   * @param excludes
   * @return
   * @throws Exception
   */
  public static <T extends struct> T xcopy(struct from, T to, String... excludes) throws Exception {
    Arrays.sort(excludes);
    for (Field f : from.fields()) {
      if (Arrays.binarySearch(excludes, f.getName()) >= 0) continue;

      Object o = f.get(from);
      if (o == null) continue;

      Field tof = to.getField(f.getName());
      if (tof != null)
        try {
          tof.set(to, Converter.cnv(tof.getGenericType(), o));
        } catch (Exception e) {
          System.out.println(
              "Failed to convert "
                  + f.getName()
                  + " from "
                  + from.getClass()
                  + " to "
                  + to.getClass()
                  + " value "
                  + o
                  + " exception "
                  + e);
        }
    }

    return to;
  }

  public JpmRepo getLibrary() {
    return library;
  }

  public void setLocalInstall(boolean b) {
    localInstall = b;
  }

  public String what(String key, boolean oneliner) throws Exception {
    byte[] sha;

    Matcher m = SHA_P.matcher(key);
    if (m.matches()) {
      sha = Hex.toByteArray(key);
    } else {
      m = URL_P.matcher(key);
      if (m.matches()) {
        URL url = new URL(key);
        sha = SHA1.digest(url.openStream()).digest();
      } else {
        File jarfile = new File(key);
        if (!jarfile.exists()) {
          reporter.error("File does not exist: %s", jarfile.getCanonicalPath());
        }
        sha = SHA1.digest(jarfile).digest();
      }
    }
    reporter.trace("sha %s", Hex.toHexString(sha));
    Revision revision = library.getRevision(sha);
    if (revision == null) {
      return null;
    }

    StringBuilder sb = new StringBuilder();
    Formatter f = new Formatter(sb);
    Justif justif = new Justif(120, 20, 70, 20, 75);
    DateFormat dateFormat = DateFormat.getDateInstance();

    try {
      if (oneliner) {
        f.format("%20s %s%n", Hex.toHexString(revision._id), createCoord(revision));
      } else {
        f.format("Artifact: %s%n", revision.artifactId);
        if (revision.organization != null && revision.organization.name != null) {
          f.format(" (%s)", revision.organization.name);
        }
        f.format("%n");
        f.format("Coordinates\t0: %s%n", createCoord(revision));
        f.format("Created\t0: %s%n", dateFormat.format(new Date(revision.created)));
        f.format("Size\t0: %d%n", revision.size);
        f.format("Sha\t0: %s%n", Hex.toHexString(revision._id));
        f.format("URL\t0: %s%n", createJpmLink(revision));
        f.format("%n");
        f.format("%s%n", revision.description);
        f.format("%n");
        f.format("Dependencies\t0:%n");
        boolean flag = false;
        Iterable<RevisionRef> closure = library.getClosure(revision._id, true);
        for (RevisionRef dep : closure) {
          f.format(
              " - %s \t2- %s \t3- %s%n",
              dep.name, createCoord(dep), dateFormat.format(new Date(dep.created)));
          flag = true;
        }
        if (!flag) {
          f.format("     None%n");
        }
        f.format("%n");
      }
      f.flush();
      justif.wrap(sb);
      return sb.toString();
    } finally {
      f.close();
    }
  }

  private String createCoord(Revision rev) {
    return String.format("%s:%s@%s [%s]", rev.groupId, rev.artifactId, rev.version, rev.phase);
  }

  private String createCoord(RevisionRef rev) {
    return String.format("%s:%s@%s [%s]", rev.groupId, rev.artifactId, rev.version, rev.phase);
  }

  private String createJpmLink(Revision rev) {
    return String.format(
        "http://jpm4j.org/#!/p/sha/%s//%s", Hex.toHexString(rev._id), rev.baseline);
  }

  public class UpdateMemo {
    public CommandData current; // Works for commandData and
    // ServiceData, as ServiceData --|>
    // CommandData
    public RevisionRef best;
  }

  public void listUpdates(
      List<UpdateMemo> notFound,
      List<UpdateMemo> upToDate,
      List<UpdateMemo> toUpdate,
      CommandData data,
      boolean staged)
      throws Exception {

    // UpdateMemo memo = new UpdateMemo();
    // memo.current = data;
    //
    // Matcher m = data.coordinates == null ? null :
    // COORD_P.matcher(data.coordinates);
    //
    // if (data.version == null || m == null || !m.matches()) {
    // Revision revision = library.getRevision(data.sha);
    // if (revision == null) {
    // notFound.add(memo);
    // return;
    // }
    // data.version = new Version(revision.version);
    // data.coordinates = getCoordinates(revision);
    // if (data instanceof ServiceData) {
    // storeData(IO.getFile(new File(serviceDir, data.name), "data"), data);
    // } else {
    // storeData(IO.getFile(commandDir, data.name), data);
    // }
    // }
    //
    // Iterable< ? extends Program> programs =
    // library.getQueryPrograms(data.coordinates, 0, 0);
    // int count = 0;
    // RevisionRef best = null;
    // for (Program p : programs) {
    // // best = selectBest(p.revisions, staged, null);
    // count++;
    // }
    // if (count != 1 || best == null) {
    // notFound.add(memo);
    // return;
    // }
    // Version bestVersion = new Version(best.version);
    //
    // if (data.version.compareTo(bestVersion) < 0) { // Update available
    // memo.best = best;
    // toUpdate.add(memo);
    // } else { // up to date
    // upToDate.add(memo);
    // }
  }

  public void update(UpdateMemo memo) throws Exception {

    ArtifactData target = put(memo.best.urls.iterator().next());

    memo.current.version = new Version(memo.best.version);
    target.sync();
    memo.current.sha = target.sha;
    // memo.current.dependencies = target.dependencies;
    // memo.current.dependencies.add((new File(repoDir,
    // Hex.toHexString(target.sha))).getCanonicalPath());
    // memo.current.runbundles = target.runbundles;
    // memo.current.description = target.description;
    memo.current.time = target.time;

    if (memo.current instanceof ServiceData) {
      Service service = getService((ServiceData) memo.current);
      service.remove();
      createService((ServiceData) memo.current);
      IO.delete(new File(IO.getFile(serviceDir, memo.current.name), "data"));
      storeData(new File(IO.getFile(serviceDir, memo.current.name), "data"), memo.current);
    } else {
      platform.deleteCommand(memo.current);
      createCommand(memo.current, false);
      IO.delete(IO.getFile(commandDir, memo.current.name));
      storeData(IO.getFile(commandDir, memo.current.name), memo.current);
    }
  }

  private boolean isUrl(String coordinate) {
    return URL_P.matcher(coordinate).matches();
  }

  /**
   * Find programs
   *
   * @throws Exception
   */
  public List<Program> find(String query, int skip, int limit) throws Exception {
    return library.getQueryPrograms(query, skip, limit);
  }

  public boolean isWildcard(String coordinate) {
    return coordinate != null && coordinate.endsWith("@*");
  }

  public CommandData parseCommandData(ArtifactData artifact) throws Exception {
    File source = new File(artifact.file);
    if (!source.isFile()) throw new FileNotFoundException();

    CommandData data = new CommandData();
    data.sha = artifact.sha;
    data.jpmRepoDir = repoDir.getCanonicalPath();
    JarFile jar = new JarFile(source);
    try {
      reporter.trace("Parsing %s", source);
      Manifest m = jar.getManifest();
      Attributes main = m.getMainAttributes();
      data.name = data.bsn = main.getValue("Bundle-SymbolicName");
      String version = main.getValue("Bundle-Version");
      if (version == null) data.version = Version.LOWEST;
      else data.version = new Version(version);

      data.main = main.getValue("Main-Class");
      data.description = main.getValue("Bundle-Description");
      data.title = main.getValue("JPM-Name");

      reporter.trace("name " + data.name + " " + data.main + " " + data.title);
      DependencyCollector path = new DependencyCollector(this);
      path.add(artifact);
      DependencyCollector bundles = new DependencyCollector(this);
      if (main.getValue("JPM-Classpath") != null) {
        Parameters requires = OSGiHeader.parseHeader(main.getValue("JPM-Classpath"));

        for (Map.Entry<String, Attrs> e : requires.entrySet()) {
          path.add(e.getKey(), e.getValue().get("name")); // coordinate
        }
      } else if (!artifact.local) { // No JPM-Classpath, falling back to
        // server's revision
        // Iterable<RevisionRef> closure =
        // library.getClosure(artifact.sha,
        // false);
        // System.out.println("getting closure " + artifact.url + " " +
        // Strings.join("\n",closure));

        // if (closure != null) {
        // for (RevisionRef ref : closure) {
        // path.add(Hex.toHexString(ref.revision));
        // }
        // }
      }

      if (main.getValue("JPM-Runbundles") != null) {
        Parameters jpmrunbundles = OSGiHeader.parseHeader(main.getValue("JPM-Runbundles"));

        for (Map.Entry<String, Attrs> e : jpmrunbundles.entrySet()) {
          bundles.add(e.getKey(), e.getValue().get("name"));
        }
      }

      reporter.trace("collect digests runpath");
      data.dependencies.addAll(path.getDigests());
      reporter.trace("collect digests bundles");
      data.runbundles.addAll(bundles.getDigests());

      Parameters command = OSGiHeader.parseHeader(main.getValue("JPM-Command"));
      if (command.size() > 1) reporter.error("Only one command can be specified");

      for (Map.Entry<String, Attrs> e : command.entrySet()) {
        data.name = e.getKey();

        Attrs attrs = e.getValue();

        if (attrs.containsKey("jvmargs")) data.jvmArgs = attrs.get("jvmargs");

        if (attrs.containsKey("title")) data.title = attrs.get("title");

        if (data.title != null) data.title = data.name;
      }
      return data;
    } finally {
      jar.close();
    }
  }

  public void setUnderTest() {
    underTest = true;
  }

  /**
   * Turn the shas into a readable form
   *
   * @param dependencies
   * @return
   * @throws Exception
   */
  public List<?> toString(List<byte[]> dependencies) throws Exception {
    List<String> out = new ArrayList<String>();
    for (byte[] dependency : dependencies) {
      ArtifactData data = get(dependency);
      if (data == null) out.add(Hex.toHexString(dependency));
      else {
        out.add(Strings.display(data.name, Hex.toHexString(dependency)));
      }
    }
    return out;
  }

  /**
   * Get a list of candidates from a coordinate
   *
   * @param c
   * @throws Exception
   */
  public Iterable<Revision> getCandidates(Coordinate c) throws Exception {
    return library.getRevisionsByCoordinate(c);
  }

  /** Post install */
  public void doPostInstall() {
    getPlatform().doPostInstall();
  }
}
 private boolean isUrl(String coordinate) {
   return URL_P.matcher(coordinate).matches();
 }
Beispiel #13
0
/**
 * This class is used with the aQute.bnd.osgi package, it signs jars with DSA signature. -sign: md5,
 * sha1
 */
public class Signer extends Processor {
  static final int BUFFER_SIZE = IOConstants.PAGE_SIZE * 1;

  static Pattern METAINFDIR = Pattern.compile("META-INF/[^/]*");
  String digestNames[] = new String[] {"MD5"};
  File keystoreFile = new File("keystore");
  String password;
  String alias;

  public void signJar(Jar jar) {
    if (digestNames == null || digestNames.length == 0)
      error("Need at least one digest algorithm name, none are specified");

    if (keystoreFile == null || !keystoreFile.getAbsoluteFile().exists()) {
      error("No such keystore file: " + keystoreFile);
      return;
    }

    if (alias == null) {
      error("Private key alias not set for signing");
      return;
    }

    MessageDigest digestAlgorithms[] = new MessageDigest[digestNames.length];

    getAlgorithms(digestNames, digestAlgorithms);

    try {
      Manifest manifest = jar.getManifest();
      manifest.getMainAttributes().putValue("Signed-By", "Bnd");

      // Create a new manifest that contains the
      // Name parts with the specified digests

      ByteArrayOutputStream o = new ByteArrayOutputStream();
      manifest.write(o);
      doManifest(jar, digestNames, digestAlgorithms, o);
      o.flush();
      byte newManifestBytes[] = o.toByteArray();
      jar.putResource("META-INF/MANIFEST.MF", new EmbeddedResource(newManifestBytes, 0));

      // Use the bytes from the new manifest to create
      // a signature file

      byte[] signatureFileBytes = doSignatureFile(digestNames, digestAlgorithms, newManifestBytes);
      jar.putResource("META-INF/BND.SF", new EmbeddedResource(signatureFileBytes, 0));

      // Now we must create an RSA signature
      // this requires the private key from the keystore

      KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());

      KeyStore.PrivateKeyEntry privateKeyEntry = null;

      java.io.FileInputStream keystoreInputStream = null;
      try {
        keystoreInputStream = new java.io.FileInputStream(keystoreFile);
        char[] pw = password == null ? new char[0] : password.toCharArray();

        keystore.load(keystoreInputStream, pw);
        keystoreInputStream.close();
        privateKeyEntry =
            (PrivateKeyEntry) keystore.getEntry(alias, new KeyStore.PasswordProtection(pw));
      } catch (Exception e) {
        error(
            "No able to load the private key from the give keystore("
                + keystoreFile.getAbsolutePath()
                + ") with alias "
                + alias
                + " : "
                + e);
        return;
      } finally {
        IO.close(keystoreInputStream);
      }
      PrivateKey privateKey = privateKeyEntry.getPrivateKey();

      Signature signature = Signature.getInstance("MD5withRSA");
      signature.initSign(privateKey);

      signature.update(signatureFileBytes);

      signature.sign();

      // TODO, place the SF in a PCKS#7 structure ...
      // no standard class for this? The following
      // is an idea but we will to have do ASN.1 BER
      // encoding ...

      ByteArrayOutputStream tmpStream = new ByteArrayOutputStream();
      jar.putResource("META-INF/BND.RSA", new EmbeddedResource(tmpStream.toByteArray(), 0));
    } catch (Exception e) {
      error("During signing: " + e);
    }
  }

  private byte[] doSignatureFile(String[] digestNames, MessageDigest[] algorithms, byte[] manbytes)
      throws IOException {
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    PrintWriter ps = IO.writer(out);
    ps.print("Signature-Version: 1.0\r\n");

    for (int a = 0; a < algorithms.length; a++) {
      if (algorithms[a] != null) {
        byte[] digest = algorithms[a].digest(manbytes);
        ps.print(digestNames[a] + "-Digest-Manifest: ");
        ps.print(new Base64(digest));
        ps.print("\r\n");
      }
    }
    return out.toByteArray();
  }

  private void doManifest(
      Jar jar, String[] digestNames, MessageDigest[] algorithms, OutputStream out)
      throws Exception {

    for (Map.Entry<String, Resource> entry : jar.getResources().entrySet()) {
      String name = entry.getKey();
      if (!METAINFDIR.matcher(name).matches()) {
        out.write("\r\n".getBytes("UTF-8"));
        out.write("Name: ".getBytes("UTF-8"));
        out.write(name.getBytes("UTF-8"));
        out.write("\r\n".getBytes("UTF-8"));

        digest(algorithms, entry.getValue());
        for (int a = 0; a < algorithms.length; a++) {
          if (algorithms[a] != null) {
            byte[] digest = algorithms[a].digest();
            String header = digestNames[a] + "-Digest: " + new Base64(digest) + "\r\n";
            out.write(header.getBytes("UTF-8"));
          }
        }
      }
    }
  }

  private void digest(MessageDigest[] algorithms, Resource r) throws Exception {
    InputStream in = r.openInputStream();
    byte[] data = new byte[BUFFER_SIZE];
    int size = in.read(data);
    while (size > 0) {
      for (int a = 0; a < algorithms.length; a++) {
        if (algorithms[a] != null) {
          algorithms[a].update(data, 0, size);
        }
      }
      size = in.read(data);
    }
  }

  private void getAlgorithms(String[] digestNames, MessageDigest[] algorithms) {
    for (int i = 0; i < algorithms.length; i++) {
      String name = digestNames[i];
      try {
        algorithms[i] = MessageDigest.getInstance(name);
      } catch (NoSuchAlgorithmException e) {
        error(
            "Specified digest algorithm "
                + digestNames[i]
                + ", but not such algorithm was found: "
                + e);
      }
    }
  }

  public void setPassword(String string) {
    password = string;
  }

  public void setKeystore(File keystore) {
    this.keystoreFile = keystore;
  }

  public void setAlias(String string) {
    this.alias = string;
  }
}