OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / libcore / luni / src / main / java / org / apache / harmony / luni / internal / net / www / protocol / jar / JarURLConnectionImpl.java
1 /*
2  *  Licensed to the Apache Software Foundation (ASF) under one or more
3  *  contributor license agreements.  See the NOTICE file distributed with
4  *  this work for additional information regarding copyright ownership.
5  *  The ASF licenses this file to You under the Apache License, Version 2.0
6  *  (the "License"); you may not use this file except in compliance with
7  *  the License.  You may obtain a copy of the License at
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  */
17
18 package org.apache.harmony.luni.internal.net.www.protocol.jar;
19
20 import java.io.File;
21 import java.io.FileNotFoundException;
22 import java.io.FileOutputStream;
23 import java.io.FilterInputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.net.ContentHandler;
27 import java.net.ContentHandlerFactory;
28 import java.net.JarURLConnection;
29 import java.net.MalformedURLException;
30 import java.net.URL;
31 import java.security.AccessController;
32 import java.security.Permission;
33 import java.security.PrivilegedAction;
34 import java.util.HashMap;
35 import java.util.Iterator;
36 import java.util.Map;
37 import java.util.Set;
38 import java.util.jar.JarEntry;
39 import java.util.jar.JarFile;
40 import java.util.zip.ZipFile;
41 import org.apache.harmony.luni.util.Util;
42
43 /**
44  * This subclass extends {@code URLConnection}.
45  * <p>
46  *
47  * This class is responsible for connecting and retrieving resources from a Jar
48  * file which can be anywhere that can be referred to by an URL.
49  */
50 public class JarURLConnectionImpl extends JarURLConnection {
51
52     static HashMap<URL, JarFile> jarCache = new HashMap<URL, JarFile>();
53
54     private URL jarFileURL;
55
56     private InputStream jarInput;
57
58     private JarFile jarFile;
59
60     private JarEntry jarEntry;
61
62     private boolean closed;
63
64     /**
65      * @param url
66      *            the URL of the JAR
67      * @throws MalformedURLException
68      *             if the URL is malformed
69      * @throws IOException
70      *             if there is a problem opening the connection.
71      */
72     public JarURLConnectionImpl(URL url) throws MalformedURLException,
73             IOException {
74         super(url);
75         jarFileURL = getJarFileURL();
76         jarFileURLConnection = jarFileURL.openConnection();
77     }
78
79     /**
80      * @see java.net.URLConnection#connect()
81      */
82     @Override
83     public void connect() throws IOException {
84         if (!connected) {
85             findJarFile(); // ensure the file can be found
86             findJarEntry(); // ensure the entry, if any, can be found
87             connected = true;
88         }
89     }
90
91     /**
92      * Returns the Jar file referred by this {@code URLConnection}.
93      *
94      * @return the JAR file referenced by this connection
95      *
96      * @throws IOException
97      *             thrown if an IO error occurs while connecting to the
98      *             resource.
99      */
100     @Override
101     public JarFile getJarFile() throws IOException {
102         connect();
103         return jarFile;
104     }
105
106     /**
107      * Returns the Jar file referred by this {@code URLConnection}
108      *
109      * @throws IOException
110      *             if an IO error occurs while connecting to the resource.
111      */
112     private void findJarFile() throws IOException {
113         JarFile jar = null;
114         if (getUseCaches()) {
115             synchronized (jarCache) {
116                 jarFile = jarCache.get(jarFileURL);
117             }
118             if (jarFile == null) {
119                 jar = openJarFile();
120                 synchronized (jarCache) {
121                     jarFile = jarCache.get(jarFileURL);
122                     if (jarFile == null) {
123                         jarCache.put(jarFileURL, jar);
124                         jarFile = jar;
125                     } else {
126                         jar.close();
127                     }
128                 }
129             }
130         } else {
131             jarFile = openJarFile();
132         }
133
134         if (jarFile == null) {
135             throw new IOException();
136         }
137     }
138
139     @SuppressWarnings("nls")
140     JarFile openJarFile() throws IOException {
141         JarFile jar = null;
142         if (jarFileURL.getProtocol().equals("file")) {
143             jar = new JarFile(new File(Util.decode(jarFileURL.getFile(), false,
144                     "UTF-8")), true, ZipFile.OPEN_READ);
145         } else {
146             final InputStream is = jarFileURL.openConnection().getInputStream();
147             try {
148                 jar = AccessController
149                         .doPrivileged(new PrivilegedAction<JarFile>() {
150                             public JarFile run() {
151                                 FileOutputStream fos = null;
152                                 JarFile result = null;
153                                 try {
154                                     File tempJar = File.createTempFile("hyjar_", ".tmp", null);
155                                     tempJar.deleteOnExit();
156                                     fos = new FileOutputStream(tempJar);
157                                     byte[] buf = new byte[4096];
158                                     int nbytes = 0;
159                                     while ((nbytes = is.read(buf)) > -1) {
160                                         fos.write(buf, 0, nbytes);
161                                     }
162                                     fos.close();
163                                     result = new JarFile(tempJar, true,
164                                             ZipFile.OPEN_READ | ZipFile.OPEN_DELETE);
165                                 } catch (IOException e) {
166                                     return null;
167                                 } finally {
168                                     if (fos != null) {
169                                         try {
170                                             fos.close();
171                                         } catch (IOException ex) {
172                                             result = null;
173                                         }
174                                     }
175                                 }
176                                 return result;
177                             }
178                         });
179             } finally {
180                 if (is != null) {
181                     is.close();
182                 }
183             }
184         }
185
186         return jar;
187     }
188
189     /**
190      * Returns the JarEntry of the entry referenced by this {@code
191      * URLConnection}.
192      *
193      * @return the JarEntry referenced
194      *
195      * @throws IOException
196      *             if an IO error occurs while getting the entry
197      */
198     @Override
199     public JarEntry getJarEntry() throws IOException {
200         connect();
201         return jarEntry;
202
203     }
204
205     /**
206      * Look up the JarEntry of the entry referenced by this {@code
207      * URLConnection}.
208      */
209     private void findJarEntry() throws IOException {
210         if (getEntryName() == null) {
211             return;
212         }
213         jarEntry = jarFile.getJarEntry(getEntryName());
214         if (jarEntry == null) {
215             throw new FileNotFoundException(getEntryName());
216         }
217     }
218
219     /**
220      * Creates an input stream for reading from this URL Connection.
221      *
222      * @return the input stream
223      *
224      * @throws IOException
225      *             if an IO error occurs while connecting to the resource.
226      */
227     @Override
228     public InputStream getInputStream() throws IOException {
229         if (closed) {
230             throw new IllegalStateException("JarURLConnection InputStream has been closed");
231         }
232         connect();
233         if (jarInput != null) {
234             return jarInput;
235         }
236         if (jarEntry == null) {
237             throw new IOException("Jar entry not specified");
238         }
239         return jarInput = new JarURLConnectionInputStream(jarFile
240                 .getInputStream(jarEntry), jarFile);
241     }
242
243     /**
244      * Returns the content type of the resource. For jar file itself
245      * "x-java/jar" should be returned, for jar entries the content type of the
246      * entry should be returned. Returns non-null results ("content/unknown" for
247      * unknown types).
248      *
249      * @return the content type
250      */
251     @Override
252     public String getContentType() {
253         if (url.getFile().endsWith("!/")) {
254             // the type for jar file itself is always "x-java/jar"
255             return "x-java/jar";
256         }
257         String cType = null;
258         String entryName = getEntryName();
259
260         if (entryName != null) {
261             // if there is an Jar Entry, get the content type from the name
262             cType = guessContentTypeFromName(entryName);
263         } else {
264             try {
265                 connect();
266                 cType = jarFileURLConnection.getContentType();
267             } catch (IOException ioe) {
268                 // Ignore
269             }
270         }
271         if (cType == null) {
272             cType = "content/unknown";
273         }
274         return cType;
275     }
276
277     /**
278      * Returns the content length of the resource. Test cases reveal that if the
279      * URL is referring to a Jar file, this method answers a content-length
280      * returned by URLConnection. For jar entry it should return it's size.
281      * Otherwise, it will return -1.
282      *
283      * @return the content length
284      */
285     @Override
286     public int getContentLength() {
287         try {
288             connect();
289             if (jarEntry == null) {
290                 return jarFileURLConnection.getContentLength();
291             }
292             return (int) getJarEntry().getSize();
293         } catch (IOException e) {
294             // Ignored
295         }
296         return -1;
297     }
298
299     /**
300      * Returns the object pointed by this {@code URL}. If this URLConnection is
301      * pointing to a Jar File (no Jar Entry), this method will return a {@code
302      * JarFile} If there is a Jar Entry, it will return the object corresponding
303      * to the Jar entry content type.
304      *
305      * @return a non-null object
306      *
307      * @throws IOException
308      *             if an IO error occurred
309      *
310      * @see ContentHandler
311      * @see ContentHandlerFactory
312      * @see java.io.IOException
313      * @see #setContentHandlerFactory(ContentHandlerFactory)
314      */
315     @Override
316     public Object getContent() throws IOException {
317         connect();
318         // if there is no Jar Entry, return a JarFile
319         if (jarEntry == null) {
320             return jarFile;
321         }
322         return super.getContent();
323     }
324
325     /**
326      * Returns the permission, in this case the subclass, FilePermission object
327      * which represents the permission necessary for this URLConnection to
328      * establish the connection.
329      *
330      * @return the permission required for this URLConnection.
331      *
332      * @throws IOException
333      *             thrown when an IO exception occurs while creating the
334      *             permission.
335      */
336
337     @Override
338     public Permission getPermission() throws IOException {
339         return jarFileURLConnection.getPermission();
340     }
341
342     @Override
343     public boolean getUseCaches() {
344         return jarFileURLConnection.getUseCaches();
345     }
346
347     @Override
348     public void setUseCaches(boolean usecaches) {
349         jarFileURLConnection.setUseCaches(usecaches);
350     }
351
352     @Override
353     public boolean getDefaultUseCaches() {
354         return jarFileURLConnection.getDefaultUseCaches();
355     }
356
357     @Override
358     public void setDefaultUseCaches(boolean defaultusecaches) {
359         jarFileURLConnection.setDefaultUseCaches(defaultusecaches);
360     }
361
362     /**
363      * Closes the cached files.
364      */
365     public static void closeCachedFiles() {
366         Set<Map.Entry<URL, JarFile>> s = jarCache.entrySet();
367         synchronized (jarCache) {
368             Iterator<Map.Entry<URL, JarFile>> i = s.iterator();
369             while (i.hasNext()) {
370                 try {
371                     ZipFile zip = i.next().getValue();
372                     if (zip != null) {
373                         zip.close();
374                     }
375                 } catch (IOException e) {
376                     // Ignored
377                 }
378             }
379         }
380     }
381
382     private class JarURLConnectionInputStream extends FilterInputStream {
383         InputStream inputStream;
384
385         JarFile jarFile;
386
387         protected JarURLConnectionInputStream(InputStream in, JarFile file) {
388             super(in);
389             inputStream = in;
390             jarFile = file;
391         }
392
393         @Override
394         public void close() throws IOException {
395             super.close();
396             if (!getUseCaches()) {
397                 closed = true;
398                 jarFile.close();
399             }
400         }
401
402         @Override
403         public int read() throws IOException {
404             return inputStream.read();
405         }
406
407         @Override
408         public int read(byte[] buf, int off, int nbytes) throws IOException {
409             return inputStream.read(buf, off, nbytes);
410         }
411
412         @Override
413         public long skip(long nbytes) throws IOException {
414             return inputStream.skip(nbytes);
415         }
416     }
417 }