15
15
*/
16
16
package com .google .auto .value .processor ;
17
17
18
- import com .google .common .base .Throwables ;
18
+ import static java .nio .charset .StandardCharsets .UTF_8 ;
19
+
20
+ import com .google .common .base .Ascii ;
19
21
import com .google .common .collect .ImmutableList ;
20
22
import com .google .common .collect .ImmutableMap ;
23
+ import com .google .common .io .ByteStreams ;
21
24
import com .google .escapevelocity .Template ;
22
25
import java .io .File ;
23
26
import java .io .FileInputStream ;
24
- import java .io .FilterInputStream ;
25
27
import java .io .IOException ;
26
28
import java .io .InputStream ;
27
29
import java .io .InputStreamReader ;
28
30
import java .io .Reader ;
29
- import java .io .UnsupportedEncodingException ;
31
+ import java .io .StringReader ;
32
+ import java .io .UncheckedIOException ;
30
33
import java .lang .reflect .Field ;
31
34
import java .lang .reflect .Modifier ;
32
35
import java .net .URI ;
33
36
import java .net .URISyntaxException ;
34
37
import java .net .URL ;
35
- import java .nio .charset .StandardCharsets ;
36
38
import java .util .Map ;
37
39
import java .util .TreeMap ;
38
- import java .util .jar .JarEntry ;
39
40
import java .util .jar .JarFile ;
40
41
41
42
/**
@@ -95,7 +96,7 @@ private static void addFields(
95
96
* concrete subclass of TemplateVars) into the template returned by {@link #parsedTemplate()}.
96
97
*/
97
98
String toText () {
98
- Map <String , Object > vars = toVars ();
99
+ ImmutableMap <String , Object > vars = toVars ();
99
100
return parsedTemplate ().evaluate (vars );
100
101
}
101
102
@@ -120,99 +121,63 @@ public String toString() {
120
121
}
121
122
122
123
static Template parsedTemplateForResource (String resourceName ) {
123
- try {
124
- return Template .parseFrom (resourceName , TemplateVars ::readerFromResource );
125
- } catch (UnsupportedEncodingException e ) {
126
- throw new AssertionError (e );
127
- } catch (IOException | NullPointerException | IllegalStateException e ) {
128
- // https://github.com/google/auto/pull/439 says that we can get NullPointerException.
129
- // https://github.com/google/auto/issues/715 says that we can get IllegalStateException
130
- return retryParseAfterException (resourceName , e );
131
- }
132
- }
133
-
134
- private static Template retryParseAfterException (String resourceName , Exception exception ) {
135
124
try {
136
125
return Template .parseFrom (resourceName , TemplateVars ::readerFromUrl );
137
- } catch (IOException t ) {
138
- // Chain the original exception so we can see both problems.
139
- Throwables .getRootCause (exception ).initCause (t );
140
- throw new AssertionError (exception );
126
+ } catch (IOException e ) {
127
+ throw new UncheckedIOException (e );
141
128
}
142
129
}
143
130
144
- private static Reader readerFromResource (String resourceName ) {
145
- InputStream in = TemplateVars .class .getResourceAsStream (resourceName );
146
- if (in == null ) {
147
- throw new IllegalArgumentException ("Could not find resource: " + resourceName );
148
- }
149
- return new InputStreamReader (in , StandardCharsets .UTF_8 );
150
- }
151
-
152
131
// This is an ugly workaround for https://bugs.openjdk.java.net/browse/JDK-6947916, as
153
132
// reported in https://github.com/google/auto/issues/365.
154
133
// The issue is that sometimes the InputStream returned by JarURLCollection.getInputStream()
155
134
// can be closed prematurely, which leads to an IOException saying "Stream closed".
156
- // We catch all IOExceptions, and fall back on logic that opens the jar file directly and
157
- // loads the resource from it. Since that doesn't use JarURLConnection, it shouldn't be
158
- // susceptible to the same bug. We only use this as fallback logic rather than doing it always,
159
- // because jars are memory-mapped by URLClassLoader, so loading a resource in the usual way
160
- // through the getResourceAsStream should be a lot more efficient than reopening the jar.
135
+ // To avoid that issue, we open the jar file directly and load the resource from it. Since that
136
+ // doesn't use JarURLConnection, it shouldn't be susceptible to the same bug.
161
137
private static Reader readerFromUrl (String resourceName ) throws IOException {
162
138
URL resourceUrl = TemplateVars .class .getResource (resourceName );
163
139
if (resourceUrl == null ) {
164
- // This is unlikely, since getResourceAsStream has already succeeded for the same resource.
165
140
throw new IllegalArgumentException ("Could not find resource: " + resourceName );
166
141
}
167
- InputStream in ;
168
142
try {
169
- if (resourceUrl .getProtocol (). equalsIgnoreCase ( "file" )) {
170
- in = inputStreamFromFile (resourceUrl );
171
- } else if (resourceUrl .getProtocol (). equalsIgnoreCase ( "jar" )) {
172
- in = inputStreamFromJar (resourceUrl );
143
+ if (Ascii . equalsIgnoreCase ( resourceUrl .getProtocol (), "file" )) {
144
+ return readerFromFile (resourceUrl );
145
+ } else if (Ascii . equalsIgnoreCase ( resourceUrl .getProtocol (), "jar" )) {
146
+ return readerFromJar (resourceUrl );
173
147
} else {
174
- throw new AssertionError ("Template fallback logic fails for: " + resourceUrl );
148
+ throw new AssertionError ("Template search logic fails for: " + resourceUrl );
175
149
}
176
150
} catch (URISyntaxException e ) {
177
151
throw new IOException (e );
178
152
}
179
- return new InputStreamReader (in , StandardCharsets .UTF_8 );
180
153
}
181
154
182
- private static InputStream inputStreamFromJar (URL resourceUrl )
183
- throws URISyntaxException , IOException {
155
+ private static Reader readerFromJar (URL resourceUrl ) throws URISyntaxException , IOException {
184
156
// Jar URLs look like this: jar:file:/path/to/file.jar!/entry/within/jar
185
157
// So take apart the URL to open the jar /path/to/file.jar and read the entry
186
158
// entry/within/jar from it.
159
+ // We could use the methods from JarURLConnection here, but that would risk provoking the same
160
+ // problem that prompted this workaround in the first place.
187
161
String resourceUrlString = resourceUrl .toString ().substring ("jar:" .length ());
188
162
int bang = resourceUrlString .lastIndexOf ('!' );
189
163
String entryName = resourceUrlString .substring (bang + 1 );
190
164
if (entryName .startsWith ("/" )) {
191
165
entryName = entryName .substring (1 );
192
166
}
193
167
URI jarUri = new URI (resourceUrlString .substring (0 , bang ));
194
- JarFile jar = new JarFile (new File (jarUri ));
195
- JarEntry entry = jar .getJarEntry (entryName );
196
- InputStream in = jar .getInputStream (entry );
197
- // We have to be careful not to close the JarFile before the stream has been read, because
198
- // that would also close the stream. So we defer closing the JarFile until the stream is closed.
199
- return new FilterInputStream (in ) {
200
- @ Override
201
- public void close () throws IOException {
202
- super .close ();
203
- jar .close ();
204
- }
205
- };
168
+ try (JarFile jar = new JarFile (new File (jarUri ));
169
+ InputStream in = jar .getInputStream (jar .getJarEntry (entryName ))) {
170
+ String contents = new String (ByteStreams .toByteArray (in ), UTF_8 );
171
+ return new StringReader (contents );
172
+ }
206
173
}
207
174
208
- // We don't really expect this case to arise, since the bug we're working around concerns jars
209
- // not individual files. However, when running the test for this workaround from Maven, we do
210
- // have files. That does mean the test is basically useless there, but Google's internal build
211
- // system does run it using a jar, so we do have coverage.
212
- private static InputStream inputStreamFromFile (URL resourceUrl )
175
+ // In most execution environments, we'll be dealing with a jar, but we handle individual files
176
+ // just for cases like running our tests with Maven.
177
+ private static Reader readerFromFile (URL resourceUrl )
213
178
throws IOException , URISyntaxException {
214
179
File resourceFile = new File (resourceUrl .toURI ());
215
- return new FileInputStream (resourceFile );
180
+ return new InputStreamReader ( new FileInputStream (resourceFile ), UTF_8 );
216
181
}
217
182
218
183
private static Object fieldValue (Field field , Object container ) {
0 commit comments