am 8aad9a92: Merge change 24897 into eclair

Merge commit '8aad9a92ff05b12d3ab930de29a47f29ab6ea9b0' into eclair-plus-aosp

* commit '8aad9a92ff05b12d3ab930de29a47f29ab6ea9b0':
  First cut at a tool to perform automated syncs from Harmony to Dalvik.
diff --git a/tools/integrate/Android.mk b/tools/integrate/Android.mk
new file mode 100644
index 0000000..629a5fd
--- /dev/null
+++ b/tools/integrate/Android.mk
@@ -0,0 +1,19 @@
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	Command.java  \
+	Filesystem.java \
+	Git.java \
+	Module.java \
+	Modules.java \
+	MappedDirectory.java \
+	PullHarmonyCode.java \
+	Svn.java
+
+LOCAL_MODULE:= integrate
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(call all-subdir-makefiles)
diff --git a/tools/integrate/Command.java b/tools/integrate/Command.java
new file mode 100644
index 0000000..5e7796f
--- /dev/null
+++ b/tools/integrate/Command.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * An out of process executable.
+ */
+class Command {
+
+    private final List<String> args;
+    private final boolean permitNonZeroExitStatus;
+
+    Command(String... args) {
+        this(Arrays.asList(args));
+    }
+
+    Command(List<String> args) {
+        this.args = new ArrayList<String>(args);
+        this.permitNonZeroExitStatus = false;
+    }
+
+    private Command(Builder builder) {
+        this.args = new ArrayList<String>(builder.args);
+        this.permitNonZeroExitStatus = builder.permitNonZeroExitStatus;
+    }
+
+    static class Builder {
+        private final List<String> args = new ArrayList<String>();
+        private boolean permitNonZeroExitStatus = false;
+
+        public Builder args(String... args) {
+            return args(Arrays.asList(args));
+        }
+
+        public Builder args(Collection<String> args) {
+            this.args.addAll(args);
+            return this;
+        }
+
+        public Builder permitNonZeroExitStatus() {
+            permitNonZeroExitStatus = true;
+            return this;
+        }
+
+        public Command build() {
+            return new Command(this);
+        }
+
+        public List<String> execute() {
+            return build().execute();
+        }
+    }
+
+    public List<String> execute() {
+        try {
+            Process process = new ProcessBuilder()
+                    .command(args)
+                    .redirectErrorStream(true)
+                    .start();
+
+            BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream()));
+            List<String> outputLines = new ArrayList<String>();
+            String outputLine;
+            while ((outputLine = in.readLine()) != null) {
+                outputLines.add(outputLine);
+            }
+
+            if (process.waitFor() != 0 && !permitNonZeroExitStatus) {
+                StringBuilder message = new StringBuilder();
+                for (String line : outputLines) {
+                    message.append("\n").append(line);
+                }
+                throw new RuntimeException("Process failed: " + args + message);
+            }
+
+            return outputLines;
+        } catch (IOException e) {
+            throw new RuntimeException("Process failed: " + args, e);
+        } catch (InterruptedException e) {
+            throw new RuntimeException("Process failed: " + args, e);
+        }
+    }
+
+}
diff --git a/tools/integrate/Filesystem.java b/tools/integrate/Filesystem.java
new file mode 100644
index 0000000..a9c1789
--- /dev/null
+++ b/tools/integrate/Filesystem.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Factory for filesystem commands.
+ */
+class Filesystem {
+
+    public void move(String source, String target) {
+        new Command("mv", source, target).execute();
+    }
+
+    /**
+     * Moves all of the files in {@code source} to {@code target}, one at a
+     * time. Unlike {@code move}, this approach works even if the target
+     * directory is nonempty.
+     */
+    public int moveContents(String source, String target) {
+        List<String> files = new Command("find", source, "-type", "f") .execute();
+        for (String file : files) {
+            String targetFile = target + "/" + file.substring(source.length());
+            mkdir(parent(targetFile));
+            new Command("mv", "-i", file, targetFile).execute();
+        }
+        return files.size();
+    }
+
+    private String parent(String file) {
+        return file.substring(0, file.lastIndexOf('/'));
+    }
+
+    public void mkdir(String dir) {
+        new Command("mkdir", "-p", dir).execute();
+    }
+
+    public List<String> find(String where, String name) {
+        return new Command("find", where, "-name", name).execute();
+    }
+
+    public void rm(Collection<String> files) {
+        new Command.Builder().args("rm", "-r").args(files).execute();
+    }
+
+    public void rm(String file) {
+        new Command("rm", "-r", file).execute();
+    }
+}
diff --git a/tools/integrate/Git.java b/tools/integrate/Git.java
new file mode 100644
index 0000000..da7dcfa
--- /dev/null
+++ b/tools/integrate/Git.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Factory for git commands.
+ */
+class Git {
+
+    private static final Pattern STATUS_DELETED
+            = Pattern.compile("#\\tdeleted:    (.*)");
+
+    public void branch(String newBranch) {
+        branch(newBranch, "HEAD");
+    }
+
+    /**
+     * @param base another branch, or a revision identifier like {@code HEAD}.
+     */
+    public void branch(String newBranch, String base) {
+        // -b is used by git to branch from another checkout
+        new Command("git", "checkout", "-b", newBranch, base).execute();
+    }
+
+    public void commit(String message) {
+        new Command("git", "commit", "-m", message).execute();
+    }
+
+    public void add(String path) {
+        new Command("git", "add", path).execute();
+    }
+
+    public void remove(Collection<String> paths) {
+        new Command.Builder().args("git", "rm").args(paths).execute();
+    }
+
+    public List<String> merge(String otherBranch) {
+        return new Command.Builder()
+                .args("git", "merge", "-s", "recursive", otherBranch)
+                .permitNonZeroExitStatus()
+                .execute();
+    }
+
+    /**
+     * Returns the files that have been deleted from the filesystem, but that
+     * don't exist in the active git change.
+     */
+    public List<String> listDeleted() {
+        List<String> statusLines = new Command.Builder()
+                .args("git", "status")
+                .permitNonZeroExitStatus()
+                .execute();
+
+        List<String> deletedFiles = new ArrayList<String>();
+        Matcher matcher = STATUS_DELETED.matcher("");
+        for (String line : statusLines) {
+            matcher.reset(line);
+            if (matcher.matches()) {
+                deletedFiles.add(matcher.group(1));
+            }
+        }
+        return deletedFiles;
+    }
+
+    public void rm(List<String> files) {
+        new Command.Builder()
+                .args("git", "rm").args(files)
+                .permitNonZeroExitStatus()
+                .build()
+                .execute();
+    }
+}
diff --git a/tools/integrate/MappedDirectory.java b/tools/integrate/MappedDirectory.java
new file mode 100644
index 0000000..8e28d29
--- /dev/null
+++ b/tools/integrate/MappedDirectory.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * A logical directory that has a different location in Harmony and Dalvik.
+ */
+class MappedDirectory {
+
+    private final String svnPath;
+    private final String gitPath;
+
+    public MappedDirectory(String svnPath, String gitPath) {
+        this.svnPath = svnPath;
+        this.gitPath = gitPath;
+    }
+
+    public String svnPath() {
+        return svnPath;
+    }
+
+    public String gitPath() {
+        return gitPath;
+    }
+
+    @Override public String toString() {
+        return "svn:" + svnPath + " -> git:" + gitPath;
+    }
+}
diff --git a/tools/integrate/Module.java b/tools/integrate/Module.java
new file mode 100644
index 0000000..5cb7035
--- /dev/null
+++ b/tools/integrate/Module.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+/**
+ * A logical unit of code shared between Apache Harmony and Dalvik.
+ */
+class Module {
+
+    private final String svnBaseUrl;
+    private final String path;
+    private final Set<MappedDirectory> mappedDirectories;
+
+    public String getSvnBaseUrl() {
+        return svnBaseUrl;
+    }
+
+    public String path() {
+        return path;
+    }
+
+    public Set<MappedDirectory> getMappedDirectories() {
+        return mappedDirectories;
+    }
+
+    private Module(Builder builder) {
+        this.svnBaseUrl = builder.svnBaseUrl;
+        this.path = builder.path;
+        this.mappedDirectories = new LinkedHashSet<MappedDirectory>(builder.mappedDirectories);
+    }
+
+    public static class Builder {
+        private final String svnBaseUrl;
+        private final String path;
+        private final Set<MappedDirectory> mappedDirectories
+                = new LinkedHashSet<MappedDirectory>();
+
+        public Builder(String svnBaseUrl, String path) {
+            this.svnBaseUrl = svnBaseUrl;
+            this.path = path;
+        }
+
+        public Builder mapDirectory(String svnPath, String gitPath) {
+            mappedDirectories.add(new MappedDirectory(svnPath, gitPath));
+            return this;
+        }
+
+        public Module build() {
+            return new Module(this);
+        }
+    }
+}
diff --git a/tools/integrate/Modules.java b/tools/integrate/Modules.java
new file mode 100644
index 0000000..2475852
--- /dev/null
+++ b/tools/integrate/Modules.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Constants that define modules shared by Harmony and Dalvik.
+ */
+public class Modules {
+
+    private static final String SVN_ROOT
+            = "http://svn.apache.org/repos/asf/harmony/enhanced/classlib/trunk/modules";
+
+    public static final Module ARCHIVE = new Module.Builder(SVN_ROOT, "archive")
+            .mapDirectory("archive/src/main/native/archive/shared",
+                    "archive/src/main/native")
+            .mapDirectory("archive/src/main/native/zip/shared",
+                    "archive/src/main/native")
+            .build();
+
+    public static final Module CRYPTO = new Module.Builder(SVN_ROOT, "crypto")
+            .mapDirectory("crypto/src/test/api/java.injected/javax",
+                    "crypto/src/test/java/org/apache/harmony/crypto/tests/javax")
+            .mapDirectory("crypto/src/test/api/java",
+                    "crypto/src/test/java")
+            .mapDirectory("crypto/src/test/resources/serialization",
+                    "crypto/src/test/java/serialization")
+            .mapDirectory("crypto/src/test/support/common/java",
+                    "crypto/src/test/java")
+            .build();
+
+    public static final Module REGEX
+            = new Module.Builder(SVN_ROOT, "regex").build();
+
+    public static final Module SECURITY = new Module.Builder(SVN_ROOT, "security")
+            .mapDirectory("security/src/main/java/common",
+                    "security/src/main/java")
+            .mapDirectory("security/src/main/java/unix/org",
+                    "security/src/main/java/org")
+            .mapDirectory("security/src/test/api/java",
+                    "security/src/test/java")
+            .build();
+
+    public static final Module TEXT
+            = new Module.Builder(SVN_ROOT, "text").build();
+
+    public static final Module X_NET
+            = new Module.Builder(SVN_ROOT, "x-net").build();
+
+    // TODO: add the other modules
+}
diff --git a/tools/integrate/PullHarmonyCode.java b/tools/integrate/PullHarmonyCode.java
new file mode 100644
index 0000000..6710801
--- /dev/null
+++ b/tools/integrate/PullHarmonyCode.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Download two versions of Apache Harmony from their SVN version, and use it
+ * to perform a three-way merge with Dalvik.
+ */
+public class PullHarmonyCode {
+
+    private final int currentVersion;
+    private final int targetVersion;
+
+    public PullHarmonyCode(int currentVersion, int targetVersion) {
+        this.currentVersion = currentVersion;
+        this.targetVersion = targetVersion;
+    }
+
+    public void pull(Module module) {
+        String path = module.path();
+        String svnOldBranch = path + "_" + currentVersion;
+        String svnNewBranch = path + "_" + targetVersion;
+        String dalvikBranch = path + "_dalvik";
+
+        Git git = new Git();
+        Filesystem filesystem = new Filesystem();
+        Svn svn = new Svn();
+
+        // Assume we're starting with the current Dalvik code. Tuck this away
+        // somewhere while we rewrite history.
+        String temp = "/tmp/" + UUID.randomUUID();
+        filesystem.mkdir(temp);
+
+        // To prepare a three-way-merge, we need a common starting point: the
+        // time at which Dalvik and Harmony were most the same. We'll use the
+        // previous Harmony SVN code as this starting point. We grab the old
+        // code from their repository, and commit it as a git branch.
+        System.out.print("Creating branch " + svnOldBranch + "...");
+        git.branch(svnOldBranch);
+        filesystem.move(path, temp + "/" + path);
+        svn.checkOut(currentVersion, module.getSvnBaseUrl() + "/" + path);
+        filesystem.rm(filesystem.find(path, ".svn"));
+        for (MappedDirectory mappedDirectory : module.getMappedDirectories()) {
+            filesystem.moveContents(mappedDirectory.svnPath(), mappedDirectory.gitPath());
+        }
+        git.rm(git.listDeleted());
+        git.add(path);
+        git.commit(svnOldBranch);
+        System.out.println("done");
+
+        // Create a branch that's derived from the starting point. It will
+        // contain all of the changes Harmony has made from then until now.
+        System.out.print("Creating branch " + svnNewBranch + "...");
+        git.branch(svnNewBranch, svnOldBranch);
+        filesystem.rm(path);
+        svn.checkOut(targetVersion, module.getSvnBaseUrl() + "/" + path);
+        filesystem.rm(filesystem.find(path, ".svn"));
+        for (MappedDirectory mappedDirectory : module.getMappedDirectories()) {
+            filesystem.moveContents(mappedDirectory.svnPath(), mappedDirectory.gitPath());
+        }
+        git.rm(git.listDeleted());
+        git.add(path);
+        git.commit(svnNewBranch);
+        System.out.println("done");
+
+        // Create another branch that's derived from the starting point. It will
+        // contain all of the changes Dalvik has made from then until now.
+        System.out.print("Creating branch " + dalvikBranch + "...");
+        git.branch(dalvikBranch, svnOldBranch);
+        filesystem.rm(path);
+        filesystem.move(temp + "/" + path, path);
+        git.rm(git.listDeleted());
+        git.add(path);
+        git.commit(dalvikBranch);
+        System.out.println("done");
+
+        // Merge the two sets of changes together: Harmony's and Dalvik's. By
+        // initializing a common starting point, git can make better decisions
+        // when the two new versions differ. For example, if today's Dalvik has
+        // a method that today's Harmony does not, it may be because Dalvik
+        // added it, or because Harmony deleted it!
+        System.out.println("Merging " + svnNewBranch + " into " + dalvikBranch + ":");
+        List<String> mergeResults = git.merge(svnNewBranch);
+        for (String mergeResult : mergeResults) {
+            System.out.print("  ");
+            System.out.println(mergeResult);
+        }
+    }
+
+    public static void main(String[] args) {
+//        new PullHarmonyCode(527399, 802921).pull(Modules.CRYPTO);
+        new PullHarmonyCode(772995, 802921).pull(Modules.ARCHIVE);
+    }
+}
diff --git a/tools/integrate/Svn.java b/tools/integrate/Svn.java
new file mode 100644
index 0000000..dc9be35
--- /dev/null
+++ b/tools/integrate/Svn.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Factory for Subversion commands.
+ */
+class Svn {
+
+    public void checkOut(int version, String url) {
+        new Command("svn", "co", "-r", "" + version, url).execute();
+    }
+}