Java程序  |  185行  |  6.41 KB

/*
 * Copyright (C) 2010 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.
 */

package vogar;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import vogar.tasks.Task;

/**
 * A target runtime environment such as a remote device or the local host
 */
public abstract class Target {
    protected abstract ImmutableList<String> targetProcessPrefix();
    public abstract String getDeviceUserName();

    public abstract List<File> ls(File directory) throws FileNotFoundException;
    public abstract void await(File nonEmptyDirectory);
    public abstract void rm(File file);
    public abstract void mkdirs(File file);
    public abstract void forwardTcp(int port);
    public abstract void push(File local, File remote);
    public abstract void pull(File remote, File local);

    public final Task pushTask(final File local, final File remote) {
        return new Task("push " + remote) {
            @Override protected Result execute() throws Exception {
                push(local, remote);
                return Result.SUCCESS;
            }
        };
    }

    public final Task rmTask(final File remote) {
        return new Task("rm " + remote) {
            @Override protected Result execute() throws Exception {
                rm(remote);
                return Result.SUCCESS;
            }
        };
    }

    /**
     * Create a {@link ScriptBuilder} appropriate for this target.
     */
    public ScriptBuilder newScriptBuilder() {
        return new ScriptBuilder(targetProcessPrefix());
    }

    /**
     * Responsible for constructing a one line script appropriate for a specific target.
     */
    public static class ScriptBuilder {

        private static final Joiner SCRIPT_JOINER = Joiner.on(" ");

        /**
         * Escape any special shell characters so that the target shell treats them as literal
         * characters.
         *
         * <p>e.g. an escaped space will not be treated as a token separator, an escaped dollar will
         * not cause shell substitution.
         */
        @VisibleForTesting
        static String escape(String token) {
            int length = token.length();
            StringBuilder builder = new StringBuilder(length);
            for (int i = 0; i < length; ++i) {
                char c = token.charAt(i);
                if (Character.isWhitespace(c)
                        || c == '\'' || c == '\"'
                        || c == '|' || c == '&'
                        || c == '<' || c == '>'
                        || c == '$' || c == '!'
                        || c == '(' || c == ')') {
                    builder.append('\\');
                }

                builder.append(c);
            }

            return builder.toString();
        }

        /**
         * The prefix to insert before the script to produce a command line that will execute the
         * script within a shell.
         */
        private final ImmutableList<String> commandLinePrefix;

        /**
         * The list of tokens making up the script, they were escaped where necessary before they
         * were added to the list.
         */
        private final List<String> escapedTokens;

        private ScriptBuilder(ImmutableList<String> commandLinePrefix) {
            this.commandLinePrefix = commandLinePrefix;
            escapedTokens = new ArrayList<>();
        }

        /**
         * Set the working directory in the target shell before running the command.
         */
        public ScriptBuilder workingDirectory(File workingDirectory) {
            escapedTokens.add("cd");
            escapedTokens.add(escape(workingDirectory.getPath()));
            escapedTokens.add("&&");
            return this;
        }

        /**
         * Set inline environment variables on the target shell that will affect the command.
         */
        public void env(Map<String, String> env) {
            for (Map.Entry<String, String> entry : env.entrySet()) {
                String name = entry.getKey();
                String value = entry.getValue();
                escapedTokens.add(name + "=" + escape(value));
            }
        }

        /**
         * Add tokens to the script.
         *
         * <p>Each token is escaped before adding it to the list. This method is suitable for adding
         * the command line name and arguments.
         */
        public ScriptBuilder tokens(List<String> tokens) {
            for (String token : tokens) {
                escapedTokens.add(escape(token));
            }
            return this;
        }

        /**
         * Add tokens to the script.
         *
         * <p>Each token is escaped before adding it to the list. This method is suitable for adding
         * the command line name and arguments.
         */
        public ScriptBuilder tokens(String... tokens) {
            for (String token : tokens) {
                escapedTokens.add(escape(token));
            }
            return this;
        }

        /**
         * Construct a command line to execute the script in the target shell.
         */
        public List<String> commandLine() {
            // Group the tokens into a single String argument. This is necessary for running in
            // a local shell where the first argument after the -c option is the script and the
            // remainder are treated as arguments to the script. This has no effect on either
            // adb or ssh shells as they both concatenate all their arguments into one single
            // string before parsing.
            String grouped = SCRIPT_JOINER.join(escapedTokens);
            return new ImmutableList.Builder<String>()
                    .addAll(commandLinePrefix)
                    .add(grouped)
                    .build();
        }
    }
}