Skip to content

Commit 6363313

Browse files
DeviceInfracopybara-github
DeviceInfra
authored andcommitted
Internal change
PiperOrigin-RevId: 742604817
1 parent 058197a commit 6363313

File tree

4 files changed

+322
-1
lines changed

4 files changed

+322
-1
lines changed

src/java/com/google/devtools/mobileharness/shared/util/file/local/BUILD

+6
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,9 @@ java_library(
5454
"@maven//:com_google_guava_guava",
5555
],
5656
)
57+
58+
java_library(
59+
name = "unzip_file_name",
60+
srcs = ["UnzipFileName.java"],
61+
deps = ["@maven//:com_google_guava_guava"],
62+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
/*
2+
* Copyright 2022 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.devtools.mobileharness.shared.util.file.local;
18+
19+
import com.google.common.collect.ImmutableList;
20+
21+
/**
22+
* A class to represent a file name argument to the unzip command.
23+
*
24+
* <p>See `$ man unzip` for more details.
25+
*/
26+
public class UnzipFileName {
27+
private final String arg;
28+
private final ImmutableList<Token> tokens;
29+
30+
/**
31+
* Constructs an UnzipFileName from the given argument.
32+
*
33+
* @param arg the raw command line argument to the unzip command.
34+
*/
35+
public UnzipFileName(String arg) {
36+
this.arg = arg;
37+
this.tokens = this.parse();
38+
}
39+
40+
/** Constructs a list of UnzipFileName from the given arguments. */
41+
public static ImmutableList<UnzipFileName> listOf(String... args) {
42+
ImmutableList.Builder<UnzipFileName> builder = ImmutableList.builder();
43+
for (String arg : args) {
44+
builder.add(new UnzipFileName(arg));
45+
}
46+
return builder.build();
47+
}
48+
49+
@Override
50+
public boolean equals(Object o) {
51+
if (this == o) {
52+
return true;
53+
}
54+
if (o instanceof UnzipFileName) {
55+
UnzipFileName that = (UnzipFileName) o;
56+
return arg.equals(that.arg);
57+
}
58+
return false;
59+
}
60+
61+
@Override
62+
public int hashCode() {
63+
return arg.hashCode();
64+
}
65+
66+
private enum Kind {
67+
CHAR,
68+
WILDCARD,
69+
IN_BRACKET
70+
}
71+
72+
private static class Token {
73+
final Kind kind;
74+
final char ch;
75+
final String regex;
76+
77+
Token(Kind kind, char ch, String regex) {
78+
this.kind = kind;
79+
this.ch = ch;
80+
this.regex = regex;
81+
}
82+
83+
static Token literal(char ch) {
84+
return new Token(Kind.CHAR, ch, "");
85+
}
86+
87+
static Token wildcard(char ch, String regex) {
88+
return new Token(Kind.WILDCARD, ch, regex);
89+
}
90+
91+
static Token bracketNegation(char ch) {
92+
return new Token(Kind.IN_BRACKET, ch, "^");
93+
}
94+
95+
static Token bracketChar(char ch) {
96+
return new Token(
97+
Kind.IN_BRACKET, ch, ch == '\\' || ch == ']' ? "\\" + ch : Character.toString(ch));
98+
}
99+
}
100+
101+
private ImmutableList<Token> parse() {
102+
ImmutableList.Builder<Token> builder = ImmutableList.builder();
103+
for (int i = 0; i < arg.length(); ++i) {
104+
char ch = arg.charAt(i);
105+
switch (ch) {
106+
case '\\':
107+
if (++i < arg.length()) {
108+
builder.add(Token.literal(arg.charAt(i)));
109+
} // else invalid escape, discarded
110+
break;
111+
case '*':
112+
builder.add(Token.wildcard(ch, ".*"));
113+
break;
114+
case '?':
115+
builder.add(Token.wildcard(ch, "."));
116+
break;
117+
case '[':
118+
i = parseBracket(i, builder);
119+
break;
120+
default:
121+
builder.add(Token.literal(ch));
122+
}
123+
}
124+
return builder.build();
125+
}
126+
127+
private int parseBracket(int start, ImmutableList.Builder<Token> builder) {
128+
builder.add(Token.wildcard('[', "["));
129+
int i = start + 1;
130+
for (; i < arg.length(); ++i) {
131+
char ch = arg.charAt(i);
132+
switch (ch) {
133+
case '\\':
134+
if (++i < arg.length()) {
135+
builder.add(Token.bracketChar(arg.charAt(i)));
136+
} // else invalid escape, discarded
137+
break;
138+
case ']':
139+
builder.add(Token.wildcard(ch, "]"));
140+
return i;
141+
case '!', '^':
142+
if (i == start + 1) {
143+
builder.add(Token.bracketNegation(ch));
144+
} else {
145+
builder.add(Token.bracketChar(ch));
146+
}
147+
break;
148+
default:
149+
builder.add(Token.bracketChar(ch));
150+
}
151+
}
152+
return i;
153+
}
154+
155+
/** Returns the raw command line argument to the unzip command. */
156+
public String arg() {
157+
return arg;
158+
}
159+
160+
/** Returns true if the name contains any unescaped '*', '?' or '['. */
161+
public boolean hasWildcard() {
162+
return tokens.stream().anyMatch(token -> token.kind == Kind.WILDCARD);
163+
}
164+
165+
/**
166+
* Returns the unescaped literal path.
167+
*
168+
* <p>Any wildcards will be kept as is. So this is inappropriate for use if hasWildcard() is true.
169+
*/
170+
public String path() {
171+
StringBuilder builder = new StringBuilder();
172+
tokens.forEach(token -> builder.append(token.ch));
173+
return builder.toString();
174+
}
175+
176+
/** A builder used by the regex() method to manage \Q...\E quoting. */
177+
private static class RegexBuilder {
178+
private final StringBuilder builder = new StringBuilder();
179+
private boolean inQuote = false;
180+
181+
void appendQuote(char c) {
182+
if (!inQuote) {
183+
builder.append("\\Q");
184+
inQuote = true;
185+
}
186+
builder.append(c);
187+
}
188+
189+
void appendRegex(String w) {
190+
if (inQuote) {
191+
builder.append("\\E");
192+
inQuote = false;
193+
}
194+
builder.append(w);
195+
}
196+
197+
String regex() {
198+
return builder.toString();
199+
}
200+
}
201+
202+
/**
203+
* Returns the equivalent regular expression.
204+
*
205+
* <p>The return value will be passed to a Go binary, so the Go regex syntax is used. See
206+
* http://godoc/pkg/regexp/syntax.
207+
*
208+
* <p>Invalid '[...]' wildcards will convert to invalid regular expressions.
209+
*/
210+
public String regex() {
211+
UnzipFileName.RegexBuilder builder = new UnzipFileName.RegexBuilder();
212+
builder.appendRegex("^");
213+
for (Token token : tokens) {
214+
if (token.kind == Kind.CHAR) {
215+
builder.appendQuote(token.ch);
216+
} else {
217+
builder.appendRegex(token.regex);
218+
}
219+
}
220+
builder.appendRegex("$");
221+
return builder.regex();
222+
}
223+
}

src/javatests/com/google/devtools/mobileharness/shared/util/file/local/BUILD

+12-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# limitations under the License.
1414
#
1515

16-
load("@rules_java//java:defs.bzl", "java_library")
16+
load("@rules_java//java:defs.bzl", "java_library", "java_test")
1717

1818
package(
1919
default_applicable_licenses = ["//:license"],
@@ -33,3 +33,14 @@ java_library(
3333
"@maven//:com_google_guava_guava",
3434
],
3535
)
36+
37+
java_test(
38+
name = "UnzipFileNameTest",
39+
srcs = ["UnzipFileNameTest.java"],
40+
deps = [
41+
"//src/java/com/google/devtools/mobileharness/shared/util/file/local:unzip_file_name",
42+
"//src/javatests/com/google/devtools/mobileharness/builddefs:truth",
43+
"@maven//:com_google_testparameterinjector_test_parameter_injector",
44+
"@maven//:junit_junit",
45+
],
46+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2022 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.devtools.mobileharness.shared.util.file.local;
18+
19+
import static com.google.common.truth.Truth.assertThat;
20+
21+
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
22+
import com.google.testing.junit.testparameterinjector.TestParameters;
23+
import org.junit.Test;
24+
import org.junit.runner.RunWith;
25+
26+
@RunWith(TestParameterInjector.class)
27+
public final class UnzipFileNameTest {
28+
@Test
29+
// no special characters
30+
@TestParameters("{arg: 'foo_bar', expected: false}")
31+
// regex wildcard but not unzip wildcard
32+
@TestParameters("{arg: 'foo.bar', expected: false}")
33+
// unzip wildcards
34+
@TestParameters("{arg: 'foo*bar', expected: true}")
35+
@TestParameters("{arg: 'foo?bar', expected: true}")
36+
@TestParameters("{arg: 'foo[a-z]bar', expected: true}")
37+
// escaped wildcards
38+
@TestParameters("{arg: 'foo\\*bar', expected: false}")
39+
@TestParameters("{arg: 'foo\\?bar', expected: false}")
40+
@TestParameters("{arg: 'foo\\[a-z]bar', expected: false}")
41+
// escaped backslash
42+
@TestParameters("{arg: 'foo\\\\*bar', expected: true}")
43+
public void testHasWildcard(String arg, boolean expected) {
44+
UnzipFileName name = new UnzipFileName(arg);
45+
assertThat(name.hasWildcard()).isEqualTo(expected);
46+
}
47+
48+
@Test
49+
@TestParameters("{arg: 'foo.bar', expected: 'foo.bar'}")
50+
@TestParameters("{arg: 'foo*bar', expected: 'foo*bar'}")
51+
@TestParameters("{arg: 'foo\\*bar', expected: 'foo*bar'}")
52+
@TestParameters("{arg: 'foo\\\\*bar', expected: 'foo\\*bar'}")
53+
public void testUnescape(String arg, String expected) {
54+
UnzipFileName name = new UnzipFileName(arg);
55+
assertThat(name.path()).isEqualTo(expected);
56+
}
57+
58+
@Test
59+
// no wildcards
60+
@TestParameters("{arg: 'foo_bar', expected: '^\\Qfoo_bar\\E$'}")
61+
@TestParameters("{arg: 'foo.bar', expected: '^\\Qfoo.bar\\E$'}")
62+
// wildcards
63+
@TestParameters("{arg: 'foo*bar', expected: '^\\Qfoo\\E.*\\Qbar\\E$'}")
64+
@TestParameters("{arg: 'foo?bar', expected: '^\\Qfoo\\E.\\Qbar\\E$'}")
65+
@TestParameters("{arg: 'foo[a-z]bar', expected: '^\\Qfoo\\E[a-z]\\Qbar\\E$'}")
66+
// escaped wildcards
67+
@TestParameters("{arg: 'foo\\*bar', expected: '^\\Qfoo*bar\\E$'}")
68+
@TestParameters("{arg: 'foo\\?bar', expected: '^\\Qfoo?bar\\E$'}")
69+
@TestParameters("{arg: 'foo\\[a-z]bar', expected: '^\\Qfoo[a-z]bar\\E$'}")
70+
// escaped backslash
71+
@TestParameters("{arg: 'foo\\\\*bar', expected: '^\\Qfoo\\\\E.*\\Qbar\\E$'}")
72+
// brackets
73+
@TestParameters("{arg: '[]]', expected: '^[]\\Q]\\E$'}")
74+
@TestParameters("{arg: '[\\]]', expected: '^[\\]]$'}")
75+
@TestParameters("{arg: '[\\\\]', expected: '^[\\\\]$'}")
76+
@TestParameters("{arg: '[abc', expected: '^[abc$'}") // unclosed bracket
77+
public void testRegex(String arg, String expected) {
78+
UnzipFileName name = new UnzipFileName(arg);
79+
assertThat(name.regex()).isEqualTo(expected);
80+
}
81+
}

0 commit comments

Comments
 (0)