17850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com/*
27850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Copyright (C) 2010 The Android Open Source Project
37850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
47850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Licensed under the Apache License, Version 2.0 (the "License");
57850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * you may not use this file except in compliance with the License.
67850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * You may obtain a copy of the License at
77850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
87850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *      http://www.apache.org/licenses/LICENSE-2.0
97850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com *
107850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * Unless required by applicable law or agreed to in writing, software
117850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * distributed under the License is distributed on an "AS IS" BASIS,
127850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
137850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * See the License for the specific language governing permissions and
147850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * limitations under the License.
157850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com */
167850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
177850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.compackage vogar;
187850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
19411cb1fb67241125203629229600ecbd821eb9c7Paul Duffinimport com.google.common.annotations.VisibleForTesting;
20411cb1fb67241125203629229600ecbd821eb9c7Paul Duffinimport com.google.common.base.Joiner;
21411cb1fb67241125203629229600ecbd821eb9c7Paul Duffinimport com.google.common.collect.ImmutableList;
227850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.comimport java.io.File;
230eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.comimport java.io.FileNotFoundException;
24411cb1fb67241125203629229600ecbd821eb9c7Paul Duffinimport java.util.ArrayList;
250eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.comimport java.util.List;
26411cb1fb67241125203629229600ecbd821eb9c7Paul Duffinimport java.util.Map;
27f83be5e4273263df2bb9ef609946b911695b3996jessewilson@google.comimport vogar.tasks.Task;
287850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com
297850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com/**
307850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com * A target runtime environment such as a remote device or the local host
317850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com */
320eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.compublic abstract class Target {
33411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin    protected abstract ImmutableList<String> targetProcessPrefix();
340eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    public abstract String getDeviceUserName();
350eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com
360eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    public abstract List<File> ls(File directory) throws FileNotFoundException;
370eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    public abstract void await(File nonEmptyDirectory);
380eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    public abstract void rm(File file);
390eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    public abstract void mkdirs(File file);
400eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    public abstract void forwardTcp(int port);
410eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    public abstract void push(File local, File remote);
420eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    public abstract void pull(File remote, File local);
430eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com
440eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    public final Task pushTask(final File local, final File remote) {
450eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com        return new Task("push " + remote) {
460eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com            @Override protected Result execute() throws Exception {
470eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com                push(local, remote);
480eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com                return Result.SUCCESS;
490eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com            }
500eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com        };
510eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    }
520eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com
530eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    public final Task rmTask(final File remote) {
540eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com        return new Task("rm " + remote) {
550eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com            @Override protected Result execute() throws Exception {
560eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com                rm(remote);
570eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com                return Result.SUCCESS;
580eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com            }
590eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com        };
600eb0936e90bf11463c8ee937ca996d7bd654a098jessewilson@google.com    }
61411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin
62411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin    /**
63411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin     * Create a {@link ScriptBuilder} appropriate for this target.
64411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin     */
65411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin    public ScriptBuilder newScriptBuilder() {
66411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        return new ScriptBuilder(targetProcessPrefix());
67411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin    }
68411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin
69411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin    /**
70411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin     * Responsible for constructing a one line script appropriate for a specific target.
71411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin     */
72411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin    public static class ScriptBuilder {
73411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin
74411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        private static final Joiner SCRIPT_JOINER = Joiner.on(" ");
75411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin
76411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        /**
77411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         * Escape any special shell characters so that the target shell treats them as literal
78411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         * characters.
79411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         *
809a8263b4e14fe4d9498db2c44130eae47695444cPaul Duffin         * <p>e.g. an escaped space will not be treated as a token separator, an escaped dollar will
81411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         * not cause shell substitution.
82411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         */
83411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        @VisibleForTesting
84411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        static String escape(String token) {
85411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            int length = token.length();
86411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            StringBuilder builder = new StringBuilder(length);
87411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            for (int i = 0; i < length; ++i) {
88411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin                char c = token.charAt(i);
89411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin                if (Character.isWhitespace(c)
90411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin                        || c == '\'' || c == '\"'
91411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin                        || c == '|' || c == '&'
92411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin                        || c == '<' || c == '>'
93411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin                        || c == '$' || c == '!'
94411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin                        || c == '(' || c == ')') {
95411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin                    builder.append('\\');
96411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin                }
97411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin
98411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin                builder.append(c);
99411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            }
100411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin
101411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            return builder.toString();
102411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        }
103411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin
104411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        /**
105411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         * The prefix to insert before the script to produce a command line that will execute the
106411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         * script within a shell.
107411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         */
108411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        private final ImmutableList<String> commandLinePrefix;
109411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin
110411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        /**
111411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         * The list of tokens making up the script, they were escaped where necessary before they
112411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         * were added to the list.
113411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         */
114411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        private final List<String> escapedTokens;
115411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin
116411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        private ScriptBuilder(ImmutableList<String> commandLinePrefix) {
117411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            this.commandLinePrefix = commandLinePrefix;
118411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            escapedTokens = new ArrayList<>();
119411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        }
120411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin
121411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        /**
122411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         * Set the working directory in the target shell before running the command.
123411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         */
124411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        public ScriptBuilder workingDirectory(File workingDirectory) {
125411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            escapedTokens.add("cd");
126411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            escapedTokens.add(escape(workingDirectory.getPath()));
127411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            escapedTokens.add("&&");
128411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            return this;
129411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        }
130411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin
131411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        /**
132411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         * Set inline environment variables on the target shell that will affect the command.
133411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         */
134411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        public void env(Map<String, String> env) {
135411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            for (Map.Entry<String, String> entry : env.entrySet()) {
136411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin                String name = entry.getKey();
137411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin                String value = entry.getValue();
138411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin                escapedTokens.add(name + "=" + escape(value));
139411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            }
140411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        }
141411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin
142411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        /**
143411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         * Add tokens to the script.
144411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         *
145411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         * <p>Each token is escaped before adding it to the list. This method is suitable for adding
146411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         * the command line name and arguments.
147411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         */
148411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        public ScriptBuilder tokens(List<String> tokens) {
149411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            for (String token : tokens) {
150411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin                escapedTokens.add(escape(token));
151411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            }
152411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            return this;
153411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        }
154411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin
155411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        /**
156411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         * Add tokens to the script.
157411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         *
158411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         * <p>Each token is escaped before adding it to the list. This method is suitable for adding
159411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         * the command line name and arguments.
160411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         */
161411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        public ScriptBuilder tokens(String... tokens) {
162411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            for (String token : tokens) {
163411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin                escapedTokens.add(escape(token));
164411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            }
165411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            return this;
166411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        }
167411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin
168411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        /**
169411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         * Construct a command line to execute the script in the target shell.
170411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin         */
171411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        public List<String> commandLine() {
172411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            // Group the tokens into a single String argument. This is necessary for running in
173411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            // a local shell where the first argument after the -c option is the script and the
174411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            // remainder are treated as arguments to the script. This has no effect on either
175411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            // adb or ssh shells as they both concatenate all their arguments into one single
176411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            // string before parsing.
177411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            String grouped = SCRIPT_JOINER.join(escapedTokens);
178411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin            return new ImmutableList.Builder<String>()
179411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin                    .addAll(commandLinePrefix)
180411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin                    .add(grouped)
181411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin                    .build();
182411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin        }
183411cb1fb67241125203629229600ecbd821eb9c7Paul Duffin    }
1847850f3f3da0099b76f09ed64d23e0a43ba4a5c76jessewilson@google.com}
185